gsd-pi 2.39.0 → 2.40.0-dev.4a93031
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resource-loader.js +66 -2
- package/dist/resources/extensions/async-jobs/index.js +10 -0
- package/dist/resources/extensions/get-secrets-from-user.js +1 -1
- package/dist/resources/extensions/gsd/auto-dashboard.js +7 -0
- package/dist/resources/extensions/gsd/auto-loop.js +761 -673
- package/dist/resources/extensions/gsd/auto-post-unit.js +10 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +3 -3
- package/dist/resources/extensions/gsd/auto-start.js +6 -1
- package/dist/resources/extensions/gsd/auto.js +6 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +126 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +233 -0
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +59 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +38 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +156 -0
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +46 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +300 -0
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +278 -0
- package/dist/resources/extensions/gsd/commands/context.js +84 -0
- package/dist/resources/extensions/gsd/commands/dispatcher.js +21 -0
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +72 -0
- package/dist/resources/extensions/gsd/commands/handlers/core.js +246 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +166 -0
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +94 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +102 -0
- package/dist/resources/extensions/gsd/commands/index.js +11 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +8 -1190
- package/dist/resources/extensions/gsd/dashboard-overlay.js +9 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +80 -10
- package/dist/resources/extensions/gsd/doctor.js +32 -2
- package/dist/resources/extensions/gsd/export-html.js +46 -0
- package/dist/resources/extensions/gsd/files.js +1 -1
- package/dist/resources/extensions/gsd/health-widget.js +1 -1
- package/dist/resources/extensions/gsd/index.js +4 -1115
- package/dist/resources/extensions/gsd/progress-score.js +20 -1
- package/dist/resources/extensions/gsd/prompts/forensics.md +121 -46
- package/dist/resources/extensions/gsd/visualizer-data.js +26 -1
- package/dist/resources/extensions/gsd/visualizer-views.js +52 -0
- package/dist/welcome-screen.d.ts +3 -2
- package/dist/welcome-screen.js +66 -22
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +107 -24
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js +70 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +2 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +244 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +58 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +54 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +63 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +38 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -457
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +122 -23
- package/packages/pi-coding-agent/src/core/skill-tool.test.ts +89 -0
- package/packages/pi-coding-agent/src/core/skills.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +302 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +59 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +68 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +71 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +37 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +18 -510
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/index.ts +11 -0
- package/src/resources/extensions/get-secrets-from-user.ts +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +10 -0
- package/src/resources/extensions/gsd/auto-loop.ts +1075 -921
- package/src/resources/extensions/gsd/auto-post-unit.ts +10 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +3 -3
- package/src/resources/extensions/gsd/auto-start.ts +6 -1
- package/src/resources/extensions/gsd/auto.ts +13 -10
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +142 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +238 -0
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +90 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +46 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +167 -0
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +55 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +340 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +51 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +301 -0
- package/src/resources/extensions/gsd/commands/context.ts +101 -0
- package/src/resources/extensions/gsd/commands/dispatcher.ts +32 -0
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +74 -0
- package/src/resources/extensions/gsd/commands/handlers/core.ts +274 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +169 -0
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +118 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +109 -0
- package/src/resources/extensions/gsd/commands/index.ts +14 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +10 -1329
- package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +106 -10
- package/src/resources/extensions/gsd/doctor.ts +47 -3
- package/src/resources/extensions/gsd/export-html.ts +51 -0
- package/src/resources/extensions/gsd/files.ts +1 -1
- package/src/resources/extensions/gsd/health-widget.ts +2 -1
- package/src/resources/extensions/gsd/index.ts +12 -1314
- package/src/resources/extensions/gsd/progress-score.ts +23 -0
- package/src/resources/extensions/gsd/prompts/forensics.md +121 -46
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +13 -9
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -16
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +10 -10
- package/src/resources/extensions/gsd/visualizer-data.ts +51 -1
- package/src/resources/extensions/gsd/visualizer-views.ts +58 -0
- /package/dist/resources/extensions/{env-utils.js → gsd/env-utils.js} +0 -0
- /package/src/resources/extensions/{env-utils.ts → gsd/env-utils.ts} +0 -0
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
} from "@gsd/pi-agent-core";
|
|
26
26
|
import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@gsd/pi-ai";
|
|
27
27
|
import { modelsAreEqual, resetApiProviders, supportsXhigh } from "@gsd/pi-ai";
|
|
28
|
+
import { Type } from "@sinclair/typebox";
|
|
28
29
|
import { getDocsPath } from "../config.js";
|
|
29
30
|
import { getErrorMessage } from "../utils/error.js";
|
|
30
31
|
import { theme } from "../modes/interactive/theme/theme.js";
|
|
@@ -732,9 +733,10 @@ export class AgentSession {
|
|
|
732
733
|
* Changes take effect on the next agent turn.
|
|
733
734
|
*/
|
|
734
735
|
setActiveToolsByName(toolNames: string[]): void {
|
|
736
|
+
const requestedToolNames = [...new Set([...toolNames, ...this._getBuiltinToolNames()])];
|
|
735
737
|
const tools: AgentTool[] = [];
|
|
736
738
|
const validToolNames: string[] = [];
|
|
737
|
-
for (const name of
|
|
739
|
+
for (const name of requestedToolNames) {
|
|
738
740
|
const tool = this._toolRegistry.get(name);
|
|
739
741
|
if (tool) {
|
|
740
742
|
tools.push(tool);
|
|
@@ -743,6 +745,7 @@ export class AgentSession {
|
|
|
743
745
|
}
|
|
744
746
|
this.agent.setTools(tools);
|
|
745
747
|
|
|
748
|
+
|
|
746
749
|
// Rebuild base system prompt with new tool set
|
|
747
750
|
this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
|
|
748
751
|
this.agent.setSystemPrompt(this._baseSystemPrompt);
|
|
@@ -858,6 +861,48 @@ export class AgentSession {
|
|
|
858
861
|
return Array.from(unique);
|
|
859
862
|
}
|
|
860
863
|
|
|
864
|
+
private _findSkillByName(skillName: string) {
|
|
865
|
+
return this.resourceLoader.getSkills().skills.find((skill) => skill.name === skillName);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
private _formatMissingSkillMessage(skillName: string): string {
|
|
869
|
+
const availableSkills = this.resourceLoader.getSkills().skills.map((skill) => skill.name).join(", ") || "(none)";
|
|
870
|
+
return `Skill "${skillName}" not found. Available skills: ${availableSkills}`;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
private _emitSkillExpansionError(skillFilePath: string, err: unknown): void {
|
|
874
|
+
this._extensionRunner?.emitError({
|
|
875
|
+
extensionPath: skillFilePath,
|
|
876
|
+
event: "skill_expansion",
|
|
877
|
+
error: getErrorMessage(err),
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
private _renderSkillInvocation(skill: { name: string; filePath: string; baseDir: string }, args?: string): string {
|
|
882
|
+
const content = readFileSync(skill.filePath, "utf-8");
|
|
883
|
+
const body = stripFrontmatter(content).trim();
|
|
884
|
+
const skillBlock = `<skill name="${skill.name}" location="${skill.filePath}">\nReferences are relative to ${skill.baseDir}.\n\n${body}\n</skill>`;
|
|
885
|
+
return args && args.trim() ? `${skillBlock}\n\n${args.trim()}` : skillBlock;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
private _expandSkillByName(skillName: string, args?: string): string {
|
|
889
|
+
const skill = this._findSkillByName(skillName);
|
|
890
|
+
if (!skill) {
|
|
891
|
+
throw new Error(this._formatMissingSkillMessage(skillName));
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
try {
|
|
895
|
+
return this._renderSkillInvocation(skill, args);
|
|
896
|
+
} catch (err) {
|
|
897
|
+
this._emitSkillExpansionError(skill.filePath, err);
|
|
898
|
+
throw err;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
private _formatSkillInvocation(skillName: string, args?: string): string {
|
|
903
|
+
return this._expandSkillByName(skillName, args);
|
|
904
|
+
}
|
|
905
|
+
|
|
861
906
|
private _rebuildSystemPrompt(toolNames: string[]): string {
|
|
862
907
|
const validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));
|
|
863
908
|
const toolSnippets: Record<string, string> = {};
|
|
@@ -1103,25 +1148,78 @@ export class AgentSession {
|
|
|
1103
1148
|
const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
|
|
1104
1149
|
const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
|
|
1105
1150
|
|
|
1106
|
-
|
|
1107
|
-
if (!skill) return text; // Unknown skill, pass through
|
|
1151
|
+
if (!this._findSkillByName(skillName)) return text;
|
|
1108
1152
|
|
|
1109
1153
|
try {
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
return args ? `${skillBlock}\n\n${args}` : skillBlock;
|
|
1114
|
-
} catch (err) {
|
|
1115
|
-
// Emit error like extension commands do
|
|
1116
|
-
this._extensionRunner?.emitError({
|
|
1117
|
-
extensionPath: skill.filePath,
|
|
1118
|
-
event: "skill_expansion",
|
|
1119
|
-
error: getErrorMessage(err),
|
|
1120
|
-
});
|
|
1121
|
-
return text; // Return original on error
|
|
1154
|
+
return this._formatSkillInvocation(skillName, args);
|
|
1155
|
+
} catch {
|
|
1156
|
+
return text;
|
|
1122
1157
|
}
|
|
1123
1158
|
}
|
|
1124
1159
|
|
|
1160
|
+
private _createBuiltInSkillTool(): AgentTool {
|
|
1161
|
+
const skillSchema = Type.Object({
|
|
1162
|
+
skill: Type.String({ description: "The skill name. E.g., 'commit', 'review-pr', or 'pdf'" }),
|
|
1163
|
+
args: Type.Optional(Type.String({ description: "Optional arguments for the skill" })),
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
return {
|
|
1167
|
+
name: "Skill",
|
|
1168
|
+
label: "Skill",
|
|
1169
|
+
description:
|
|
1170
|
+
"Execute a skill within the main conversation. Use this tool when users ask for a slash command or reference a skill by name. Returns the expanded skill block and appends args after it.",
|
|
1171
|
+
parameters: skillSchema,
|
|
1172
|
+
execute: async (_toolCallId, params: unknown) => {
|
|
1173
|
+
const input = params as { skill: string; args?: string };
|
|
1174
|
+
try {
|
|
1175
|
+
return {
|
|
1176
|
+
content: [
|
|
1177
|
+
{
|
|
1178
|
+
type: "text",
|
|
1179
|
+
text: this._expandSkillByName(input.skill, input.args),
|
|
1180
|
+
},
|
|
1181
|
+
],
|
|
1182
|
+
details: undefined,
|
|
1183
|
+
};
|
|
1184
|
+
} catch (err) {
|
|
1185
|
+
return {
|
|
1186
|
+
content: [{ type: "text", text: getErrorMessage(err) }],
|
|
1187
|
+
details: undefined,
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
},
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
private _getBuiltinToolNames(): string[] {
|
|
1195
|
+
return this._getBuiltinTools().map((tool) => tool.name);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
private _getBuiltinTools(): AgentTool[] {
|
|
1199
|
+
return [this._createBuiltInSkillTool()];
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
private _getRegisteredToolDefinitions(): ToolDefinition[] {
|
|
1203
|
+
const registeredTools = this._extensionRunner?.getAllRegisteredTools() ?? [];
|
|
1204
|
+
return registeredTools.map((tool) => tool.definition);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
private _getBuiltinToolDefinitions(): ToolDefinition[] {
|
|
1208
|
+
return this._getBuiltinTools().map((tool) => ({
|
|
1209
|
+
name: tool.name,
|
|
1210
|
+
label: tool.label,
|
|
1211
|
+
description: tool.description,
|
|
1212
|
+
parameters: tool.parameters,
|
|
1213
|
+
execute: async () => ({ content: [], details: undefined }),
|
|
1214
|
+
}));
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
getRenderableToolDefinition(toolName: string): ToolDefinition | undefined {
|
|
1218
|
+
return [...this._getBuiltinToolDefinitions(), ...this._getRegisteredToolDefinitions()].find(
|
|
1219
|
+
(tool) => tool.name === toolName,
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1125
1223
|
/**
|
|
1126
1224
|
* Queue a steering message to interrupt the agent mid-run.
|
|
1127
1225
|
* Delivered after current tool execution, skips remaining tools.
|
|
@@ -1967,8 +2065,12 @@ export class AgentSession {
|
|
|
1967
2065
|
const wrappedExtensionTools = this._extensionRunner
|
|
1968
2066
|
? wrapRegisteredTools(allCustomTools, this._extensionRunner)
|
|
1969
2067
|
: [];
|
|
2068
|
+
const builtinTools = this._getBuiltinTools();
|
|
1970
2069
|
|
|
1971
2070
|
const toolRegistry = new Map(this._baseToolRegistry);
|
|
2071
|
+
for (const tool of builtinTools) {
|
|
2072
|
+
toolRegistry.set(tool.name, tool);
|
|
2073
|
+
}
|
|
1972
2074
|
for (const tool of wrappedExtensionTools as AgentTool[]) {
|
|
1973
2075
|
toolRegistry.set(tool.name, tool);
|
|
1974
2076
|
}
|
|
@@ -2694,14 +2796,11 @@ export class AgentSession {
|
|
|
2694
2796
|
async exportToHtml(outputPath?: string): Promise<string> {
|
|
2695
2797
|
const themeName = this.settingsManager.getTheme();
|
|
2696
2798
|
|
|
2697
|
-
// Create tool renderer
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
theme,
|
|
2703
|
-
});
|
|
2704
|
-
}
|
|
2799
|
+
// Create tool renderer for extension and built-in tool HTML rendering
|
|
2800
|
+
const toolRenderer = createToolHtmlRenderer({
|
|
2801
|
+
getToolDefinition: (name) => this.getRenderableToolDefinition(name),
|
|
2802
|
+
theme,
|
|
2803
|
+
});
|
|
2705
2804
|
|
|
2706
2805
|
return await exportSessionToHtml(this.sessionManager, this.state, {
|
|
2707
2806
|
outputPath,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
6
|
+
|
|
7
|
+
import { Agent } from "@gsd/pi-agent-core";
|
|
8
|
+
import { AuthStorage } from "./auth-storage.js";
|
|
9
|
+
import { AgentSession } from "./agent-session.js";
|
|
10
|
+
import { ModelRegistry } from "./model-registry.js";
|
|
11
|
+
import { DefaultResourceLoader } from "./resource-loader.js";
|
|
12
|
+
import { SessionManager } from "./session-manager.js";
|
|
13
|
+
import { SettingsManager } from "./settings-manager.js";
|
|
14
|
+
|
|
15
|
+
let testDir: string;
|
|
16
|
+
|
|
17
|
+
function writeSkill(cwd: string, name: string, description: string, body = `# ${name}\n`): string {
|
|
18
|
+
const skillDir = join(cwd, ".pi", "skills", name);
|
|
19
|
+
mkdirSync(skillDir, { recursive: true });
|
|
20
|
+
const skillPath = join(skillDir, "SKILL.md");
|
|
21
|
+
writeFileSync(skillPath, `---\nname: ${name}\ndescription: ${description}\n---\n\n${body}`);
|
|
22
|
+
return skillPath;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("Skill tool", () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
testDir = mkdtempSync(join(tmpdir(), "skill-tool-test-"));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
async function createSession() {
|
|
35
|
+
const agentDir = join(testDir, "agent-home");
|
|
36
|
+
const authStorage = AuthStorage.inMemory({});
|
|
37
|
+
const modelRegistry = new ModelRegistry(authStorage, join(agentDir, "models.json"));
|
|
38
|
+
const settingsManager = SettingsManager.inMemory();
|
|
39
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
40
|
+
cwd: testDir,
|
|
41
|
+
agentDir,
|
|
42
|
+
settingsManager,
|
|
43
|
+
noExtensions: true,
|
|
44
|
+
noPromptTemplates: true,
|
|
45
|
+
noThemes: true,
|
|
46
|
+
});
|
|
47
|
+
await resourceLoader.reload();
|
|
48
|
+
|
|
49
|
+
return new AgentSession({
|
|
50
|
+
agent: new Agent(),
|
|
51
|
+
sessionManager: SessionManager.inMemory(testDir),
|
|
52
|
+
settingsManager,
|
|
53
|
+
cwd: testDir,
|
|
54
|
+
resourceLoader,
|
|
55
|
+
modelRegistry,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
it("resolves a project-level skill to the exact skill block format", async () => {
|
|
60
|
+
const skillPath = writeSkill(
|
|
61
|
+
testDir,
|
|
62
|
+
"swift-testing",
|
|
63
|
+
"Use for Swift Testing assertions and verification patterns.",
|
|
64
|
+
"# Swift Testing\nUse this skill.\n",
|
|
65
|
+
);
|
|
66
|
+
const session = await createSession();
|
|
67
|
+
|
|
68
|
+
const tool = session.state.tools.find((entry) => entry.name === "Skill");
|
|
69
|
+
assert.ok(tool, "Skill tool should be registered");
|
|
70
|
+
|
|
71
|
+
const result = await tool.execute("call-1", { skill: "swift-testing" });
|
|
72
|
+
assert.equal(
|
|
73
|
+
result.content[0]?.type === "text" ? result.content[0].text : "",
|
|
74
|
+
`<skill name="swift-testing" location="${skillPath}">\nReferences are relative to ${join(testDir, ".pi", "skills", "swift-testing")}.\n\n# Swift Testing\nUse this skill.\n</skill>`,
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("returns a helpful error for unknown skills", async () => {
|
|
79
|
+
writeSkill(testDir, "swift-testing", "Use for Swift Testing assertions and verification patterns.");
|
|
80
|
+
const session = await createSession();
|
|
81
|
+
const tool = session.state.tools.find((entry) => entry.name === "Skill");
|
|
82
|
+
assert.ok(tool, "Skill tool should be registered");
|
|
83
|
+
|
|
84
|
+
const result = await tool.execute("call-2", { skill: "nonexistent" });
|
|
85
|
+
const message = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
86
|
+
assert.match(message, /^Skill "nonexistent" not found\. Available skills: /);
|
|
87
|
+
assert.match(message, /swift-testing/);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -299,7 +299,8 @@ export function formatSkillsForPrompt(skills: Skill[]): string {
|
|
|
299
299
|
|
|
300
300
|
const lines = [
|
|
301
301
|
"\n\nThe following skills provide specialized instructions for specific tasks.",
|
|
302
|
-
"Use the
|
|
302
|
+
"Use the Skill tool with the exact skill name from <available_skills> when the task matches its description.",
|
|
303
|
+
"If the Skill tool reports an unknown skill, do not guess: use an exact name from <available_skills> or tell the user the skill is unavailable.",
|
|
303
304
|
"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.",
|
|
304
305
|
"",
|
|
305
306
|
"<available_skills>",
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { Loader, Spacer, Text } from "@gsd/pi-tui";
|
|
2
|
+
|
|
3
|
+
import type { InteractiveModeEvent, InteractiveModeStateHost } from "../interactive-mode-state.js";
|
|
4
|
+
import { theme } from "../theme/theme.js";
|
|
5
|
+
import { AssistantMessageComponent } from "../components/assistant-message.js";
|
|
6
|
+
import { ToolExecutionComponent } from "../components/tool-execution.js";
|
|
7
|
+
import { appKey } from "../components/keybinding-hints.js";
|
|
8
|
+
|
|
9
|
+
export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
10
|
+
init: () => Promise<void>;
|
|
11
|
+
getMarkdownThemeWithSettings: () => any;
|
|
12
|
+
addMessageToChat: (message: any, options?: any) => void;
|
|
13
|
+
formatWebSearchResult: (content: unknown) => string;
|
|
14
|
+
getRegisteredToolDefinition: (toolName: string) => any;
|
|
15
|
+
checkShutdownRequested: () => Promise<void>;
|
|
16
|
+
rebuildChatFromMessages: () => void;
|
|
17
|
+
flushCompactionQueue: (options?: { willRetry?: boolean }) => Promise<void>;
|
|
18
|
+
showStatus: (message: string) => void;
|
|
19
|
+
showError: (message: string) => void;
|
|
20
|
+
updatePendingMessagesDisplay: () => void;
|
|
21
|
+
}, event: InteractiveModeEvent): Promise<void> {
|
|
22
|
+
if (!host.isInitialized) {
|
|
23
|
+
await host.init();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
host.footer.invalidate();
|
|
27
|
+
|
|
28
|
+
switch (event.type) {
|
|
29
|
+
case "agent_start":
|
|
30
|
+
if (host.retryEscapeHandler) {
|
|
31
|
+
host.defaultEditor.onEscape = host.retryEscapeHandler;
|
|
32
|
+
host.retryEscapeHandler = undefined;
|
|
33
|
+
}
|
|
34
|
+
if (host.retryLoader) {
|
|
35
|
+
host.retryLoader.stop();
|
|
36
|
+
host.retryLoader = undefined;
|
|
37
|
+
}
|
|
38
|
+
if (host.loadingAnimation) {
|
|
39
|
+
host.loadingAnimation.stop();
|
|
40
|
+
}
|
|
41
|
+
host.statusContainer.clear();
|
|
42
|
+
host.loadingAnimation = new Loader(
|
|
43
|
+
host.ui,
|
|
44
|
+
(spinner) => theme.fg("accent", spinner),
|
|
45
|
+
(text) => theme.fg("muted", text),
|
|
46
|
+
host.defaultWorkingMessage,
|
|
47
|
+
);
|
|
48
|
+
host.statusContainer.addChild(host.loadingAnimation);
|
|
49
|
+
if (host.pendingWorkingMessage !== undefined) {
|
|
50
|
+
if (host.pendingWorkingMessage) {
|
|
51
|
+
host.loadingAnimation.setMessage(host.pendingWorkingMessage);
|
|
52
|
+
}
|
|
53
|
+
host.pendingWorkingMessage = undefined;
|
|
54
|
+
}
|
|
55
|
+
host.ui.requestRender();
|
|
56
|
+
break;
|
|
57
|
+
|
|
58
|
+
case "message_start":
|
|
59
|
+
if (event.message.role === "custom") {
|
|
60
|
+
host.addMessageToChat(event.message);
|
|
61
|
+
host.ui.requestRender();
|
|
62
|
+
} else if (event.message.role === "user") {
|
|
63
|
+
host.addMessageToChat(event.message);
|
|
64
|
+
host.updatePendingMessagesDisplay();
|
|
65
|
+
host.ui.requestRender();
|
|
66
|
+
} else if (event.message.role === "assistant") {
|
|
67
|
+
host.streamingComponent = new AssistantMessageComponent(
|
|
68
|
+
undefined,
|
|
69
|
+
host.hideThinkingBlock,
|
|
70
|
+
host.getMarkdownThemeWithSettings(),
|
|
71
|
+
);
|
|
72
|
+
host.streamingMessage = event.message;
|
|
73
|
+
host.chatContainer.addChild(host.streamingComponent);
|
|
74
|
+
host.streamingComponent.updateContent(host.streamingMessage);
|
|
75
|
+
host.ui.requestRender();
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case "message_update":
|
|
80
|
+
if (host.streamingComponent && event.message.role === "assistant") {
|
|
81
|
+
host.streamingMessage = event.message;
|
|
82
|
+
host.streamingComponent.updateContent(host.streamingMessage);
|
|
83
|
+
for (const content of host.streamingMessage.content) {
|
|
84
|
+
if (content.type === "toolCall") {
|
|
85
|
+
if (!host.pendingTools.has(content.id)) {
|
|
86
|
+
const component = new ToolExecutionComponent(
|
|
87
|
+
content.name,
|
|
88
|
+
content.arguments,
|
|
89
|
+
{ showImages: host.settingsManager.getShowImages() },
|
|
90
|
+
host.getRegisteredToolDefinition(content.name),
|
|
91
|
+
host.ui,
|
|
92
|
+
);
|
|
93
|
+
component.setExpanded(host.toolOutputExpanded);
|
|
94
|
+
host.chatContainer.addChild(component);
|
|
95
|
+
host.pendingTools.set(content.id, component);
|
|
96
|
+
} else {
|
|
97
|
+
host.pendingTools.get(content.id)?.updateArgs(content.arguments);
|
|
98
|
+
}
|
|
99
|
+
} else if (content.type === "serverToolUse") {
|
|
100
|
+
if (!host.pendingTools.has(content.id)) {
|
|
101
|
+
const component = new ToolExecutionComponent(
|
|
102
|
+
content.name,
|
|
103
|
+
content.input ?? {},
|
|
104
|
+
{ showImages: host.settingsManager.getShowImages() },
|
|
105
|
+
undefined,
|
|
106
|
+
host.ui,
|
|
107
|
+
);
|
|
108
|
+
component.setExpanded(host.toolOutputExpanded);
|
|
109
|
+
host.chatContainer.addChild(component);
|
|
110
|
+
host.pendingTools.set(content.id, component);
|
|
111
|
+
}
|
|
112
|
+
} else if (content.type === "webSearchResult") {
|
|
113
|
+
const component = host.pendingTools.get(content.toolUseId);
|
|
114
|
+
if (component) {
|
|
115
|
+
const searchContent = content.content;
|
|
116
|
+
const isError = searchContent && typeof searchContent === "object" && "type" in (searchContent as any) && (searchContent as any).type === "web_search_tool_result_error";
|
|
117
|
+
component.updateResult({
|
|
118
|
+
content: [{ type: "text", text: host.formatWebSearchResult(searchContent) }],
|
|
119
|
+
isError: !!isError,
|
|
120
|
+
});
|
|
121
|
+
host.pendingTools.delete(content.toolUseId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
host.ui.requestRender();
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
|
|
129
|
+
case "message_end":
|
|
130
|
+
if (event.message.role === "user") break;
|
|
131
|
+
if (host.streamingComponent && event.message.role === "assistant") {
|
|
132
|
+
host.streamingMessage = event.message;
|
|
133
|
+
let errorMessage: string | undefined;
|
|
134
|
+
if (host.streamingMessage.stopReason === "aborted") {
|
|
135
|
+
const retryAttempt = host.session.retryAttempt;
|
|
136
|
+
errorMessage = retryAttempt > 0
|
|
137
|
+
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
138
|
+
: "Operation aborted";
|
|
139
|
+
host.streamingMessage.errorMessage = errorMessage;
|
|
140
|
+
}
|
|
141
|
+
host.streamingComponent.updateContent(host.streamingMessage);
|
|
142
|
+
if (host.streamingMessage.stopReason === "aborted" || host.streamingMessage.stopReason === "error") {
|
|
143
|
+
if (!errorMessage) {
|
|
144
|
+
errorMessage = host.streamingMessage.errorMessage || "Error";
|
|
145
|
+
}
|
|
146
|
+
for (const [, component] of host.pendingTools.entries()) {
|
|
147
|
+
component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
|
|
148
|
+
}
|
|
149
|
+
host.pendingTools.clear();
|
|
150
|
+
} else {
|
|
151
|
+
for (const [, component] of host.pendingTools.entries()) {
|
|
152
|
+
component.setArgsComplete();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
host.streamingComponent = undefined;
|
|
156
|
+
host.streamingMessage = undefined;
|
|
157
|
+
host.footer.invalidate();
|
|
158
|
+
}
|
|
159
|
+
host.ui.requestRender();
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
case "tool_execution_start":
|
|
163
|
+
if (!host.pendingTools.has(event.toolCallId)) {
|
|
164
|
+
const component = new ToolExecutionComponent(
|
|
165
|
+
event.toolName,
|
|
166
|
+
event.args,
|
|
167
|
+
{ showImages: host.settingsManager.getShowImages() },
|
|
168
|
+
host.getRegisteredToolDefinition(event.toolName),
|
|
169
|
+
host.ui,
|
|
170
|
+
);
|
|
171
|
+
component.setExpanded(host.toolOutputExpanded);
|
|
172
|
+
host.chatContainer.addChild(component);
|
|
173
|
+
host.pendingTools.set(event.toolCallId, component);
|
|
174
|
+
host.ui.requestRender();
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
|
|
178
|
+
case "tool_execution_update": {
|
|
179
|
+
const component = host.pendingTools.get(event.toolCallId);
|
|
180
|
+
if (component) {
|
|
181
|
+
component.updateResult({ ...event.partialResult, isError: false }, true);
|
|
182
|
+
host.ui.requestRender();
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
case "tool_execution_end": {
|
|
188
|
+
const component = host.pendingTools.get(event.toolCallId);
|
|
189
|
+
if (component) {
|
|
190
|
+
component.updateResult({ ...event.result, isError: event.isError });
|
|
191
|
+
host.pendingTools.delete(event.toolCallId);
|
|
192
|
+
host.ui.requestRender();
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
case "agent_end":
|
|
198
|
+
if (host.loadingAnimation) {
|
|
199
|
+
host.loadingAnimation.stop();
|
|
200
|
+
host.loadingAnimation = undefined;
|
|
201
|
+
host.statusContainer.clear();
|
|
202
|
+
}
|
|
203
|
+
if (host.streamingComponent) {
|
|
204
|
+
host.chatContainer.removeChild(host.streamingComponent);
|
|
205
|
+
host.streamingComponent = undefined;
|
|
206
|
+
host.streamingMessage = undefined;
|
|
207
|
+
}
|
|
208
|
+
host.pendingTools.clear();
|
|
209
|
+
await host.checkShutdownRequested();
|
|
210
|
+
host.ui.requestRender();
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
case "auto_compaction_start":
|
|
214
|
+
host.autoCompactionEscapeHandler = host.defaultEditor.onEscape;
|
|
215
|
+
host.defaultEditor.onEscape = () => host.session.abortCompaction();
|
|
216
|
+
host.statusContainer.clear();
|
|
217
|
+
host.autoCompactionLoader = new Loader(
|
|
218
|
+
host.ui,
|
|
219
|
+
(spinner) => theme.fg("accent", spinner),
|
|
220
|
+
(text) => theme.fg("muted", text),
|
|
221
|
+
`${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... (${appKey(host.keybindings, "interrupt")} to cancel)`,
|
|
222
|
+
);
|
|
223
|
+
host.statusContainer.addChild(host.autoCompactionLoader);
|
|
224
|
+
host.ui.requestRender();
|
|
225
|
+
break;
|
|
226
|
+
|
|
227
|
+
case "auto_compaction_end":
|
|
228
|
+
if (host.autoCompactionEscapeHandler) {
|
|
229
|
+
host.defaultEditor.onEscape = host.autoCompactionEscapeHandler;
|
|
230
|
+
host.autoCompactionEscapeHandler = undefined;
|
|
231
|
+
}
|
|
232
|
+
if (host.autoCompactionLoader) {
|
|
233
|
+
host.autoCompactionLoader.stop();
|
|
234
|
+
host.autoCompactionLoader = undefined;
|
|
235
|
+
host.statusContainer.clear();
|
|
236
|
+
}
|
|
237
|
+
if (event.aborted) {
|
|
238
|
+
host.showStatus("Auto-compaction cancelled");
|
|
239
|
+
} else if (event.result) {
|
|
240
|
+
host.chatContainer.clear();
|
|
241
|
+
host.rebuildChatFromMessages();
|
|
242
|
+
host.addMessageToChat({
|
|
243
|
+
role: "compactionSummary",
|
|
244
|
+
tokensBefore: event.result.tokensBefore,
|
|
245
|
+
summary: event.result.summary,
|
|
246
|
+
timestamp: Date.now(),
|
|
247
|
+
});
|
|
248
|
+
host.footer.invalidate();
|
|
249
|
+
} else if (event.errorMessage) {
|
|
250
|
+
host.chatContainer.addChild(new Spacer(1));
|
|
251
|
+
host.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
|
|
252
|
+
}
|
|
253
|
+
void host.flushCompactionQueue({ willRetry: event.willRetry });
|
|
254
|
+
host.ui.requestRender();
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
case "auto_retry_start":
|
|
258
|
+
host.retryEscapeHandler = host.defaultEditor.onEscape;
|
|
259
|
+
host.defaultEditor.onEscape = () => host.session.abortRetry();
|
|
260
|
+
host.statusContainer.clear();
|
|
261
|
+
host.retryLoader = new Loader(
|
|
262
|
+
host.ui,
|
|
263
|
+
(spinner) => theme.fg("warning", spinner),
|
|
264
|
+
(text) => theme.fg("muted", text),
|
|
265
|
+
`Retrying (${event.attempt}/${event.maxAttempts}) in ${Math.round(event.delayMs / 1000)}s... (${appKey(host.keybindings, "interrupt")} to cancel)`,
|
|
266
|
+
);
|
|
267
|
+
host.statusContainer.addChild(host.retryLoader);
|
|
268
|
+
host.ui.requestRender();
|
|
269
|
+
break;
|
|
270
|
+
|
|
271
|
+
case "auto_retry_end":
|
|
272
|
+
if (host.retryEscapeHandler) {
|
|
273
|
+
host.defaultEditor.onEscape = host.retryEscapeHandler;
|
|
274
|
+
host.retryEscapeHandler = undefined;
|
|
275
|
+
}
|
|
276
|
+
if (host.retryLoader) {
|
|
277
|
+
host.retryLoader.stop();
|
|
278
|
+
host.retryLoader = undefined;
|
|
279
|
+
host.statusContainer.clear();
|
|
280
|
+
}
|
|
281
|
+
if (!event.success) {
|
|
282
|
+
host.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
|
|
283
|
+
}
|
|
284
|
+
host.ui.requestRender();
|
|
285
|
+
break;
|
|
286
|
+
|
|
287
|
+
case "fallback_provider_switch":
|
|
288
|
+
host.showStatus(`Switched from ${event.from} → ${event.to} (${event.reason})`);
|
|
289
|
+
host.ui.requestRender();
|
|
290
|
+
break;
|
|
291
|
+
|
|
292
|
+
case "fallback_provider_restored":
|
|
293
|
+
host.showStatus(`Restored to ${event.provider}`);
|
|
294
|
+
host.ui.requestRender();
|
|
295
|
+
break;
|
|
296
|
+
|
|
297
|
+
case "fallback_chain_exhausted":
|
|
298
|
+
host.showError(event.reason);
|
|
299
|
+
host.ui.requestRender();
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ExtensionUIContext } from "../../../core/extensions/index.js";
|
|
2
|
+
|
|
3
|
+
import { Theme, getAvailableThemesWithPaths, getThemeByName, setTheme, setThemeInstance, theme } from "../theme/theme.js";
|
|
4
|
+
import { appKey } from "../components/keybinding-hints.js";
|
|
5
|
+
|
|
6
|
+
export function createExtensionUIContext(host: any): ExtensionUIContext {
|
|
7
|
+
return {
|
|
8
|
+
select: (title, options, opts) => host.showExtensionSelector(title, options, opts),
|
|
9
|
+
confirm: (title, message, opts) => host.showExtensionConfirm(title, message, opts),
|
|
10
|
+
input: (title, placeholder, opts) => host.showExtensionInput(title, placeholder, opts),
|
|
11
|
+
notify: (message, type) => host.showExtensionNotify(message, type),
|
|
12
|
+
onTerminalInput: (handler) => host.addExtensionTerminalInputListener(handler),
|
|
13
|
+
setStatus: (key, text) => host.setExtensionStatus(key, text),
|
|
14
|
+
setWorkingMessage: (message) => {
|
|
15
|
+
if (host.loadingAnimation) {
|
|
16
|
+
if (message) {
|
|
17
|
+
host.loadingAnimation.setMessage(message);
|
|
18
|
+
} else {
|
|
19
|
+
host.loadingAnimation.setMessage(`${host.defaultWorkingMessage} (${appKey(host.keybindings, "interrupt")} to interrupt)`);
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
host.pendingWorkingMessage = message;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
setWidget: (key, content, options) => host.setExtensionWidget(key, content, options),
|
|
26
|
+
setFooter: (factory) => host.setExtensionFooter(factory),
|
|
27
|
+
setHeader: (factory) => host.setExtensionHeader(factory),
|
|
28
|
+
setTitle: (title) => host.ui.terminal.setTitle(title),
|
|
29
|
+
custom: (factory, options) => host.showExtensionCustom(factory, options),
|
|
30
|
+
pasteToEditor: (text) => host.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
|
|
31
|
+
setEditorText: (text) => host.editor.setText(text),
|
|
32
|
+
getEditorText: () => host.editor.getText(),
|
|
33
|
+
editor: (title, prefill) => host.showExtensionEditor(title, prefill),
|
|
34
|
+
setEditorComponent: (factory) => host.setCustomEditorComponent(factory),
|
|
35
|
+
get theme() {
|
|
36
|
+
return theme;
|
|
37
|
+
},
|
|
38
|
+
getAllThemes: () => getAvailableThemesWithPaths(),
|
|
39
|
+
getTheme: (name) => getThemeByName(name),
|
|
40
|
+
setTheme: (themeOrName) => {
|
|
41
|
+
if (themeOrName instanceof Theme) {
|
|
42
|
+
setThemeInstance(themeOrName);
|
|
43
|
+
host.ui.requestRender();
|
|
44
|
+
return { success: true };
|
|
45
|
+
}
|
|
46
|
+
const result = setTheme(themeOrName, true);
|
|
47
|
+
if (result.success) {
|
|
48
|
+
if (host.settingsManager.getTheme() !== themeOrName) {
|
|
49
|
+
host.settingsManager.setTheme(themeOrName);
|
|
50
|
+
}
|
|
51
|
+
host.ui.requestRender();
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
},
|
|
55
|
+
getToolsExpanded: () => host.toolOutputExpanded,
|
|
56
|
+
setToolsExpanded: (expanded) => host.setToolsExpanded(expanded),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|