@kolisachint/hoocode-agent 0.5.30 → 0.5.32
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/CHANGELOG.md +234 -0
- package/dist/core/agent-selection-eval.d.ts +91 -0
- package/dist/core/agent-selection-eval.d.ts.map +1 -0
- package/dist/core/agent-selection-eval.js +186 -0
- package/dist/core/agent-selection-eval.js.map +1 -0
- package/dist/core/agent-session-runtime.d.ts +28 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +61 -2
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/builtin-skills.d.ts +61 -0
- package/dist/core/builtin-skills.d.ts.map +1 -0
- package/dist/core/builtin-skills.js +106 -0
- package/dist/core/builtin-skills.js.map +1 -0
- package/dist/core/extensions/plugins/default-marketplace/.agents-plugin/marketplace.json +5 -1
- package/dist/core/extensions/plugins/trigger-judge.d.ts +42 -0
- package/dist/core/extensions/plugins/trigger-judge.d.ts.map +1 -0
- package/dist/core/extensions/plugins/trigger-judge.js +121 -0
- package/dist/core/extensions/plugins/trigger-judge.js.map +1 -0
- package/dist/core/external-tools.d.ts +67 -0
- package/dist/core/external-tools.d.ts.map +1 -0
- package/dist/core/external-tools.js +120 -0
- package/dist/core/external-tools.js.map +1 -0
- package/dist/core/keybindings.d.ts +102 -30
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +123 -29
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/mode-prompts.d.ts +15 -3
- package/dist/core/mode-prompts.d.ts.map +1 -1
- package/dist/core/mode-prompts.js +17 -29
- package/dist/core/mode-prompts.js.map +1 -1
- package/dist/core/settings-defaults.d.ts +1 -1
- package/dist/core/settings-defaults.d.ts.map +1 -1
- package/dist/core/settings-defaults.js +1 -1
- package/dist/core/settings-defaults.js.map +1 -1
- package/dist/core/settings-manager.d.ts +10 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +19 -8
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-types.d.ts +1 -1
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +5 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tool-output-view.d.ts +33 -0
- package/dist/core/tool-output-view.d.ts.map +1 -0
- package/dist/core/tool-output-view.js +31 -0
- package/dist/core/tool-output-view.js.map +1 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +4 -0
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/propose-plugin.d.ts.map +1 -1
- package/dist/core/tools/propose-plugin.js +7 -7
- package/dist/core/tools/propose-plugin.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +7 -1
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +12 -24
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/extensions/core/modes.d.ts.map +1 -1
- package/dist/extensions/core/modes.js +21 -23
- package/dist/extensions/core/modes.js.map +1 -1
- package/dist/extensions/core/scaffold.d.ts.map +1 -1
- package/dist/extensions/core/scaffold.js +28 -24
- package/dist/extensions/core/scaffold.js.map +1 -1
- package/dist/init-templates.generated.d.ts +3 -0
- package/dist/init-templates.generated.d.ts.map +1 -1
- package/dist/init-templates.generated.js +12 -0
- package/dist/init-templates.generated.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +14 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/command-executor.d.ts +20 -0
- package/dist/modes/interactive/command-executor.d.ts.map +1 -1
- package/dist/modes/interactive/command-executor.js +138 -9
- package/dist/modes/interactive/command-executor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +4 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +31 -2
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +2 -2
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +2 -2
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +10 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +165 -23
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-chain-summary.d.ts +65 -0
- package/dist/modes/interactive/components/tool-chain-summary.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-chain-summary.js +216 -0
- package/dist/modes/interactive/components/tool-chain-summary.js.map +1 -0
- package/dist/modes/interactive/components/tool-chain.d.ts +61 -0
- package/dist/modes/interactive/components/tool-chain.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-chain.js +156 -0
- package/dist/modes/interactive/components/tool-chain.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts +55 -9
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +173 -33
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-signal.d.ts +50 -0
- package/dist/modes/interactive/components/tool-signal.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-signal.js +151 -0
- package/dist/modes/interactive/components/tool-signal.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +50 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +244 -29
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/resource-display.d.ts +9 -0
- package/dist/modes/interactive/resource-display.d.ts.map +1 -1
- package/dist/modes/interactive/resource-display.js +13 -0
- package/dist/modes/interactive/resource-display.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +30 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +35 -0
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/modes.md +4 -0
- package/docs/plugins.md +10 -0
- package/docs/settings.md +42 -0
- package/docs/skills.md +30 -0
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +5 -4
- package/templates/prompts/grill-bridge.md +1 -0
- package/templates/prompts/grill-me.md +7 -0
- package/templates/prompts/grill-plan.md +9 -0
- package/templates/prompts/task-background-agents.md +2 -0
- package/templates/prompts/task-background-none.md +1 -0
- package/templates/prompts/task-main.md +19 -0
- package/templates/skills/plugin-authoring/SKILL.md +81 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slash-commands.js","sourceRoot":"","sources":["../../src/core/slash-commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAiBxC,MAAM,CAAC,MAAM,sBAAsB,GAAuC;IACzE,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,oBAAoB,EAAE;IACvD,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,kCAAkC,EAAE;IAClE,EAAE,IAAI,EAAE,eAAe,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"slash-commands.js","sourceRoot":"","sources":["../../src/core/slash-commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAiBxC,MAAM,CAAC,MAAM,sBAAsB,GAAuC;IACzE,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,oBAAoB,EAAE;IACvD,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,kCAAkC,EAAE;IAClE,EAAE,IAAI,EAAE,eAAe,EAAE,WAAW,EAAE,yCAAyC,EAAE;IACjF,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8DAA8D,EAAE;IAC/F,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+CAA+C,EAAE;IAChF,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,uCAAuC,EAAE;IACvE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,sCAAsC,EAAE;IACrE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,0BAA0B,EAAE;IACzD,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,6BAA6B,EAAE;IAC/D,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,wBAAwB,EAAE;IAC5D,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,6BAA6B,EAAE;IAC/D,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,gDAAgD,EAAE;IAC/E,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,uDAAuD,EAAE;IACvF,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,yCAAyC,EAAE;IACxE,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,mCAAmC,EAAE;IACnE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gCAAgC,EAAE;IACjE,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE;IACnD,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,sCAAsC,EAAE;IACxE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,4BAA4B,EAAE;IAC7D;QACC,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,2FAA2F;KACxG;IACD,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6DAA6D,EAAE;IAC9F,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,QAAQ,EAAE,EAAE;IACjD,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,oDAAoD,EAAE;CACvF,CAAC","sourcesContent":["import { APP_NAME } from \"../config.js\";\nimport type { SourceInfo } from \"./source-info.js\";\n\nexport type SlashCommandSource = \"extension\" | \"prompt\" | \"skill\";\n\nexport interface SlashCommandInfo {\n\tname: string;\n\tdescription?: string;\n\tsource: SlashCommandSource;\n\tsourceInfo: SourceInfo;\n}\n\nexport interface BuiltinSlashCommand {\n\tname: string;\n\tdescription: string;\n}\n\nexport const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [\n\t{ name: \"settings\", description: \"Open settings menu\" },\n\t{ name: \"model\", description: \"Select model (opens selector UI)\" },\n\t{ name: \"scoped-models\", description: \"Enable/disable models for model cycling\" },\n\t{ name: \"export\", description: \"Export session (HTML default, or specify path: .html/.jsonl)\" },\n\t{ name: \"import\", description: \"Import and resume a session from a JSONL file\" },\n\t{ name: \"share\", description: \"Share session as a secret GitHub gist\" },\n\t{ name: \"copy\", description: \"Copy last agent message to clipboard\" },\n\t{ name: \"name\", description: \"Set session display name\" },\n\t{ name: \"session\", description: \"Show session info and stats\" },\n\t{ name: \"changelog\", description: \"Show changelog entries\" },\n\t{ name: \"hotkeys\", description: \"Show all keyboard shortcuts\" },\n\t{ name: \"fork\", description: \"Create a new fork from a previous user message\" },\n\t{ name: \"clone\", description: \"Duplicate the current session at the current position\" },\n\t{ name: \"tree\", description: \"Navigate session tree (switch branches)\" },\n\t{ name: \"login\", description: \"Configure provider authentication\" },\n\t{ name: \"logout\", description: \"Remove provider authentication\" },\n\t{ name: \"new\", description: \"Start a new session\" },\n\t{ name: \"compact\", description: \"Manually compact the session context\" },\n\t{ name: \"resume\", description: \"Resume a different session\" },\n\t{\n\t\tname: \"cd\",\n\t\tdescription: \"Change working directory: /cd <path> (bare = home, - = previous). Starts a session there.\",\n\t},\n\t{ name: \"reload\", description: \"Reload keybindings, extensions, skills, prompts, and themes\" },\n\t{ name: \"quit\", description: `Quit ${APP_NAME}` },\n\t{ name: \"subagent\", description: \"Spawn a subagent directly: /subagent <mode> <task>\" },\n];\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much of a tool call the transcript shows. Three stops on one dial, least
|
|
3
|
+
* to most, cycled with `app.view.cycleForward` / `app.view.cycleBackward`:
|
|
4
|
+
*
|
|
5
|
+
* - `radar` — one line per *chain*: a run of consecutive tool calls collapsed to
|
|
6
|
+
* the shape it had while working, or what it amounted to once it is over.
|
|
7
|
+
* - `glance` — the tool's own call line, body folded away. The default.
|
|
8
|
+
* - `full` — call line plus the result body, truncated then expandable.
|
|
9
|
+
*
|
|
10
|
+
* The expand key (`app.tools.expand`) is orthogonal: it opens what is in front
|
|
11
|
+
* of you without moving the dial.
|
|
12
|
+
*
|
|
13
|
+
* This lives in core rather than next to the renderer because the settings
|
|
14
|
+
* manager persists it, and core must not reach into the interactive UI.
|
|
15
|
+
*/
|
|
16
|
+
export type ToolOutputView = "radar" | "glance" | "full";
|
|
17
|
+
/** The dial's order, least to most. Cycling wraps at both ends. */
|
|
18
|
+
export declare const TOOL_OUTPUT_VIEWS: readonly ToolOutputView[];
|
|
19
|
+
/** One-line description per view, shared by the settings pane and `/hotkeys`. */
|
|
20
|
+
export declare const TOOL_OUTPUT_VIEW_DESCRIPTIONS: Record<ToolOutputView, string>;
|
|
21
|
+
/**
|
|
22
|
+
* Values written by versions that had `collapsed` / `peek` / `standard`.
|
|
23
|
+
*
|
|
24
|
+
* `peek` was already what most people wanted and `standard` was the default
|
|
25
|
+
* nobody kept, so the mapping is not a rename: `peek` becomes the new default
|
|
26
|
+
* `glance`, and `collapsed` — which hid a call's result with no way to see it —
|
|
27
|
+
* becomes `radar`, which hides the same body but says what came back.
|
|
28
|
+
*/
|
|
29
|
+
export declare const LEGACY_TOOL_OUTPUT_VIEWS: Record<string, ToolOutputView>;
|
|
30
|
+
export declare function isToolOutputView(value: unknown): value is ToolOutputView;
|
|
31
|
+
/** Next stop on the dial, wrapping at both ends. */
|
|
32
|
+
export declare function cycleToolOutputView(current: ToolOutputView, direction: "forward" | "backward"): ToolOutputView;
|
|
33
|
+
//# sourceMappingURL=tool-output-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-output-view.d.ts","sourceRoot":"","sources":["../../src/core/tool-output-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEzD,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,EAAE,SAAS,cAAc,EAAgC,CAAC;AAExF,iFAAiF;AACjF,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAIxE,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAInE,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAExE;AAED,oDAAoD;AACpD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,cAAc,CAI9G","sourcesContent":["/**\n * How much of a tool call the transcript shows. Three stops on one dial, least\n * to most, cycled with `app.view.cycleForward` / `app.view.cycleBackward`:\n *\n * - `radar` — one line per *chain*: a run of consecutive tool calls collapsed to\n * the shape it had while working, or what it amounted to once it is over.\n * - `glance` — the tool's own call line, body folded away. The default.\n * - `full` — call line plus the result body, truncated then expandable.\n *\n * The expand key (`app.tools.expand`) is orthogonal: it opens what is in front\n * of you without moving the dial.\n *\n * This lives in core rather than next to the renderer because the settings\n * manager persists it, and core must not reach into the interactive UI.\n */\nexport type ToolOutputView = \"radar\" | \"glance\" | \"full\";\n\n/** The dial's order, least to most. Cycling wraps at both ends. */\nexport const TOOL_OUTPUT_VIEWS: readonly ToolOutputView[] = [\"radar\", \"glance\", \"full\"];\n\n/** One-line description per view, shared by the settings pane and `/hotkeys`. */\nexport const TOOL_OUTPUT_VIEW_DESCRIPTIONS: Record<ToolOutputView, string> = {\n\tradar: \"one line per run of tool calls; failures still show why they failed\",\n\tglance: \"the call line alone; failures still show why they failed\",\n\tfull: \"call line plus the result body\",\n};\n\n/**\n * Values written by versions that had `collapsed` / `peek` / `standard`.\n *\n * `peek` was already what most people wanted and `standard` was the default\n * nobody kept, so the mapping is not a rename: `peek` becomes the new default\n * `glance`, and `collapsed` — which hid a call's result with no way to see it —\n * becomes `radar`, which hides the same body but says what came back.\n */\nexport const LEGACY_TOOL_OUTPUT_VIEWS: Record<string, ToolOutputView> = {\n\tcollapsed: \"radar\",\n\tpeek: \"glance\",\n\tstandard: \"full\",\n};\n\nexport function isToolOutputView(value: unknown): value is ToolOutputView {\n\treturn typeof value === \"string\" && (TOOL_OUTPUT_VIEWS as readonly string[]).includes(value);\n}\n\n/** Next stop on the dial, wrapping at both ends. */\nexport function cycleToolOutputView(current: ToolOutputView, direction: \"forward\" | \"backward\"): ToolOutputView {\n\tconst index = TOOL_OUTPUT_VIEWS.indexOf(current);\n\tconst step = direction === \"forward\" ? 1 : TOOL_OUTPUT_VIEWS.length - 1;\n\treturn TOOL_OUTPUT_VIEWS[(index + step) % TOOL_OUTPUT_VIEWS.length];\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** The dial's order, least to most. Cycling wraps at both ends. */
|
|
2
|
+
export const TOOL_OUTPUT_VIEWS = ["radar", "glance", "full"];
|
|
3
|
+
/** One-line description per view, shared by the settings pane and `/hotkeys`. */
|
|
4
|
+
export const TOOL_OUTPUT_VIEW_DESCRIPTIONS = {
|
|
5
|
+
radar: "one line per run of tool calls; failures still show why they failed",
|
|
6
|
+
glance: "the call line alone; failures still show why they failed",
|
|
7
|
+
full: "call line plus the result body",
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Values written by versions that had `collapsed` / `peek` / `standard`.
|
|
11
|
+
*
|
|
12
|
+
* `peek` was already what most people wanted and `standard` was the default
|
|
13
|
+
* nobody kept, so the mapping is not a rename: `peek` becomes the new default
|
|
14
|
+
* `glance`, and `collapsed` — which hid a call's result with no way to see it —
|
|
15
|
+
* becomes `radar`, which hides the same body but says what came back.
|
|
16
|
+
*/
|
|
17
|
+
export const LEGACY_TOOL_OUTPUT_VIEWS = {
|
|
18
|
+
collapsed: "radar",
|
|
19
|
+
peek: "glance",
|
|
20
|
+
standard: "full",
|
|
21
|
+
};
|
|
22
|
+
export function isToolOutputView(value) {
|
|
23
|
+
return typeof value === "string" && TOOL_OUTPUT_VIEWS.includes(value);
|
|
24
|
+
}
|
|
25
|
+
/** Next stop on the dial, wrapping at both ends. */
|
|
26
|
+
export function cycleToolOutputView(current, direction) {
|
|
27
|
+
const index = TOOL_OUTPUT_VIEWS.indexOf(current);
|
|
28
|
+
const step = direction === "forward" ? 1 : TOOL_OUTPUT_VIEWS.length - 1;
|
|
29
|
+
return TOOL_OUTPUT_VIEWS[(index + step) % TOOL_OUTPUT_VIEWS.length];
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=tool-output-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-output-view.js","sourceRoot":"","sources":["../../src/core/tool-output-view.ts"],"names":[],"mappings":"AAiBA,mEAAmE;AACnE,MAAM,CAAC,MAAM,iBAAiB,GAA8B,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAExF,iFAAiF;AACjF,MAAM,CAAC,MAAM,6BAA6B,GAAmC;IAC5E,KAAK,EAAE,qEAAqE;IAC5E,MAAM,EAAE,0DAA0D;IAClE,IAAI,EAAE,gCAAgC;CACtC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAmC;IACvE,SAAS,EAAE,OAAO;IAClB,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,MAAM;CAChB,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,KAAc,EAA2B;IACzE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,iBAAuC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAAA,CAC7F;AAED,oDAAoD;AACpD,MAAM,UAAU,mBAAmB,CAAC,OAAuB,EAAE,SAAiC,EAAkB;IAC/G,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC;IACxE,OAAO,iBAAiB,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAAA,CACpE","sourcesContent":["/**\n * How much of a tool call the transcript shows. Three stops on one dial, least\n * to most, cycled with `app.view.cycleForward` / `app.view.cycleBackward`:\n *\n * - `radar` — one line per *chain*: a run of consecutive tool calls collapsed to\n * the shape it had while working, or what it amounted to once it is over.\n * - `glance` — the tool's own call line, body folded away. The default.\n * - `full` — call line plus the result body, truncated then expandable.\n *\n * The expand key (`app.tools.expand`) is orthogonal: it opens what is in front\n * of you without moving the dial.\n *\n * This lives in core rather than next to the renderer because the settings\n * manager persists it, and core must not reach into the interactive UI.\n */\nexport type ToolOutputView = \"radar\" | \"glance\" | \"full\";\n\n/** The dial's order, least to most. Cycling wraps at both ends. */\nexport const TOOL_OUTPUT_VIEWS: readonly ToolOutputView[] = [\"radar\", \"glance\", \"full\"];\n\n/** One-line description per view, shared by the settings pane and `/hotkeys`. */\nexport const TOOL_OUTPUT_VIEW_DESCRIPTIONS: Record<ToolOutputView, string> = {\n\tradar: \"one line per run of tool calls; failures still show why they failed\",\n\tglance: \"the call line alone; failures still show why they failed\",\n\tfull: \"call line plus the result body\",\n};\n\n/**\n * Values written by versions that had `collapsed` / `peek` / `standard`.\n *\n * `peek` was already what most people wanted and `standard` was the default\n * nobody kept, so the mapping is not a rename: `peek` becomes the new default\n * `glance`, and `collapsed` — which hid a call's result with no way to see it —\n * becomes `radar`, which hides the same body but says what came back.\n */\nexport const LEGACY_TOOL_OUTPUT_VIEWS: Record<string, ToolOutputView> = {\n\tcollapsed: \"radar\",\n\tpeek: \"glance\",\n\tstandard: \"full\",\n};\n\nexport function isToolOutputView(value: unknown): value is ToolOutputView {\n\treturn typeof value === \"string\" && (TOOL_OUTPUT_VIEWS as readonly string[]).includes(value);\n}\n\n/** Next stop on the dial, wrapping at both ends. */\nexport function cycleToolOutputView(current: ToolOutputView, direction: \"forward\" | \"backward\"): ToolOutputView {\n\tconst index = TOOL_OUTPUT_VIEWS.indexOf(current);\n\tconst step = direction === \"forward\" ? 1 : TOOL_OUTPUT_VIEWS.length - 1;\n\treturn TOOL_OUTPUT_VIEWS[(index + step) % TOOL_OUTPUT_VIEWS.length];\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../../src/core/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,GAAG,EAA2B,MAAM,0BAA0B,CAAC;AAGxE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAKN,KAAK,aAAa,EAClB,KAAK,cAAc,EAKnB,MAAM,gBAAgB,CAAC;AASxB,KAAK,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC;AAElD,KAAK,eAAe,GAAG;IACtB,aAAa,CAAC,EAAE,uBAAuB,CAAC;CACxC,CAAC;AAmBF,QAAA,MAAM,UAAU;;;;;;;EASf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAMtD,MAAM,WAAW,eAAe;IAC/B,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,qCAAqC;IACrC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,8BAA8B;IAC9B,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,4DAA4D;IAC5D,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAQD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAgDD,KAAK,uBAAuB,GAAG,GAAG,GAAG;IACpC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAmJF,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,EAAE,eAAe,CAAC,CAwMjF;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Container, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { renderDiff } from \"../../modes/interactive/components/diff.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport {\n\tapplyEditsToNormalizedContent,\n\tcomputeEditsDiff,\n\tdetectLineEnding,\n\ttype Edit,\n\ttype EditDiffError,\n\ttype EditDiffResult,\n\tgenerateDiffString,\n\tnormalizeToLF,\n\trestoreLineEndings,\n\tstripBom,\n} from \"./edit-diff.js\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\n/** Maximum characters for diff output sent to the LLM. */\nconst MAX_DIFF_CHARS = 4000;\n\ntype EditPreview = EditDiffResult | EditDiffError;\n\ntype EditRenderState = {\n\tcallComponent?: EditCallRenderComponent;\n};\n\nconst replaceEditSchema = Type.Object(\n\t{\n\t\toldText: Type.String({\n\t\t\tdescription:\n\t\t\t\t\"Exact text for one targeted replacement. It must be unique in the original file (unless replaceAll is true) and must not overlap with any other edits[].oldText in the same call.\",\n\t\t}),\n\t\tnewText: Type.String({ description: \"Replacement text for this targeted edit.\" }),\n\t\treplaceAll: Type.Optional(\n\t\t\tType.Boolean({\n\t\t\t\tdescription:\n\t\t\t\t\t\"When true, replace every occurrence of oldText instead of requiring it to be unique. Use for renaming a symbol/string throughout the file. Default false.\",\n\t\t\t}),\n\t\t),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst editSchema = Type.Object(\n\t{\n\t\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\t\tedits: Type.Array(replaceEditSchema, {\n\t\t\tdescription:\n\t\t\t\t\"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead.\",\n\t\t}),\n\t},\n\t{ additionalProperties: false },\n);\n\nexport type EditToolInput = Static<typeof editSchema>;\ntype LegacyEditToolInput = EditToolInput & {\n\toldText?: unknown;\n\tnewText?: unknown;\n};\n\nexport interface EditToolDetails {\n\t/** Unified diff of the changes made */\n\tdiff: string;\n\t/** Line number of the first change in the new file (for editor navigation) */\n\tfirstChangedLine?: number;\n}\n\n/**\n * Pluggable operations for the edit tool.\n * Override these to delegate file editing to remote systems (for example SSH).\n */\nexport interface EditOperations {\n\t/** Read file contents as a Buffer */\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\t/** Write content to a file */\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\t/** Check if file is readable and writable (throw if not) */\n\taccess: (absolutePath: string) => Promise<void>;\n}\n\nconst defaultEditOperations: EditOperations = {\n\treadFile: (path) => fsReadFile(path),\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\taccess: (path) => fsAccess(path, constants.R_OK | constants.W_OK),\n};\n\nexport interface EditToolOptions {\n\t/** Custom operations for file editing. Default: local filesystem */\n\toperations?: EditOperations;\n}\n\nfunction prepareEditArguments(input: unknown): EditToolInput {\n\tif (!input || typeof input !== \"object\") {\n\t\treturn input as EditToolInput;\n\t}\n\n\tconst args = input as Record<string, unknown>;\n\n\t// Some models (Opus 4.6, GLM-5.1) send edits as a JSON string instead of an array\n\tif (typeof args.edits === \"string\") {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(args.edits);\n\t\t\tif (Array.isArray(parsed)) args.edits = parsed;\n\t\t} catch {}\n\t}\n\n\tconst legacy = args as LegacyEditToolInput;\n\tif (typeof legacy.oldText !== \"string\" || typeof legacy.newText !== \"string\") {\n\t\treturn args as EditToolInput;\n\t}\n\n\tconst edits = Array.isArray(legacy.edits) ? [...legacy.edits] : [];\n\tedits.push({ oldText: legacy.oldText, newText: legacy.newText });\n\tconst { oldText: _oldText, newText: _newText, ...rest } = legacy;\n\treturn { ...rest, edits } as EditToolInput;\n}\n\nfunction validateEditInput(input: EditToolInput): { path: string; edits: Edit[] } {\n\tif (!Array.isArray(input.edits) || input.edits.length === 0) {\n\t\tthrow new Error(\"Edit tool input is invalid. edits must contain at least one replacement.\");\n\t}\n\treturn { path: input.path, edits: input.edits };\n}\n\ntype RenderableEditArgs = {\n\tpath?: string;\n\tfile_path?: string;\n\tedits?: Edit[];\n\toldText?: string;\n\tnewText?: string;\n};\n\ntype EditToolResultLike = {\n\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\tdetails?: EditToolDetails;\n};\n\ntype EditCallRenderComponent = Box & {\n\tpreview?: EditPreview;\n\tpreviewArgsKey?: string;\n\tpreviewPending?: boolean;\n\tsettledError?: boolean;\n};\n\nfunction createEditCallRenderComponent(): EditCallRenderComponent {\n\treturn Object.assign(new Box(1, 0, (text: string) => text), {\n\t\tpreview: undefined as EditPreview | undefined,\n\t\tpreviewArgsKey: undefined as string | undefined,\n\t\tpreviewPending: false,\n\t\tsettledError: false,\n\t});\n}\n\nfunction getEditCallRenderComponent(state: EditRenderState, lastComponent: unknown): EditCallRenderComponent {\n\tif (lastComponent instanceof Box) {\n\t\tconst component = lastComponent as EditCallRenderComponent;\n\t\tstate.callComponent = component;\n\t\treturn component;\n\t}\n\tif (state.callComponent) {\n\t\treturn state.callComponent;\n\t}\n\tconst component = createEditCallRenderComponent();\n\tstate.callComponent = component;\n\treturn component;\n}\n\nfunction getRenderablePreviewInput(args: RenderableEditArgs | undefined): { path: string; edits: Edit[] } | null {\n\tif (!args) {\n\t\treturn null;\n\t}\n\n\tconst path = typeof args.path === \"string\" ? args.path : typeof args.file_path === \"string\" ? args.file_path : null;\n\tif (!path) {\n\t\treturn null;\n\t}\n\n\tif (\n\t\tArray.isArray(args.edits) &&\n\t\targs.edits.length > 0 &&\n\t\targs.edits.every((edit) => typeof edit?.oldText === \"string\" && typeof edit?.newText === \"string\")\n\t) {\n\t\treturn { path, edits: args.edits };\n\t}\n\n\tif (typeof args.oldText === \"string\" && typeof args.newText === \"string\") {\n\t\treturn { path, edits: [{ oldText: args.oldText, newText: args.newText }] };\n\t}\n\n\treturn null;\n}\n\nfunction formatEditCall(\n\targs: RenderableEditArgs | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst invalidArg = invalidArgText(theme);\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath) : null;\n\tconst pathDisplay = path === null ? invalidArg : path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\");\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"edit\"))} ${pathDisplay}`;\n}\n\nfunction formatEditResult(\n\targs: RenderableEditArgs | undefined,\n\tpreview: EditPreview | undefined,\n\tresult: EditToolResultLike,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tisError: boolean,\n): string | undefined {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst previewDiff = preview && !(\"error\" in preview) ? preview.diff : undefined;\n\tconst previewError = preview && \"error\" in preview ? preview.error : undefined;\n\tif (isError) {\n\t\tconst errorText = result.content\n\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t.map((c) => c.text || \"\")\n\t\t\t.join(\"\\n\");\n\t\tif (!errorText || errorText === previewError) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn theme.fg(\"error\", errorText);\n\t}\n\n\tconst resultDiff = result.details?.diff;\n\tif (resultDiff && resultDiff !== previewDiff) {\n\t\treturn renderDiff(resultDiff, { filePath: rawPath ?? undefined });\n\t}\n\n\treturn undefined;\n}\n\nfunction getEditHeaderBg(\n\tpreview: EditPreview | undefined,\n\tsettledError: boolean | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): (text: string) => string {\n\tif (preview) {\n\t\tif (\"error\" in preview) {\n\t\t\treturn (text: string) => theme.bg(\"toolErrorBg\", text);\n\t\t}\n\t\treturn (text: string) => theme.bg(\"toolSuccessBg\", text);\n\t}\n\tif (settledError) {\n\t\treturn (text: string) => theme.bg(\"toolErrorBg\", text);\n\t}\n\treturn (text: string) => theme.bg(\"toolPendingBg\", text);\n}\n\nfunction buildEditCallComponent(\n\tcomponent: EditCallRenderComponent,\n\targs: RenderableEditArgs | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): EditCallRenderComponent {\n\tcomponent.setBgFn(getEditHeaderBg(component.preview, component.settledError, theme));\n\tcomponent.clear();\n\tcomponent.addChild(new Text(formatEditCall(args, theme), 0, 0));\n\n\tif (!component.preview) {\n\t\treturn component;\n\t}\n\n\tconst body =\n\t\t\"error\" in component.preview ? theme.fg(\"error\", component.preview.error) : renderDiff(component.preview.diff);\n\tcomponent.addChild(new Spacer(1));\n\tcomponent.addChild(new Text(body, 0, 0));\n\treturn component;\n}\n\nfunction setEditPreview(\n\tcomponent: EditCallRenderComponent,\n\tpreview: EditPreview,\n\targsKey: string | undefined,\n): boolean {\n\tconst current = component.preview;\n\tconst changed =\n\t\tcurrent === undefined ||\n\t\t(\"error\" in current && \"error\" in preview\n\t\t\t? current.error !== preview.error\n\t\t\t: \"error\" in current !== \"error\" in preview) ||\n\t\t(!(\"error\" in current) &&\n\t\t\t!(\"error\" in preview) &&\n\t\t\t(current.diff !== preview.diff || current.firstChangedLine !== preview.firstChangedLine));\n\tcomponent.preview = preview;\n\tcomponent.previewArgsKey = argsKey;\n\tcomponent.previewPending = false;\n\treturn changed;\n}\n\nexport function createEditToolDefinition(\n\tcwd: string,\n\toptions?: EditToolOptions,\n): ToolDefinition<typeof editSchema, EditToolDetails | undefined, EditRenderState> {\n\tconst ops = options?.operations ?? defaultEditOperations;\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file, unless that edit sets replaceAll: true to replace all of its occurrences. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.\",\n\t\tpromptSnippet:\n\t\t\t\"Make precise file edits with exact text replacement, including multiple disjoint edits in one call\",\n\t\t// Only the batching preference lives here. The matching semantics\n\t\t// (original-file matching, no overlaps, replaceAll) are already stated in the\n\t\t// `edits`/`oldText`/`replaceAll` schema descriptions, which ship on every turn\n\t\t// alongside these — repeating them spent the tokens twice.\n\t\tpromptGuidelines: [\n\t\t\t\"When changing several separate locations in one file, send one edit call with multiple entries in edits[] rather than several edit calls. Keep each oldText as small as it can be while staying unique.\",\n\t\t],\n\t\tparameters: editSchema,\n\t\trenderShell: \"self\",\n\t\tprepareArguments: prepareEditArguments,\n\t\tasync execute(_toolCallId, input: EditToolInput, signal?: AbortSignal, _onUpdate?, _ctx?) {\n\t\t\tconst { path, edits } = validateEditInput(input);\n\t\t\tconst absolutePath = resolveToCwd(path, cwd);\n\n\t\t\treturn withFileMutationQueue(\n\t\t\t\tabsolutePath,\n\t\t\t\t() =>\n\t\t\t\t\tnew Promise<{\n\t\t\t\t\t\tcontent: Array<{ type: \"text\"; text: string }>;\n\t\t\t\t\t\tdetails: EditToolDetails | undefined;\n\t\t\t\t\t}>((resolve, reject) => {\n\t\t\t\t\t\t// Check if already aborted.\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet aborted = false;\n\n\t\t\t\t\t\t// Set up abort handler.\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Perform the edit operation.\n\t\t\t\t\t\tvoid (async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// Check if file exists.\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tawait ops.access(absolutePath);\n\t\t\t\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\t\t\t\terror instanceof Error && \"code\" in error ? `Error code: ${error.code}` : String(error);\n\t\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treject(new Error(`Could not edit file: ${path}. ${errorMessage}.`));\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Check if aborted before reading.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Read the file.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst rawContent = buffer.toString(\"utf-8\");\n\n\t\t\t\t\t\t\t\t// Check if aborted after reading.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Strip BOM before matching. The model will not include an invisible BOM in oldText.\n\t\t\t\t\t\t\t\tconst { bom, text: content } = stripBom(rawContent);\n\t\t\t\t\t\t\t\tconst originalEnding = detectLineEnding(content);\n\t\t\t\t\t\t\t\tconst normalizedContent = normalizeToLF(content);\n\t\t\t\t\t\t\t\tconst { baseContent, newContent } = applyEditsToNormalizedContent(\n\t\t\t\t\t\t\t\t\tnormalizedContent,\n\t\t\t\t\t\t\t\t\tedits,\n\t\t\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t// Check if aborted before writing.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst finalContent = bom + restoreLineEndings(newContent, originalEnding);\n\t\t\t\t\t\t\t\tawait ops.writeFile(absolutePath, finalContent);\n\n\t\t\t\t\t\t\t\t// Check if aborted after writing.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Clean up abort handler.\n\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst diffResult = generateDiffString(baseContent, newContent);\n\t\t\t\t\t\t\t\t// Truncate large diffs to save tokens\n\t\t\t\t\t\t\t\tconst diff =\n\t\t\t\t\t\t\t\t\tdiffResult.diff.length > MAX_DIFF_CHARS\n\t\t\t\t\t\t\t\t\t\t? `${diffResult.diff.slice(0, MAX_DIFF_CHARS)}\\n\\n[diff truncated for brevity]`\n\t\t\t\t\t\t\t\t\t\t: diffResult.diff;\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\t\ttext: `Successfully replaced ${edits.length} block(s) in ${path}.`,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\tdetails: { diff, firstChangedLine: diffResult.firstChangedLine },\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t\t\t// Clean up abort handler.\n\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (!aborted) {\n\t\t\t\t\t\t\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\t\t\t\t\t}),\n\t\t\t);\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst component = getEditCallRenderComponent(context.state, context.lastComponent);\n\t\t\tconst previewInput = getRenderablePreviewInput(args as RenderableEditArgs | undefined);\n\t\t\tconst argsKey = previewInput\n\t\t\t\t? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })\n\t\t\t\t: undefined;\n\n\t\t\tif (component.previewArgsKey !== argsKey) {\n\t\t\t\tcomponent.preview = undefined;\n\t\t\t\tcomponent.previewArgsKey = argsKey;\n\t\t\t\tcomponent.previewPending = false;\n\t\t\t\tcomponent.settledError = false;\n\t\t\t}\n\n\t\t\tif (context.argsComplete && previewInput && !component.preview && !component.previewPending) {\n\t\t\t\tcomponent.previewPending = true;\n\t\t\t\tconst requestKey = argsKey;\n\t\t\t\tvoid computeEditsDiff(previewInput.path, previewInput.edits, context.cwd).then((preview) => {\n\t\t\t\t\tif (component.previewArgsKey === requestKey) {\n\t\t\t\t\t\tsetEditPreview(component, preview, requestKey);\n\t\t\t\t\t\tcontext.invalidate();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn buildEditCallComponent(component, args, theme);\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst callComponent = context.state.callComponent;\n\t\t\tconst previewInput = getRenderablePreviewInput(context.args as RenderableEditArgs | undefined);\n\t\t\tconst argsKey = previewInput\n\t\t\t\t? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })\n\t\t\t\t: undefined;\n\t\t\tconst typedResult = result as EditToolResultLike;\n\t\t\tconst resultDiff = !context.isError ? typedResult.details?.diff : undefined;\n\t\t\tlet changed = false;\n\t\t\tif (callComponent) {\n\t\t\t\tif (typeof resultDiff === \"string\") {\n\t\t\t\t\tchanged =\n\t\t\t\t\t\tsetEditPreview(\n\t\t\t\t\t\t\tcallComponent,\n\t\t\t\t\t\t\t{ diff: resultDiff, firstChangedLine: typedResult.details?.firstChangedLine },\n\t\t\t\t\t\t\targsKey,\n\t\t\t\t\t\t) || changed;\n\t\t\t\t}\n\t\t\t\tif (callComponent.settledError !== context.isError) {\n\t\t\t\t\tcallComponent.settledError = context.isError;\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t\tif (changed) {\n\t\t\t\t\tbuildEditCallComponent(callComponent, context.args as RenderableEditArgs | undefined, theme);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst output = formatEditResult(context.args, callComponent?.preview, typedResult, theme, context.isError);\n\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\tcomponent.clear();\n\t\t\tif (!output) {\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tcomponent.addChild(new Spacer(1));\n\t\t\tcomponent.addChild(new Text(output, 1, 0));\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createEditTool(cwd: string, options?: EditToolOptions): AgentTool<typeof editSchema> {\n\treturn wrapToolDefinition(createEditToolDefinition(cwd, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../../src/core/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,GAAG,EAA2B,MAAM,0BAA0B,CAAC;AAGxE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAKN,KAAK,aAAa,EAClB,KAAK,cAAc,EAKnB,MAAM,gBAAgB,CAAC;AASxB,KAAK,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC;AAElD,KAAK,eAAe,GAAG;IACtB,aAAa,CAAC,EAAE,uBAAuB,CAAC;CACxC,CAAC;AAmBF,QAAA,MAAM,UAAU;;;;;;;EASf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAMtD,MAAM,WAAW,eAAe;IAC/B,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,qCAAqC;IACrC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,8BAA8B;IAC9B,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,4DAA4D;IAC5D,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAQD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAgDD,KAAK,uBAAuB,GAAG,GAAG,GAAG;IACpC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAuJF,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,EAAE,eAAe,CAAC,CAwMjF;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Container, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { renderDiff } from \"../../modes/interactive/components/diff.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport {\n\tapplyEditsToNormalizedContent,\n\tcomputeEditsDiff,\n\tdetectLineEnding,\n\ttype Edit,\n\ttype EditDiffError,\n\ttype EditDiffResult,\n\tgenerateDiffString,\n\tnormalizeToLF,\n\trestoreLineEndings,\n\tstripBom,\n} from \"./edit-diff.js\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\n/** Maximum characters for diff output sent to the LLM. */\nconst MAX_DIFF_CHARS = 4000;\n\ntype EditPreview = EditDiffResult | EditDiffError;\n\ntype EditRenderState = {\n\tcallComponent?: EditCallRenderComponent;\n};\n\nconst replaceEditSchema = Type.Object(\n\t{\n\t\toldText: Type.String({\n\t\t\tdescription:\n\t\t\t\t\"Exact text for one targeted replacement. It must be unique in the original file (unless replaceAll is true) and must not overlap with any other edits[].oldText in the same call.\",\n\t\t}),\n\t\tnewText: Type.String({ description: \"Replacement text for this targeted edit.\" }),\n\t\treplaceAll: Type.Optional(\n\t\t\tType.Boolean({\n\t\t\t\tdescription:\n\t\t\t\t\t\"When true, replace every occurrence of oldText instead of requiring it to be unique. Use for renaming a symbol/string throughout the file. Default false.\",\n\t\t\t}),\n\t\t),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst editSchema = Type.Object(\n\t{\n\t\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\t\tedits: Type.Array(replaceEditSchema, {\n\t\t\tdescription:\n\t\t\t\t\"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead.\",\n\t\t}),\n\t},\n\t{ additionalProperties: false },\n);\n\nexport type EditToolInput = Static<typeof editSchema>;\ntype LegacyEditToolInput = EditToolInput & {\n\toldText?: unknown;\n\tnewText?: unknown;\n};\n\nexport interface EditToolDetails {\n\t/** Unified diff of the changes made */\n\tdiff: string;\n\t/** Line number of the first change in the new file (for editor navigation) */\n\tfirstChangedLine?: number;\n}\n\n/**\n * Pluggable operations for the edit tool.\n * Override these to delegate file editing to remote systems (for example SSH).\n */\nexport interface EditOperations {\n\t/** Read file contents as a Buffer */\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\t/** Write content to a file */\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\t/** Check if file is readable and writable (throw if not) */\n\taccess: (absolutePath: string) => Promise<void>;\n}\n\nconst defaultEditOperations: EditOperations = {\n\treadFile: (path) => fsReadFile(path),\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\taccess: (path) => fsAccess(path, constants.R_OK | constants.W_OK),\n};\n\nexport interface EditToolOptions {\n\t/** Custom operations for file editing. Default: local filesystem */\n\toperations?: EditOperations;\n}\n\nfunction prepareEditArguments(input: unknown): EditToolInput {\n\tif (!input || typeof input !== \"object\") {\n\t\treturn input as EditToolInput;\n\t}\n\n\tconst args = input as Record<string, unknown>;\n\n\t// Some models (Opus 4.6, GLM-5.1) send edits as a JSON string instead of an array\n\tif (typeof args.edits === \"string\") {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(args.edits);\n\t\t\tif (Array.isArray(parsed)) args.edits = parsed;\n\t\t} catch {}\n\t}\n\n\tconst legacy = args as LegacyEditToolInput;\n\tif (typeof legacy.oldText !== \"string\" || typeof legacy.newText !== \"string\") {\n\t\treturn args as EditToolInput;\n\t}\n\n\tconst edits = Array.isArray(legacy.edits) ? [...legacy.edits] : [];\n\tedits.push({ oldText: legacy.oldText, newText: legacy.newText });\n\tconst { oldText: _oldText, newText: _newText, ...rest } = legacy;\n\treturn { ...rest, edits } as EditToolInput;\n}\n\nfunction validateEditInput(input: EditToolInput): { path: string; edits: Edit[] } {\n\tif (!Array.isArray(input.edits) || input.edits.length === 0) {\n\t\tthrow new Error(\"Edit tool input is invalid. edits must contain at least one replacement.\");\n\t}\n\treturn { path: input.path, edits: input.edits };\n}\n\ntype RenderableEditArgs = {\n\tpath?: string;\n\tfile_path?: string;\n\tedits?: Edit[];\n\toldText?: string;\n\tnewText?: string;\n};\n\ntype EditToolResultLike = {\n\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\tdetails?: EditToolDetails;\n};\n\ntype EditCallRenderComponent = Box & {\n\tpreview?: EditPreview;\n\tpreviewArgsKey?: string;\n\tpreviewPending?: boolean;\n\tsettledError?: boolean;\n};\n\nfunction createEditCallRenderComponent(): EditCallRenderComponent {\n\treturn Object.assign(new Box(1, 0, (text: string) => text), {\n\t\tpreview: undefined as EditPreview | undefined,\n\t\tpreviewArgsKey: undefined as string | undefined,\n\t\tpreviewPending: false,\n\t\tsettledError: false,\n\t});\n}\n\nfunction getEditCallRenderComponent(state: EditRenderState, lastComponent: unknown): EditCallRenderComponent {\n\tif (lastComponent instanceof Box) {\n\t\tconst component = lastComponent as EditCallRenderComponent;\n\t\tstate.callComponent = component;\n\t\treturn component;\n\t}\n\tif (state.callComponent) {\n\t\treturn state.callComponent;\n\t}\n\tconst component = createEditCallRenderComponent();\n\tstate.callComponent = component;\n\treturn component;\n}\n\nfunction getRenderablePreviewInput(args: RenderableEditArgs | undefined): { path: string; edits: Edit[] } | null {\n\tif (!args) {\n\t\treturn null;\n\t}\n\n\tconst path = typeof args.path === \"string\" ? args.path : typeof args.file_path === \"string\" ? args.file_path : null;\n\tif (!path) {\n\t\treturn null;\n\t}\n\n\tif (\n\t\tArray.isArray(args.edits) &&\n\t\targs.edits.length > 0 &&\n\t\targs.edits.every((edit) => typeof edit?.oldText === \"string\" && typeof edit?.newText === \"string\")\n\t) {\n\t\treturn { path, edits: args.edits };\n\t}\n\n\tif (typeof args.oldText === \"string\" && typeof args.newText === \"string\") {\n\t\treturn { path, edits: [{ oldText: args.oldText, newText: args.newText }] };\n\t}\n\n\treturn null;\n}\n\nfunction formatEditCall(\n\targs: RenderableEditArgs | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst invalidArg = invalidArgText(theme);\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath) : null;\n\tconst pathDisplay = path === null ? invalidArg : path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\");\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"edit\"))} ${pathDisplay}`;\n}\n\nfunction formatEditResult(\n\targs: RenderableEditArgs | undefined,\n\tpreview: EditPreview | undefined,\n\tresult: EditToolResultLike,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tisError: boolean,\n): string | undefined {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst previewDiff = preview && !(\"error\" in preview) ? preview.diff : undefined;\n\tconst previewError = preview && \"error\" in preview ? preview.error : undefined;\n\tif (isError) {\n\t\tconst errorText = result.content\n\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t.map((c) => c.text || \"\")\n\t\t\t.join(\"\\n\");\n\t\tif (!errorText || errorText === previewError) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn theme.fg(\"error\", errorText);\n\t}\n\n\tconst resultDiff = result.details?.diff;\n\tif (resultDiff && resultDiff !== previewDiff) {\n\t\treturn renderDiff(resultDiff, { filePath: rawPath ?? undefined });\n\t}\n\n\treturn undefined;\n}\n\nfunction getEditHeaderBg(\n\tpreview: EditPreview | undefined,\n\tsettledError: boolean | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): (text: string) => string {\n\tif (preview) {\n\t\tif (\"error\" in preview) {\n\t\t\treturn (text: string) => theme.bg(\"toolErrorBg\", text);\n\t\t}\n\t\treturn (text: string) => theme.bg(\"toolSuccessBg\", text);\n\t}\n\tif (settledError) {\n\t\treturn (text: string) => theme.bg(\"toolErrorBg\", text);\n\t}\n\treturn (text: string) => theme.bg(\"toolPendingBg\", text);\n}\n\nfunction buildEditCallComponent(\n\tcomponent: EditCallRenderComponent,\n\targs: RenderableEditArgs | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): EditCallRenderComponent {\n\tcomponent.setBgFn(getEditHeaderBg(component.preview, component.settledError, theme));\n\t// The horizontal padding exists to carry the header band. With no band —\n\t// a bare call line in a folded view — it is just an indent that pushes this\n\t// tool's text one column right of every other tool's.\n\tcomponent.setPaddingX(component.preview || component.settledError ? 1 : 0);\n\tcomponent.clear();\n\tcomponent.addChild(new Text(formatEditCall(args, theme), 0, 0));\n\n\tif (!component.preview) {\n\t\treturn component;\n\t}\n\n\tconst body =\n\t\t\"error\" in component.preview ? theme.fg(\"error\", component.preview.error) : renderDiff(component.preview.diff);\n\tcomponent.addChild(new Spacer(1));\n\tcomponent.addChild(new Text(body, 0, 0));\n\treturn component;\n}\n\nfunction setEditPreview(\n\tcomponent: EditCallRenderComponent,\n\tpreview: EditPreview,\n\targsKey: string | undefined,\n): boolean {\n\tconst current = component.preview;\n\tconst changed =\n\t\tcurrent === undefined ||\n\t\t(\"error\" in current && \"error\" in preview\n\t\t\t? current.error !== preview.error\n\t\t\t: \"error\" in current !== \"error\" in preview) ||\n\t\t(!(\"error\" in current) &&\n\t\t\t!(\"error\" in preview) &&\n\t\t\t(current.diff !== preview.diff || current.firstChangedLine !== preview.firstChangedLine));\n\tcomponent.preview = preview;\n\tcomponent.previewArgsKey = argsKey;\n\tcomponent.previewPending = false;\n\treturn changed;\n}\n\nexport function createEditToolDefinition(\n\tcwd: string,\n\toptions?: EditToolOptions,\n): ToolDefinition<typeof editSchema, EditToolDetails | undefined, EditRenderState> {\n\tconst ops = options?.operations ?? defaultEditOperations;\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file, unless that edit sets replaceAll: true to replace all of its occurrences. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.\",\n\t\tpromptSnippet:\n\t\t\t\"Make precise file edits with exact text replacement, including multiple disjoint edits in one call\",\n\t\t// Only the batching preference lives here. The matching semantics\n\t\t// (original-file matching, no overlaps, replaceAll) are already stated in the\n\t\t// `edits`/`oldText`/`replaceAll` schema descriptions, which ship on every turn\n\t\t// alongside these — repeating them spent the tokens twice.\n\t\tpromptGuidelines: [\n\t\t\t\"When changing several separate locations in one file, send one edit call with multiple entries in edits[] rather than several edit calls. Keep each oldText as small as it can be while staying unique.\",\n\t\t],\n\t\tparameters: editSchema,\n\t\trenderShell: \"self\",\n\t\tprepareArguments: prepareEditArguments,\n\t\tasync execute(_toolCallId, input: EditToolInput, signal?: AbortSignal, _onUpdate?, _ctx?) {\n\t\t\tconst { path, edits } = validateEditInput(input);\n\t\t\tconst absolutePath = resolveToCwd(path, cwd);\n\n\t\t\treturn withFileMutationQueue(\n\t\t\t\tabsolutePath,\n\t\t\t\t() =>\n\t\t\t\t\tnew Promise<{\n\t\t\t\t\t\tcontent: Array<{ type: \"text\"; text: string }>;\n\t\t\t\t\t\tdetails: EditToolDetails | undefined;\n\t\t\t\t\t}>((resolve, reject) => {\n\t\t\t\t\t\t// Check if already aborted.\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet aborted = false;\n\n\t\t\t\t\t\t// Set up abort handler.\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Perform the edit operation.\n\t\t\t\t\t\tvoid (async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// Check if file exists.\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tawait ops.access(absolutePath);\n\t\t\t\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\t\t\t\terror instanceof Error && \"code\" in error ? `Error code: ${error.code}` : String(error);\n\t\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treject(new Error(`Could not edit file: ${path}. ${errorMessage}.`));\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Check if aborted before reading.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Read the file.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst rawContent = buffer.toString(\"utf-8\");\n\n\t\t\t\t\t\t\t\t// Check if aborted after reading.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Strip BOM before matching. The model will not include an invisible BOM in oldText.\n\t\t\t\t\t\t\t\tconst { bom, text: content } = stripBom(rawContent);\n\t\t\t\t\t\t\t\tconst originalEnding = detectLineEnding(content);\n\t\t\t\t\t\t\t\tconst normalizedContent = normalizeToLF(content);\n\t\t\t\t\t\t\t\tconst { baseContent, newContent } = applyEditsToNormalizedContent(\n\t\t\t\t\t\t\t\t\tnormalizedContent,\n\t\t\t\t\t\t\t\t\tedits,\n\t\t\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t// Check if aborted before writing.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst finalContent = bom + restoreLineEndings(newContent, originalEnding);\n\t\t\t\t\t\t\t\tawait ops.writeFile(absolutePath, finalContent);\n\n\t\t\t\t\t\t\t\t// Check if aborted after writing.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Clean up abort handler.\n\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst diffResult = generateDiffString(baseContent, newContent);\n\t\t\t\t\t\t\t\t// Truncate large diffs to save tokens\n\t\t\t\t\t\t\t\tconst diff =\n\t\t\t\t\t\t\t\t\tdiffResult.diff.length > MAX_DIFF_CHARS\n\t\t\t\t\t\t\t\t\t\t? `${diffResult.diff.slice(0, MAX_DIFF_CHARS)}\\n\\n[diff truncated for brevity]`\n\t\t\t\t\t\t\t\t\t\t: diffResult.diff;\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\t\ttext: `Successfully replaced ${edits.length} block(s) in ${path}.`,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\tdetails: { diff, firstChangedLine: diffResult.firstChangedLine },\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t\t\t// Clean up abort handler.\n\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (!aborted) {\n\t\t\t\t\t\t\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\t\t\t\t\t}),\n\t\t\t);\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst component = getEditCallRenderComponent(context.state, context.lastComponent);\n\t\t\tconst previewInput = getRenderablePreviewInput(args as RenderableEditArgs | undefined);\n\t\t\tconst argsKey = previewInput\n\t\t\t\t? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })\n\t\t\t\t: undefined;\n\n\t\t\tif (component.previewArgsKey !== argsKey) {\n\t\t\t\tcomponent.preview = undefined;\n\t\t\t\tcomponent.previewArgsKey = argsKey;\n\t\t\t\tcomponent.previewPending = false;\n\t\t\t\tcomponent.settledError = false;\n\t\t\t}\n\n\t\t\tif (context.argsComplete && previewInput && !component.preview && !component.previewPending) {\n\t\t\t\tcomponent.previewPending = true;\n\t\t\t\tconst requestKey = argsKey;\n\t\t\t\tvoid computeEditsDiff(previewInput.path, previewInput.edits, context.cwd).then((preview) => {\n\t\t\t\t\tif (component.previewArgsKey === requestKey) {\n\t\t\t\t\t\tsetEditPreview(component, preview, requestKey);\n\t\t\t\t\t\tcontext.invalidate();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn buildEditCallComponent(component, args, theme);\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst callComponent = context.state.callComponent;\n\t\t\tconst previewInput = getRenderablePreviewInput(context.args as RenderableEditArgs | undefined);\n\t\t\tconst argsKey = previewInput\n\t\t\t\t? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })\n\t\t\t\t: undefined;\n\t\t\tconst typedResult = result as EditToolResultLike;\n\t\t\tconst resultDiff = !context.isError ? typedResult.details?.diff : undefined;\n\t\t\tlet changed = false;\n\t\t\tif (callComponent) {\n\t\t\t\tif (typeof resultDiff === \"string\") {\n\t\t\t\t\tchanged =\n\t\t\t\t\t\tsetEditPreview(\n\t\t\t\t\t\t\tcallComponent,\n\t\t\t\t\t\t\t{ diff: resultDiff, firstChangedLine: typedResult.details?.firstChangedLine },\n\t\t\t\t\t\t\targsKey,\n\t\t\t\t\t\t) || changed;\n\t\t\t\t}\n\t\t\t\tif (callComponent.settledError !== context.isError) {\n\t\t\t\t\tcallComponent.settledError = context.isError;\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t\tif (changed) {\n\t\t\t\t\tbuildEditCallComponent(callComponent, context.args as RenderableEditArgs | undefined, theme);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst output = formatEditResult(context.args, callComponent?.preview, typedResult, theme, context.isError);\n\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\tcomponent.clear();\n\t\t\tif (!output) {\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tcomponent.addChild(new Spacer(1));\n\t\t\tcomponent.addChild(new Text(output, 1, 0));\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createEditTool(cwd: string, options?: EditToolOptions): AgentTool<typeof editSchema> {\n\treturn wrapToolDefinition(createEditToolDefinition(cwd, options));\n}\n"]}
|
package/dist/core/tools/edit.js
CHANGED
|
@@ -139,6 +139,10 @@ function getEditHeaderBg(preview, settledError, theme) {
|
|
|
139
139
|
}
|
|
140
140
|
function buildEditCallComponent(component, args, theme) {
|
|
141
141
|
component.setBgFn(getEditHeaderBg(component.preview, component.settledError, theme));
|
|
142
|
+
// The horizontal padding exists to carry the header band. With no band —
|
|
143
|
+
// a bare call line in a folded view — it is just an indent that pushes this
|
|
144
|
+
// tool's text one column right of every other tool's.
|
|
145
|
+
component.setPaddingX(component.preview || component.settledError ? 1 : 0);
|
|
142
146
|
component.clear();
|
|
143
147
|
component.addChild(new Text(formatEditCall(args, theme), 0, 0));
|
|
144
148
|
if (!component.preview) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit.js","sourceRoot":"","sources":["../../../src/core/tools/edit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAAE,MAAM,IAAI,QAAQ,EAAE,QAAQ,IAAI,UAAU,EAAE,SAAS,IAAI,WAAW,EAAE,MAAM,aAAa,CAAC;AACnG,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,4CAA4C,CAAC;AAExE,OAAO,EACN,6BAA6B,EAC7B,gBAAgB,EAChB,gBAAgB,EAIhB,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,QAAQ,GACR,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAElE,0DAA0D;AAC1D,MAAM,cAAc,GAAG,IAAI,CAAC;AAQ5B,MAAM,iBAAiB,GAAG,IAAI,CAAC,MAAM,CACpC;IACC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC;QACpB,WAAW,EACV,mLAAmL;KACpL,CAAC;IACF,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,0CAA0C,EAAE,CAAC;IACjF,UAAU,EAAE,IAAI,CAAC,QAAQ,CACxB,IAAI,CAAC,OAAO,CAAC;QACZ,WAAW,EACV,2JAA2J;KAC5J,CAAC,CACF;CACD,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAC/B,CAAC;AAEF,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAC7B;IACC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,iDAAiD,EAAE,CAAC;IACrF,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE;QACpC,WAAW,EACV,0OAA0O;KAC3O,CAAC;CACF,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAC/B,CAAC;AA4BF,MAAM,qBAAqB,GAAmB;IAC7C,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;IACpC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC;IACjE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC;CACjE,CAAC;AAOF,SAAS,oBAAoB,CAAC,KAAc,EAAiB;IAC5D,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACzC,OAAO,KAAsB,CAAC;IAC/B,CAAC;IAED,MAAM,IAAI,GAAG,KAAgC,CAAC;IAE9C,kFAAkF;IAClF,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACpC,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACtC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC;IAED,MAAM,MAAM,GAAG,IAA2B,CAAC;IAC3C,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC9E,OAAO,IAAqB,CAAC;IAC9B,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IACjE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC;IACjE,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAmB,CAAC;AAAA,CAC3C;AAED,SAAS,iBAAiB,CAAC,KAAoB,EAAmC;IACjF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;AAAA,CAChD;AAsBD,SAAS,6BAA6B,GAA4B;IACjE,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE;QAC3D,OAAO,EAAE,SAAoC;QAC7C,cAAc,EAAE,SAA+B;QAC/C,cAAc,EAAE,KAAK;QACrB,YAAY,EAAE,KAAK;KACnB,CAAC,CAAC;AAAA,CACH;AAED,SAAS,0BAA0B,CAAC,KAAsB,EAAE,aAAsB,EAA2B;IAC5G,IAAI,aAAa,YAAY,GAAG,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,aAAwC,CAAC;QAC3D,KAAK,CAAC,aAAa,GAAG,SAAS,CAAC;QAChC,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,aAAa,CAAC;IAC5B,CAAC;IACD,MAAM,SAAS,GAAG,6BAA6B,EAAE,CAAC;IAClD,KAAK,CAAC,aAAa,GAAG,SAAS,CAAC;IAChC,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,SAAS,yBAAyB,CAAC,IAAoC,EAA0C;IAChH,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;IACpH,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IACC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QACrB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI,OAAO,IAAI,EAAE,OAAO,KAAK,QAAQ,CAAC,EACjG,CAAC;QACF,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;IACpC,CAAC;IAED,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC1E,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;IAC5E,CAAC;IAED,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,SAAS,cAAc,CACtB,IAAoC,EACpC,KAAoE,EAC3D;IACT,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,SAAS,IAAI,IAAI,EAAE,IAAI,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5D,MAAM,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACjH,OAAO,GAAG,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC;AAAA,CACrE;AAED,SAAS,gBAAgB,CACxB,IAAoC,EACpC,OAAgC,EAChC,MAA0B,EAC1B,KAAoE,EACpE,OAAgB,EACK;IACrB,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,SAAS,IAAI,IAAI,EAAE,IAAI,CAAC,CAAC;IACnD,MAAM,WAAW,GAAG,OAAO,IAAI,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAChF,MAAM,YAAY,GAAG,OAAO,IAAI,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/E,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO;aAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;aAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;aACxB,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,IAAI,CAAC,SAAS,IAAI,SAAS,KAAK,YAAY,EAAE,CAAC;YAC9C,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,OAAO,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC;IACxC,IAAI,UAAU,IAAI,UAAU,KAAK,WAAW,EAAE,CAAC;QAC9C,OAAO,UAAU,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,SAAS,eAAe,CACvB,OAAgC,EAChC,YAAiC,EACjC,KAAoE,EACzC;IAC3B,IAAI,OAAO,EAAE,CAAC;QACb,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;YACxB,OAAO,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,YAAY,EAAE,CAAC;QAClB,OAAO,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;AAAA,CACzD;AAED,SAAS,sBAAsB,CAC9B,SAAkC,EAClC,IAAoC,EACpC,KAAoE,EAC1C;IAC1B,SAAS,CAAC,OAAO,CAAC,eAAe,CAAC,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC;IACrF,SAAS,CAAC,KAAK,EAAE,CAAC;IAClB,SAAS,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEhE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QACxB,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,MAAM,IAAI,GACT,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAChH,SAAS,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,SAAS,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACzC,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,SAAS,cAAc,CACtB,SAAkC,EAClC,OAAoB,EACpB,OAA2B,EACjB;IACV,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC;IAClC,MAAM,OAAO,GACZ,OAAO,KAAK,SAAS;QACrB,CAAC,OAAO,IAAI,OAAO,IAAI,OAAO,IAAI,OAAO;YACxC,CAAC,CAAC,OAAO,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK;YACjC,CAAC,CAAC,OAAO,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,CAAC;QAC7C,CAAC,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC;YACrB,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC;YACrB,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,gBAAgB,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC5F,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC;IAC5B,SAAS,CAAC,cAAc,GAAG,OAAO,CAAC;IACnC,SAAS,CAAC,cAAc,GAAG,KAAK,CAAC;IACjC,OAAO,OAAO,CAAC;AAAA,CACf;AAED,MAAM,UAAU,wBAAwB,CACvC,GAAW,EACX,OAAyB,EACyD;IAClF,MAAM,GAAG,GAAG,OAAO,EAAE,UAAU,IAAI,qBAAqB,CAAC;IACzD,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,MAAM;QACb,WAAW,EACV,kZAAkZ;QACnZ,aAAa,EACZ,oGAAoG;QACrG,kEAAkE;QAClE,8EAA8E;QAC9E,+EAA+E;QAC/E,6DAA2D;QAC3D,gBAAgB,EAAE;YACjB,yMAAyM;SACzM;QACD,UAAU,EAAE,UAAU;QACtB,WAAW,EAAE,MAAM;QACnB,gBAAgB,EAAE,oBAAoB;QACtC,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,KAAoB,EAAE,MAAoB,EAAE,SAAU,EAAE,IAAK,EAAE;YACzF,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;YACjD,MAAM,YAAY,GAAG,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAE7C,OAAO,qBAAqB,CAC3B,YAAY,EACZ,GAAG,EAAE,CACJ,IAAI,OAAO,CAGR,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;gBACvB,4BAA4B;gBAC5B,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;oBACrB,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;oBACvC,OAAO;gBACR,CAAC;gBAED,IAAI,OAAO,GAAG,KAAK,CAAC;gBAEpB,wBAAwB;gBACxB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;oBACrB,OAAO,GAAG,IAAI,CAAC;oBACf,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;gBAAA,CACvC,CAAC;gBAEF,IAAI,MAAM,EAAE,CAAC;oBACZ,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC3D,CAAC;gBAED,8BAA8B;gBAC9B,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;oBACjB,IAAI,CAAC;wBACJ,wBAAwB;wBACxB,IAAI,CAAC;4BACJ,MAAM,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;wBAChC,CAAC;wBAAC,OAAO,KAAc,EAAE,CAAC;4BACzB,MAAM,YAAY,GACjB,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,eAAe,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;4BACzF,IAAI,MAAM,EAAE,CAAC;gCACZ,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;4BAC9C,CAAC;4BACD,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,IAAI,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC;4BACpE,OAAO;wBACR,CAAC;wBAED,mCAAmC;wBACnC,IAAI,OAAO,EAAE,CAAC;4BACb,OAAO;wBACR,CAAC;wBAED,iBAAiB;wBACjB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;wBAChD,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;wBAE5C,kCAAkC;wBAClC,IAAI,OAAO,EAAE,CAAC;4BACb,OAAO;wBACR,CAAC;wBAED,qFAAqF;wBACrF,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;wBACpD,MAAM,cAAc,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;wBACjD,MAAM,iBAAiB,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;wBACjD,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,6BAA6B,CAChE,iBAAiB,EACjB,KAAK,EACL,IAAI,CACJ,CAAC;wBAEF,mCAAmC;wBACnC,IAAI,OAAO,EAAE,CAAC;4BACb,OAAO;wBACR,CAAC;wBAED,MAAM,YAAY,GAAG,GAAG,GAAG,kBAAkB,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;wBAC1E,MAAM,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;wBAEhD,kCAAkC;wBAClC,IAAI,OAAO,EAAE,CAAC;4BACb,OAAO;wBACR,CAAC;wBAED,0BAA0B;wBAC1B,IAAI,MAAM,EAAE,CAAC;4BACZ,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;wBAC9C,CAAC;wBAED,MAAM,UAAU,GAAG,kBAAkB,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;wBAC/D,sCAAsC;wBACtC,MAAM,IAAI,GACT,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,cAAc;4BACtC,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,kCAAkC;4BAC/E,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;wBACpB,OAAO,CAAC;4BACP,OAAO,EAAE;gCACR;oCACC,IAAI,EAAE,MAAM;oCACZ,IAAI,EAAE,yBAAyB,KAAK,CAAC,MAAM,gBAAgB,IAAI,GAAG;iCAClE;6BACD;4BACD,OAAO,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,UAAU,CAAC,gBAAgB,EAAE;yBAChE,CAAC,CAAC;oBACJ,CAAC;oBAAC,OAAO,KAAc,EAAE,CAAC;wBACzB,0BAA0B;wBAC1B,IAAI,MAAM,EAAE,CAAC;4BACZ,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;wBAC9C,CAAC;wBAED,IAAI,CAAC,OAAO,EAAE,CAAC;4BACd,MAAM,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;wBACnE,CAAC;oBACF,CAAC;gBAAA,CACD,CAAC,EAAE,CAAC;YAAA,CACL,CAAC,CACH,CAAC;QAAA,CACF;QACD,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE;YAChC,MAAM,SAAS,GAAG,0BAA0B,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;YACnF,MAAM,YAAY,GAAG,yBAAyB,CAAC,IAAsC,CAAC,CAAC;YACvF,MAAM,OAAO,GAAG,YAAY;gBAC3B,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC,KAAK,EAAE,CAAC;gBACxE,CAAC,CAAC,SAAS,CAAC;YAEb,IAAI,SAAS,CAAC,cAAc,KAAK,OAAO,EAAE,CAAC;gBAC1C,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC;gBAC9B,SAAS,CAAC,cAAc,GAAG,OAAO,CAAC;gBACnC,SAAS,CAAC,cAAc,GAAG,KAAK,CAAC;gBACjC,SAAS,CAAC,YAAY,GAAG,KAAK,CAAC;YAChC,CAAC;YAED,IAAI,OAAO,CAAC,YAAY,IAAI,YAAY,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,CAAC;gBAC7F,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC;gBAChC,MAAM,UAAU,GAAG,OAAO,CAAC;gBAC3B,KAAK,gBAAgB,CAAC,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;oBAC3F,IAAI,SAAS,CAAC,cAAc,KAAK,UAAU,EAAE,CAAC;wBAC7C,cAAc,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;wBAC/C,OAAO,CAAC,UAAU,EAAE,CAAC;oBACtB,CAAC;gBAAA,CACD,CAAC,CAAC;YACJ,CAAC;YAED,OAAO,sBAAsB,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAAA,CACtD;QACD,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE;YAC9C,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC;YAClD,MAAM,YAAY,GAAG,yBAAyB,CAAC,OAAO,CAAC,IAAsC,CAAC,CAAC;YAC/F,MAAM,OAAO,GAAG,YAAY;gBAC3B,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC,KAAK,EAAE,CAAC;gBACxE,CAAC,CAAC,SAAS,CAAC;YACb,MAAM,WAAW,GAAG,MAA4B,CAAC;YACjD,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;YAC5E,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,aAAa,EAAE,CAAC;gBACnB,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;oBACpC,OAAO;wBACN,cAAc,CACb,aAAa,EACb,EAAE,IAAI,EAAE,UAAU,EAAE,gBAAgB,EAAE,WAAW,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAC7E,OAAO,CACP,IAAI,OAAO,CAAC;gBACf,CAAC;gBACD,IAAI,aAAa,CAAC,YAAY,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;oBACpD,aAAa,CAAC,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC;oBAC7C,OAAO,GAAG,IAAI,CAAC;gBAChB,CAAC;gBACD,IAAI,OAAO,EAAE,CAAC;oBACb,sBAAsB,CAAC,aAAa,EAAE,OAAO,CAAC,IAAsC,EAAE,KAAK,CAAC,CAAC;gBAC9F,CAAC;YACF,CAAC;YAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YAC3G,MAAM,SAAS,GAAI,OAAO,CAAC,aAAuC,IAAI,IAAI,SAAS,EAAE,CAAC;YACtF,SAAS,CAAC,KAAK,EAAE,CAAC;YAClB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACb,OAAO,SAAS,CAAC;YAClB,CAAC;YACD,SAAS,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAClC,SAAS,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC3C,OAAO,SAAS,CAAC;QAAA,CACjB;KACD,CAAC;AAAA,CACF;AAED,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,OAAyB,EAAgC;IACpG,OAAO,kBAAkB,CAAC,wBAAwB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,CAClE","sourcesContent":["import type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Container, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { renderDiff } from \"../../modes/interactive/components/diff.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport {\n\tapplyEditsToNormalizedContent,\n\tcomputeEditsDiff,\n\tdetectLineEnding,\n\ttype Edit,\n\ttype EditDiffError,\n\ttype EditDiffResult,\n\tgenerateDiffString,\n\tnormalizeToLF,\n\trestoreLineEndings,\n\tstripBom,\n} from \"./edit-diff.js\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\n/** Maximum characters for diff output sent to the LLM. */\nconst MAX_DIFF_CHARS = 4000;\n\ntype EditPreview = EditDiffResult | EditDiffError;\n\ntype EditRenderState = {\n\tcallComponent?: EditCallRenderComponent;\n};\n\nconst replaceEditSchema = Type.Object(\n\t{\n\t\toldText: Type.String({\n\t\t\tdescription:\n\t\t\t\t\"Exact text for one targeted replacement. It must be unique in the original file (unless replaceAll is true) and must not overlap with any other edits[].oldText in the same call.\",\n\t\t}),\n\t\tnewText: Type.String({ description: \"Replacement text for this targeted edit.\" }),\n\t\treplaceAll: Type.Optional(\n\t\t\tType.Boolean({\n\t\t\t\tdescription:\n\t\t\t\t\t\"When true, replace every occurrence of oldText instead of requiring it to be unique. Use for renaming a symbol/string throughout the file. Default false.\",\n\t\t\t}),\n\t\t),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst editSchema = Type.Object(\n\t{\n\t\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\t\tedits: Type.Array(replaceEditSchema, {\n\t\t\tdescription:\n\t\t\t\t\"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead.\",\n\t\t}),\n\t},\n\t{ additionalProperties: false },\n);\n\nexport type EditToolInput = Static<typeof editSchema>;\ntype LegacyEditToolInput = EditToolInput & {\n\toldText?: unknown;\n\tnewText?: unknown;\n};\n\nexport interface EditToolDetails {\n\t/** Unified diff of the changes made */\n\tdiff: string;\n\t/** Line number of the first change in the new file (for editor navigation) */\n\tfirstChangedLine?: number;\n}\n\n/**\n * Pluggable operations for the edit tool.\n * Override these to delegate file editing to remote systems (for example SSH).\n */\nexport interface EditOperations {\n\t/** Read file contents as a Buffer */\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\t/** Write content to a file */\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\t/** Check if file is readable and writable (throw if not) */\n\taccess: (absolutePath: string) => Promise<void>;\n}\n\nconst defaultEditOperations: EditOperations = {\n\treadFile: (path) => fsReadFile(path),\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\taccess: (path) => fsAccess(path, constants.R_OK | constants.W_OK),\n};\n\nexport interface EditToolOptions {\n\t/** Custom operations for file editing. Default: local filesystem */\n\toperations?: EditOperations;\n}\n\nfunction prepareEditArguments(input: unknown): EditToolInput {\n\tif (!input || typeof input !== \"object\") {\n\t\treturn input as EditToolInput;\n\t}\n\n\tconst args = input as Record<string, unknown>;\n\n\t// Some models (Opus 4.6, GLM-5.1) send edits as a JSON string instead of an array\n\tif (typeof args.edits === \"string\") {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(args.edits);\n\t\t\tif (Array.isArray(parsed)) args.edits = parsed;\n\t\t} catch {}\n\t}\n\n\tconst legacy = args as LegacyEditToolInput;\n\tif (typeof legacy.oldText !== \"string\" || typeof legacy.newText !== \"string\") {\n\t\treturn args as EditToolInput;\n\t}\n\n\tconst edits = Array.isArray(legacy.edits) ? [...legacy.edits] : [];\n\tedits.push({ oldText: legacy.oldText, newText: legacy.newText });\n\tconst { oldText: _oldText, newText: _newText, ...rest } = legacy;\n\treturn { ...rest, edits } as EditToolInput;\n}\n\nfunction validateEditInput(input: EditToolInput): { path: string; edits: Edit[] } {\n\tif (!Array.isArray(input.edits) || input.edits.length === 0) {\n\t\tthrow new Error(\"Edit tool input is invalid. edits must contain at least one replacement.\");\n\t}\n\treturn { path: input.path, edits: input.edits };\n}\n\ntype RenderableEditArgs = {\n\tpath?: string;\n\tfile_path?: string;\n\tedits?: Edit[];\n\toldText?: string;\n\tnewText?: string;\n};\n\ntype EditToolResultLike = {\n\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\tdetails?: EditToolDetails;\n};\n\ntype EditCallRenderComponent = Box & {\n\tpreview?: EditPreview;\n\tpreviewArgsKey?: string;\n\tpreviewPending?: boolean;\n\tsettledError?: boolean;\n};\n\nfunction createEditCallRenderComponent(): EditCallRenderComponent {\n\treturn Object.assign(new Box(1, 0, (text: string) => text), {\n\t\tpreview: undefined as EditPreview | undefined,\n\t\tpreviewArgsKey: undefined as string | undefined,\n\t\tpreviewPending: false,\n\t\tsettledError: false,\n\t});\n}\n\nfunction getEditCallRenderComponent(state: EditRenderState, lastComponent: unknown): EditCallRenderComponent {\n\tif (lastComponent instanceof Box) {\n\t\tconst component = lastComponent as EditCallRenderComponent;\n\t\tstate.callComponent = component;\n\t\treturn component;\n\t}\n\tif (state.callComponent) {\n\t\treturn state.callComponent;\n\t}\n\tconst component = createEditCallRenderComponent();\n\tstate.callComponent = component;\n\treturn component;\n}\n\nfunction getRenderablePreviewInput(args: RenderableEditArgs | undefined): { path: string; edits: Edit[] } | null {\n\tif (!args) {\n\t\treturn null;\n\t}\n\n\tconst path = typeof args.path === \"string\" ? args.path : typeof args.file_path === \"string\" ? args.file_path : null;\n\tif (!path) {\n\t\treturn null;\n\t}\n\n\tif (\n\t\tArray.isArray(args.edits) &&\n\t\targs.edits.length > 0 &&\n\t\targs.edits.every((edit) => typeof edit?.oldText === \"string\" && typeof edit?.newText === \"string\")\n\t) {\n\t\treturn { path, edits: args.edits };\n\t}\n\n\tif (typeof args.oldText === \"string\" && typeof args.newText === \"string\") {\n\t\treturn { path, edits: [{ oldText: args.oldText, newText: args.newText }] };\n\t}\n\n\treturn null;\n}\n\nfunction formatEditCall(\n\targs: RenderableEditArgs | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst invalidArg = invalidArgText(theme);\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath) : null;\n\tconst pathDisplay = path === null ? invalidArg : path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\");\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"edit\"))} ${pathDisplay}`;\n}\n\nfunction formatEditResult(\n\targs: RenderableEditArgs | undefined,\n\tpreview: EditPreview | undefined,\n\tresult: EditToolResultLike,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tisError: boolean,\n): string | undefined {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst previewDiff = preview && !(\"error\" in preview) ? preview.diff : undefined;\n\tconst previewError = preview && \"error\" in preview ? preview.error : undefined;\n\tif (isError) {\n\t\tconst errorText = result.content\n\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t.map((c) => c.text || \"\")\n\t\t\t.join(\"\\n\");\n\t\tif (!errorText || errorText === previewError) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn theme.fg(\"error\", errorText);\n\t}\n\n\tconst resultDiff = result.details?.diff;\n\tif (resultDiff && resultDiff !== previewDiff) {\n\t\treturn renderDiff(resultDiff, { filePath: rawPath ?? undefined });\n\t}\n\n\treturn undefined;\n}\n\nfunction getEditHeaderBg(\n\tpreview: EditPreview | undefined,\n\tsettledError: boolean | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): (text: string) => string {\n\tif (preview) {\n\t\tif (\"error\" in preview) {\n\t\t\treturn (text: string) => theme.bg(\"toolErrorBg\", text);\n\t\t}\n\t\treturn (text: string) => theme.bg(\"toolSuccessBg\", text);\n\t}\n\tif (settledError) {\n\t\treturn (text: string) => theme.bg(\"toolErrorBg\", text);\n\t}\n\treturn (text: string) => theme.bg(\"toolPendingBg\", text);\n}\n\nfunction buildEditCallComponent(\n\tcomponent: EditCallRenderComponent,\n\targs: RenderableEditArgs | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): EditCallRenderComponent {\n\tcomponent.setBgFn(getEditHeaderBg(component.preview, component.settledError, theme));\n\tcomponent.clear();\n\tcomponent.addChild(new Text(formatEditCall(args, theme), 0, 0));\n\n\tif (!component.preview) {\n\t\treturn component;\n\t}\n\n\tconst body =\n\t\t\"error\" in component.preview ? theme.fg(\"error\", component.preview.error) : renderDiff(component.preview.diff);\n\tcomponent.addChild(new Spacer(1));\n\tcomponent.addChild(new Text(body, 0, 0));\n\treturn component;\n}\n\nfunction setEditPreview(\n\tcomponent: EditCallRenderComponent,\n\tpreview: EditPreview,\n\targsKey: string | undefined,\n): boolean {\n\tconst current = component.preview;\n\tconst changed =\n\t\tcurrent === undefined ||\n\t\t(\"error\" in current && \"error\" in preview\n\t\t\t? current.error !== preview.error\n\t\t\t: \"error\" in current !== \"error\" in preview) ||\n\t\t(!(\"error\" in current) &&\n\t\t\t!(\"error\" in preview) &&\n\t\t\t(current.diff !== preview.diff || current.firstChangedLine !== preview.firstChangedLine));\n\tcomponent.preview = preview;\n\tcomponent.previewArgsKey = argsKey;\n\tcomponent.previewPending = false;\n\treturn changed;\n}\n\nexport function createEditToolDefinition(\n\tcwd: string,\n\toptions?: EditToolOptions,\n): ToolDefinition<typeof editSchema, EditToolDetails | undefined, EditRenderState> {\n\tconst ops = options?.operations ?? defaultEditOperations;\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file, unless that edit sets replaceAll: true to replace all of its occurrences. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.\",\n\t\tpromptSnippet:\n\t\t\t\"Make precise file edits with exact text replacement, including multiple disjoint edits in one call\",\n\t\t// Only the batching preference lives here. The matching semantics\n\t\t// (original-file matching, no overlaps, replaceAll) are already stated in the\n\t\t// `edits`/`oldText`/`replaceAll` schema descriptions, which ship on every turn\n\t\t// alongside these — repeating them spent the tokens twice.\n\t\tpromptGuidelines: [\n\t\t\t\"When changing several separate locations in one file, send one edit call with multiple entries in edits[] rather than several edit calls. Keep each oldText as small as it can be while staying unique.\",\n\t\t],\n\t\tparameters: editSchema,\n\t\trenderShell: \"self\",\n\t\tprepareArguments: prepareEditArguments,\n\t\tasync execute(_toolCallId, input: EditToolInput, signal?: AbortSignal, _onUpdate?, _ctx?) {\n\t\t\tconst { path, edits } = validateEditInput(input);\n\t\t\tconst absolutePath = resolveToCwd(path, cwd);\n\n\t\t\treturn withFileMutationQueue(\n\t\t\t\tabsolutePath,\n\t\t\t\t() =>\n\t\t\t\t\tnew Promise<{\n\t\t\t\t\t\tcontent: Array<{ type: \"text\"; text: string }>;\n\t\t\t\t\t\tdetails: EditToolDetails | undefined;\n\t\t\t\t\t}>((resolve, reject) => {\n\t\t\t\t\t\t// Check if already aborted.\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet aborted = false;\n\n\t\t\t\t\t\t// Set up abort handler.\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Perform the edit operation.\n\t\t\t\t\t\tvoid (async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// Check if file exists.\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tawait ops.access(absolutePath);\n\t\t\t\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\t\t\t\terror instanceof Error && \"code\" in error ? `Error code: ${error.code}` : String(error);\n\t\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treject(new Error(`Could not edit file: ${path}. ${errorMessage}.`));\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Check if aborted before reading.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Read the file.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst rawContent = buffer.toString(\"utf-8\");\n\n\t\t\t\t\t\t\t\t// Check if aborted after reading.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Strip BOM before matching. The model will not include an invisible BOM in oldText.\n\t\t\t\t\t\t\t\tconst { bom, text: content } = stripBom(rawContent);\n\t\t\t\t\t\t\t\tconst originalEnding = detectLineEnding(content);\n\t\t\t\t\t\t\t\tconst normalizedContent = normalizeToLF(content);\n\t\t\t\t\t\t\t\tconst { baseContent, newContent } = applyEditsToNormalizedContent(\n\t\t\t\t\t\t\t\t\tnormalizedContent,\n\t\t\t\t\t\t\t\t\tedits,\n\t\t\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t// Check if aborted before writing.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst finalContent = bom + restoreLineEndings(newContent, originalEnding);\n\t\t\t\t\t\t\t\tawait ops.writeFile(absolutePath, finalContent);\n\n\t\t\t\t\t\t\t\t// Check if aborted after writing.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Clean up abort handler.\n\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst diffResult = generateDiffString(baseContent, newContent);\n\t\t\t\t\t\t\t\t// Truncate large diffs to save tokens\n\t\t\t\t\t\t\t\tconst diff =\n\t\t\t\t\t\t\t\t\tdiffResult.diff.length > MAX_DIFF_CHARS\n\t\t\t\t\t\t\t\t\t\t? `${diffResult.diff.slice(0, MAX_DIFF_CHARS)}\\n\\n[diff truncated for brevity]`\n\t\t\t\t\t\t\t\t\t\t: diffResult.diff;\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\t\ttext: `Successfully replaced ${edits.length} block(s) in ${path}.`,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\tdetails: { diff, firstChangedLine: diffResult.firstChangedLine },\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t\t\t// Clean up abort handler.\n\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (!aborted) {\n\t\t\t\t\t\t\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\t\t\t\t\t}),\n\t\t\t);\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst component = getEditCallRenderComponent(context.state, context.lastComponent);\n\t\t\tconst previewInput = getRenderablePreviewInput(args as RenderableEditArgs | undefined);\n\t\t\tconst argsKey = previewInput\n\t\t\t\t? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })\n\t\t\t\t: undefined;\n\n\t\t\tif (component.previewArgsKey !== argsKey) {\n\t\t\t\tcomponent.preview = undefined;\n\t\t\t\tcomponent.previewArgsKey = argsKey;\n\t\t\t\tcomponent.previewPending = false;\n\t\t\t\tcomponent.settledError = false;\n\t\t\t}\n\n\t\t\tif (context.argsComplete && previewInput && !component.preview && !component.previewPending) {\n\t\t\t\tcomponent.previewPending = true;\n\t\t\t\tconst requestKey = argsKey;\n\t\t\t\tvoid computeEditsDiff(previewInput.path, previewInput.edits, context.cwd).then((preview) => {\n\t\t\t\t\tif (component.previewArgsKey === requestKey) {\n\t\t\t\t\t\tsetEditPreview(component, preview, requestKey);\n\t\t\t\t\t\tcontext.invalidate();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn buildEditCallComponent(component, args, theme);\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst callComponent = context.state.callComponent;\n\t\t\tconst previewInput = getRenderablePreviewInput(context.args as RenderableEditArgs | undefined);\n\t\t\tconst argsKey = previewInput\n\t\t\t\t? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })\n\t\t\t\t: undefined;\n\t\t\tconst typedResult = result as EditToolResultLike;\n\t\t\tconst resultDiff = !context.isError ? typedResult.details?.diff : undefined;\n\t\t\tlet changed = false;\n\t\t\tif (callComponent) {\n\t\t\t\tif (typeof resultDiff === \"string\") {\n\t\t\t\t\tchanged =\n\t\t\t\t\t\tsetEditPreview(\n\t\t\t\t\t\t\tcallComponent,\n\t\t\t\t\t\t\t{ diff: resultDiff, firstChangedLine: typedResult.details?.firstChangedLine },\n\t\t\t\t\t\t\targsKey,\n\t\t\t\t\t\t) || changed;\n\t\t\t\t}\n\t\t\t\tif (callComponent.settledError !== context.isError) {\n\t\t\t\t\tcallComponent.settledError = context.isError;\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t\tif (changed) {\n\t\t\t\t\tbuildEditCallComponent(callComponent, context.args as RenderableEditArgs | undefined, theme);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst output = formatEditResult(context.args, callComponent?.preview, typedResult, theme, context.isError);\n\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\tcomponent.clear();\n\t\t\tif (!output) {\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tcomponent.addChild(new Spacer(1));\n\t\t\tcomponent.addChild(new Text(output, 1, 0));\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createEditTool(cwd: string, options?: EditToolOptions): AgentTool<typeof editSchema> {\n\treturn wrapToolDefinition(createEditToolDefinition(cwd, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"edit.js","sourceRoot":"","sources":["../../../src/core/tools/edit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Container, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { renderDiff } from \"../../modes/interactive/components/diff.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport {\n\tapplyEditsToNormalizedContent,\n\tcomputeEditsDiff,\n\tdetectLineEnding,\n\ttype Edit,\n\ttype EditDiffError,\n\ttype EditDiffResult,\n\tgenerateDiffString,\n\tnormalizeToLF,\n\trestoreLineEndings,\n\tstripBom,\n} from \"./edit-diff.js\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\n/** Maximum characters for diff output sent to the LLM. */\nconst MAX_DIFF_CHARS = 4000;\n\ntype EditPreview = EditDiffResult | EditDiffError;\n\ntype EditRenderState = {\n\tcallComponent?: EditCallRenderComponent;\n};\n\nconst replaceEditSchema = Type.Object(\n\t{\n\t\toldText: Type.String({\n\t\t\tdescription:\n\t\t\t\t\"Exact text for one targeted replacement. It must be unique in the original file (unless replaceAll is true) and must not overlap with any other edits[].oldText in the same call.\",\n\t\t}),\n\t\tnewText: Type.String({ description: \"Replacement text for this targeted edit.\" }),\n\t\treplaceAll: Type.Optional(\n\t\t\tType.Boolean({\n\t\t\t\tdescription:\n\t\t\t\t\t\"When true, replace every occurrence of oldText instead of requiring it to be unique. Use for renaming a symbol/string throughout the file. Default false.\",\n\t\t\t}),\n\t\t),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst editSchema = Type.Object(\n\t{\n\t\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\t\tedits: Type.Array(replaceEditSchema, {\n\t\t\tdescription:\n\t\t\t\t\"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead.\",\n\t\t}),\n\t},\n\t{ additionalProperties: false },\n);\n\nexport type EditToolInput = Static<typeof editSchema>;\ntype LegacyEditToolInput = EditToolInput & {\n\toldText?: unknown;\n\tnewText?: unknown;\n};\n\nexport interface EditToolDetails {\n\t/** Unified diff of the changes made */\n\tdiff: string;\n\t/** Line number of the first change in the new file (for editor navigation) */\n\tfirstChangedLine?: number;\n}\n\n/**\n * Pluggable operations for the edit tool.\n * Override these to delegate file editing to remote systems (for example SSH).\n */\nexport interface EditOperations {\n\t/** Read file contents as a Buffer */\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\t/** Write content to a file */\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\t/** Check if file is readable and writable (throw if not) */\n\taccess: (absolutePath: string) => Promise<void>;\n}\n\nconst defaultEditOperations: EditOperations = {\n\treadFile: (path) => fsReadFile(path),\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\taccess: (path) => fsAccess(path, constants.R_OK | constants.W_OK),\n};\n\nexport interface EditToolOptions {\n\t/** Custom operations for file editing. Default: local filesystem */\n\toperations?: EditOperations;\n}\n\nfunction prepareEditArguments(input: unknown): EditToolInput {\n\tif (!input || typeof input !== \"object\") {\n\t\treturn input as EditToolInput;\n\t}\n\n\tconst args = input as Record<string, unknown>;\n\n\t// Some models (Opus 4.6, GLM-5.1) send edits as a JSON string instead of an array\n\tif (typeof args.edits === \"string\") {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(args.edits);\n\t\t\tif (Array.isArray(parsed)) args.edits = parsed;\n\t\t} catch {}\n\t}\n\n\tconst legacy = args as LegacyEditToolInput;\n\tif (typeof legacy.oldText !== \"string\" || typeof legacy.newText !== \"string\") {\n\t\treturn args as EditToolInput;\n\t}\n\n\tconst edits = Array.isArray(legacy.edits) ? [...legacy.edits] : [];\n\tedits.push({ oldText: legacy.oldText, newText: legacy.newText });\n\tconst { oldText: _oldText, newText: _newText, ...rest } = legacy;\n\treturn { ...rest, edits } as EditToolInput;\n}\n\nfunction validateEditInput(input: EditToolInput): { path: string; edits: Edit[] } {\n\tif (!Array.isArray(input.edits) || input.edits.length === 0) {\n\t\tthrow new Error(\"Edit tool input is invalid. edits must contain at least one replacement.\");\n\t}\n\treturn { path: input.path, edits: input.edits };\n}\n\ntype RenderableEditArgs = {\n\tpath?: string;\n\tfile_path?: string;\n\tedits?: Edit[];\n\toldText?: string;\n\tnewText?: string;\n};\n\ntype EditToolResultLike = {\n\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\tdetails?: EditToolDetails;\n};\n\ntype EditCallRenderComponent = Box & {\n\tpreview?: EditPreview;\n\tpreviewArgsKey?: string;\n\tpreviewPending?: boolean;\n\tsettledError?: boolean;\n};\n\nfunction createEditCallRenderComponent(): EditCallRenderComponent {\n\treturn Object.assign(new Box(1, 0, (text: string) => text), {\n\t\tpreview: undefined as EditPreview | undefined,\n\t\tpreviewArgsKey: undefined as string | undefined,\n\t\tpreviewPending: false,\n\t\tsettledError: false,\n\t});\n}\n\nfunction getEditCallRenderComponent(state: EditRenderState, lastComponent: unknown): EditCallRenderComponent {\n\tif (lastComponent instanceof Box) {\n\t\tconst component = lastComponent as EditCallRenderComponent;\n\t\tstate.callComponent = component;\n\t\treturn component;\n\t}\n\tif (state.callComponent) {\n\t\treturn state.callComponent;\n\t}\n\tconst component = createEditCallRenderComponent();\n\tstate.callComponent = component;\n\treturn component;\n}\n\nfunction getRenderablePreviewInput(args: RenderableEditArgs | undefined): { path: string; edits: Edit[] } | null {\n\tif (!args) {\n\t\treturn null;\n\t}\n\n\tconst path = typeof args.path === \"string\" ? args.path : typeof args.file_path === \"string\" ? args.file_path : null;\n\tif (!path) {\n\t\treturn null;\n\t}\n\n\tif (\n\t\tArray.isArray(args.edits) &&\n\t\targs.edits.length > 0 &&\n\t\targs.edits.every((edit) => typeof edit?.oldText === \"string\" && typeof edit?.newText === \"string\")\n\t) {\n\t\treturn { path, edits: args.edits };\n\t}\n\n\tif (typeof args.oldText === \"string\" && typeof args.newText === \"string\") {\n\t\treturn { path, edits: [{ oldText: args.oldText, newText: args.newText }] };\n\t}\n\n\treturn null;\n}\n\nfunction formatEditCall(\n\targs: RenderableEditArgs | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst invalidArg = invalidArgText(theme);\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath) : null;\n\tconst pathDisplay = path === null ? invalidArg : path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\");\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"edit\"))} ${pathDisplay}`;\n}\n\nfunction formatEditResult(\n\targs: RenderableEditArgs | undefined,\n\tpreview: EditPreview | undefined,\n\tresult: EditToolResultLike,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tisError: boolean,\n): string | undefined {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst previewDiff = preview && !(\"error\" in preview) ? preview.diff : undefined;\n\tconst previewError = preview && \"error\" in preview ? preview.error : undefined;\n\tif (isError) {\n\t\tconst errorText = result.content\n\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t.map((c) => c.text || \"\")\n\t\t\t.join(\"\\n\");\n\t\tif (!errorText || errorText === previewError) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn theme.fg(\"error\", errorText);\n\t}\n\n\tconst resultDiff = result.details?.diff;\n\tif (resultDiff && resultDiff !== previewDiff) {\n\t\treturn renderDiff(resultDiff, { filePath: rawPath ?? undefined });\n\t}\n\n\treturn undefined;\n}\n\nfunction getEditHeaderBg(\n\tpreview: EditPreview | undefined,\n\tsettledError: boolean | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): (text: string) => string {\n\tif (preview) {\n\t\tif (\"error\" in preview) {\n\t\t\treturn (text: string) => theme.bg(\"toolErrorBg\", text);\n\t\t}\n\t\treturn (text: string) => theme.bg(\"toolSuccessBg\", text);\n\t}\n\tif (settledError) {\n\t\treturn (text: string) => theme.bg(\"toolErrorBg\", text);\n\t}\n\treturn (text: string) => theme.bg(\"toolPendingBg\", text);\n}\n\nfunction buildEditCallComponent(\n\tcomponent: EditCallRenderComponent,\n\targs: RenderableEditArgs | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): EditCallRenderComponent {\n\tcomponent.setBgFn(getEditHeaderBg(component.preview, component.settledError, theme));\n\t// The horizontal padding exists to carry the header band. With no band —\n\t// a bare call line in a folded view — it is just an indent that pushes this\n\t// tool's text one column right of every other tool's.\n\tcomponent.setPaddingX(component.preview || component.settledError ? 1 : 0);\n\tcomponent.clear();\n\tcomponent.addChild(new Text(formatEditCall(args, theme), 0, 0));\n\n\tif (!component.preview) {\n\t\treturn component;\n\t}\n\n\tconst body =\n\t\t\"error\" in component.preview ? theme.fg(\"error\", component.preview.error) : renderDiff(component.preview.diff);\n\tcomponent.addChild(new Spacer(1));\n\tcomponent.addChild(new Text(body, 0, 0));\n\treturn component;\n}\n\nfunction setEditPreview(\n\tcomponent: EditCallRenderComponent,\n\tpreview: EditPreview,\n\targsKey: string | undefined,\n): boolean {\n\tconst current = component.preview;\n\tconst changed =\n\t\tcurrent === undefined ||\n\t\t(\"error\" in current && \"error\" in preview\n\t\t\t? current.error !== preview.error\n\t\t\t: \"error\" in current !== \"error\" in preview) ||\n\t\t(!(\"error\" in current) &&\n\t\t\t!(\"error\" in preview) &&\n\t\t\t(current.diff !== preview.diff || current.firstChangedLine !== preview.firstChangedLine));\n\tcomponent.preview = preview;\n\tcomponent.previewArgsKey = argsKey;\n\tcomponent.previewPending = false;\n\treturn changed;\n}\n\nexport function createEditToolDefinition(\n\tcwd: string,\n\toptions?: EditToolOptions,\n): ToolDefinition<typeof editSchema, EditToolDetails | undefined, EditRenderState> {\n\tconst ops = options?.operations ?? defaultEditOperations;\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file, unless that edit sets replaceAll: true to replace all of its occurrences. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.\",\n\t\tpromptSnippet:\n\t\t\t\"Make precise file edits with exact text replacement, including multiple disjoint edits in one call\",\n\t\t// Only the batching preference lives here. The matching semantics\n\t\t// (original-file matching, no overlaps, replaceAll) are already stated in the\n\t\t// `edits`/`oldText`/`replaceAll` schema descriptions, which ship on every turn\n\t\t// alongside these — repeating them spent the tokens twice.\n\t\tpromptGuidelines: [\n\t\t\t\"When changing several separate locations in one file, send one edit call with multiple entries in edits[] rather than several edit calls. Keep each oldText as small as it can be while staying unique.\",\n\t\t],\n\t\tparameters: editSchema,\n\t\trenderShell: \"self\",\n\t\tprepareArguments: prepareEditArguments,\n\t\tasync execute(_toolCallId, input: EditToolInput, signal?: AbortSignal, _onUpdate?, _ctx?) {\n\t\t\tconst { path, edits } = validateEditInput(input);\n\t\t\tconst absolutePath = resolveToCwd(path, cwd);\n\n\t\t\treturn withFileMutationQueue(\n\t\t\t\tabsolutePath,\n\t\t\t\t() =>\n\t\t\t\t\tnew Promise<{\n\t\t\t\t\t\tcontent: Array<{ type: \"text\"; text: string }>;\n\t\t\t\t\t\tdetails: EditToolDetails | undefined;\n\t\t\t\t\t}>((resolve, reject) => {\n\t\t\t\t\t\t// Check if already aborted.\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet aborted = false;\n\n\t\t\t\t\t\t// Set up abort handler.\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Perform the edit operation.\n\t\t\t\t\t\tvoid (async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// Check if file exists.\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tawait ops.access(absolutePath);\n\t\t\t\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\t\t\t\terror instanceof Error && \"code\" in error ? `Error code: ${error.code}` : String(error);\n\t\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treject(new Error(`Could not edit file: ${path}. ${errorMessage}.`));\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Check if aborted before reading.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Read the file.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst rawContent = buffer.toString(\"utf-8\");\n\n\t\t\t\t\t\t\t\t// Check if aborted after reading.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Strip BOM before matching. The model will not include an invisible BOM in oldText.\n\t\t\t\t\t\t\t\tconst { bom, text: content } = stripBom(rawContent);\n\t\t\t\t\t\t\t\tconst originalEnding = detectLineEnding(content);\n\t\t\t\t\t\t\t\tconst normalizedContent = normalizeToLF(content);\n\t\t\t\t\t\t\t\tconst { baseContent, newContent } = applyEditsToNormalizedContent(\n\t\t\t\t\t\t\t\t\tnormalizedContent,\n\t\t\t\t\t\t\t\t\tedits,\n\t\t\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t// Check if aborted before writing.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst finalContent = bom + restoreLineEndings(newContent, originalEnding);\n\t\t\t\t\t\t\t\tawait ops.writeFile(absolutePath, finalContent);\n\n\t\t\t\t\t\t\t\t// Check if aborted after writing.\n\t\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Clean up abort handler.\n\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst diffResult = generateDiffString(baseContent, newContent);\n\t\t\t\t\t\t\t\t// Truncate large diffs to save tokens\n\t\t\t\t\t\t\t\tconst diff =\n\t\t\t\t\t\t\t\t\tdiffResult.diff.length > MAX_DIFF_CHARS\n\t\t\t\t\t\t\t\t\t\t? `${diffResult.diff.slice(0, MAX_DIFF_CHARS)}\\n\\n[diff truncated for brevity]`\n\t\t\t\t\t\t\t\t\t\t: diffResult.diff;\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\t\ttext: `Successfully replaced ${edits.length} block(s) in ${path}.`,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\tdetails: { diff, firstChangedLine: diffResult.firstChangedLine },\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t\t\t// Clean up abort handler.\n\t\t\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (!aborted) {\n\t\t\t\t\t\t\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\t\t\t\t\t}),\n\t\t\t);\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst component = getEditCallRenderComponent(context.state, context.lastComponent);\n\t\t\tconst previewInput = getRenderablePreviewInput(args as RenderableEditArgs | undefined);\n\t\t\tconst argsKey = previewInput\n\t\t\t\t? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })\n\t\t\t\t: undefined;\n\n\t\t\tif (component.previewArgsKey !== argsKey) {\n\t\t\t\tcomponent.preview = undefined;\n\t\t\t\tcomponent.previewArgsKey = argsKey;\n\t\t\t\tcomponent.previewPending = false;\n\t\t\t\tcomponent.settledError = false;\n\t\t\t}\n\n\t\t\tif (context.argsComplete && previewInput && !component.preview && !component.previewPending) {\n\t\t\t\tcomponent.previewPending = true;\n\t\t\t\tconst requestKey = argsKey;\n\t\t\t\tvoid computeEditsDiff(previewInput.path, previewInput.edits, context.cwd).then((preview) => {\n\t\t\t\t\tif (component.previewArgsKey === requestKey) {\n\t\t\t\t\t\tsetEditPreview(component, preview, requestKey);\n\t\t\t\t\t\tcontext.invalidate();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn buildEditCallComponent(component, args, theme);\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst callComponent = context.state.callComponent;\n\t\t\tconst previewInput = getRenderablePreviewInput(context.args as RenderableEditArgs | undefined);\n\t\t\tconst argsKey = previewInput\n\t\t\t\t? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })\n\t\t\t\t: undefined;\n\t\t\tconst typedResult = result as EditToolResultLike;\n\t\t\tconst resultDiff = !context.isError ? typedResult.details?.diff : undefined;\n\t\t\tlet changed = false;\n\t\t\tif (callComponent) {\n\t\t\t\tif (typeof resultDiff === \"string\") {\n\t\t\t\t\tchanged =\n\t\t\t\t\t\tsetEditPreview(\n\t\t\t\t\t\t\tcallComponent,\n\t\t\t\t\t\t\t{ diff: resultDiff, firstChangedLine: typedResult.details?.firstChangedLine },\n\t\t\t\t\t\t\targsKey,\n\t\t\t\t\t\t) || changed;\n\t\t\t\t}\n\t\t\t\tif (callComponent.settledError !== context.isError) {\n\t\t\t\t\tcallComponent.settledError = context.isError;\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t\tif (changed) {\n\t\t\t\t\tbuildEditCallComponent(callComponent, context.args as RenderableEditArgs | undefined, theme);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst output = formatEditResult(context.args, callComponent?.preview, typedResult, theme, context.isError);\n\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\tcomponent.clear();\n\t\t\tif (!output) {\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tcomponent.addChild(new Spacer(1));\n\t\t\tcomponent.addChild(new Text(output, 1, 0));\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createEditTool(cwd: string, options?: EditToolOptions): AgentTool<typeof editSchema> {\n\treturn wrapToolDefinition(createEditToolDefinition(cwd, options));\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"propose-plugin.d.ts","sourceRoot":"","sources":["../../../src/core/tools/propose-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAoBH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAsPzE,wBAAgB,iCAAiC,IAAI,cAAc,CA+ElE;AASD,wBAAgB,gCAAgC,IAAI,cAAc,CAkEjE;AAmCD,wBAAgB,0CAA0C,IAAI,cAAc,CAiE3E;AAED,qFAAqF;AACrF,wBAAgB,kCAAkC,IAAI,cAAc,EAAE,CAMrE","sourcesContent":["/**\n * Capability authoring tools (spec §3), refactored to a single risk-gated path.\n *\n * ProposePlugin author a NEW plugin from any capability mix — skills,\n * commands, subagents, hooks, MCP servers. The risk gate is\n * *computed from content*, not pre-declared by tool choice:\n * passive content (skills, commands, read-only subagents) is\n * authored autonomously; executable content (hooks, MCP servers,\n * mutating/high-privilege subagents) auto-triggers a \"show the\n * code + tool grant → human confirms → activate\" gate in the\n * same call. A mixed plugin (skill + hook) is authored in one\n * call, and a hook can never be mis-routed through a \"passive\"\n * tool because the gate keys off what the draft contains.\n * UpdatePlugin merge inline-authored capabilities into an EXISTING local\n * plugin. Nothing is fetched from a remote, so the supply-chain\n * \"benign v1 → hostile v2\" risk that keeps a marketplace\n * UpdatePlugin out of the model's hands does not apply here;\n * executable additions still pass through the same confirm gate.\n *\n * Both author into `.agents/plugins/<id>/` in the requested vendor layouts\n * (Claude Code + GitHub Copilot by default) via the format registry, so results\n * are proper, publishable plugins that round-trip through parsePluginDir.\n *\n * Privilege-amplification guardrail: an authored subagent may never carry a\n * plugin-system (capability-acquisition) tool in its allowlist — enforced in\n * both tools — so a low-trust authored agent cannot bootstrap privilege.\n */\n\nimport { type Static, Type } from \"typebox\";\nimport {\n\tclassifyAllowlist,\n\tgetPlugin,\n\tisAuthoredPlugin,\n\tmergePluginDraft,\n\tpluginExists,\n\tpromoteDraft,\n\tremoveFromPlugin,\n\tresolvePluginPlatforms,\n\twritePluginDraft,\n} from \"../extensions/plugins/authoring.js\";\nimport type { PluginPlatform } from \"../extensions/plugins/formats/platform-targets.js\";\nimport type { MarketplacePlatform, PluginDraft } from \"../extensions/plugins/formats/types.js\";\nimport { formatGateFindings, runStaticGates, withFindings } from \"../extensions/plugins/gates.js\";\nimport { discardDraftDir } from \"../extensions/plugins/locations.js\";\nimport { runSmokeGate } from \"../extensions/plugins/smoke.js\";\nimport type { ExtensionContext } from \"../extensions/types.js\";\nimport { defineTool, type ToolDefinition } from \"../extensions/types.js\";\nimport {\n\tPROPOSE_PLUGIN_TOOL_NAME,\n\tREMOVE_PLUGIN_CAPABILITY_TOOL_NAME,\n\tUPDATE_PLUGIN_TOOL_NAME,\n} from \"./plugin-tool-names.js\";\n\nconst skillSchema = Type.Object(\n\t{\n\t\tname: Type.String({ description: \"Skill name.\" }),\n\t\tdescription: Type.Optional(Type.String({ description: \"One-line trigger description (kept lazy in context).\" })),\n\t\tbody: Type.String({ description: \"SKILL.md instruction body (markdown).\" }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst commandSchema = Type.Object(\n\t{\n\t\tname: Type.String({ description: \"Command name (invoked as /name).\" }),\n\t\tdescription: Type.Optional(Type.String({ description: \"One-line description.\" })),\n\t\tbody: Type.String({ description: \"Prompt template body (markdown).\" }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst subagentSchema = Type.Object(\n\t{\n\t\tname: Type.String({ description: \"Subagent name.\" }),\n\t\tdescription: Type.Optional(Type.String({ description: \"When to dispatch this subagent.\" })),\n\t\ttools: Type.Optional(\n\t\t\tType.String({\n\t\t\t\tdescription:\n\t\t\t\t\t\"Comma-separated allowed-tools, e.g. 'read, grep, glob'. Read-only grants are autonomous; \" +\n\t\t\t\t\t\"mutating/exec/network grants (Bash, Write, Edit, MCP) or '*' require human confirmation. Omit for none.\",\n\t\t\t}),\n\t\t),\n\t\tmodel: Type.Optional(Type.String({ description: \"Model override, or 'inherit'.\" })),\n\t\tbody: Type.String({ description: \"System-prompt / instruction body (markdown).\" }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst hookSchema = Type.Object(\n\t{\n\t\tevent: Type.String({ description: \"Event: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ...\" }),\n\t\tmatcher: Type.Optional(Type.String({ description: \"Regex matched against the tool name. Empty/'*' = all.\" })),\n\t\tcommand: Type.String({ description: \"Shell command to run on the event.\" }),\n\t\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds.\" })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst mcpServerSchema = Type.Object(\n\t{\n\t\tname: Type.String({ description: \"MCP server name.\" }),\n\t\tcommand: Type.String({ description: \"Executable to launch the server.\" }),\n\t\targs: Type.Optional(Type.Array(Type.String(), { description: \"Command arguments.\" })),\n\t\tenv: Type.Optional(Type.Record(Type.String(), Type.String(), { description: \"Environment variables.\" })),\n\t},\n\t{ additionalProperties: false },\n);\n\n/** The capability params shared by ProposePlugin (create) and UpdatePlugin (merge). */\nconst capabilityProps = {\n\tdescription: Type.Optional(Type.String({ description: \"Plugin description.\" })),\n\tversion: Type.Optional(Type.String({ description: \"Plugin version, e.g. '0.1.0'.\" })),\n\tskills: Type.Optional(Type.Array(skillSchema)),\n\tcommands: Type.Optional(Type.Array(commandSchema)),\n\tsubagents: Type.Optional(\n\t\tType.Array(subagentSchema, {\n\t\t\tdescription: \"Subagents. Read-only allowlists are autonomous; mutating ones trigger human confirmation.\",\n\t\t}),\n\t),\n\thooks: Type.Optional(Type.Array(hookSchema, { description: \"Shell hooks (executable — trigger confirmation).\" })),\n\tmcpServers: Type.Optional(\n\t\tType.Array(mcpServerSchema, { description: \"MCP servers (executable — trigger confirmation).\" }),\n\t),\n} as const;\n\n/** Union of every capability a draft can carry (used to build a draft and to classify risk). */\ninterface CapabilityInput {\n\tdescription?: string;\n\tversion?: string;\n\tskills?: Static<typeof skillSchema>[];\n\tcommands?: Static<typeof commandSchema>[];\n\tsubagents?: Static<typeof subagentSchema>[];\n\thooks?: Static<typeof hookSchema>[];\n\tmcpServers?: Static<typeof mcpServerSchema>[];\n}\n\nfunction resolvePlatforms(): PluginPlatform[] {\n\t// No model-facing platform selection: the human picks the target ecosystem\n\t// with --platform, defaulting to claude. A plugin is a distribution unit, so\n\t// there is no vendor-neutral option here. See resolvePluginPlatforms.\n\treturn resolvePluginPlatforms();\n}\n\nfunction draftFrom(id: string, params: CapabilityInput, platforms: MarketplacePlatform[]): PluginDraft {\n\treturn {\n\t\tid,\n\t\tversion: params.version,\n\t\tdescription: params.description,\n\t\tsupportPlatform: platforms,\n\t\tskills: params.skills,\n\t\tcommands: params.commands,\n\t\tagents: params.subagents,\n\t\thooks: params.hooks,\n\t\tmcpServers: params.mcpServers,\n\t};\n}\n\n/** Total capabilities carried by the draft (empty arrays count as nothing). */\nfunction capabilityCount(params: CapabilityInput): number {\n\treturn (\n\t\t(params.skills?.length ?? 0) +\n\t\t(params.commands?.length ?? 0) +\n\t\t(params.subagents?.length ?? 0) +\n\t\t(params.hooks?.length ?? 0) +\n\t\t(params.mcpServers?.length ?? 0)\n\t);\n}\n\n/** The subagents whose allowlist makes them mutating/high-privilege (need the confirm gate). */\nfunction mutatingSubagents(params: CapabilityInput): Static<typeof subagentSchema>[] {\n\treturn (params.subagents ?? []).filter((sa) => classifyAllowlist(sa.tools).risk === \"mutating\");\n}\n\n/** True when the draft carries anything executable — hooks, MCP servers, or a mutating subagent. */\nfunction hasExecutable(params: CapabilityInput): boolean {\n\treturn (\n\t\t(params.hooks?.length ?? 0) > 0 || (params.mcpServers?.length ?? 0) > 0 || mutatingSubagents(params).length > 0\n\t);\n}\n\n/** Reject if any subagent carries a plugin-system tool (privilege-amplification guardrail). Returns the message, or null. */\nfunction guardrailViolation(params: CapabilityInput): string | null {\n\tfor (const sa of params.subagents ?? []) {\n\t\tconst cls = classifyAllowlist(sa.tools);\n\t\tif (cls.pluginTools.length > 0) {\n\t\t\treturn (\n\t\t\t\t`Subagent \"${sa.name}\" requests plugin-system tools (${cls.pluginTools.join(\", \")}). ` +\n\t\t\t\t\"Authored subagents may never carry capability-acquisition tools.\"\n\t\t\t);\n\t\t}\n\t}\n\treturn null;\n}\n\n/** Build the human-facing review text: the executable code and every mutating tool grant. */\nfunction buildReview(id: string, params: CapabilityInput): string {\n\tconst lines: string[] = [`Plugin \"${id}\" wants to install executable capabilities:`];\n\tfor (const h of params.hooks ?? []) {\n\t\tlines.push(` hook [${h.event}${h.matcher ? ` matcher=${h.matcher}` : \"\"}]: ${h.command}`);\n\t}\n\tfor (const s of params.mcpServers ?? []) {\n\t\tlines.push(` mcp server \"${s.name}\": ${s.command}${s.args?.length ? ` ${s.args.join(\" \")}` : \"\"}`);\n\t}\n\tfor (const sa of mutatingSubagents(params)) {\n\t\tlines.push(` subagent \"${sa.name}\" tools: ${sa.tools ?? \"(none)\"} (${classifyAllowlist(sa.tools).reason})`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nfunction summarizeWrite(\n\tid: string,\n\tplatforms: MarketplacePlatform[],\n\tfiles: string[],\n\tdest: string,\n\tverb: string,\n): string {\n\treturn (\n\t\t`${verb} plugin \"${id}\" (${platforms.join(\", \")}) with ${files.length} file(s) at ${dest}:\\n` +\n\t\tfiles.map((f) => ` ${f}`).join(\"\\n\") +\n\t\t`\\nRemove it with UninstallPlugin.`\n\t);\n}\n\ninterface AuthorPluginDetails {\n\tid: string;\n\tauthored: boolean;\n\t/** Whether an executable-capability confirmation gate ran (and was accepted). */\n\tconfirmed?: boolean;\n}\n\nfunction reject(\n\tid: string,\n\tmessage: string,\n): {\n\tcontent: { type: \"text\"; text: string }[];\n\tdetails: AuthorPluginDetails;\n} {\n\treturn { content: [{ type: \"text\" as const, text: message }], details: { id, authored: false } };\n}\n\n/**\n * Run the shared \"executable capabilities → show → confirm\" gate. Returns:\n * - `{ ok: true }` when there is nothing executable, or the human confirmed;\n * - a tool result (authored:false) when there is no UI to confirm on, or the\n * human declined.\n */\nasync function passExecutableGate(\n\tid: string,\n\tparams: CapabilityInput,\n\tctx: ExtensionContext,\n\tevidence?: string,\n): Promise<{ ok: true; gated: boolean } | { ok: false; result: ReturnType<typeof reject> }> {\n\tif (!hasExecutable(params)) return { ok: true, gated: false };\n\n\t// The gate results go in the prompt, not just the tool result. A human shown a\n\t// bare shell command has no basis to approve it; \"ran the checks, here is what\n\t// they found\" is what makes the confirmation a decision rather than a formality.\n\tconst review = evidence ? `${buildReview(id, params)}\\n\\n${evidence}` : buildReview(id, params);\n\tctx.ui.notify(review, \"warning\");\n\tif (!ctx.hasUI) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tresult: reject(\n\t\t\t\tid,\n\t\t\t\t\"Authoring executable capabilities requires human confirmation, which is unavailable in this mode. \" +\n\t\t\t\t\t`Not activated.\\n${review}`,\n\t\t\t),\n\t\t};\n\t}\n\tconst confirmed = await ctx.ui.confirm(\n\t\t`Author executable plugin \"${id}\"?`,\n\t\t`${review}\\n\\nThis installs and can run the code above. Activate it?`,\n\t);\n\tif (!confirmed) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tresult: {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: `Declined — plugin \"${id}\" was not authored.` }],\n\t\t\t\tdetails: { id, authored: false, confirmed: false },\n\t\t\t},\n\t\t};\n\t}\n\treturn { ok: true, gated: true };\n}\n\n// ── ProposePlugin (create) ────────────────────────────────────────────────────\n\nconst proposeParams = Type.Object(\n\t{ id: Type.String({ description: \"Plugin id (directory + manifest name).\" }), ...capabilityProps },\n\t{ additionalProperties: false },\n);\n\nexport function createProposePluginToolDefinition(): ToolDefinition {\n\treturn defineTool<typeof proposeParams, AuthorPluginDetails>({\n\t\tname: PROPOSE_PLUGIN_TOOL_NAME,\n\t\tlabel: PROPOSE_PLUGIN_TOOL_NAME,\n\t\tdescription:\n\t\t\t\"Author a NEW portable, reusable plugin to fill a capability gap when no marketplace plugin fits. Accepts any \" +\n\t\t\t\"capability mix — skills, slash commands, subagents, hooks, MCP servers. Authored as one self-contained \" +\n\t\t\t\"artifact usable across sessions and projects. Passive content (skills, commands, read-only \" +\n\t\t\t\"subagents) is authored autonomously; executable content (hooks, MCP servers, mutating subagents) is shown \" +\n\t\t\t\"and requires human confirmation before it activates. To change an existing plugin, use UpdatePlugin.\",\n\t\tpromptSnippet:\n\t\t\t\"Author a new portable, reusable plugin to fill a capability gap (passive is autonomous; executable asks to confirm).\",\n\t\tpromptGuidelines: [\n\t\t\t\"Sense reusability proactively: when you complete a multi-step recipe you'd plausibly repeat (or repeat the same pattern twice in one session) and SearchPlugins finds nothing that covers it, author it with ProposePlugin. Name and describe it by the capability, not the one-off task that prompted it, so it triggers again in other contexts. Passive skills/commands activate immediately and are reversible with UninstallPlugin — announce what you created and why.\",\n\t\t\t\"Author for portability: write self-contained content — no absolute or machine-specific paths, no embedded secrets or environment-specific values, no assumptions about the current repo unless that is the capability's point. Prefer relative paths and runtime discovery, and state any prerequisites in the body. The layout is the session's target platform; you never choose it.\",\n\t\t\t\"One tool for the whole plugin: put skills + a hook in a single call. The risk gate is computed from content — you don't pre-classify. Read-only subagents and skills/commands go straight through; hooks, MCP servers, or a subagent needing Bash/Write/Edit/MCP or tools:* pause for human confirmation.\",\n\t\t\t\"Never grant a subagent any plugin-system tool (InstallPlugin, ProposePlugin, ...); that is always rejected.\",\n\t\t\t\"Publishing a proven-useful plugin to a marketplace stays a human action — do not do it autonomously.\",\n\t\t],\n\t\tparameters: proposeParams,\n\t\tasync execute(_id, params: Static<typeof proposeParams>, _signal, _onUpdate, ctx: ExtensionContext) {\n\t\t\tconst violation = guardrailViolation(params);\n\t\t\tif (violation) return reject(params.id, violation);\n\n\t\t\tif (capabilityCount(params) === 0) {\n\t\t\t\treturn reject(params.id, \"Nothing to author. Provide skills, commands, subagents, hooks, or mcpServers.\");\n\t\t\t}\n\n\t\t\tconst platforms = resolvePlatforms();\n\t\t\t// Scoped to the target platform's home: the same id on another platform is\n\t\t\t// a separate ecosystem artifact, not a duplicate.\n\t\t\tif (pluginExists(params.id, platforms)) {\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t`A ${platforms.join(\"/\")} plugin named \"${params.id}\" already exists. ` +\n\t\t\t\t\t\t\"Use UpdatePlugin to change it, or pick another id.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Emit to an ephemeral draft first: the gates need something real to run\n\t\t\t// against, and a production home is live (Claude Code loads\n\t\t\t// ~/.claude/skills on its next session), so nothing may land there until\n\t\t\t// the checks pass and — for executable content — the human agrees.\n\t\t\tconst draft = writePluginDraft(draftFrom(params.id, params, platforms), platforms, { promote: false });\n\t\t\tlet evaluation = runStaticGates(draft.dest, { platform: platforms[0] });\n\t\t\t// G3 only for executable content, and only once the cheap gates are\n\t\t\t// happy: no point running a hook whose command G2 already rejected.\n\t\t\tif (evaluation.ok && evaluation.plugin && hasExecutable(params)) {\n\t\t\t\tevaluation = withFindings(evaluation, await runSmokeGate(evaluation.plugin));\n\t\t\t}\n\t\t\tif (!evaluation.ok) {\n\t\t\t\tdiscardDraftDir(draft.dest);\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t`Plugin \"${params.id}\" was not authored — it failed its checks.\\n` +\n\t\t\t\t\t\t`${formatGateFindings(evaluation)}\\nFix the issues and try again.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst gate = await passExecutableGate(params.id, params, ctx, formatGateFindings(evaluation));\n\t\t\tif (!gate.ok) {\n\t\t\t\tdiscardDraftDir(draft.dest);\n\t\t\t\treturn gate.result;\n\t\t\t}\n\n\t\t\tconst dest = promoteDraft(draft.dest, params.id, platforms);\n\t\t\t// Passive capabilities activate live — usable on the very next model request,\n\t\t\t// this same turn; hooks/MCP servers activate via the reload once the turn ends.\n\t\t\tconst activation = ctx.activatePlugin(dest);\n\t\t\tconst text =\n\t\t\t\t`${summarizeWrite(params.id, platforms, draft.files, dest, \"Authored\")}\\n` +\n\t\t\t\t`${formatGateFindings(evaluation)}\\n${activation.message}`;\n\t\t\tctx.ui.notify(`Authored plugin \"${params.id}\" (${platforms.join(\", \")}).`, \"info\");\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\tdetails: { id: params.id, authored: true, confirmed: gate.gated },\n\t\t\t};\n\t\t},\n\t});\n}\n\n// ── UpdatePlugin (merge into an existing local plugin) ─────────────────────────\n\nconst updateParams = Type.Object(\n\t{ id: Type.String({ description: \"Id of the existing local plugin to update.\" }), ...capabilityProps },\n\t{ additionalProperties: false },\n);\n\nexport function createUpdatePluginToolDefinition(): ToolDefinition {\n\treturn defineTool<typeof updateParams, AuthorPluginDetails>({\n\t\tname: UPDATE_PLUGIN_TOOL_NAME,\n\t\tlabel: UPDATE_PLUGIN_TOOL_NAME,\n\t\tdescription:\n\t\t\t\"Merge inline-authored capabilities into an EXISTING locally AUTHORED plugin (marketplace-installed plugins \" +\n\t\t\t\"are refused). Skills/commands/subagents are added or replaced by name; hooks and MCP servers are unioned \" +\n\t\t\t\"with what's already there; metadata is overwritten only where you supply it. Additive only — remove a \" +\n\t\t\t\"capability with RemovePluginCapability. Nothing is fetched from a remote. Keep additions as portable as \" +\n\t\t\t\"the original, and in its existing layout. Passive additions apply autonomously; executable additions (hooks, MCP \" +\n\t\t\t\"servers, mutating subagents) require human confirmation. Use ProposePlugin to create.\",\n\t\tpromptSnippet:\n\t\t\t\"Add/replace capabilities in a portable plugin you authored (additive; executable additions ask to confirm).\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use UpdatePlugin to grow a plugin you already authored — e.g. add a skill to it, or attach a hook. Supply only the delta; existing capabilities are preserved (a matching name replaces just that one). It cannot remove a capability — use RemovePluginCapability for that.\",\n\t\t\t\"Keep additions portable: same content rules as ProposePlugin — no absolute paths, no secrets, capability-not-task naming.\",\n\t\t\t\"Hooks cannot be modified in place: they have no name, so supplying a changed command ADDS a second hook alongside the old one (both fire). To change a hook, RemovePluginCapability the old one first, then add the new one here.\",\n\t\t\t\"Only executable *additions* trigger confirmation — adding a passive skill to an already-executable plugin does not re-prompt.\",\n\t\t\t\"Never grant a subagent any plugin-system tool (InstallPlugin, ProposePlugin, ...); that is always rejected.\",\n\t\t],\n\t\tparameters: updateParams,\n\t\tasync execute(_id, params: Static<typeof updateParams>, _signal, _onUpdate, ctx: ExtensionContext) {\n\t\t\tconst violation = guardrailViolation(params);\n\t\t\tif (violation) return reject(params.id, violation);\n\n\t\t\tconst existing = getPlugin(ctx.cwd, params.id);\n\t\t\tif (!existing) {\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t`No plugin named \"${params.id}\" is installed. Use ProposePlugin to create it first.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Authored-only: marketplace installs land in the same directory but don't\n\t\t\t// round-trip losslessly through our emitters (see mergePluginDraft).\n\t\t\tif (!isAuthoredPlugin(ctx.cwd, params.id)) {\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t`Plugin \"${params.id}\" was not authored in this workspace (likely installed from a marketplace). ` +\n\t\t\t\t\t\t\"UpdatePlugin only modifies locally authored plugins — updating a marketplace plugin is a human \" +\n\t\t\t\t\t\t\"action (uninstall it and install a newer version instead).\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (capabilityCount(params) === 0 && !params.version && !params.description) {\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t\"Nothing to update. Provide skills, commands, subagents, hooks, mcpServers, or metadata.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Gate on the DELTA only — existing executables aren't re-confirmed.\n\t\t\tconst gate = await passExecutableGate(params.id, params, ctx);\n\t\t\tif (!gate.ok) return gate.result;\n\n\t\t\t// No model-facing platform selection: a merge keeps the plugin's existing\n\t\t\t// layout (mergePluginDraft defaults to existing.supportPlatform).\n\t\t\tconst result = mergePluginDraft(ctx.cwd, params.id, draftFrom(params.id, params, existing.supportPlatform));\n\t\t\tconst platforms = result.plugin?.supportPlatform ?? existing.supportPlatform;\n\t\t\tconst activation = ctx.activatePlugin(result.dest);\n\t\t\tconst text = `${summarizeWrite(params.id, platforms, result.files, result.dest, \"Updated\")}\\n${activation.message}`;\n\t\t\tctx.ui.notify(`Updated plugin \"${params.id}\" (${platforms.join(\", \")}).`, \"info\");\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\tdetails: { id: params.id, authored: true, confirmed: gate.gated },\n\t\t\t};\n\t\t},\n\t});\n}\n\n// ── RemovePluginCapability (subtract from an authored plugin) ─────────────────\n\nconst hookRemovalSchema = Type.Object(\n\t{\n\t\tevent: Type.String({ description: \"Event of the hook(s) to remove, e.g. PreToolUse.\" }),\n\t\tmatcher: Type.Optional(Type.String({ description: \"Narrow to hooks with exactly this matcher.\" })),\n\t\tcommand: Type.Optional(Type.String({ description: \"Narrow to hooks with exactly this command.\" })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst removeParams = Type.Object(\n\t{\n\t\tid: Type.String({ description: \"Id of the authored plugin to remove capabilities from.\" }),\n\t\tskills: Type.Optional(Type.Array(Type.String(), { description: \"Skill names to remove.\" })),\n\t\tcommands: Type.Optional(Type.Array(Type.String(), { description: \"Command names to remove.\" })),\n\t\tsubagents: Type.Optional(Type.Array(Type.String(), { description: \"Subagent names to remove.\" })),\n\t\tmcpServers: Type.Optional(Type.Array(Type.String(), { description: \"MCP server names to remove.\" })),\n\t\thooks: Type.Optional(\n\t\t\tType.Array(hookRemovalSchema, {\n\t\t\t\tdescription: \"Hooks to remove, matched by event and narrowed by matcher/command when provided.\",\n\t\t\t}),\n\t\t),\n\t},\n\t{ additionalProperties: false },\n);\n\ninterface RemovePluginCapabilityDetails {\n\tid: string;\n\tremoved: string[];\n\tmissing: string[];\n}\n\nexport function createRemovePluginCapabilityToolDefinition(): ToolDefinition {\n\treturn defineTool<typeof removeParams, RemovePluginCapabilityDetails>({\n\t\tname: REMOVE_PLUGIN_CAPABILITY_TOOL_NAME,\n\t\tlabel: REMOVE_PLUGIN_CAPABILITY_TOOL_NAME,\n\t\tdescription:\n\t\t\t\"Remove named capabilities from a locally AUTHORED plugin — skills, commands, subagents, and MCP servers by \" +\n\t\t\t\"name; hooks by event (narrowed by matcher/command). The subtractive half of UpdatePlugin. Removal is \" +\n\t\t\t\"low-risk and autonomous (deleting capabilities cannot execute code). To remove the whole plugin, use \" +\n\t\t\t\"UninstallPlugin; marketplace-installed plugins are refused here.\",\n\t\tpromptSnippet: \"Remove capabilities from a plugin you authored (low risk; autonomous).\",\n\t\tpromptGuidelines: [\n\t\t\t\"Removal runs autonomously (the low-risk direction) — announce what you removed and why.\",\n\t\t\t\"To CHANGE a hook (hooks have no name to replace by): RemovePluginCapability the old hook, then UpdatePlugin the new one (which asks for confirmation).\",\n\t\t],\n\t\tparameters: removeParams,\n\t\tasync execute(_id, params: Static<typeof removeParams>, _signal, _onUpdate, ctx: ExtensionContext) {\n\t\t\tconst noDetails = (msg: string) => ({\n\t\t\t\tcontent: [{ type: \"text\" as const, text: msg }],\n\t\t\t\tdetails: { id: params.id, removed: [], missing: [] },\n\t\t\t});\n\n\t\t\tconst existing = getPlugin(ctx.cwd, params.id);\n\t\t\tif (!existing) {\n\t\t\t\treturn noDetails(`No plugin named \"${params.id}\" is installed.`);\n\t\t\t}\n\t\t\tif (!isAuthoredPlugin(ctx.cwd, params.id)) {\n\t\t\t\treturn noDetails(\n\t\t\t\t\t`Plugin \"${params.id}\" was not authored in this workspace (likely installed from a marketplace). ` +\n\t\t\t\t\t\t\"RemovePluginCapability only edits locally authored plugins — use UninstallPlugin to remove it entirely.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst requested =\n\t\t\t\t(params.skills?.length ?? 0) +\n\t\t\t\t(params.commands?.length ?? 0) +\n\t\t\t\t(params.subagents?.length ?? 0) +\n\t\t\t\t(params.mcpServers?.length ?? 0) +\n\t\t\t\t(params.hooks?.length ?? 0);\n\t\t\tif (requested === 0) {\n\t\t\t\treturn noDetails(\"Nothing to remove. Name skills, commands, subagents, mcpServers, or hooks.\");\n\t\t\t}\n\n\t\t\tconst result = removeFromPlugin(ctx.cwd, params.id, {\n\t\t\t\tskills: params.skills,\n\t\t\t\tcommands: params.commands,\n\t\t\t\tsubagents: params.subagents,\n\t\t\t\tmcpServers: params.mcpServers,\n\t\t\t\thooks: params.hooks,\n\t\t\t});\n\t\t\tconst lines: string[] = [];\n\t\t\tif (result.removed.length > 0) {\n\t\t\t\tlines.push(`Removed from plugin \"${params.id}\":`, ...result.removed.map((r) => ` ${r}`));\n\t\t\t}\n\t\t\tif (result.missing.length > 0) {\n\t\t\t\tlines.push(`Not found (nothing removed):`, ...result.missing.map((m) => ` ${m}`));\n\t\t\t}\n\t\t\tconst text = lines.join(\"\\n\");\n\t\t\t// Removal takes effect through the reload path, same as UninstallPlugin.\n\t\t\tif (result.removed.length > 0) ctx.requestReloadWhenIdle();\n\t\t\tctx.ui.notify(text, result.removed.length > 0 ? \"info\" : \"warning\");\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\tdetails: { id: params.id, removed: result.removed, missing: result.missing },\n\t\t\t};\n\t\t},\n\t});\n}\n\n/** All three authoring tool definitions, for registration on the top-level agent. */\nexport function createProposePluginToolDefinitions(): ToolDefinition[] {\n\treturn [\n\t\tcreateProposePluginToolDefinition(),\n\t\tcreateUpdatePluginToolDefinition(),\n\t\tcreateRemovePluginCapabilityToolDefinition(),\n\t];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"propose-plugin.d.ts","sourceRoot":"","sources":["../../../src/core/tools/propose-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAoBH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAsPzE,wBAAgB,iCAAiC,IAAI,cAAc,CAkFlE;AASD,wBAAgB,gCAAgC,IAAI,cAAc,CA+DjE;AAmCD,wBAAgB,0CAA0C,IAAI,cAAc,CAiE3E;AAED,qFAAqF;AACrF,wBAAgB,kCAAkC,IAAI,cAAc,EAAE,CAMrE","sourcesContent":["/**\n * Capability authoring tools (spec §3), refactored to a single risk-gated path.\n *\n * ProposePlugin author a NEW plugin from any capability mix — skills,\n * commands, subagents, hooks, MCP servers. The risk gate is\n * *computed from content*, not pre-declared by tool choice:\n * passive content (skills, commands, read-only subagents) is\n * authored autonomously; executable content (hooks, MCP servers,\n * mutating/high-privilege subagents) auto-triggers a \"show the\n * code + tool grant → human confirms → activate\" gate in the\n * same call. A mixed plugin (skill + hook) is authored in one\n * call, and a hook can never be mis-routed through a \"passive\"\n * tool because the gate keys off what the draft contains.\n * UpdatePlugin merge inline-authored capabilities into an EXISTING local\n * plugin. Nothing is fetched from a remote, so the supply-chain\n * \"benign v1 → hostile v2\" risk that keeps a marketplace\n * UpdatePlugin out of the model's hands does not apply here;\n * executable additions still pass through the same confirm gate.\n *\n * Both author into `.agents/plugins/<id>/` in the requested vendor layouts\n * (Claude Code + GitHub Copilot by default) via the format registry, so results\n * are proper, publishable plugins that round-trip through parsePluginDir.\n *\n * Privilege-amplification guardrail: an authored subagent may never carry a\n * plugin-system (capability-acquisition) tool in its allowlist — enforced in\n * both tools — so a low-trust authored agent cannot bootstrap privilege.\n */\n\nimport { type Static, Type } from \"typebox\";\nimport {\n\tclassifyAllowlist,\n\tgetPlugin,\n\tisAuthoredPlugin,\n\tmergePluginDraft,\n\tpluginExists,\n\tpromoteDraft,\n\tremoveFromPlugin,\n\tresolvePluginPlatforms,\n\twritePluginDraft,\n} from \"../extensions/plugins/authoring.js\";\nimport type { PluginPlatform } from \"../extensions/plugins/formats/platform-targets.js\";\nimport type { MarketplacePlatform, PluginDraft } from \"../extensions/plugins/formats/types.js\";\nimport { formatGateFindings, runStaticGates, withFindings } from \"../extensions/plugins/gates.js\";\nimport { discardDraftDir } from \"../extensions/plugins/locations.js\";\nimport { runSmokeGate } from \"../extensions/plugins/smoke.js\";\nimport type { ExtensionContext } from \"../extensions/types.js\";\nimport { defineTool, type ToolDefinition } from \"../extensions/types.js\";\nimport {\n\tPROPOSE_PLUGIN_TOOL_NAME,\n\tREMOVE_PLUGIN_CAPABILITY_TOOL_NAME,\n\tUPDATE_PLUGIN_TOOL_NAME,\n} from \"./plugin-tool-names.js\";\n\nconst skillSchema = Type.Object(\n\t{\n\t\tname: Type.String({ description: \"Skill name.\" }),\n\t\tdescription: Type.Optional(Type.String({ description: \"One-line trigger description (kept lazy in context).\" })),\n\t\tbody: Type.String({ description: \"SKILL.md instruction body (markdown).\" }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst commandSchema = Type.Object(\n\t{\n\t\tname: Type.String({ description: \"Command name (invoked as /name).\" }),\n\t\tdescription: Type.Optional(Type.String({ description: \"One-line description.\" })),\n\t\tbody: Type.String({ description: \"Prompt template body (markdown).\" }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst subagentSchema = Type.Object(\n\t{\n\t\tname: Type.String({ description: \"Subagent name.\" }),\n\t\tdescription: Type.Optional(Type.String({ description: \"When to dispatch this subagent.\" })),\n\t\ttools: Type.Optional(\n\t\t\tType.String({\n\t\t\t\tdescription:\n\t\t\t\t\t\"Comma-separated allowed-tools, e.g. 'read, grep, glob'. Read-only grants are autonomous; \" +\n\t\t\t\t\t\"mutating/exec/network grants (Bash, Write, Edit, MCP) or '*' require human confirmation. Omit for none.\",\n\t\t\t}),\n\t\t),\n\t\tmodel: Type.Optional(Type.String({ description: \"Model override, or 'inherit'.\" })),\n\t\tbody: Type.String({ description: \"System-prompt / instruction body (markdown).\" }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst hookSchema = Type.Object(\n\t{\n\t\tevent: Type.String({ description: \"Event: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ...\" }),\n\t\tmatcher: Type.Optional(Type.String({ description: \"Regex matched against the tool name. Empty/'*' = all.\" })),\n\t\tcommand: Type.String({ description: \"Shell command to run on the event.\" }),\n\t\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds.\" })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst mcpServerSchema = Type.Object(\n\t{\n\t\tname: Type.String({ description: \"MCP server name.\" }),\n\t\tcommand: Type.String({ description: \"Executable to launch the server.\" }),\n\t\targs: Type.Optional(Type.Array(Type.String(), { description: \"Command arguments.\" })),\n\t\tenv: Type.Optional(Type.Record(Type.String(), Type.String(), { description: \"Environment variables.\" })),\n\t},\n\t{ additionalProperties: false },\n);\n\n/** The capability params shared by ProposePlugin (create) and UpdatePlugin (merge). */\nconst capabilityProps = {\n\tdescription: Type.Optional(Type.String({ description: \"Plugin description.\" })),\n\tversion: Type.Optional(Type.String({ description: \"Plugin version, e.g. '0.1.0'.\" })),\n\tskills: Type.Optional(Type.Array(skillSchema)),\n\tcommands: Type.Optional(Type.Array(commandSchema)),\n\tsubagents: Type.Optional(\n\t\tType.Array(subagentSchema, {\n\t\t\tdescription: \"Subagents. Read-only allowlists are autonomous; mutating ones trigger human confirmation.\",\n\t\t}),\n\t),\n\thooks: Type.Optional(Type.Array(hookSchema, { description: \"Shell hooks (executable — trigger confirmation).\" })),\n\tmcpServers: Type.Optional(\n\t\tType.Array(mcpServerSchema, { description: \"MCP servers (executable — trigger confirmation).\" }),\n\t),\n} as const;\n\n/** Union of every capability a draft can carry (used to build a draft and to classify risk). */\ninterface CapabilityInput {\n\tdescription?: string;\n\tversion?: string;\n\tskills?: Static<typeof skillSchema>[];\n\tcommands?: Static<typeof commandSchema>[];\n\tsubagents?: Static<typeof subagentSchema>[];\n\thooks?: Static<typeof hookSchema>[];\n\tmcpServers?: Static<typeof mcpServerSchema>[];\n}\n\nfunction resolvePlatforms(): PluginPlatform[] {\n\t// No model-facing platform selection: the human picks the target ecosystem\n\t// with --platform, defaulting to claude. A plugin is a distribution unit, so\n\t// there is no vendor-neutral option here. See resolvePluginPlatforms.\n\treturn resolvePluginPlatforms();\n}\n\nfunction draftFrom(id: string, params: CapabilityInput, platforms: MarketplacePlatform[]): PluginDraft {\n\treturn {\n\t\tid,\n\t\tversion: params.version,\n\t\tdescription: params.description,\n\t\tsupportPlatform: platforms,\n\t\tskills: params.skills,\n\t\tcommands: params.commands,\n\t\tagents: params.subagents,\n\t\thooks: params.hooks,\n\t\tmcpServers: params.mcpServers,\n\t};\n}\n\n/** Total capabilities carried by the draft (empty arrays count as nothing). */\nfunction capabilityCount(params: CapabilityInput): number {\n\treturn (\n\t\t(params.skills?.length ?? 0) +\n\t\t(params.commands?.length ?? 0) +\n\t\t(params.subagents?.length ?? 0) +\n\t\t(params.hooks?.length ?? 0) +\n\t\t(params.mcpServers?.length ?? 0)\n\t);\n}\n\n/** The subagents whose allowlist makes them mutating/high-privilege (need the confirm gate). */\nfunction mutatingSubagents(params: CapabilityInput): Static<typeof subagentSchema>[] {\n\treturn (params.subagents ?? []).filter((sa) => classifyAllowlist(sa.tools).risk === \"mutating\");\n}\n\n/** True when the draft carries anything executable — hooks, MCP servers, or a mutating subagent. */\nfunction hasExecutable(params: CapabilityInput): boolean {\n\treturn (\n\t\t(params.hooks?.length ?? 0) > 0 || (params.mcpServers?.length ?? 0) > 0 || mutatingSubagents(params).length > 0\n\t);\n}\n\n/** Reject if any subagent carries a plugin-system tool (privilege-amplification guardrail). Returns the message, or null. */\nfunction guardrailViolation(params: CapabilityInput): string | null {\n\tfor (const sa of params.subagents ?? []) {\n\t\tconst cls = classifyAllowlist(sa.tools);\n\t\tif (cls.pluginTools.length > 0) {\n\t\t\treturn (\n\t\t\t\t`Subagent \"${sa.name}\" requests plugin-system tools (${cls.pluginTools.join(\", \")}). ` +\n\t\t\t\t\"Authored subagents may never carry capability-acquisition tools.\"\n\t\t\t);\n\t\t}\n\t}\n\treturn null;\n}\n\n/** Build the human-facing review text: the executable code and every mutating tool grant. */\nfunction buildReview(id: string, params: CapabilityInput): string {\n\tconst lines: string[] = [`Plugin \"${id}\" wants to install executable capabilities:`];\n\tfor (const h of params.hooks ?? []) {\n\t\tlines.push(` hook [${h.event}${h.matcher ? ` matcher=${h.matcher}` : \"\"}]: ${h.command}`);\n\t}\n\tfor (const s of params.mcpServers ?? []) {\n\t\tlines.push(` mcp server \"${s.name}\": ${s.command}${s.args?.length ? ` ${s.args.join(\" \")}` : \"\"}`);\n\t}\n\tfor (const sa of mutatingSubagents(params)) {\n\t\tlines.push(` subagent \"${sa.name}\" tools: ${sa.tools ?? \"(none)\"} (${classifyAllowlist(sa.tools).reason})`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nfunction summarizeWrite(\n\tid: string,\n\tplatforms: MarketplacePlatform[],\n\tfiles: string[],\n\tdest: string,\n\tverb: string,\n): string {\n\treturn (\n\t\t`${verb} plugin \"${id}\" (${platforms.join(\", \")}) with ${files.length} file(s) at ${dest}:\\n` +\n\t\tfiles.map((f) => ` ${f}`).join(\"\\n\") +\n\t\t`\\nRemove it with UninstallPlugin.`\n\t);\n}\n\ninterface AuthorPluginDetails {\n\tid: string;\n\tauthored: boolean;\n\t/** Whether an executable-capability confirmation gate ran (and was accepted). */\n\tconfirmed?: boolean;\n}\n\nfunction reject(\n\tid: string,\n\tmessage: string,\n): {\n\tcontent: { type: \"text\"; text: string }[];\n\tdetails: AuthorPluginDetails;\n} {\n\treturn { content: [{ type: \"text\" as const, text: message }], details: { id, authored: false } };\n}\n\n/**\n * Run the shared \"executable capabilities → show → confirm\" gate. Returns:\n * - `{ ok: true }` when there is nothing executable, or the human confirmed;\n * - a tool result (authored:false) when there is no UI to confirm on, or the\n * human declined.\n */\nasync function passExecutableGate(\n\tid: string,\n\tparams: CapabilityInput,\n\tctx: ExtensionContext,\n\tevidence?: string,\n): Promise<{ ok: true; gated: boolean } | { ok: false; result: ReturnType<typeof reject> }> {\n\tif (!hasExecutable(params)) return { ok: true, gated: false };\n\n\t// The gate results go in the prompt, not just the tool result. A human shown a\n\t// bare shell command has no basis to approve it; \"ran the checks, here is what\n\t// they found\" is what makes the confirmation a decision rather than a formality.\n\tconst review = evidence ? `${buildReview(id, params)}\\n\\n${evidence}` : buildReview(id, params);\n\tctx.ui.notify(review, \"warning\");\n\tif (!ctx.hasUI) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tresult: reject(\n\t\t\t\tid,\n\t\t\t\t\"Authoring executable capabilities requires human confirmation, which is unavailable in this mode. \" +\n\t\t\t\t\t`Not activated.\\n${review}`,\n\t\t\t),\n\t\t};\n\t}\n\tconst confirmed = await ctx.ui.confirm(\n\t\t`Author executable plugin \"${id}\"?`,\n\t\t`${review}\\n\\nThis installs and can run the code above. Activate it?`,\n\t);\n\tif (!confirmed) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tresult: {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: `Declined — plugin \"${id}\" was not authored.` }],\n\t\t\t\tdetails: { id, authored: false, confirmed: false },\n\t\t\t},\n\t\t};\n\t}\n\treturn { ok: true, gated: true };\n}\n\n// ── ProposePlugin (create) ────────────────────────────────────────────────────\n\nconst proposeParams = Type.Object(\n\t{ id: Type.String({ description: \"Plugin id (directory + manifest name).\" }), ...capabilityProps },\n\t{ additionalProperties: false },\n);\n\nexport function createProposePluginToolDefinition(): ToolDefinition {\n\treturn defineTool<typeof proposeParams, AuthorPluginDetails>({\n\t\tname: PROPOSE_PLUGIN_TOOL_NAME,\n\t\tlabel: PROPOSE_PLUGIN_TOOL_NAME,\n\t\tdescription:\n\t\t\t\"Author a NEW portable, reusable plugin to fill a capability gap when no marketplace plugin fits. Accepts any \" +\n\t\t\t\"capability mix — skills, slash commands, subagents, hooks, MCP servers. Authored as one self-contained \" +\n\t\t\t\"artifact usable across sessions and projects. Passive content (skills, commands, read-only \" +\n\t\t\t\"subagents) is authored autonomously; executable content (hooks, MCP servers, mutating subagents) is shown \" +\n\t\t\t\"and requires human confirmation before it activates. To change an existing plugin, use UpdatePlugin.\",\n\t\tpromptSnippet:\n\t\t\t\"Author a new portable, reusable plugin to fill a capability gap (passive is autonomous; executable asks to confirm).\",\n\t\t// Trimmed to the trigger and the two hard prohibitions. The craft guidance —\n\t\t// portability rules, capability-not-task naming, the one-call shape — was\n\t\t// ~600 tok/turn of always-on prose restating a how-to. It is the\n\t\t// `plugin-authoring` skill in the bundled marketplace now, read on demand.\n\t\tpromptGuidelines: [\n\t\t\t\"Sense reusability proactively: a multi-step recipe you'd plausibly repeat (or repeated twice in one session) that SearchPlugins does not already cover is worth authoring. Read the plugin-authoring skill first if it is available; it decides naming, portability and shape.\",\n\t\t\t\"Announce what you authored and why — passive content activates without asking, so the user learns it exists only if you say so.\",\n\t\t\t\"Never grant a subagent any plugin-system tool (InstallPlugin, ProposePlugin, ...); that is always rejected.\",\n\t\t\t\"Publishing to a marketplace stays a human action — never do it autonomously.\",\n\t\t],\n\t\tparameters: proposeParams,\n\t\tasync execute(_id, params: Static<typeof proposeParams>, _signal, _onUpdate, ctx: ExtensionContext) {\n\t\t\tconst violation = guardrailViolation(params);\n\t\t\tif (violation) return reject(params.id, violation);\n\n\t\t\tif (capabilityCount(params) === 0) {\n\t\t\t\treturn reject(params.id, \"Nothing to author. Provide skills, commands, subagents, hooks, or mcpServers.\");\n\t\t\t}\n\n\t\t\tconst platforms = resolvePlatforms();\n\t\t\t// Scoped to the target platform's home: the same id on another platform is\n\t\t\t// a separate ecosystem artifact, not a duplicate.\n\t\t\tif (pluginExists(params.id, platforms)) {\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t`A ${platforms.join(\"/\")} plugin named \"${params.id}\" already exists. ` +\n\t\t\t\t\t\t\"Use UpdatePlugin to change it, or pick another id.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Emit to an ephemeral draft first: the gates need something real to run\n\t\t\t// against, and a production home is live (Claude Code loads\n\t\t\t// ~/.claude/skills on its next session), so nothing may land there until\n\t\t\t// the checks pass and — for executable content — the human agrees.\n\t\t\tconst draft = writePluginDraft(draftFrom(params.id, params, platforms), platforms, { promote: false });\n\t\t\tlet evaluation = runStaticGates(draft.dest, { platform: platforms[0] });\n\t\t\t// G3 only for executable content, and only once the cheap gates are\n\t\t\t// happy: no point running a hook whose command G2 already rejected.\n\t\t\tif (evaluation.ok && evaluation.plugin && hasExecutable(params)) {\n\t\t\t\tevaluation = withFindings(evaluation, await runSmokeGate(evaluation.plugin));\n\t\t\t}\n\t\t\tif (!evaluation.ok) {\n\t\t\t\tdiscardDraftDir(draft.dest);\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t`Plugin \"${params.id}\" was not authored — it failed its checks.\\n` +\n\t\t\t\t\t\t`${formatGateFindings(evaluation)}\\nFix the issues and try again.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst gate = await passExecutableGate(params.id, params, ctx, formatGateFindings(evaluation));\n\t\t\tif (!gate.ok) {\n\t\t\t\tdiscardDraftDir(draft.dest);\n\t\t\t\treturn gate.result;\n\t\t\t}\n\n\t\t\tconst dest = promoteDraft(draft.dest, params.id, platforms);\n\t\t\t// Passive capabilities activate live — usable on the very next model request,\n\t\t\t// this same turn; hooks/MCP servers activate via the reload once the turn ends.\n\t\t\tconst activation = ctx.activatePlugin(dest);\n\t\t\tconst text =\n\t\t\t\t`${summarizeWrite(params.id, platforms, draft.files, dest, \"Authored\")}\\n` +\n\t\t\t\t`${formatGateFindings(evaluation)}\\n${activation.message}`;\n\t\t\tctx.ui.notify(`Authored plugin \"${params.id}\" (${platforms.join(\", \")}).`, \"info\");\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\tdetails: { id: params.id, authored: true, confirmed: gate.gated },\n\t\t\t};\n\t\t},\n\t});\n}\n\n// ── UpdatePlugin (merge into an existing local plugin) ─────────────────────────\n\nconst updateParams = Type.Object(\n\t{ id: Type.String({ description: \"Id of the existing local plugin to update.\" }), ...capabilityProps },\n\t{ additionalProperties: false },\n);\n\nexport function createUpdatePluginToolDefinition(): ToolDefinition {\n\treturn defineTool<typeof updateParams, AuthorPluginDetails>({\n\t\tname: UPDATE_PLUGIN_TOOL_NAME,\n\t\tlabel: UPDATE_PLUGIN_TOOL_NAME,\n\t\tdescription:\n\t\t\t\"Merge inline-authored capabilities into an EXISTING locally AUTHORED plugin (marketplace-installed plugins \" +\n\t\t\t\"are refused). Skills/commands/subagents are added or replaced by name; hooks and MCP servers are unioned \" +\n\t\t\t\"with what's already there; metadata is overwritten only where you supply it. Additive only — remove a \" +\n\t\t\t\"capability with RemovePluginCapability. Nothing is fetched from a remote. Keep additions as portable as \" +\n\t\t\t\"the original, and in its existing layout. Passive additions apply autonomously; executable additions (hooks, MCP \" +\n\t\t\t\"servers, mutating subagents) require human confirmation. Use ProposePlugin to create.\",\n\t\tpromptSnippet:\n\t\t\t\"Add/replace capabilities in a portable plugin you authored (additive; executable additions ask to confirm).\",\n\t\tpromptGuidelines: [\n\t\t\t\"Hooks cannot be modified in place: they have no name, so supplying a changed command ADDS a second hook alongside the old one (both fire). To change a hook, RemovePluginCapability the old one first, then add the new one here.\",\n\t\t\t\"Never grant a subagent any plugin-system tool (InstallPlugin, ProposePlugin, ...); that is always rejected.\",\n\t\t],\n\t\tparameters: updateParams,\n\t\tasync execute(_id, params: Static<typeof updateParams>, _signal, _onUpdate, ctx: ExtensionContext) {\n\t\t\tconst violation = guardrailViolation(params);\n\t\t\tif (violation) return reject(params.id, violation);\n\n\t\t\tconst existing = getPlugin(ctx.cwd, params.id);\n\t\t\tif (!existing) {\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t`No plugin named \"${params.id}\" is installed. Use ProposePlugin to create it first.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Authored-only: marketplace installs land in the same directory but don't\n\t\t\t// round-trip losslessly through our emitters (see mergePluginDraft).\n\t\t\tif (!isAuthoredPlugin(ctx.cwd, params.id)) {\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t`Plugin \"${params.id}\" was not authored in this workspace (likely installed from a marketplace). ` +\n\t\t\t\t\t\t\"UpdatePlugin only modifies locally authored plugins — updating a marketplace plugin is a human \" +\n\t\t\t\t\t\t\"action (uninstall it and install a newer version instead).\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (capabilityCount(params) === 0 && !params.version && !params.description) {\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t\"Nothing to update. Provide skills, commands, subagents, hooks, mcpServers, or metadata.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Gate on the DELTA only — existing executables aren't re-confirmed.\n\t\t\tconst gate = await passExecutableGate(params.id, params, ctx);\n\t\t\tif (!gate.ok) return gate.result;\n\n\t\t\t// No model-facing platform selection: a merge keeps the plugin's existing\n\t\t\t// layout (mergePluginDraft defaults to existing.supportPlatform).\n\t\t\tconst result = mergePluginDraft(ctx.cwd, params.id, draftFrom(params.id, params, existing.supportPlatform));\n\t\t\tconst platforms = result.plugin?.supportPlatform ?? existing.supportPlatform;\n\t\t\tconst activation = ctx.activatePlugin(result.dest);\n\t\t\tconst text = `${summarizeWrite(params.id, platforms, result.files, result.dest, \"Updated\")}\\n${activation.message}`;\n\t\t\tctx.ui.notify(`Updated plugin \"${params.id}\" (${platforms.join(\", \")}).`, \"info\");\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\tdetails: { id: params.id, authored: true, confirmed: gate.gated },\n\t\t\t};\n\t\t},\n\t});\n}\n\n// ── RemovePluginCapability (subtract from an authored plugin) ─────────────────\n\nconst hookRemovalSchema = Type.Object(\n\t{\n\t\tevent: Type.String({ description: \"Event of the hook(s) to remove, e.g. PreToolUse.\" }),\n\t\tmatcher: Type.Optional(Type.String({ description: \"Narrow to hooks with exactly this matcher.\" })),\n\t\tcommand: Type.Optional(Type.String({ description: \"Narrow to hooks with exactly this command.\" })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst removeParams = Type.Object(\n\t{\n\t\tid: Type.String({ description: \"Id of the authored plugin to remove capabilities from.\" }),\n\t\tskills: Type.Optional(Type.Array(Type.String(), { description: \"Skill names to remove.\" })),\n\t\tcommands: Type.Optional(Type.Array(Type.String(), { description: \"Command names to remove.\" })),\n\t\tsubagents: Type.Optional(Type.Array(Type.String(), { description: \"Subagent names to remove.\" })),\n\t\tmcpServers: Type.Optional(Type.Array(Type.String(), { description: \"MCP server names to remove.\" })),\n\t\thooks: Type.Optional(\n\t\t\tType.Array(hookRemovalSchema, {\n\t\t\t\tdescription: \"Hooks to remove, matched by event and narrowed by matcher/command when provided.\",\n\t\t\t}),\n\t\t),\n\t},\n\t{ additionalProperties: false },\n);\n\ninterface RemovePluginCapabilityDetails {\n\tid: string;\n\tremoved: string[];\n\tmissing: string[];\n}\n\nexport function createRemovePluginCapabilityToolDefinition(): ToolDefinition {\n\treturn defineTool<typeof removeParams, RemovePluginCapabilityDetails>({\n\t\tname: REMOVE_PLUGIN_CAPABILITY_TOOL_NAME,\n\t\tlabel: REMOVE_PLUGIN_CAPABILITY_TOOL_NAME,\n\t\tdescription:\n\t\t\t\"Remove named capabilities from a locally AUTHORED plugin — skills, commands, subagents, and MCP servers by \" +\n\t\t\t\"name; hooks by event (narrowed by matcher/command). The subtractive half of UpdatePlugin. Removal is \" +\n\t\t\t\"low-risk and autonomous (deleting capabilities cannot execute code). To remove the whole plugin, use \" +\n\t\t\t\"UninstallPlugin; marketplace-installed plugins are refused here.\",\n\t\tpromptSnippet: \"Remove capabilities from a plugin you authored (low risk; autonomous).\",\n\t\tpromptGuidelines: [\n\t\t\t\"Removal runs autonomously (the low-risk direction) — announce what you removed and why.\",\n\t\t\t\"To CHANGE a hook (hooks have no name to replace by): RemovePluginCapability the old hook, then UpdatePlugin the new one (which asks for confirmation).\",\n\t\t],\n\t\tparameters: removeParams,\n\t\tasync execute(_id, params: Static<typeof removeParams>, _signal, _onUpdate, ctx: ExtensionContext) {\n\t\t\tconst noDetails = (msg: string) => ({\n\t\t\t\tcontent: [{ type: \"text\" as const, text: msg }],\n\t\t\t\tdetails: { id: params.id, removed: [], missing: [] },\n\t\t\t});\n\n\t\t\tconst existing = getPlugin(ctx.cwd, params.id);\n\t\t\tif (!existing) {\n\t\t\t\treturn noDetails(`No plugin named \"${params.id}\" is installed.`);\n\t\t\t}\n\t\t\tif (!isAuthoredPlugin(ctx.cwd, params.id)) {\n\t\t\t\treturn noDetails(\n\t\t\t\t\t`Plugin \"${params.id}\" was not authored in this workspace (likely installed from a marketplace). ` +\n\t\t\t\t\t\t\"RemovePluginCapability only edits locally authored plugins — use UninstallPlugin to remove it entirely.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst requested =\n\t\t\t\t(params.skills?.length ?? 0) +\n\t\t\t\t(params.commands?.length ?? 0) +\n\t\t\t\t(params.subagents?.length ?? 0) +\n\t\t\t\t(params.mcpServers?.length ?? 0) +\n\t\t\t\t(params.hooks?.length ?? 0);\n\t\t\tif (requested === 0) {\n\t\t\t\treturn noDetails(\"Nothing to remove. Name skills, commands, subagents, mcpServers, or hooks.\");\n\t\t\t}\n\n\t\t\tconst result = removeFromPlugin(ctx.cwd, params.id, {\n\t\t\t\tskills: params.skills,\n\t\t\t\tcommands: params.commands,\n\t\t\t\tsubagents: params.subagents,\n\t\t\t\tmcpServers: params.mcpServers,\n\t\t\t\thooks: params.hooks,\n\t\t\t});\n\t\t\tconst lines: string[] = [];\n\t\t\tif (result.removed.length > 0) {\n\t\t\t\tlines.push(`Removed from plugin \"${params.id}\":`, ...result.removed.map((r) => ` ${r}`));\n\t\t\t}\n\t\t\tif (result.missing.length > 0) {\n\t\t\t\tlines.push(`Not found (nothing removed):`, ...result.missing.map((m) => ` ${m}`));\n\t\t\t}\n\t\t\tconst text = lines.join(\"\\n\");\n\t\t\t// Removal takes effect through the reload path, same as UninstallPlugin.\n\t\t\tif (result.removed.length > 0) ctx.requestReloadWhenIdle();\n\t\t\tctx.ui.notify(text, result.removed.length > 0 ? \"info\" : \"warning\");\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\tdetails: { id: params.id, removed: result.removed, missing: result.missing },\n\t\t\t};\n\t\t},\n\t});\n}\n\n/** All three authoring tool definitions, for registration on the top-level agent. */\nexport function createProposePluginToolDefinitions(): ToolDefinition[] {\n\treturn [\n\t\tcreateProposePluginToolDefinition(),\n\t\tcreateUpdatePluginToolDefinition(),\n\t\tcreateRemovePluginCapabilityToolDefinition(),\n\t];\n}\n"]}
|
|
@@ -189,12 +189,15 @@ export function createProposePluginToolDefinition() {
|
|
|
189
189
|
"subagents) is authored autonomously; executable content (hooks, MCP servers, mutating subagents) is shown " +
|
|
190
190
|
"and requires human confirmation before it activates. To change an existing plugin, use UpdatePlugin.",
|
|
191
191
|
promptSnippet: "Author a new portable, reusable plugin to fill a capability gap (passive is autonomous; executable asks to confirm).",
|
|
192
|
+
// Trimmed to the trigger and the two hard prohibitions. The craft guidance —
|
|
193
|
+
// portability rules, capability-not-task naming, the one-call shape — was
|
|
194
|
+
// ~600 tok/turn of always-on prose restating a how-to. It is the
|
|
195
|
+
// `plugin-authoring` skill in the bundled marketplace now, read on demand.
|
|
192
196
|
promptGuidelines: [
|
|
193
|
-
"Sense reusability proactively:
|
|
194
|
-
"
|
|
195
|
-
"One tool for the whole plugin: put skills + a hook in a single call. The risk gate is computed from content — you don't pre-classify. Read-only subagents and skills/commands go straight through; hooks, MCP servers, or a subagent needing Bash/Write/Edit/MCP or tools:* pause for human confirmation.",
|
|
197
|
+
"Sense reusability proactively: a multi-step recipe you'd plausibly repeat (or repeated twice in one session) that SearchPlugins does not already cover is worth authoring. Read the plugin-authoring skill first if it is available; it decides naming, portability and shape.",
|
|
198
|
+
"Announce what you authored and why — passive content activates without asking, so the user learns it exists only if you say so.",
|
|
196
199
|
"Never grant a subagent any plugin-system tool (InstallPlugin, ProposePlugin, ...); that is always rejected.",
|
|
197
|
-
"Publishing
|
|
200
|
+
"Publishing to a marketplace stays a human action — never do it autonomously.",
|
|
198
201
|
],
|
|
199
202
|
parameters: proposeParams,
|
|
200
203
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
@@ -260,10 +263,7 @@ export function createUpdatePluginToolDefinition() {
|
|
|
260
263
|
"servers, mutating subagents) require human confirmation. Use ProposePlugin to create.",
|
|
261
264
|
promptSnippet: "Add/replace capabilities in a portable plugin you authored (additive; executable additions ask to confirm).",
|
|
262
265
|
promptGuidelines: [
|
|
263
|
-
"Use UpdatePlugin to grow a plugin you already authored — e.g. add a skill to it, or attach a hook. Supply only the delta; existing capabilities are preserved (a matching name replaces just that one). It cannot remove a capability — use RemovePluginCapability for that.",
|
|
264
|
-
"Keep additions portable: same content rules as ProposePlugin — no absolute paths, no secrets, capability-not-task naming.",
|
|
265
266
|
"Hooks cannot be modified in place: they have no name, so supplying a changed command ADDS a second hook alongside the old one (both fire). To change a hook, RemovePluginCapability the old one first, then add the new one here.",
|
|
266
|
-
"Only executable *additions* trigger confirmation — adding a passive skill to an already-executable plugin does not re-prompt.",
|
|
267
267
|
"Never grant a subagent any plugin-system tool (InstallPlugin, ProposePlugin, ...); that is always rejected.",
|
|
268
268
|
],
|
|
269
269
|
parameters: updateParams,
|