mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-20 13:02:15 +00:00
fix(coding-agent): respect quietStartup on /reload while keeping diagnostics (fixes #1336)
This commit is contained in:
parent
e9f94ba6c3
commit
fe6f4d3a9d
2 changed files with 197 additions and 96 deletions
|
|
@ -858,119 +858,137 @@ export class InteractiveMode {
|
||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
private showLoadedResources(options?: { extensionPaths?: string[]; force?: boolean }): void {
|
private showLoadedResources(options?: {
|
||||||
const shouldShow = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
|
extensionPaths?: string[];
|
||||||
if (!shouldShow) {
|
force?: boolean;
|
||||||
|
showDiagnosticsWhenQuiet?: boolean;
|
||||||
|
}): void {
|
||||||
|
const showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
|
||||||
|
const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
|
||||||
|
if (!showListing && !showDiagnostics) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = this.session.resourceLoader.getPathMetadata();
|
const metadata = this.session.resourceLoader.getPathMetadata();
|
||||||
|
|
||||||
const sectionHeader = (name: string, color: ThemeColor = "mdHeading") => theme.fg(color, `[${name}]`);
|
const sectionHeader = (name: string, color: ThemeColor = "mdHeading") => theme.fg(color, `[${name}]`);
|
||||||
|
|
||||||
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
const skillsResult = this.session.resourceLoader.getSkills();
|
||||||
if (contextFiles.length > 0) {
|
const promptsResult = this.session.resourceLoader.getPrompts();
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
const themesResult = this.session.resourceLoader.getThemes();
|
||||||
const contextList = contextFiles.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`)).join("\n");
|
|
||||||
this.chatContainer.addChild(new Text(`${sectionHeader("Context")}\n${contextList}`, 0, 0));
|
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
const skills = this.session.resourceLoader.getSkills().skills;
|
if (showListing) {
|
||||||
if (skills.length > 0) {
|
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
||||||
const skillPaths = skills.map((s) => s.filePath);
|
if (contextFiles.length > 0) {
|
||||||
const groups = this.buildScopeGroups(skillPaths, metadata);
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
const skillList = this.formatScopeGroups(groups, {
|
const contextList = contextFiles
|
||||||
formatPath: (p) => this.formatDisplayPath(p),
|
.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`))
|
||||||
formatPackagePath: (p, source) => this.getShortPath(p, source),
|
.join("\n");
|
||||||
});
|
this.chatContainer.addChild(new Text(`${sectionHeader("Context")}\n${contextList}`, 0, 0));
|
||||||
this.chatContainer.addChild(new Text(`${sectionHeader("Skills")}\n${skillList}`, 0, 0));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const skillDiagnostics = this.session.resourceLoader.getSkills().diagnostics;
|
const skills = skillsResult.skills;
|
||||||
if (skillDiagnostics.length > 0) {
|
if (skills.length > 0) {
|
||||||
const warningLines = this.formatDiagnostics(skillDiagnostics, metadata);
|
const skillPaths = skills.map((s) => s.filePath);
|
||||||
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
const groups = this.buildScopeGroups(skillPaths, metadata);
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
const skillList = this.formatScopeGroups(groups, {
|
||||||
}
|
formatPath: (p) => this.formatDisplayPath(p),
|
||||||
|
formatPackagePath: (p, source) => this.getShortPath(p, source),
|
||||||
|
});
|
||||||
|
this.chatContainer.addChild(new Text(`${sectionHeader("Skills")}\n${skillList}`, 0, 0));
|
||||||
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
|
}
|
||||||
|
|
||||||
const templates = this.session.promptTemplates;
|
const templates = this.session.promptTemplates;
|
||||||
if (templates.length > 0) {
|
if (templates.length > 0) {
|
||||||
const templatePaths = templates.map((t) => t.filePath);
|
const templatePaths = templates.map((t) => t.filePath);
|
||||||
const groups = this.buildScopeGroups(templatePaths, metadata);
|
const groups = this.buildScopeGroups(templatePaths, metadata);
|
||||||
const templateByPath = new Map(templates.map((t) => [t.filePath, t]));
|
const templateByPath = new Map(templates.map((t) => [t.filePath, t]));
|
||||||
const templateList = this.formatScopeGroups(groups, {
|
const templateList = this.formatScopeGroups(groups, {
|
||||||
formatPath: (p) => {
|
formatPath: (p) => {
|
||||||
const template = templateByPath.get(p);
|
const template = templateByPath.get(p);
|
||||||
return template ? `/${template.name}` : this.formatDisplayPath(p);
|
return template ? `/${template.name}` : this.formatDisplayPath(p);
|
||||||
},
|
},
|
||||||
formatPackagePath: (p) => {
|
formatPackagePath: (p) => {
|
||||||
const template = templateByPath.get(p);
|
const template = templateByPath.get(p);
|
||||||
return template ? `/${template.name}` : this.formatDisplayPath(p);
|
return template ? `/${template.name}` : this.formatDisplayPath(p);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.chatContainer.addChild(new Text(`${sectionHeader("Prompts")}\n${templateList}`, 0, 0));
|
this.chatContainer.addChild(new Text(`${sectionHeader("Prompts")}\n${templateList}`, 0, 0));
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
const promptDiagnostics = this.session.resourceLoader.getPrompts().diagnostics;
|
const extensionPaths = options?.extensionPaths ?? [];
|
||||||
if (promptDiagnostics.length > 0) {
|
if (extensionPaths.length > 0) {
|
||||||
const warningLines = this.formatDiagnostics(promptDiagnostics, metadata);
|
const groups = this.buildScopeGroups(extensionPaths, metadata);
|
||||||
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
|
const extList = this.formatScopeGroups(groups, {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
formatPath: (p) => this.formatDisplayPath(p),
|
||||||
}
|
formatPackagePath: (p, source) => this.getShortPath(p, source),
|
||||||
|
});
|
||||||
|
this.chatContainer.addChild(new Text(`${sectionHeader("Extensions", "mdHeading")}\n${extList}`, 0, 0));
|
||||||
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
|
}
|
||||||
|
|
||||||
const extensionPaths = options?.extensionPaths ?? [];
|
// Show loaded themes (excluding built-in)
|
||||||
if (extensionPaths.length > 0) {
|
const loadedThemes = themesResult.themes;
|
||||||
const groups = this.buildScopeGroups(extensionPaths, metadata);
|
const customThemes = loadedThemes.filter((t) => t.sourcePath);
|
||||||
const extList = this.formatScopeGroups(groups, {
|
if (customThemes.length > 0) {
|
||||||
formatPath: (p) => this.formatDisplayPath(p),
|
const themePaths = customThemes.map((t) => t.sourcePath!);
|
||||||
formatPackagePath: (p, source) => this.getShortPath(p, source),
|
const groups = this.buildScopeGroups(themePaths, metadata);
|
||||||
});
|
const themeList = this.formatScopeGroups(groups, {
|
||||||
this.chatContainer.addChild(new Text(`${sectionHeader("Extensions", "mdHeading")}\n${extList}`, 0, 0));
|
formatPath: (p) => this.formatDisplayPath(p),
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
formatPackagePath: (p, source) => this.getShortPath(p, source),
|
||||||
}
|
});
|
||||||
|
this.chatContainer.addChild(new Text(`${sectionHeader("Themes")}\n${themeList}`, 0, 0));
|
||||||
const extensionDiagnostics: ResourceDiagnostic[] = [];
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
const extensionErrors = this.session.resourceLoader.getExtensions().errors;
|
|
||||||
if (extensionErrors.length > 0) {
|
|
||||||
for (const error of extensionErrors) {
|
|
||||||
extensionDiagnostics.push({ type: "error", message: error.error, path: error.path });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandDiagnostics = this.session.extensionRunner?.getCommandDiagnostics() ?? [];
|
if (showDiagnostics) {
|
||||||
extensionDiagnostics.push(...commandDiagnostics);
|
const skillDiagnostics = skillsResult.diagnostics;
|
||||||
|
if (skillDiagnostics.length > 0) {
|
||||||
|
const warningLines = this.formatDiagnostics(skillDiagnostics, metadata);
|
||||||
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
||||||
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
|
}
|
||||||
|
|
||||||
const shortcutDiagnostics = this.session.extensionRunner?.getShortcutDiagnostics() ?? [];
|
const promptDiagnostics = promptsResult.diagnostics;
|
||||||
extensionDiagnostics.push(...shortcutDiagnostics);
|
if (promptDiagnostics.length > 0) {
|
||||||
|
const warningLines = this.formatDiagnostics(promptDiagnostics, metadata);
|
||||||
|
this.chatContainer.addChild(
|
||||||
|
new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0),
|
||||||
|
);
|
||||||
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
|
}
|
||||||
|
|
||||||
if (extensionDiagnostics.length > 0) {
|
const extensionDiagnostics: ResourceDiagnostic[] = [];
|
||||||
const warningLines = this.formatDiagnostics(extensionDiagnostics, metadata);
|
const extensionErrors = this.session.resourceLoader.getExtensions().errors;
|
||||||
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
|
if (extensionErrors.length > 0) {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
for (const error of extensionErrors) {
|
||||||
}
|
extensionDiagnostics.push({ type: "error", message: error.error, path: error.path });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Show loaded themes (excluding built-in)
|
const commandDiagnostics = this.session.extensionRunner?.getCommandDiagnostics() ?? [];
|
||||||
const loadedThemes = this.session.resourceLoader.getThemes().themes;
|
extensionDiagnostics.push(...commandDiagnostics);
|
||||||
const customThemes = loadedThemes.filter((t) => t.sourcePath);
|
|
||||||
if (customThemes.length > 0) {
|
|
||||||
const themePaths = customThemes.map((t) => t.sourcePath!);
|
|
||||||
const groups = this.buildScopeGroups(themePaths, metadata);
|
|
||||||
const themeList = this.formatScopeGroups(groups, {
|
|
||||||
formatPath: (p) => this.formatDisplayPath(p),
|
|
||||||
formatPackagePath: (p, source) => this.getShortPath(p, source),
|
|
||||||
});
|
|
||||||
this.chatContainer.addChild(new Text(`${sectionHeader("Themes")}\n${themeList}`, 0, 0));
|
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
const themeDiagnostics = this.session.resourceLoader.getThemes().diagnostics;
|
const shortcutDiagnostics = this.session.extensionRunner?.getShortcutDiagnostics() ?? [];
|
||||||
if (themeDiagnostics.length > 0) {
|
extensionDiagnostics.push(...shortcutDiagnostics);
|
||||||
const warningLines = this.formatDiagnostics(themeDiagnostics, metadata);
|
|
||||||
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
if (extensionDiagnostics.length > 0) {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
const warningLines = this.formatDiagnostics(extensionDiagnostics, metadata);
|
||||||
|
this.chatContainer.addChild(
|
||||||
|
new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0),
|
||||||
|
);
|
||||||
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeDiagnostics = themesResult.diagnostics;
|
||||||
|
if (themeDiagnostics.length > 0) {
|
||||||
|
const warningLines = this.formatDiagnostics(themeDiagnostics, metadata);
|
||||||
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
||||||
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3728,7 +3746,11 @@ export class InteractiveMode {
|
||||||
}
|
}
|
||||||
this.rebuildChatFromMessages();
|
this.rebuildChatFromMessages();
|
||||||
dismissLoader(this.editor as Component);
|
dismissLoader(this.editor as Component);
|
||||||
this.showLoadedResources({ extensionPaths: runner?.getExtensionPaths() ?? [], force: true });
|
this.showLoadedResources({
|
||||||
|
extensionPaths: runner?.getExtensionPaths() ?? [],
|
||||||
|
force: false,
|
||||||
|
showDiagnosticsWhenQuiet: true,
|
||||||
|
});
|
||||||
const modelsJsonError = this.session.modelRegistry.getError();
|
const modelsJsonError = this.session.modelRegistry.getError();
|
||||||
if (modelsJsonError) {
|
if (modelsJsonError) {
|
||||||
this.showError(`models.json error: ${modelsJsonError}`);
|
this.showError(`models.json error: ${modelsJsonError}`);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@ function renderLastLine(container: Container, width = 120): string {
|
||||||
return last.render(width).join("\n");
|
return last.render(width).join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderAll(container: Container, width = 120): string {
|
||||||
|
return container.children.flatMap((child) => child.render(width)).join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
describe("InteractiveMode.showStatus", () => {
|
describe("InteractiveMode.showStatus", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
// showStatus uses the global theme instance
|
// showStatus uses the global theme instance
|
||||||
|
|
@ -55,3 +59,78 @@ describe("InteractiveMode.showStatus", () => {
|
||||||
expect(renderLastLine(fakeThis.chatContainer)).toContain("STATUS_TWO");
|
expect(renderLastLine(fakeThis.chatContainer)).toContain("STATUS_TWO");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("InteractiveMode.showLoadedResources", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
initTheme("dark");
|
||||||
|
});
|
||||||
|
|
||||||
|
function createShowLoadedResourcesThis(options: {
|
||||||
|
quietStartup: boolean;
|
||||||
|
verbose?: boolean;
|
||||||
|
skills?: Array<{ filePath: string }>;
|
||||||
|
skillDiagnostics?: Array<{ type: "warning" | "error" | "collision"; message: string }>;
|
||||||
|
}) {
|
||||||
|
const fakeThis: any = {
|
||||||
|
options: { verbose: options.verbose ?? false },
|
||||||
|
chatContainer: new Container(),
|
||||||
|
settingsManager: {
|
||||||
|
getQuietStartup: () => options.quietStartup,
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
promptTemplates: [],
|
||||||
|
extensionRunner: undefined,
|
||||||
|
resourceLoader: {
|
||||||
|
getPathMetadata: () => new Map(),
|
||||||
|
getAgentsFiles: () => ({ agentsFiles: [] }),
|
||||||
|
getSkills: () => ({
|
||||||
|
skills: options.skills ?? [],
|
||||||
|
diagnostics: options.skillDiagnostics ?? [],
|
||||||
|
}),
|
||||||
|
getPrompts: () => ({ prompts: [], diagnostics: [] }),
|
||||||
|
getExtensions: () => ({ errors: [] }),
|
||||||
|
getThemes: () => ({ themes: [], diagnostics: [] }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formatDisplayPath: (p: string) => p,
|
||||||
|
buildScopeGroups: () => [],
|
||||||
|
formatScopeGroups: () => "resource-list",
|
||||||
|
getShortPath: (p: string) => p,
|
||||||
|
formatDiagnostics: () => "diagnostics",
|
||||||
|
};
|
||||||
|
|
||||||
|
return fakeThis;
|
||||||
|
}
|
||||||
|
|
||||||
|
test("does not show verbose listing on quiet startup during reload", () => {
|
||||||
|
const fakeThis = createShowLoadedResourcesThis({
|
||||||
|
quietStartup: true,
|
||||||
|
skills: [{ filePath: "/tmp/skill/SKILL.md" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
||||||
|
extensionPaths: ["/tmp/ext/index.ts"],
|
||||||
|
force: false,
|
||||||
|
showDiagnosticsWhenQuiet: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fakeThis.chatContainer.children).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("still shows diagnostics on quiet startup when requested", () => {
|
||||||
|
const fakeThis = createShowLoadedResourcesThis({
|
||||||
|
quietStartup: true,
|
||||||
|
skills: [{ filePath: "/tmp/skill/SKILL.md" }],
|
||||||
|
skillDiagnostics: [{ type: "warning", message: "duplicate skill name" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
||||||
|
force: false,
|
||||||
|
showDiagnosticsWhenQuiet: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const output = renderAll(fakeThis.chatContainer);
|
||||||
|
expect(output).toContain("[Skill conflicts]");
|
||||||
|
expect(output).not.toContain("[Skills]");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue