Merge pull request #6048 from haoqixu/fix-resources-position
fix(coding-agent): show resources before messages when resuming session
This commit is contained in:
@@ -265,6 +265,7 @@ export interface InteractiveModeOptions {
|
|||||||
export class InteractiveMode {
|
export class InteractiveMode {
|
||||||
private runtimeHost: AgentSessionRuntime;
|
private runtimeHost: AgentSessionRuntime;
|
||||||
private ui: TUI;
|
private ui: TUI;
|
||||||
|
private loadedResourcesContainer: Container;
|
||||||
private chatContainer: Container;
|
private chatContainer: Container;
|
||||||
private pendingMessagesContainer: Container;
|
private pendingMessagesContainer: Container;
|
||||||
private statusContainer: Container;
|
private statusContainer: Container;
|
||||||
@@ -401,6 +402,7 @@ export class InteractiveMode {
|
|||||||
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
|
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
|
||||||
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
||||||
this.headerContainer = new Container();
|
this.headerContainer = new Container();
|
||||||
|
this.loadedResourcesContainer = new Container();
|
||||||
this.chatContainer = new Container();
|
this.chatContainer = new Container();
|
||||||
this.pendingMessagesContainer = new Container();
|
this.pendingMessagesContainer = new Container();
|
||||||
this.statusContainer = new Container();
|
this.statusContainer = new Container();
|
||||||
@@ -636,8 +638,10 @@ export class InteractiveMode {
|
|||||||
console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
|
console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add header container as first child. Populate it after detectThemeIfUnset.
|
// Add header container as first child. Populate it after applying theme settings.
|
||||||
|
// Keep loaded resources before chat so restored session messages never precede them.
|
||||||
this.ui.addChild(this.headerContainer);
|
this.ui.addChild(this.headerContainer);
|
||||||
|
this.ui.addChild(this.loadedResourcesContainer);
|
||||||
|
|
||||||
this.ui.addChild(this.chatContainer);
|
this.ui.addChild(this.chatContainer);
|
||||||
this.ui.addChild(this.pendingMessagesContainer);
|
this.ui.addChild(this.pendingMessagesContainer);
|
||||||
@@ -1330,6 +1334,9 @@ export class InteractiveMode {
|
|||||||
force?: boolean;
|
force?: boolean;
|
||||||
showDiagnosticsWhenQuiet?: boolean;
|
showDiagnosticsWhenQuiet?: boolean;
|
||||||
}): void {
|
}): void {
|
||||||
|
// Resource rendering is idempotent; chat clears no longer clear this separate container.
|
||||||
|
this.loadedResourcesContainer.clear();
|
||||||
|
|
||||||
const showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
|
const showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
|
||||||
const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
|
const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
|
||||||
if (!showListing && !showDiagnostics) {
|
if (!showListing && !showDiagnostics) {
|
||||||
@@ -1357,8 +1364,8 @@ export class InteractiveMode {
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
this.chatContainer.addChild(section);
|
this.loadedResourcesContainer.addChild(section);
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.loadedResourcesContainer.addChild(new Spacer(1));
|
||||||
};
|
};
|
||||||
|
|
||||||
const skillsResult = this.session.resourceLoader.getSkills();
|
const skillsResult = this.session.resourceLoader.getSkills();
|
||||||
@@ -1395,7 +1402,7 @@ export class InteractiveMode {
|
|||||||
if (showListing) {
|
if (showListing) {
|
||||||
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
||||||
if (contextFiles.length > 0) {
|
if (contextFiles.length > 0) {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.loadedResourcesContainer.addChild(new Spacer(1));
|
||||||
const contextList = contextFiles
|
const contextList = contextFiles
|
||||||
.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`))
|
.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`))
|
||||||
.join("\n");
|
.join("\n");
|
||||||
@@ -1478,17 +1485,19 @@ export class InteractiveMode {
|
|||||||
const skillDiagnostics = skillsResult.diagnostics;
|
const skillDiagnostics = skillsResult.diagnostics;
|
||||||
if (skillDiagnostics.length > 0) {
|
if (skillDiagnostics.length > 0) {
|
||||||
const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
|
const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
|
||||||
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
this.loadedResourcesContainer.addChild(
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0),
|
||||||
|
);
|
||||||
|
this.loadedResourcesContainer.addChild(new Spacer(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
const promptDiagnostics = promptsResult.diagnostics;
|
const promptDiagnostics = promptsResult.diagnostics;
|
||||||
if (promptDiagnostics.length > 0) {
|
if (promptDiagnostics.length > 0) {
|
||||||
const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
|
const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
|
||||||
this.chatContainer.addChild(
|
this.loadedResourcesContainer.addChild(
|
||||||
new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0),
|
new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0),
|
||||||
);
|
);
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.loadedResourcesContainer.addChild(new Spacer(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
const extensionDiagnostics: ResourceDiagnostic[] = [];
|
const extensionDiagnostics: ResourceDiagnostic[] = [];
|
||||||
@@ -1508,17 +1517,19 @@ export class InteractiveMode {
|
|||||||
|
|
||||||
if (extensionDiagnostics.length > 0) {
|
if (extensionDiagnostics.length > 0) {
|
||||||
const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
|
const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
|
||||||
this.chatContainer.addChild(
|
this.loadedResourcesContainer.addChild(
|
||||||
new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0),
|
new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0),
|
||||||
);
|
);
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.loadedResourcesContainer.addChild(new Spacer(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
const themeDiagnostics = themesResult.diagnostics;
|
const themeDiagnostics = themesResult.diagnostics;
|
||||||
if (themeDiagnostics.length > 0) {
|
if (themeDiagnostics.length > 0) {
|
||||||
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
||||||
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
this.loadedResourcesContainer.addChild(
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0),
|
||||||
|
);
|
||||||
|
this.loadedResourcesContainer.addChild(new Spacer(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1651,6 +1662,7 @@ export class InteractiveMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderCurrentSessionState(): void {
|
private renderCurrentSessionState(): void {
|
||||||
|
this.loadedResourcesContainer.clear();
|
||||||
this.chatContainer.clear();
|
this.chatContainer.clear();
|
||||||
this.pendingMessagesContainer.clear();
|
this.pendingMessagesContainer.clear();
|
||||||
this.compactionQueuedMessages = [];
|
this.compactionQueuedMessages = [];
|
||||||
@@ -3606,9 +3618,11 @@ export class InteractiveMode {
|
|||||||
if (isExpandable(activeHeader)) {
|
if (isExpandable(activeHeader)) {
|
||||||
activeHeader.setExpanded(expanded);
|
activeHeader.setExpanded(expanded);
|
||||||
}
|
}
|
||||||
for (const child of this.chatContainer.children) {
|
for (const container of [this.loadedResourcesContainer, this.chatContainer]) {
|
||||||
if (isExpandable(child)) {
|
for (const child of container.children) {
|
||||||
child.setExpanded(expanded);
|
if (isExpandable(child)) {
|
||||||
|
child.setExpanded(expanded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
|
|||||||
@@ -119,11 +119,13 @@ describe("InteractiveMode.showStatus", () => {
|
|||||||
describe("InteractiveMode.setToolsExpanded", () => {
|
describe("InteractiveMode.setToolsExpanded", () => {
|
||||||
test("applies expansion state to the active header and chat entries", () => {
|
test("applies expansion state to the active header and chat entries", () => {
|
||||||
const header = { setExpanded: vi.fn() };
|
const header = { setExpanded: vi.fn() };
|
||||||
|
const loadedResourcesChild = { setExpanded: vi.fn() };
|
||||||
const chatChild = { setExpanded: vi.fn() };
|
const chatChild = { setExpanded: vi.fn() };
|
||||||
const fakeThis: any = {
|
const fakeThis: any = {
|
||||||
toolOutputExpanded: false,
|
toolOutputExpanded: false,
|
||||||
customHeader: undefined,
|
customHeader: undefined,
|
||||||
builtInHeader: header,
|
builtInHeader: header,
|
||||||
|
loadedResourcesContainer: { children: [loadedResourcesChild] },
|
||||||
chatContainer: { children: [chatChild] },
|
chatContainer: { children: [chatChild] },
|
||||||
ui: { requestRender: vi.fn() },
|
ui: { requestRender: vi.fn() },
|
||||||
};
|
};
|
||||||
@@ -132,6 +134,7 @@ describe("InteractiveMode.setToolsExpanded", () => {
|
|||||||
|
|
||||||
expect(fakeThis.toolOutputExpanded).toBe(true);
|
expect(fakeThis.toolOutputExpanded).toBe(true);
|
||||||
expect(header.setExpanded).toHaveBeenCalledWith(true);
|
expect(header.setExpanded).toHaveBeenCalledWith(true);
|
||||||
|
expect(loadedResourcesChild.setExpanded).toHaveBeenCalledWith(true);
|
||||||
expect(chatChild.setExpanded).toHaveBeenCalledWith(true);
|
expect(chatChild.setExpanded).toHaveBeenCalledWith(true);
|
||||||
expect(fakeThis.ui.requestRender).toHaveBeenCalledTimes(1);
|
expect(fakeThis.ui.requestRender).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
@@ -441,6 +444,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
const fakeThis: any = {
|
const fakeThis: any = {
|
||||||
options: { verbose: options.verbose ?? false },
|
options: { verbose: options.verbose ?? false },
|
||||||
toolOutputExpanded: options.toolOutputExpanded ?? false,
|
toolOutputExpanded: options.toolOutputExpanded ?? false,
|
||||||
|
loadedResourcesContainer: new Container(),
|
||||||
chatContainer: new Container(),
|
chatContainer: new Container(),
|
||||||
settingsManager: {
|
settingsManager: {
|
||||||
getQuietStartup: () => options.quietStartup,
|
getQuietStartup: () => options.quietStartup,
|
||||||
@@ -619,7 +623,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = renderAll(fakeThis.chatContainer);
|
const output = renderAll(fakeThis.loadedResourcesContainer);
|
||||||
expect(output).toContain("[Skills]");
|
expect(output).toContain("[Skills]");
|
||||||
expect(output).toContain("commit");
|
expect(output).toContain("commit");
|
||||||
expect(output).not.toContain("resource-list");
|
expect(output).not.toContain("resource-list");
|
||||||
@@ -636,7 +640,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = renderAll(fakeThis.chatContainer);
|
const output = renderAll(fakeThis.loadedResourcesContainer);
|
||||||
expect(output).toContain("[Skills]");
|
expect(output).toContain("[Skills]");
|
||||||
expect(output).toContain("resource-list");
|
expect(output).toContain("resource-list");
|
||||||
expect(output).not.toContain("commit");
|
expect(output).not.toContain("commit");
|
||||||
@@ -654,7 +658,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = renderAll(fakeThis.chatContainer);
|
const output = renderAll(fakeThis.loadedResourcesContainer);
|
||||||
expect(output).toContain("[Skills]");
|
expect(output).toContain("[Skills]");
|
||||||
expect(output).toContain("resource-list");
|
expect(output).toContain("resource-list");
|
||||||
expect(output).not.toContain("commit");
|
expect(output).not.toContain("commit");
|
||||||
@@ -670,7 +674,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = renderAll(fakeThis.chatContainer);
|
const output = renderAll(fakeThis.loadedResourcesContainer);
|
||||||
expect(output).toContain("[Extensions]");
|
expect(output).toContain("[Extensions]");
|
||||||
expect(output).toContain("answer.ts, btw.ts");
|
expect(output).toContain("answer.ts, btw.ts");
|
||||||
expect(output).not.toContain("extensions/answer.ts");
|
expect(output).not.toContain("extensions/answer.ts");
|
||||||
@@ -687,7 +691,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[Extensions]
|
||||||
@scope/pi-scoped, answer.ts, cli-extension.ts, HazAT/pi-interactive-subagents, HazAT/pi-interactive-subagents:subagents, local-index, pi-markdown-preview, user-index"`);
|
@scope/pi-scoped, answer.ts, cli-extension.ts, HazAT/pi-interactive-subagents, HazAT/pi-interactive-subagents:subagents, local-index, pi-markdown-preview, user-index"`);
|
||||||
});
|
});
|
||||||
@@ -733,7 +737,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[Extensions]
|
||||||
alpha/one, beta/one, gamma/one"`);
|
alpha/one, beta/one, gamma/one"`);
|
||||||
});
|
});
|
||||||
@@ -761,7 +765,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[Extensions]
|
||||||
plan-mode"`);
|
plan-mode"`);
|
||||||
});
|
});
|
||||||
@@ -789,7 +793,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[Extensions]
|
||||||
plan-mode"`);
|
plan-mode"`);
|
||||||
});
|
});
|
||||||
@@ -826,7 +830,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[Extensions]
|
||||||
plan-mode, webfetch.ts"`);
|
plan-mode, webfetch.ts"`);
|
||||||
});
|
});
|
||||||
@@ -863,7 +867,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[Extensions]
|
||||||
bar, foo"`);
|
bar, foo"`);
|
||||||
});
|
});
|
||||||
@@ -900,7 +904,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[Extensions]
|
||||||
alpha/tools, beta/tools"`);
|
alpha/tools, beta/tools"`);
|
||||||
});
|
});
|
||||||
@@ -928,7 +932,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[Extensions]
|
||||||
main.ts"`);
|
main.ts"`);
|
||||||
});
|
});
|
||||||
@@ -956,7 +960,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[Extensions]
|
||||||
pi-markdown-preview"`);
|
pi-markdown-preview"`);
|
||||||
});
|
});
|
||||||
@@ -972,7 +976,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
expect(normalizeRenderedOutput(fakeThis.loadedResourcesContainer)).toMatchInlineSnapshot(`
|
||||||
"[Extensions]
|
"[Extensions]
|
||||||
project
|
project
|
||||||
/tmp/project/.pi/extensions/answer.ts
|
/tmp/project/.pi/extensions/answer.ts
|
||||||
@@ -1003,7 +1007,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = renderAll(fakeThis.chatContainer).replace(/\\/g, "/");
|
const output = renderAll(fakeThis.loadedResourcesContainer).replace(/\\/g, "/");
|
||||||
expect(output).toContain("[Context]");
|
expect(output).toContain("[Context]");
|
||||||
expect(output).toContain("~/.pi/agent/AGENTS.md, AGENTS.md");
|
expect(output).toContain("~/.pi/agent/AGENTS.md, AGENTS.md");
|
||||||
expect(output).not.toContain(`${cwd.replace(/\\/g, "/")}/AGENTS.md`);
|
expect(output).not.toContain(`${cwd.replace(/\\/g, "/")}/AGENTS.md`);
|
||||||
@@ -1023,7 +1027,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = renderAll(fakeThis.chatContainer).replace(/\\/g, "/");
|
const output = renderAll(fakeThis.loadedResourcesContainer).replace(/\\/g, "/");
|
||||||
expect(output).toContain("[Context]");
|
expect(output).toContain("[Context]");
|
||||||
expect(output).toContain("~/.pi/agent/AGENTS.md");
|
expect(output).toContain("~/.pi/agent/AGENTS.md");
|
||||||
expect(output).toContain("~/Development/pi-mono/AGENTS.md");
|
expect(output).toContain("~/Development/pi-mono/AGENTS.md");
|
||||||
@@ -1042,7 +1046,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
showDiagnosticsWhenQuiet: true,
|
showDiagnosticsWhenQuiet: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(fakeThis.chatContainer.children).toHaveLength(0);
|
expect(fakeThis.loadedResourcesContainer.children).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("still shows diagnostics on quiet startup when requested", () => {
|
test("still shows diagnostics on quiet startup when requested", () => {
|
||||||
@@ -1057,7 +1061,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
showDiagnosticsWhenQuiet: true,
|
showDiagnosticsWhenQuiet: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = renderAll(fakeThis.chatContainer);
|
const output = renderAll(fakeThis.loadedResourcesContainer);
|
||||||
expect(output).toContain("[Skill conflicts]");
|
expect(output).toContain("[Skill conflicts]");
|
||||||
expect(output).not.toContain("[Skills]");
|
expect(output).not.toContain("[Skills]");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { fauxAssistantMessage } from "@earendil-works/pi-ai";
|
import { fauxAssistantMessage } from "@earendil-works/pi-ai";
|
||||||
|
import { Container, Text } from "@earendil-works/pi-tui";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import type { AgentSessionEvent } from "../../../src/core/agent-session.ts";
|
import type { AgentSessionEvent } from "../../../src/core/agent-session.ts";
|
||||||
import type { ExtensionUIContext } from "../../../src/core/extensions/index.ts";
|
import type { ExtensionUIContext } from "../../../src/core/extensions/index.ts";
|
||||||
@@ -43,6 +44,35 @@ function createUiContext(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoadedResourcesResult<T> = { [K in keyof T]: T[K] } & { diagnostics: [] };
|
||||||
|
|
||||||
|
type LoadedResourcesContext = {
|
||||||
|
loadedResourcesContainer: Container;
|
||||||
|
chatContainer: Container;
|
||||||
|
options: { verbose?: boolean };
|
||||||
|
settingsManager: { getQuietStartup: () => boolean };
|
||||||
|
sessionManager: { getCwd: () => string };
|
||||||
|
session: {
|
||||||
|
promptTemplates: [];
|
||||||
|
resourceLoader: {
|
||||||
|
getAgentsFiles: () => LoadedResourcesResult<{ agentsFiles: Array<{ path: string }> }>;
|
||||||
|
getSkills: () => LoadedResourcesResult<{ skills: [] }>;
|
||||||
|
getPrompts: () => LoadedResourcesResult<{ prompts: [] }>;
|
||||||
|
getThemes: () => LoadedResourcesResult<{ themes: [] }>;
|
||||||
|
getExtensions: () => { extensions: []; errors: [] };
|
||||||
|
};
|
||||||
|
extensionRunner: {
|
||||||
|
getCommandDiagnostics: () => [];
|
||||||
|
getShortcutDiagnostics: () => [];
|
||||||
|
getRegisteredCommands: () => [];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
getStartupExpansionState: () => boolean;
|
||||||
|
formatDisplayPath: (resourcePath: string) => string;
|
||||||
|
formatContextPath: (resourcePath: string) => string;
|
||||||
|
getBuiltInCommandConflictDiagnostics: (extensionRunner: LoadedResourcesContext["session"]["extensionRunner"]) => [];
|
||||||
|
};
|
||||||
|
|
||||||
type RebindContext = {
|
type RebindContext = {
|
||||||
unsubscribe?: () => void;
|
unsubscribe?: () => void;
|
||||||
applyRuntimeSettings: () => void;
|
applyRuntimeSettings: () => void;
|
||||||
@@ -97,6 +127,10 @@ type ReloadCommandContext = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type InteractiveModePrototype = {
|
type InteractiveModePrototype = {
|
||||||
|
showLoadedResources(
|
||||||
|
this: LoadedResourcesContext,
|
||||||
|
options?: { extensions?: Array<{ path: string }>; force?: boolean; showDiagnosticsWhenQuiet?: boolean },
|
||||||
|
): void;
|
||||||
rebindCurrentSession(this: RebindContext, options?: { renderBeforeBind?: boolean }): Promise<void>;
|
rebindCurrentSession(this: RebindContext, options?: { renderBeforeBind?: boolean }): Promise<void>;
|
||||||
handleReloadCommand(this: ReloadCommandContext): Promise<void>;
|
handleReloadCommand(this: ReloadCommandContext): Promise<void>;
|
||||||
};
|
};
|
||||||
@@ -183,7 +217,56 @@ function getMessageText(event: MessageEvent): string {
|
|||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createLoadedResourcesContext(): LoadedResourcesContext {
|
||||||
|
return {
|
||||||
|
loadedResourcesContainer: new Container(),
|
||||||
|
chatContainer: new Container(),
|
||||||
|
options: { verbose: true },
|
||||||
|
settingsManager: { getQuietStartup: () => false },
|
||||||
|
sessionManager: { getCwd: () => "/repo" },
|
||||||
|
session: {
|
||||||
|
promptTemplates: [],
|
||||||
|
resourceLoader: {
|
||||||
|
getAgentsFiles: () => ({ agentsFiles: [{ path: "/repo/AGENTS.md" }], diagnostics: [] }),
|
||||||
|
getSkills: () => ({ skills: [], diagnostics: [] }),
|
||||||
|
getPrompts: () => ({ prompts: [], diagnostics: [] }),
|
||||||
|
getThemes: () => ({ themes: [], diagnostics: [] }),
|
||||||
|
getExtensions: () => ({ extensions: [], errors: [] }),
|
||||||
|
},
|
||||||
|
extensionRunner: {
|
||||||
|
getCommandDiagnostics: () => [],
|
||||||
|
getShortcutDiagnostics: () => [],
|
||||||
|
getRegisteredCommands: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getStartupExpansionState: () => false,
|
||||||
|
formatDisplayPath: (resourcePath) => resourcePath,
|
||||||
|
formatContextPath: (resourcePath) => resourcePath.replace("/repo/", ""),
|
||||||
|
getBuiltInCommandConflictDiagnostics: () => [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("regression #5943: session_start transient UI", () => {
|
describe("regression #5943: session_start transient UI", () => {
|
||||||
|
it("renders loaded resources before restored messages without stale entries", () => {
|
||||||
|
initTheme("dark", false);
|
||||||
|
const context = createLoadedResourcesContext();
|
||||||
|
const root = new Container();
|
||||||
|
root.addChild(context.loadedResourcesContainer);
|
||||||
|
root.addChild(context.chatContainer);
|
||||||
|
context.loadedResourcesContainer.addChild(new Text("stale resources", 0, 0));
|
||||||
|
context.chatContainer.addChild(new Text("restored message", 0, 0));
|
||||||
|
|
||||||
|
interactiveModePrototype.showLoadedResources.call(context);
|
||||||
|
|
||||||
|
const chatRendered = context.chatContainer.render(80).join("\n");
|
||||||
|
expect(chatRendered).toContain("restored message");
|
||||||
|
expect(chatRendered).not.toContain("[Context]");
|
||||||
|
|
||||||
|
const rendered = root.render(80).join("\n");
|
||||||
|
expect(rendered).not.toContain("stale resources");
|
||||||
|
expect(rendered.indexOf("[Context]")).toBeLessThan(rendered.indexOf("restored message"));
|
||||||
|
});
|
||||||
|
|
||||||
it("renders replacement session state before session_start handlers can notify", async () => {
|
it("renders replacement session state before session_start handlers can notify", async () => {
|
||||||
const events: string[] = [];
|
const events: string[] = [];
|
||||||
const harness = await createHarness({
|
const harness = await createHarness({
|
||||||
|
|||||||
Reference in New Issue
Block a user