@kolisachint/hoocode-agent 0.4.162 → 0.4.164
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 +29 -0
- package/dist/cli/args.d.ts +0 -6
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +0 -31
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts +0 -40
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -50
- package/dist/config.js.map +1 -1
- package/dist/core/agent-frontmatter.d.ts +0 -28
- package/dist/core/agent-frontmatter.d.ts.map +1 -1
- package/dist/core/agent-frontmatter.js +0 -6
- package/dist/core/agent-frontmatter.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +0 -2
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +0 -2
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +0 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-guidance.d.ts +0 -1
- package/dist/core/auth-guidance.d.ts.map +1 -1
- package/dist/core/auth-guidance.js +1 -1
- package/dist/core/auth-guidance.js.map +1 -1
- package/dist/core/embsearch/chunker.d.ts +0 -2
- package/dist/core/embsearch/chunker.d.ts.map +1 -1
- package/dist/core/embsearch/chunker.js +1 -1
- package/dist/core/embsearch/chunker.js.map +1 -1
- package/dist/core/embsearch/index-meta.d.ts +0 -2
- package/dist/core/embsearch/index-meta.d.ts.map +1 -1
- package/dist/core/embsearch/index-meta.js +0 -6
- package/dist/core/embsearch/index-meta.js.map +1 -1
- package/dist/core/export-html/ansi-to-html.d.ts +0 -4
- package/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
- package/dist/core/export-html/ansi-to-html.js +1 -1
- package/dist/core/export-html/ansi-to-html.js.map +1 -1
- package/dist/core/extensions/index.d.ts +3 -3
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/plugins/authoring.d.ts +2 -2
- package/dist/core/extensions/plugins/authoring.d.ts.map +1 -1
- package/dist/core/extensions/plugins/authoring.js +0 -0
- package/dist/core/extensions/plugins/authoring.js.map +1 -1
- package/dist/core/extensions/plugins/formats/index.d.ts +2 -6
- package/dist/core/extensions/plugins/formats/index.d.ts.map +1 -1
- package/dist/core/extensions/plugins/formats/index.js +2 -3
- package/dist/core/extensions/plugins/formats/index.js.map +1 -1
- package/dist/core/extensions/plugins/formats/shared.d.ts +0 -2
- package/dist/core/extensions/plugins/formats/shared.d.ts.map +1 -1
- package/dist/core/extensions/plugins/formats/shared.js +1 -1
- package/dist/core/extensions/plugins/formats/shared.js.map +1 -1
- package/dist/core/extensions/plugins/formats/types.d.ts +4 -3
- package/dist/core/extensions/plugins/formats/types.d.ts.map +1 -1
- package/dist/core/extensions/plugins/formats/types.js.map +1 -1
- package/dist/core/extensions/plugins/install.d.ts +0 -2
- package/dist/core/extensions/plugins/install.d.ts.map +1 -1
- package/dist/core/extensions/plugins/install.js +1 -1
- package/dist/core/extensions/plugins/install.js.map +1 -1
- package/dist/core/extensions/plugins/manifest.d.ts +0 -6
- package/dist/core/extensions/plugins/manifest.d.ts.map +1 -1
- package/dist/core/extensions/plugins/manifest.js +0 -6
- package/dist/core/extensions/plugins/manifest.js.map +1 -1
- package/dist/core/extensions/plugins/marketplace.d.ts +3 -3
- package/dist/core/extensions/plugins/marketplace.d.ts.map +1 -1
- package/dist/core/extensions/plugins/marketplace.js.map +1 -1
- package/dist/core/extensions/types.d.ts +28 -28
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/keybindings.d.ts +3 -3
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-resolver.d.ts +0 -7
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +0 -56
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-resource-discovery.d.ts +0 -10
- package/dist/core/package-resource-discovery.d.ts.map +1 -1
- package/dist/core/package-resource-discovery.js +7 -7
- package/dist/core/package-resource-discovery.js.map +1 -1
- package/dist/core/provider-health.d.ts +0 -2
- package/dist/core/provider-health.d.ts.map +1 -1
- package/dist/core/provider-health.js +1 -1
- package/dist/core/provider-health.js.map +1 -1
- package/dist/core/resolve-config-value.d.ts +0 -4
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +0 -15
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/sdk.d.ts +3 -23
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +3 -5
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/search/eval.d.ts +0 -3
- package/dist/core/search/eval.d.ts.map +1 -1
- package/dist/core/search/eval.js +1 -1
- package/dist/core/search/eval.js.map +1 -1
- package/dist/core/search/types.d.ts +2 -1
- package/dist/core/search/types.d.ts.map +1 -1
- package/dist/core/search/types.js.map +1 -1
- package/dist/core/session-cwd.d.ts +0 -1
- package/dist/core/session-cwd.d.ts.map +1 -1
- package/dist/core/session-cwd.js +1 -1
- package/dist/core/session-cwd.js.map +1 -1
- package/dist/core/settings-defaults.d.ts +0 -3
- package/dist/core/settings-defaults.d.ts.map +1 -1
- package/dist/core/settings-defaults.js +0 -3
- package/dist/core/settings-defaults.js.map +1 -1
- package/dist/core/settings-manager.d.ts +2 -9
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +0 -25
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-types.d.ts +9 -11
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/subagent-inbox.d.ts +1 -3
- package/dist/core/subagent-inbox.d.ts.map +1 -1
- package/dist/core/subagent-inbox.js +1 -1
- package/dist/core/subagent-inbox.js.map +1 -1
- package/dist/core/subagent-pool.d.ts +0 -13
- package/dist/core/subagent-pool.d.ts.map +1 -1
- package/dist/core/subagent-pool.js +2 -2
- package/dist/core/subagent-pool.js.map +1 -1
- package/dist/core/task-store.d.ts +3 -3
- package/dist/core/task-store.d.ts.map +1 -1
- package/dist/core/task-store.js.map +1 -1
- package/dist/core/team-auto.d.ts +0 -2
- package/dist/core/team-auto.d.ts.map +1 -1
- package/dist/core/team-auto.js +1 -1
- package/dist/core/team-auto.js.map +1 -1
- package/dist/core/token-budget.d.ts +0 -13
- package/dist/core/token-budget.d.ts.map +1 -1
- package/dist/core/token-budget.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +3 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +0 -38
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +2 -9
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +5 -5
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +8 -10
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +4 -4
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +0 -24
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +0 -20
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +2 -2
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/native-search.d.ts +3 -2
- package/dist/core/tools/native-search.d.ts.map +1 -1
- package/dist/core/tools/native-search.js.map +1 -1
- package/dist/core/tools/path-utils.d.ts +0 -10
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +2 -2
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/core/tools/plugins.d.ts +1 -20
- package/dist/core/tools/plugins.d.ts.map +1 -1
- package/dist/core/tools/plugins.js +2 -2
- package/dist/core/tools/plugins.js.map +1 -1
- package/dist/core/tools/propose-plugin.d.ts +0 -12
- package/dist/core/tools/propose-plugin.d.ts.map +1 -1
- package/dist/core/tools/propose-plugin.js +0 -1
- package/dist/core/tools/propose-plugin.js.map +1 -1
- package/dist/core/tools/read-dedup.d.ts +0 -2
- package/dist/core/tools/read-dedup.d.ts.map +1 -1
- package/dist/core/tools/read-dedup.js +1 -1
- package/dist/core/tools/read-dedup.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +10 -8
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +0 -5
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/search.d.ts +1 -4
- package/dist/core/tools/search.d.ts.map +1 -1
- package/dist/core/tools/search.js +6 -6
- package/dist/core/tools/search.js.map +1 -1
- package/dist/core/tools/webfetch.d.ts +1 -2
- package/dist/core/tools/webfetch.d.ts.map +1 -1
- package/dist/core/tools/webfetch.js.map +1 -1
- package/dist/core/tools/websearch.d.ts +1 -2
- package/dist/core/tools/websearch.d.ts.map +1 -1
- package/dist/core/tools/websearch.js.map +1 -1
- package/dist/core/tools/webtools-shared.d.ts +3 -5
- package/dist/core/tools/webtools-shared.d.ts.map +1 -1
- package/dist/core/tools/webtools-shared.js +1 -1
- package/dist/core/tools/webtools-shared.js.map +1 -1
- package/dist/core/warm-subagent-pool-instance.d.ts +0 -4
- package/dist/core/warm-subagent-pool-instance.d.ts.map +1 -1
- package/dist/core/warm-subagent-pool-instance.js +0 -10
- package/dist/core/warm-subagent-pool-instance.js.map +1 -1
- package/dist/core/wordmark.d.ts +1 -9
- package/dist/core/wordmark.d.ts.map +1 -1
- package/dist/core/wordmark.js +2 -26
- package/dist/core/wordmark.js.map +1 -1
- package/dist/extensions/core/config.d.ts +4 -3
- package/dist/extensions/core/config.d.ts.map +1 -1
- package/dist/extensions/core/config.js.map +1 -1
- package/dist/extensions/core/mcp-loader.d.ts +0 -18
- package/dist/extensions/core/mcp-loader.d.ts.map +1 -1
- package/dist/extensions/core/mcp-loader.js.map +1 -1
- package/dist/extensions/core/prompt-reactive/policy.d.ts +0 -8
- package/dist/extensions/core/prompt-reactive/policy.d.ts.map +1 -1
- package/dist/extensions/core/prompt-reactive/policy.js +1 -1
- package/dist/extensions/core/prompt-reactive/policy.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +0 -30
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +0 -16
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +2 -2
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/brand.d.ts +0 -4
- package/dist/modes/interactive/brand.d.ts.map +1 -1
- package/dist/modes/interactive/brand.js +0 -4
- package/dist/modes/interactive/brand.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +0 -4
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +0 -5
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/read-output.d.ts +0 -9
- package/dist/modes/interactive/components/read-output.d.ts.map +1 -1
- package/dist/modes/interactive/components/read-output.js +0 -20
- package/dist/modes/interactive/components/read-output.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts +0 -17
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js +2 -2
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +5 -14
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +2 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +0 -18
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/login-controller.d.ts +1 -1
- package/dist/modes/interactive/login-controller.d.ts.map +1 -1
- package/dist/modes/interactive/login-controller.js +1 -1
- package/dist/modes/interactive/login-controller.js.map +1 -1
- package/dist/modes/interactive/resource-display.d.ts +0 -20
- package/dist/modes/interactive/resource-display.d.ts.map +1 -1
- package/dist/modes/interactive/resource-display.js +7 -7
- package/dist/modes/interactive/resource-display.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +0 -4
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +0 -7
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +0 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +0 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js +3 -0
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli.d.ts +0 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/changelog.d.ts +0 -4
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js +1 -1
- package/dist/utils/changelog.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +1 -1
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/paths.d.ts +0 -2
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +1 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/plan/parser.d.ts +5 -4
- package/dist/utils/plan/parser.d.ts.map +1 -1
- package/dist/utils/plan/parser.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +1 -2
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +2 -50
- package/dist/utils/tools-manager.js.map +1 -1
- 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 +4 -7
- package/dist/core/index.d.ts +0 -12
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -12
- package/dist/core/index.js.map +0 -1
- package/dist/core/tools/browser/browser-continue.d.ts +0 -25
- package/dist/core/tools/browser/browser-continue.d.ts.map +0 -1
- package/dist/core/tools/browser/browser-continue.js +0 -84
- package/dist/core/tools/browser/browser-continue.js.map +0 -1
- package/dist/core/tools/browser/browser-run.d.ts +0 -48
- package/dist/core/tools/browser/browser-run.d.ts.map +0 -1
- package/dist/core/tools/browser/browser-run.js +0 -377
- package/dist/core/tools/browser/browser-run.js.map +0 -1
- package/dist/core/tools/browser/browsertools-shared.d.ts +0 -190
- package/dist/core/tools/browser/browsertools-shared.d.ts.map +0 -1
- package/dist/core/tools/browser/browsertools-shared.js +0 -335
- package/dist/core/tools/browser/browsertools-shared.js.map +0 -1
- package/dist/core/tools/browser/index.d.ts +0 -8
- package/dist/core/tools/browser/index.d.ts.map +0 -1
- package/dist/core/tools/browser/index.js +0 -8
- package/dist/core/tools/browser/index.js.map +0 -1
- package/dist/core/tools/doc/docedit.d.ts +0 -44
- package/dist/core/tools/doc/docedit.d.ts.map +0 -1
- package/dist/core/tools/doc/docedit.js +0 -133
- package/dist/core/tools/doc/docedit.js.map +0 -1
- package/dist/core/tools/doc/docgrep.d.ts +0 -28
- package/dist/core/tools/doc/docgrep.d.ts.map +0 -1
- package/dist/core/tools/doc/docgrep.js +0 -106
- package/dist/core/tools/doc/docgrep.js.map +0 -1
- package/dist/core/tools/doc/docpeek.d.ts +0 -25
- package/dist/core/tools/doc/docpeek.d.ts.map +0 -1
- package/dist/core/tools/doc/docpeek.js +0 -112
- package/dist/core/tools/doc/docpeek.js.map +0 -1
- package/dist/core/tools/doc/docread.d.ts +0 -32
- package/dist/core/tools/doc/docread.d.ts.map +0 -1
- package/dist/core/tools/doc/docread.js +0 -113
- package/dist/core/tools/doc/docread.js.map +0 -1
- package/dist/core/tools/doc/docscan.d.ts +0 -29
- package/dist/core/tools/doc/docscan.d.ts.map +0 -1
- package/dist/core/tools/doc/docscan.js +0 -110
- package/dist/core/tools/doc/docscan.js.map +0 -1
- package/dist/core/tools/doc/docwrite.d.ts +0 -44
- package/dist/core/tools/doc/docwrite.d.ts.map +0 -1
- package/dist/core/tools/doc/docwrite.js +0 -102
- package/dist/core/tools/doc/docwrite.js.map +0 -1
- package/dist/core/tools/doc/index.d.ts +0 -11
- package/dist/core/tools/doc/index.d.ts.map +0 -1
- package/dist/core/tools/doc/index.js +0 -11
- package/dist/core/tools/doc/index.js.map +0 -1
- package/dist/core/tools/filetools-shared.d.ts +0 -271
- package/dist/core/tools/filetools-shared.d.ts.map +0 -1
- package/dist/core/tools/filetools-shared.js +0 -403
- package/dist/core/tools/filetools-shared.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"propose-plugin.js","sourceRoot":"","sources":["../../../src/core/tools/propose-plugin.ts"],"names":[],"mappings":"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","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\tremoveFromPlugin,\n\tresolveAuthoringPlatforms,\n\twritePluginDraft,\n} from \"../extensions/plugins/authoring.js\";\nimport type { MarketplacePlatform, PluginDraft } from \"../extensions/plugins/formats/types.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\nexport {\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(): MarketplacePlatform[] {\n\t// No model-facing platform selection: authored artifacts default to the\n\t// portable native format, unless the human set --support-platform for the\n\t// session (interop). See resolveAuthoringPlatforms.\n\treturn resolveAuthoringPlatforms();\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\nexport interface 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): Promise<{ ok: true; gated: boolean } | { ok: false; result: ReturnType<typeof reject> }> {\n\tif (!hasExecutable(params)) return { ok: true, gated: false };\n\n\tconst review = 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\"vendor-neutral 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, vendor-neutral 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 artifact is written in the portable native layout; you never choose a vendor format.\",\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\tif (pluginExists(ctx.cwd, params.id)) {\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t`A plugin named \"${params.id}\" already exists. Use UpdatePlugin to change it, or pick another id.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst gate = await passExecutableGate(params.id, params, ctx);\n\t\t\tif (!gate.ok) return gate.result;\n\n\t\t\tconst platforms = resolvePlatforms();\n\t\t\tconst result = writePluginDraft(ctx.cwd, draftFrom(params.id, params, platforms), 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(result.dest);\n\t\t\tconst text = `${summarizeWrite(params.id, platforms, result.files, result.dest, \"Authored\")}\\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 and \" +\n\t\t\t\"vendor-neutral as the original. 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 vendor-neutral 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\nexport interface 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.js","sourceRoot":"","sources":["../../../src/core/tools/propose-plugin.ts"],"names":[],"mappings":"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","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\tremoveFromPlugin,\n\tresolveAuthoringPlatforms,\n\twritePluginDraft,\n} from \"../extensions/plugins/authoring.js\";\nimport type { MarketplacePlatform, PluginDraft } from \"../extensions/plugins/formats/types.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(): MarketplacePlatform[] {\n\t// No model-facing platform selection: authored artifacts default to the\n\t// portable native format, unless the human set --support-platform for the\n\t// session (interop). See resolveAuthoringPlatforms.\n\treturn resolveAuthoringPlatforms();\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): Promise<{ ok: true; gated: boolean } | { ok: false; result: ReturnType<typeof reject> }> {\n\tif (!hasExecutable(params)) return { ok: true, gated: false };\n\n\tconst review = 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\"vendor-neutral 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, vendor-neutral 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 artifact is written in the portable native layout; you never choose a vendor format.\",\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\tif (pluginExists(ctx.cwd, params.id)) {\n\t\t\t\treturn reject(\n\t\t\t\t\tparams.id,\n\t\t\t\t\t`A plugin named \"${params.id}\" already exists. Use UpdatePlugin to change it, or pick another id.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst gate = await passExecutableGate(params.id, params, ctx);\n\t\t\tif (!gate.ok) return gate.result;\n\n\t\t\tconst platforms = resolvePlatforms();\n\t\t\tconst result = writePluginDraft(ctx.cwd, draftFrom(params.id, params, platforms), 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(result.dest);\n\t\t\tconst text = `${summarizeWrite(params.id, platforms, result.files, result.dest, \"Authored\")}\\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 and \" +\n\t\t\t\"vendor-neutral as the original. 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 vendor-neutral 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"]}
|
|
@@ -30,8 +30,6 @@ export declare const WHOLE_FILE_RANGE: ReadRange;
|
|
|
30
30
|
export declare function readRangeFromArgs(args: Record<string, unknown> | undefined): ReadRange;
|
|
31
31
|
/** Whether two half-open line ranges intersect. */
|
|
32
32
|
export declare function rangesOverlap(a: ReadRange, b: ReadRange): boolean;
|
|
33
|
-
/** Whether `outer` fully contains `inner` (every line of inner lies within outer). */
|
|
34
|
-
export declare function rangeContains(outer: ReadRange, inner: ReadRange): boolean;
|
|
35
33
|
/**
|
|
36
34
|
* Marker prefix for the at-call dedup pointer. Kept stable so the context GC can
|
|
37
35
|
* recognise a pointer result and exclude it from supersession bookkeeping (a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-dedup.d.ts","sourceRoot":"","sources":["../../../src/core/tools/read-dedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,sEAAsE;AACtE,eAAO,MAAM,gBAAgB,EAAE,SAAuD,CAAC;AAEvF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,SAAS,CAStF;AAED,mDAAmD;AACnD,wBAAgB,aAAa,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAEjE;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAEzE;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC5B,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,uBAAuB;IACvC,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,cAAc,EAAE,SAAS,CAAC;IAC1B,mFAAmF;IACnF,aAAa,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CACzC;AAoED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,EAAE,IAAI,EAAE,uBAAuB,GAAG,YAAY,GAAG,IAAI,CA8FhH;AAED,8DAA8D;AAC9D,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAQpE","sourcesContent":["/**\n * Read de-duplication primitives.\n *\n * Two mechanisms share this module:\n *\n * - The post-hoc context GC (`context-gc.ts`), which stubs out a `read` result\n * once a later overlapping read or an edit/write has superseded it.\n * - The at-call-time guard in the `read` tool, which short-circuits a read whose\n * requested range is *already fully covered* by an earlier, still-live read in\n * the current session, returning a pointer instead of re-fetching the file.\n *\n * Both reason about half-open line ranges `[start, end)` (end exclusive), so the\n * range math lives here and cannot drift between them. The guard additionally\n * needs to (a) recognise its own pointer results so they are never treated as\n * content-bearing reads, and (b) walk the session branch to find a covering\n * read — both of which are defined here to keep the tool file lean.\n */\n\n/** Half-open line interval `[start, end)` a read call covers; end is exclusive. */\nexport interface ReadRange {\n\tstart: number;\n\tend: number;\n}\n\n/** A read with no offset/limit covers the whole file (open-ended). */\nexport const WHOLE_FILE_RANGE: ReadRange = { start: 1, end: Number.POSITIVE_INFINITY };\n\n/**\n * Derive the line range a read call covers from its `offset`/`limit` args.\n * Missing offset means \"from line 1\"; missing limit means \"to end of file\"\n * (open-ended, so it overlaps any later read of the same file).\n */\nexport function readRangeFromArgs(args: Record<string, unknown> | undefined): ReadRange {\n\tconst offsetRaw = args?.offset;\n\tconst limitRaw = args?.limit;\n\tconst offset = typeof offsetRaw === \"number\" && Number.isFinite(offsetRaw) && offsetRaw > 0 ? offsetRaw : 1;\n\tconst end =\n\t\ttypeof limitRaw === \"number\" && Number.isFinite(limitRaw)\n\t\t\t? offset + Math.max(0, limitRaw)\n\t\t\t: Number.POSITIVE_INFINITY;\n\treturn { start: offset, end };\n}\n\n/** Whether two half-open line ranges intersect. */\nexport function rangesOverlap(a: ReadRange, b: ReadRange): boolean {\n\treturn a.start < b.end && b.start < a.end;\n}\n\n/** Whether `outer` fully contains `inner` (every line of inner lies within outer). */\nexport function rangeContains(outer: ReadRange, inner: ReadRange): boolean {\n\treturn outer.start <= inner.start && outer.end >= inner.end;\n}\n\n/**\n * Marker prefix for the at-call dedup pointer. Kept stable so the context GC can\n * recognise a pointer result and exclude it from supersession bookkeeping (a\n * pointer fetched no content, so it must not stub the read it points at).\n */\nexport const DEDUP_POINTER_PREFIX = \"[Already in context:\";\n\n/** Whether a tool-result text is an at-call dedup pointer. */\nexport function isDedupPointerText(text: string): boolean {\n\treturn text.trimStart().startsWith(DEDUP_POINTER_PREFIX);\n}\n\n/** A covering earlier read, described for the pointer message. */\nexport interface CoveringRead {\n\t/** The path as the earlier read spelled it (for a friendly pointer). */\n\tdisplay: string;\n\t/** Delivered range start (1-indexed line). */\n\tstart: number;\n\t/** Delivered range end (exclusive; Infinity for a whole-file read). */\n\tend: number;\n}\n\nexport interface FindCoveringReadOptions {\n\t/** Resolved absolute path of the current read. */\n\tresolvedPath: string;\n\t/** Range the current read is asking for. */\n\trequestedRange: ReadRange;\n\t/** Tool call id of the current read, excluded from candidate/supersession sets. */\n\tcurrentCallId: string;\n\t/** Resolve a raw read-arg path the same way the current read resolved its path. */\n\tresolvePath: (rawPath: string) => string;\n}\n\ninterface ContentBlock {\n\ttype?: string;\n\tid?: string;\n\tname?: string;\n\targuments?: Record<string, unknown>;\n\ttext?: string;\n}\n\ninterface MessageLike {\n\trole?: string;\n\tcontent?: ContentBlock[];\n\ttoolCallId?: string;\n\ttoolName?: string;\n\tisError?: boolean;\n}\n\nconst READ_TOOL = \"read\";\nconst MUTATE_TOOLS = new Set([\"edit\", \"write\"]);\n\n/** A session compaction entry — the boundary that trims the live context. */\nfunction isCompactionEntry(entry: unknown): boolean {\n\treturn !!entry && typeof entry === \"object\" && (entry as { type?: unknown }).type === \"compaction\";\n}\n\n/** Accept either raw `AgentMessage`s or session entries wrapping `.message`. */\nfunction toMessage(entry: unknown): MessageLike | null {\n\tif (!entry || typeof entry !== \"object\") return null;\n\tconst e = entry as { message?: unknown; role?: unknown };\n\tif (e.message && typeof e.message === \"object\") return e.message as MessageLike;\n\tif (typeof e.role === \"string\") return e as MessageLike;\n\treturn null;\n}\n\nfunction resultText(m: MessageLike): string {\n\treturn (m.content ?? [])\n\t\t.filter((c) => c.type === \"text\")\n\t\t.map((c) => c.text ?? \"\")\n\t\t.join(\"\");\n}\n\n/**\n * Recover the range a read result actually *delivered* from its text.\n *\n * A cap-truncated read announces `[Showing lines A-B of N ...]`, so it delivered\n * only `[A, B+1)` even though its args declared a wider range. A read whose first\n * line alone exceeded the byte cap delivered nothing. Any other (untruncated)\n * read delivered its full declared range. The user-`limit` early-stop notice\n * (`[N more lines in file ...]`) is *not* a cap truncation — the declared range\n * was delivered in full — so it falls through to `declared`.\n */\nfunction deliveredRange(text: string, declared: ReadRange): ReadRange {\n\t// The truncation notice is always the trailing `\\n\\n[Showing lines A-B of N ...]`\n\t// clause the read tool appends. Anchor to the end so a `[Showing lines ...]`\n\t// string that merely appears *inside* the file's content can't spoof it.\n\tconst showing = text.match(/\\n\\n\\[Showing lines (\\d+)-(\\d+) of \\d+[^\\]]*\\]\\s*$/);\n\tif (showing) {\n\t\tconst a = Number(showing[1]);\n\t\tconst b = Number(showing[2]);\n\t\tif (Number.isFinite(a) && Number.isFinite(b) && b >= a) return { start: a, end: b + 1 };\n\t}\n\t// First line alone exceeded the byte limit: the whole result *is* that notice,\n\t// so it must start the text. Nothing usable was delivered.\n\tif (/^\\[Line \\d+ is .+ exceeds .+ limit\\./.test(text)) return { start: 1, end: 1 };\n\treturn declared;\n}\n\n/**\n * Find the latest earlier read that (a) is for the same resolved path, (b)\n * actually delivered a range containing the requested range, and (c) is still\n * live in the outgoing context — i.e. the post-hoc GC will not have stubbed it,\n * because no later edit/write and no later overlapping content read supersede\n * it. Returns that read's delivered range for the pointer, or null when the\n * current read must actually run.\n *\n * Deliberately conservative: a truncated earlier read only covers what it\n * delivered, a whole-file read must have been delivered untruncated to count as\n * covering, and any pointer results (which fetched nothing) are ignored on both\n * the candidate and the supersession side.\n */\nexport function findCoveringRead(entries: readonly unknown[], opts: FindCoveringReadOptions): CoveringRead | null {\n\t// `declared` mirrors the range the GC uses for supersession (straight from the\n\t// call args); `delivered` is what the result text shows was actually returned,\n\t// used for coverage. The current call has no result yet, so it never appears.\n\tinterface PriorRead {\n\t\tindex: number;\n\t\tdeclared: ReadRange;\n\t\tdelivered: ReadRange;\n\t\tdisplay: string;\n\t}\n\n\t// Resolve each distinct raw path once — the resolver may hit the filesystem.\n\tconst resolveCache = new Map<string, string>();\n\tconst resolvePath = (raw: string): string => {\n\t\tconst hit = resolveCache.get(raw);\n\t\tif (hit !== undefined) return hit;\n\t\tconst resolved = opts.resolvePath(raw);\n\t\tresolveCache.set(raw, resolved);\n\t\treturn resolved;\n\t};\n\n\t// Single ordered pass. `readCall`/`mutateCallPath` map a call id to its path as\n\t// the call is seen (a toolCall always precedes its result); `reads` collects\n\t// the target path's reads and `lastMutateIndex` its last mutate. `order` gives\n\t// live-context position for the supersession/mutate comparisons.\n\tconst readCall = new Map<string, { resolved: string; display: string; declared: ReadRange }>();\n\tconst mutateCallPath = new Map<string, string>();\n\tconst reads: PriorRead[] = [];\n\tlet lastMutateIndex = -1;\n\tlet order = 0;\n\n\tfor (const entry of entries) {\n\t\t// Compaction boundary: everything before it is replaced by a summary in the\n\t\t// live context, so drop the state accumulated so far. Conservative — the\n\t\t// kept tail before the boundary is dropped too — which can only miss a\n\t\t// dedup, never point at content that is no longer in context.\n\t\tif (isCompactionEntry(entry)) {\n\t\t\treadCall.clear();\n\t\t\tmutateCallPath.clear();\n\t\t\treads.length = 0;\n\t\t\tlastMutateIndex = -1;\n\t\t\torder = 0;\n\t\t\tcontinue;\n\t\t}\n\t\tconst m = toMessage(entry);\n\t\tif (!m) continue;\n\t\tconst i = order++;\n\t\tif (m.role === \"assistant\" && Array.isArray(m.content)) {\n\t\t\tfor (const b of m.content) {\n\t\t\t\tif (b.type !== \"toolCall\" || !b.id) continue;\n\t\t\t\tconst raw = b.arguments?.path;\n\t\t\t\tif (typeof raw !== \"string\" || raw.length === 0) continue;\n\t\t\t\tconst resolved = resolvePath(raw);\n\t\t\t\tif (b.name === READ_TOOL) {\n\t\t\t\t\treadCall.set(b.id, { resolved, display: raw, declared: readRangeFromArgs(b.arguments) });\n\t\t\t\t} else if (b.name && MUTATE_TOOLS.has(b.name)) {\n\t\t\t\t\tmutateCallPath.set(b.id, resolved);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (m.role === \"toolResult\" && !m.isError && m.toolCallId) {\n\t\t\tif (m.toolName === READ_TOOL) {\n\t\t\t\tif (m.toolCallId === opts.currentCallId) continue;\n\t\t\t\tconst info = readCall.get(m.toolCallId);\n\t\t\t\tif (!info || info.resolved !== opts.resolvedPath) continue;\n\t\t\t\tconst text = resultText(m);\n\t\t\t\t// A pointer fetched nothing: the GC excludes it from supersession, so we\n\t\t\t\t// must too (both as a candidate and as a superseder).\n\t\t\t\tif (isDedupPointerText(text)) continue;\n\t\t\t\treads.push({\n\t\t\t\t\tindex: i,\n\t\t\t\t\tdeclared: info.declared,\n\t\t\t\t\tdelivered: deliveredRange(text, info.declared),\n\t\t\t\t\tdisplay: info.display,\n\t\t\t\t});\n\t\t\t} else if (m.toolName && MUTATE_TOOLS.has(m.toolName)) {\n\t\t\t\tif (mutateCallPath.get(m.toolCallId) === opts.resolvedPath) lastMutateIndex = i;\n\t\t\t}\n\t\t}\n\t}\n\n\t// A read survives the GC iff no later mutate and no later read overlaps its\n\t// *declared* range — exactly the GC's own test — so predict it the same way.\n\tconst survivesGc = (r: PriorRead): boolean =>\n\t\tlastMutateIndex <= r.index && !reads.some((o) => o.index > r.index && rangesOverlap(o.declared, r.declared));\n\n\tlet best: PriorRead | null = null;\n\tfor (const r of reads) {\n\t\t// Coverage uses the *delivered* range: a truncated read only holds what it returned.\n\t\tif (!rangeContains(r.delivered, opts.requestedRange)) continue;\n\t\tif (!survivesGc(r)) continue;\n\t\tif (!best || r.index > best.index) best = r;\n\t}\n\tif (!best) return null;\n\treturn { display: best.display, start: best.delivered.start, end: best.delivered.end };\n}\n\n/** Build the pointer text returned in place of a re-fetch. */\nexport function buildDedupPointerText(covering: CoveringRead): string {\n\tconst where =\n\t\tcovering.end === Number.POSITIVE_INFINITY\n\t\t\t? \"the entire file\"\n\t\t\t: covering.end - 1 > covering.start\n\t\t\t\t? `lines ${covering.start}-${covering.end - 1}`\n\t\t\t\t: `line ${covering.start}`;\n\treturn `${DEDUP_POINTER_PREFIX} ${covering.display} (${where}) was already read earlier in this session and has not changed since. Not re-fetched to save tokens — pass a different offset/limit, or edit the file, if you need other or newer content.]`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"read-dedup.d.ts","sourceRoot":"","sources":["../../../src/core/tools/read-dedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,sEAAsE;AACtE,eAAO,MAAM,gBAAgB,EAAE,SAAuD,CAAC;AAEvF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,SAAS,CAStF;AAED,mDAAmD;AACnD,wBAAgB,aAAa,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAEjE;AAOD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC5B,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,uBAAuB;IACvC,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,cAAc,EAAE,SAAS,CAAC;IAC1B,mFAAmF;IACnF,aAAa,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CACzC;AAoED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,EAAE,IAAI,EAAE,uBAAuB,GAAG,YAAY,GAAG,IAAI,CA8FhH;AAED,8DAA8D;AAC9D,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAQpE","sourcesContent":["/**\n * Read de-duplication primitives.\n *\n * Two mechanisms share this module:\n *\n * - The post-hoc context GC (`context-gc.ts`), which stubs out a `read` result\n * once a later overlapping read or an edit/write has superseded it.\n * - The at-call-time guard in the `read` tool, which short-circuits a read whose\n * requested range is *already fully covered* by an earlier, still-live read in\n * the current session, returning a pointer instead of re-fetching the file.\n *\n * Both reason about half-open line ranges `[start, end)` (end exclusive), so the\n * range math lives here and cannot drift between them. The guard additionally\n * needs to (a) recognise its own pointer results so they are never treated as\n * content-bearing reads, and (b) walk the session branch to find a covering\n * read — both of which are defined here to keep the tool file lean.\n */\n\n/** Half-open line interval `[start, end)` a read call covers; end is exclusive. */\nexport interface ReadRange {\n\tstart: number;\n\tend: number;\n}\n\n/** A read with no offset/limit covers the whole file (open-ended). */\nexport const WHOLE_FILE_RANGE: ReadRange = { start: 1, end: Number.POSITIVE_INFINITY };\n\n/**\n * Derive the line range a read call covers from its `offset`/`limit` args.\n * Missing offset means \"from line 1\"; missing limit means \"to end of file\"\n * (open-ended, so it overlaps any later read of the same file).\n */\nexport function readRangeFromArgs(args: Record<string, unknown> | undefined): ReadRange {\n\tconst offsetRaw = args?.offset;\n\tconst limitRaw = args?.limit;\n\tconst offset = typeof offsetRaw === \"number\" && Number.isFinite(offsetRaw) && offsetRaw > 0 ? offsetRaw : 1;\n\tconst end =\n\t\ttypeof limitRaw === \"number\" && Number.isFinite(limitRaw)\n\t\t\t? offset + Math.max(0, limitRaw)\n\t\t\t: Number.POSITIVE_INFINITY;\n\treturn { start: offset, end };\n}\n\n/** Whether two half-open line ranges intersect. */\nexport function rangesOverlap(a: ReadRange, b: ReadRange): boolean {\n\treturn a.start < b.end && b.start < a.end;\n}\n\n/** Whether `outer` fully contains `inner` (every line of inner lies within outer). */\nfunction rangeContains(outer: ReadRange, inner: ReadRange): boolean {\n\treturn outer.start <= inner.start && outer.end >= inner.end;\n}\n\n/**\n * Marker prefix for the at-call dedup pointer. Kept stable so the context GC can\n * recognise a pointer result and exclude it from supersession bookkeeping (a\n * pointer fetched no content, so it must not stub the read it points at).\n */\nexport const DEDUP_POINTER_PREFIX = \"[Already in context:\";\n\n/** Whether a tool-result text is an at-call dedup pointer. */\nexport function isDedupPointerText(text: string): boolean {\n\treturn text.trimStart().startsWith(DEDUP_POINTER_PREFIX);\n}\n\n/** A covering earlier read, described for the pointer message. */\nexport interface CoveringRead {\n\t/** The path as the earlier read spelled it (for a friendly pointer). */\n\tdisplay: string;\n\t/** Delivered range start (1-indexed line). */\n\tstart: number;\n\t/** Delivered range end (exclusive; Infinity for a whole-file read). */\n\tend: number;\n}\n\nexport interface FindCoveringReadOptions {\n\t/** Resolved absolute path of the current read. */\n\tresolvedPath: string;\n\t/** Range the current read is asking for. */\n\trequestedRange: ReadRange;\n\t/** Tool call id of the current read, excluded from candidate/supersession sets. */\n\tcurrentCallId: string;\n\t/** Resolve a raw read-arg path the same way the current read resolved its path. */\n\tresolvePath: (rawPath: string) => string;\n}\n\ninterface ContentBlock {\n\ttype?: string;\n\tid?: string;\n\tname?: string;\n\targuments?: Record<string, unknown>;\n\ttext?: string;\n}\n\ninterface MessageLike {\n\trole?: string;\n\tcontent?: ContentBlock[];\n\ttoolCallId?: string;\n\ttoolName?: string;\n\tisError?: boolean;\n}\n\nconst READ_TOOL = \"read\";\nconst MUTATE_TOOLS = new Set([\"edit\", \"write\"]);\n\n/** A session compaction entry — the boundary that trims the live context. */\nfunction isCompactionEntry(entry: unknown): boolean {\n\treturn !!entry && typeof entry === \"object\" && (entry as { type?: unknown }).type === \"compaction\";\n}\n\n/** Accept either raw `AgentMessage`s or session entries wrapping `.message`. */\nfunction toMessage(entry: unknown): MessageLike | null {\n\tif (!entry || typeof entry !== \"object\") return null;\n\tconst e = entry as { message?: unknown; role?: unknown };\n\tif (e.message && typeof e.message === \"object\") return e.message as MessageLike;\n\tif (typeof e.role === \"string\") return e as MessageLike;\n\treturn null;\n}\n\nfunction resultText(m: MessageLike): string {\n\treturn (m.content ?? [])\n\t\t.filter((c) => c.type === \"text\")\n\t\t.map((c) => c.text ?? \"\")\n\t\t.join(\"\");\n}\n\n/**\n * Recover the range a read result actually *delivered* from its text.\n *\n * A cap-truncated read announces `[Showing lines A-B of N ...]`, so it delivered\n * only `[A, B+1)` even though its args declared a wider range. A read whose first\n * line alone exceeded the byte cap delivered nothing. Any other (untruncated)\n * read delivered its full declared range. The user-`limit` early-stop notice\n * (`[N more lines in file ...]`) is *not* a cap truncation — the declared range\n * was delivered in full — so it falls through to `declared`.\n */\nfunction deliveredRange(text: string, declared: ReadRange): ReadRange {\n\t// The truncation notice is always the trailing `\\n\\n[Showing lines A-B of N ...]`\n\t// clause the read tool appends. Anchor to the end so a `[Showing lines ...]`\n\t// string that merely appears *inside* the file's content can't spoof it.\n\tconst showing = text.match(/\\n\\n\\[Showing lines (\\d+)-(\\d+) of \\d+[^\\]]*\\]\\s*$/);\n\tif (showing) {\n\t\tconst a = Number(showing[1]);\n\t\tconst b = Number(showing[2]);\n\t\tif (Number.isFinite(a) && Number.isFinite(b) && b >= a) return { start: a, end: b + 1 };\n\t}\n\t// First line alone exceeded the byte limit: the whole result *is* that notice,\n\t// so it must start the text. Nothing usable was delivered.\n\tif (/^\\[Line \\d+ is .+ exceeds .+ limit\\./.test(text)) return { start: 1, end: 1 };\n\treturn declared;\n}\n\n/**\n * Find the latest earlier read that (a) is for the same resolved path, (b)\n * actually delivered a range containing the requested range, and (c) is still\n * live in the outgoing context — i.e. the post-hoc GC will not have stubbed it,\n * because no later edit/write and no later overlapping content read supersede\n * it. Returns that read's delivered range for the pointer, or null when the\n * current read must actually run.\n *\n * Deliberately conservative: a truncated earlier read only covers what it\n * delivered, a whole-file read must have been delivered untruncated to count as\n * covering, and any pointer results (which fetched nothing) are ignored on both\n * the candidate and the supersession side.\n */\nexport function findCoveringRead(entries: readonly unknown[], opts: FindCoveringReadOptions): CoveringRead | null {\n\t// `declared` mirrors the range the GC uses for supersession (straight from the\n\t// call args); `delivered` is what the result text shows was actually returned,\n\t// used for coverage. The current call has no result yet, so it never appears.\n\tinterface PriorRead {\n\t\tindex: number;\n\t\tdeclared: ReadRange;\n\t\tdelivered: ReadRange;\n\t\tdisplay: string;\n\t}\n\n\t// Resolve each distinct raw path once — the resolver may hit the filesystem.\n\tconst resolveCache = new Map<string, string>();\n\tconst resolvePath = (raw: string): string => {\n\t\tconst hit = resolveCache.get(raw);\n\t\tif (hit !== undefined) return hit;\n\t\tconst resolved = opts.resolvePath(raw);\n\t\tresolveCache.set(raw, resolved);\n\t\treturn resolved;\n\t};\n\n\t// Single ordered pass. `readCall`/`mutateCallPath` map a call id to its path as\n\t// the call is seen (a toolCall always precedes its result); `reads` collects\n\t// the target path's reads and `lastMutateIndex` its last mutate. `order` gives\n\t// live-context position for the supersession/mutate comparisons.\n\tconst readCall = new Map<string, { resolved: string; display: string; declared: ReadRange }>();\n\tconst mutateCallPath = new Map<string, string>();\n\tconst reads: PriorRead[] = [];\n\tlet lastMutateIndex = -1;\n\tlet order = 0;\n\n\tfor (const entry of entries) {\n\t\t// Compaction boundary: everything before it is replaced by a summary in the\n\t\t// live context, so drop the state accumulated so far. Conservative — the\n\t\t// kept tail before the boundary is dropped too — which can only miss a\n\t\t// dedup, never point at content that is no longer in context.\n\t\tif (isCompactionEntry(entry)) {\n\t\t\treadCall.clear();\n\t\t\tmutateCallPath.clear();\n\t\t\treads.length = 0;\n\t\t\tlastMutateIndex = -1;\n\t\t\torder = 0;\n\t\t\tcontinue;\n\t\t}\n\t\tconst m = toMessage(entry);\n\t\tif (!m) continue;\n\t\tconst i = order++;\n\t\tif (m.role === \"assistant\" && Array.isArray(m.content)) {\n\t\t\tfor (const b of m.content) {\n\t\t\t\tif (b.type !== \"toolCall\" || !b.id) continue;\n\t\t\t\tconst raw = b.arguments?.path;\n\t\t\t\tif (typeof raw !== \"string\" || raw.length === 0) continue;\n\t\t\t\tconst resolved = resolvePath(raw);\n\t\t\t\tif (b.name === READ_TOOL) {\n\t\t\t\t\treadCall.set(b.id, { resolved, display: raw, declared: readRangeFromArgs(b.arguments) });\n\t\t\t\t} else if (b.name && MUTATE_TOOLS.has(b.name)) {\n\t\t\t\t\tmutateCallPath.set(b.id, resolved);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (m.role === \"toolResult\" && !m.isError && m.toolCallId) {\n\t\t\tif (m.toolName === READ_TOOL) {\n\t\t\t\tif (m.toolCallId === opts.currentCallId) continue;\n\t\t\t\tconst info = readCall.get(m.toolCallId);\n\t\t\t\tif (!info || info.resolved !== opts.resolvedPath) continue;\n\t\t\t\tconst text = resultText(m);\n\t\t\t\t// A pointer fetched nothing: the GC excludes it from supersession, so we\n\t\t\t\t// must too (both as a candidate and as a superseder).\n\t\t\t\tif (isDedupPointerText(text)) continue;\n\t\t\t\treads.push({\n\t\t\t\t\tindex: i,\n\t\t\t\t\tdeclared: info.declared,\n\t\t\t\t\tdelivered: deliveredRange(text, info.declared),\n\t\t\t\t\tdisplay: info.display,\n\t\t\t\t});\n\t\t\t} else if (m.toolName && MUTATE_TOOLS.has(m.toolName)) {\n\t\t\t\tif (mutateCallPath.get(m.toolCallId) === opts.resolvedPath) lastMutateIndex = i;\n\t\t\t}\n\t\t}\n\t}\n\n\t// A read survives the GC iff no later mutate and no later read overlaps its\n\t// *declared* range — exactly the GC's own test — so predict it the same way.\n\tconst survivesGc = (r: PriorRead): boolean =>\n\t\tlastMutateIndex <= r.index && !reads.some((o) => o.index > r.index && rangesOverlap(o.declared, r.declared));\n\n\tlet best: PriorRead | null = null;\n\tfor (const r of reads) {\n\t\t// Coverage uses the *delivered* range: a truncated read only holds what it returned.\n\t\tif (!rangeContains(r.delivered, opts.requestedRange)) continue;\n\t\tif (!survivesGc(r)) continue;\n\t\tif (!best || r.index > best.index) best = r;\n\t}\n\tif (!best) return null;\n\treturn { display: best.display, start: best.delivered.start, end: best.delivered.end };\n}\n\n/** Build the pointer text returned in place of a re-fetch. */\nexport function buildDedupPointerText(covering: CoveringRead): string {\n\tconst where =\n\t\tcovering.end === Number.POSITIVE_INFINITY\n\t\t\t? \"the entire file\"\n\t\t\t: covering.end - 1 > covering.start\n\t\t\t\t? `lines ${covering.start}-${covering.end - 1}`\n\t\t\t\t: `line ${covering.start}`;\n\treturn `${DEDUP_POINTER_PREFIX} ${covering.display} (${where}) was already read earlier in this session and has not changed since. Not re-fetched to save tokens — pass a different offset/limit, or edit the file, if you need other or newer content.]`;\n}\n"]}
|
|
@@ -36,7 +36,7 @@ export function rangesOverlap(a, b) {
|
|
|
36
36
|
return a.start < b.end && b.start < a.end;
|
|
37
37
|
}
|
|
38
38
|
/** Whether `outer` fully contains `inner` (every line of inner lies within outer). */
|
|
39
|
-
|
|
39
|
+
function rangeContains(outer, inner) {
|
|
40
40
|
return outer.start <= inner.start && outer.end >= inner.end;
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-dedup.js","sourceRoot":"","sources":["../../../src/core/tools/read-dedup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Read de-duplication primitives.\n *\n * Two mechanisms share this module:\n *\n * - The post-hoc context GC (`context-gc.ts`), which stubs out a `read` result\n * once a later overlapping read or an edit/write has superseded it.\n * - The at-call-time guard in the `read` tool, which short-circuits a read whose\n * requested range is *already fully covered* by an earlier, still-live read in\n * the current session, returning a pointer instead of re-fetching the file.\n *\n * Both reason about half-open line ranges `[start, end)` (end exclusive), so the\n * range math lives here and cannot drift between them. The guard additionally\n * needs to (a) recognise its own pointer results so they are never treated as\n * content-bearing reads, and (b) walk the session branch to find a covering\n * read — both of which are defined here to keep the tool file lean.\n */\n\n/** Half-open line interval `[start, end)` a read call covers; end is exclusive. */\nexport interface ReadRange {\n\tstart: number;\n\tend: number;\n}\n\n/** A read with no offset/limit covers the whole file (open-ended). */\nexport const WHOLE_FILE_RANGE: ReadRange = { start: 1, end: Number.POSITIVE_INFINITY };\n\n/**\n * Derive the line range a read call covers from its `offset`/`limit` args.\n * Missing offset means \"from line 1\"; missing limit means \"to end of file\"\n * (open-ended, so it overlaps any later read of the same file).\n */\nexport function readRangeFromArgs(args: Record<string, unknown> | undefined): ReadRange {\n\tconst offsetRaw = args?.offset;\n\tconst limitRaw = args?.limit;\n\tconst offset = typeof offsetRaw === \"number\" && Number.isFinite(offsetRaw) && offsetRaw > 0 ? offsetRaw : 1;\n\tconst end =\n\t\ttypeof limitRaw === \"number\" && Number.isFinite(limitRaw)\n\t\t\t? offset + Math.max(0, limitRaw)\n\t\t\t: Number.POSITIVE_INFINITY;\n\treturn { start: offset, end };\n}\n\n/** Whether two half-open line ranges intersect. */\nexport function rangesOverlap(a: ReadRange, b: ReadRange): boolean {\n\treturn a.start < b.end && b.start < a.end;\n}\n\n/** Whether `outer` fully contains `inner` (every line of inner lies within outer). */\nexport function rangeContains(outer: ReadRange, inner: ReadRange): boolean {\n\treturn outer.start <= inner.start && outer.end >= inner.end;\n}\n\n/**\n * Marker prefix for the at-call dedup pointer. Kept stable so the context GC can\n * recognise a pointer result and exclude it from supersession bookkeeping (a\n * pointer fetched no content, so it must not stub the read it points at).\n */\nexport const DEDUP_POINTER_PREFIX = \"[Already in context:\";\n\n/** Whether a tool-result text is an at-call dedup pointer. */\nexport function isDedupPointerText(text: string): boolean {\n\treturn text.trimStart().startsWith(DEDUP_POINTER_PREFIX);\n}\n\n/** A covering earlier read, described for the pointer message. */\nexport interface CoveringRead {\n\t/** The path as the earlier read spelled it (for a friendly pointer). */\n\tdisplay: string;\n\t/** Delivered range start (1-indexed line). */\n\tstart: number;\n\t/** Delivered range end (exclusive; Infinity for a whole-file read). */\n\tend: number;\n}\n\nexport interface FindCoveringReadOptions {\n\t/** Resolved absolute path of the current read. */\n\tresolvedPath: string;\n\t/** Range the current read is asking for. */\n\trequestedRange: ReadRange;\n\t/** Tool call id of the current read, excluded from candidate/supersession sets. */\n\tcurrentCallId: string;\n\t/** Resolve a raw read-arg path the same way the current read resolved its path. */\n\tresolvePath: (rawPath: string) => string;\n}\n\ninterface ContentBlock {\n\ttype?: string;\n\tid?: string;\n\tname?: string;\n\targuments?: Record<string, unknown>;\n\ttext?: string;\n}\n\ninterface MessageLike {\n\trole?: string;\n\tcontent?: ContentBlock[];\n\ttoolCallId?: string;\n\ttoolName?: string;\n\tisError?: boolean;\n}\n\nconst READ_TOOL = \"read\";\nconst MUTATE_TOOLS = new Set([\"edit\", \"write\"]);\n\n/** A session compaction entry — the boundary that trims the live context. */\nfunction isCompactionEntry(entry: unknown): boolean {\n\treturn !!entry && typeof entry === \"object\" && (entry as { type?: unknown }).type === \"compaction\";\n}\n\n/** Accept either raw `AgentMessage`s or session entries wrapping `.message`. */\nfunction toMessage(entry: unknown): MessageLike | null {\n\tif (!entry || typeof entry !== \"object\") return null;\n\tconst e = entry as { message?: unknown; role?: unknown };\n\tif (e.message && typeof e.message === \"object\") return e.message as MessageLike;\n\tif (typeof e.role === \"string\") return e as MessageLike;\n\treturn null;\n}\n\nfunction resultText(m: MessageLike): string {\n\treturn (m.content ?? [])\n\t\t.filter((c) => c.type === \"text\")\n\t\t.map((c) => c.text ?? \"\")\n\t\t.join(\"\");\n}\n\n/**\n * Recover the range a read result actually *delivered* from its text.\n *\n * A cap-truncated read announces `[Showing lines A-B of N ...]`, so it delivered\n * only `[A, B+1)` even though its args declared a wider range. A read whose first\n * line alone exceeded the byte cap delivered nothing. Any other (untruncated)\n * read delivered its full declared range. The user-`limit` early-stop notice\n * (`[N more lines in file ...]`) is *not* a cap truncation — the declared range\n * was delivered in full — so it falls through to `declared`.\n */\nfunction deliveredRange(text: string, declared: ReadRange): ReadRange {\n\t// The truncation notice is always the trailing `\\n\\n[Showing lines A-B of N ...]`\n\t// clause the read tool appends. Anchor to the end so a `[Showing lines ...]`\n\t// string that merely appears *inside* the file's content can't spoof it.\n\tconst showing = text.match(/\\n\\n\\[Showing lines (\\d+)-(\\d+) of \\d+[^\\]]*\\]\\s*$/);\n\tif (showing) {\n\t\tconst a = Number(showing[1]);\n\t\tconst b = Number(showing[2]);\n\t\tif (Number.isFinite(a) && Number.isFinite(b) && b >= a) return { start: a, end: b + 1 };\n\t}\n\t// First line alone exceeded the byte limit: the whole result *is* that notice,\n\t// so it must start the text. Nothing usable was delivered.\n\tif (/^\\[Line \\d+ is .+ exceeds .+ limit\\./.test(text)) return { start: 1, end: 1 };\n\treturn declared;\n}\n\n/**\n * Find the latest earlier read that (a) is for the same resolved path, (b)\n * actually delivered a range containing the requested range, and (c) is still\n * live in the outgoing context — i.e. the post-hoc GC will not have stubbed it,\n * because no later edit/write and no later overlapping content read supersede\n * it. Returns that read's delivered range for the pointer, or null when the\n * current read must actually run.\n *\n * Deliberately conservative: a truncated earlier read only covers what it\n * delivered, a whole-file read must have been delivered untruncated to count as\n * covering, and any pointer results (which fetched nothing) are ignored on both\n * the candidate and the supersession side.\n */\nexport function findCoveringRead(entries: readonly unknown[], opts: FindCoveringReadOptions): CoveringRead | null {\n\t// `declared` mirrors the range the GC uses for supersession (straight from the\n\t// call args); `delivered` is what the result text shows was actually returned,\n\t// used for coverage. The current call has no result yet, so it never appears.\n\tinterface PriorRead {\n\t\tindex: number;\n\t\tdeclared: ReadRange;\n\t\tdelivered: ReadRange;\n\t\tdisplay: string;\n\t}\n\n\t// Resolve each distinct raw path once — the resolver may hit the filesystem.\n\tconst resolveCache = new Map<string, string>();\n\tconst resolvePath = (raw: string): string => {\n\t\tconst hit = resolveCache.get(raw);\n\t\tif (hit !== undefined) return hit;\n\t\tconst resolved = opts.resolvePath(raw);\n\t\tresolveCache.set(raw, resolved);\n\t\treturn resolved;\n\t};\n\n\t// Single ordered pass. `readCall`/`mutateCallPath` map a call id to its path as\n\t// the call is seen (a toolCall always precedes its result); `reads` collects\n\t// the target path's reads and `lastMutateIndex` its last mutate. `order` gives\n\t// live-context position for the supersession/mutate comparisons.\n\tconst readCall = new Map<string, { resolved: string; display: string; declared: ReadRange }>();\n\tconst mutateCallPath = new Map<string, string>();\n\tconst reads: PriorRead[] = [];\n\tlet lastMutateIndex = -1;\n\tlet order = 0;\n\n\tfor (const entry of entries) {\n\t\t// Compaction boundary: everything before it is replaced by a summary in the\n\t\t// live context, so drop the state accumulated so far. Conservative — the\n\t\t// kept tail before the boundary is dropped too — which can only miss a\n\t\t// dedup, never point at content that is no longer in context.\n\t\tif (isCompactionEntry(entry)) {\n\t\t\treadCall.clear();\n\t\t\tmutateCallPath.clear();\n\t\t\treads.length = 0;\n\t\t\tlastMutateIndex = -1;\n\t\t\torder = 0;\n\t\t\tcontinue;\n\t\t}\n\t\tconst m = toMessage(entry);\n\t\tif (!m) continue;\n\t\tconst i = order++;\n\t\tif (m.role === \"assistant\" && Array.isArray(m.content)) {\n\t\t\tfor (const b of m.content) {\n\t\t\t\tif (b.type !== \"toolCall\" || !b.id) continue;\n\t\t\t\tconst raw = b.arguments?.path;\n\t\t\t\tif (typeof raw !== \"string\" || raw.length === 0) continue;\n\t\t\t\tconst resolved = resolvePath(raw);\n\t\t\t\tif (b.name === READ_TOOL) {\n\t\t\t\t\treadCall.set(b.id, { resolved, display: raw, declared: readRangeFromArgs(b.arguments) });\n\t\t\t\t} else if (b.name && MUTATE_TOOLS.has(b.name)) {\n\t\t\t\t\tmutateCallPath.set(b.id, resolved);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (m.role === \"toolResult\" && !m.isError && m.toolCallId) {\n\t\t\tif (m.toolName === READ_TOOL) {\n\t\t\t\tif (m.toolCallId === opts.currentCallId) continue;\n\t\t\t\tconst info = readCall.get(m.toolCallId);\n\t\t\t\tif (!info || info.resolved !== opts.resolvedPath) continue;\n\t\t\t\tconst text = resultText(m);\n\t\t\t\t// A pointer fetched nothing: the GC excludes it from supersession, so we\n\t\t\t\t// must too (both as a candidate and as a superseder).\n\t\t\t\tif (isDedupPointerText(text)) continue;\n\t\t\t\treads.push({\n\t\t\t\t\tindex: i,\n\t\t\t\t\tdeclared: info.declared,\n\t\t\t\t\tdelivered: deliveredRange(text, info.declared),\n\t\t\t\t\tdisplay: info.display,\n\t\t\t\t});\n\t\t\t} else if (m.toolName && MUTATE_TOOLS.has(m.toolName)) {\n\t\t\t\tif (mutateCallPath.get(m.toolCallId) === opts.resolvedPath) lastMutateIndex = i;\n\t\t\t}\n\t\t}\n\t}\n\n\t// A read survives the GC iff no later mutate and no later read overlaps its\n\t// *declared* range — exactly the GC's own test — so predict it the same way.\n\tconst survivesGc = (r: PriorRead): boolean =>\n\t\tlastMutateIndex <= r.index && !reads.some((o) => o.index > r.index && rangesOverlap(o.declared, r.declared));\n\n\tlet best: PriorRead | null = null;\n\tfor (const r of reads) {\n\t\t// Coverage uses the *delivered* range: a truncated read only holds what it returned.\n\t\tif (!rangeContains(r.delivered, opts.requestedRange)) continue;\n\t\tif (!survivesGc(r)) continue;\n\t\tif (!best || r.index > best.index) best = r;\n\t}\n\tif (!best) return null;\n\treturn { display: best.display, start: best.delivered.start, end: best.delivered.end };\n}\n\n/** Build the pointer text returned in place of a re-fetch. */\nexport function buildDedupPointerText(covering: CoveringRead): string {\n\tconst where =\n\t\tcovering.end === Number.POSITIVE_INFINITY\n\t\t\t? \"the entire file\"\n\t\t\t: covering.end - 1 > covering.start\n\t\t\t\t? `lines ${covering.start}-${covering.end - 1}`\n\t\t\t\t: `line ${covering.start}`;\n\treturn `${DEDUP_POINTER_PREFIX} ${covering.display} (${where}) was already read earlier in this session and has not changed since. Not re-fetched to save tokens — pass a different offset/limit, or edit the file, if you need other or newer content.]`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"read-dedup.js","sourceRoot":"","sources":["../../../src/core/tools/read-dedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAQH,sEAAsE;AACtE,MAAM,CAAC,MAAM,gBAAgB,GAAc,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,iBAAiB,EAAE,CAAC;AAEvF;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAyC,EAAa;IACvF,MAAM,SAAS,GAAG,IAAI,EAAE,MAAM,CAAC;IAC/B,MAAM,QAAQ,GAAG,IAAI,EAAE,KAAK,CAAC;IAC7B,MAAM,MAAM,GAAG,OAAO,SAAS,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5G,MAAM,GAAG,GACR,OAAO,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACxD,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC;QAChC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC;IAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;AAAA,CAC9B;AAED,mDAAmD;AACnD,MAAM,UAAU,aAAa,CAAC,CAAY,EAAE,CAAY,EAAW;IAClE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC;AAAA,CAC1C;AAED,sFAAsF;AACtF,SAAS,aAAa,CAAC,KAAgB,EAAE,KAAgB,EAAW;IACnE,OAAO,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC;AAAA,CAC5D;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAC;AAE3D,8DAA8D;AAC9D,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAW;IACzD,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAC;AAAA,CACzD;AAuCD,MAAM,SAAS,GAAG,MAAM,CAAC;AACzB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEhD,+EAA6E;AAC7E,SAAS,iBAAiB,CAAC,KAAc,EAAW;IACnD,OAAO,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAK,KAA4B,CAAC,IAAI,KAAK,YAAY,CAAC;AAAA,CACnG;AAED,gFAAgF;AAChF,SAAS,SAAS,CAAC,KAAc,EAAsB;IACtD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,CAAC,GAAG,KAA8C,CAAC;IACzD,IAAI,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC,OAAsB,CAAC;IAChF,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,CAAgB,CAAC;IACxD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,SAAS,UAAU,CAAC,CAAc,EAAU;IAC3C,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;SACtB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;SAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;SACxB,IAAI,CAAC,EAAE,CAAC,CAAC;AAAA,CACX;AAED;;;;;;;;;GASG;AACH,SAAS,cAAc,CAAC,IAAY,EAAE,QAAmB,EAAa;IACrE,kFAAkF;IAClF,6EAA6E;IAC7E,yEAAyE;IACzE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACjF,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IACzF,CAAC;IACD,+EAA+E;IAC/E,2DAA2D;IAC3D,IAAI,sCAAsC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IACnF,OAAO,QAAQ,CAAC;AAAA,CAChB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA2B,EAAE,IAA6B,EAAuB;IAWjH,+EAA6E;IAC7E,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC/C,MAAM,WAAW,GAAG,CAAC,GAAW,EAAU,EAAE,CAAC;QAC5C,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,GAAG,CAAC;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACvC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAChC,OAAO,QAAQ,CAAC;IAAA,CAChB,CAAC;IAEF,gFAAgF;IAChF,6EAA6E;IAC7E,+EAA+E;IAC/E,iEAAiE;IACjE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAsE,CAAC;IAC/F,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;IACjD,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,IAAI,eAAe,GAAG,CAAC,CAAC,CAAC;IACzB,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,4EAA4E;QAC5E,2EAAyE;QACzE,yEAAuE;QACvE,8DAA8D;QAC9D,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9B,QAAQ,CAAC,KAAK,EAAE,CAAC;YACjB,cAAc,CAAC,KAAK,EAAE,CAAC;YACvB,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;YACjB,eAAe,GAAG,CAAC,CAAC,CAAC;YACrB,KAAK,GAAG,CAAC,CAAC;YACV,SAAS;QACV,CAAC;QACD,MAAM,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;QAClB,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;YACxD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;gBAC3B,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,EAAE;oBAAE,SAAS;gBAC7C,MAAM,GAAG,GAAG,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC;gBAC9B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;oBAAE,SAAS;gBAC1D,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;gBAClC,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;oBAC1B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;gBAC1F,CAAC;qBAAM,IAAI,CAAC,CAAC,IAAI,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/C,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;gBACpC,CAAC;YACF,CAAC;QACF,CAAC;aAAM,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;YAClE,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC9B,IAAI,CAAC,CAAC,UAAU,KAAK,IAAI,CAAC,aAAa;oBAAE,SAAS;gBAClD,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;gBACxC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY;oBAAE,SAAS;gBAC3D,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;gBAC3B,yEAAyE;gBACzE,sDAAsD;gBACtD,IAAI,kBAAkB,CAAC,IAAI,CAAC;oBAAE,SAAS;gBACvC,KAAK,CAAC,IAAI,CAAC;oBACV,KAAK,EAAE,CAAC;oBACR,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,SAAS,EAAE,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC;oBAC9C,OAAO,EAAE,IAAI,CAAC,OAAO;iBACrB,CAAC,CAAC;YACJ,CAAC;iBAAM,IAAI,CAAC,CAAC,QAAQ,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACvD,IAAI,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,YAAY;oBAAE,eAAe,GAAG,CAAC,CAAC;YACjF,CAAC;QACF,CAAC;IACF,CAAC;IAED,4EAA4E;IAC5E,iFAA6E;IAC7E,MAAM,UAAU,GAAG,CAAC,CAAY,EAAW,EAAE,CAC5C,eAAe,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,aAAa,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE9G,IAAI,IAAI,GAAqB,IAAI,CAAC;IAClC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACvB,qFAAqF;QACrF,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,cAAc,CAAC;YAAE,SAAS;QAC/D,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,SAAS;QAC7B,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK;YAAE,IAAI,GAAG,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;AAAA,CACvF;AAED,8DAA8D;AAC9D,MAAM,UAAU,qBAAqB,CAAC,QAAsB,EAAU;IACrE,MAAM,KAAK,GACV,QAAQ,CAAC,GAAG,KAAK,MAAM,CAAC,iBAAiB;QACxC,CAAC,CAAC,iBAAiB;QACnB,CAAC,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,GAAG,QAAQ,CAAC,KAAK;YAClC,CAAC,CAAC,SAAS,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,GAAG,GAAG,CAAC,EAAE;YAC/C,CAAC,CAAC,QAAQ,QAAQ,CAAC,KAAK,EAAE,CAAC;IAC9B,OAAO,GAAG,oBAAoB,IAAI,QAAQ,CAAC,OAAO,KAAK,KAAK,+LAA6L,CAAC;AAAA,CAC1P","sourcesContent":["/**\n * Read de-duplication primitives.\n *\n * Two mechanisms share this module:\n *\n * - The post-hoc context GC (`context-gc.ts`), which stubs out a `read` result\n * once a later overlapping read or an edit/write has superseded it.\n * - The at-call-time guard in the `read` tool, which short-circuits a read whose\n * requested range is *already fully covered* by an earlier, still-live read in\n * the current session, returning a pointer instead of re-fetching the file.\n *\n * Both reason about half-open line ranges `[start, end)` (end exclusive), so the\n * range math lives here and cannot drift between them. The guard additionally\n * needs to (a) recognise its own pointer results so they are never treated as\n * content-bearing reads, and (b) walk the session branch to find a covering\n * read — both of which are defined here to keep the tool file lean.\n */\n\n/** Half-open line interval `[start, end)` a read call covers; end is exclusive. */\nexport interface ReadRange {\n\tstart: number;\n\tend: number;\n}\n\n/** A read with no offset/limit covers the whole file (open-ended). */\nexport const WHOLE_FILE_RANGE: ReadRange = { start: 1, end: Number.POSITIVE_INFINITY };\n\n/**\n * Derive the line range a read call covers from its `offset`/`limit` args.\n * Missing offset means \"from line 1\"; missing limit means \"to end of file\"\n * (open-ended, so it overlaps any later read of the same file).\n */\nexport function readRangeFromArgs(args: Record<string, unknown> | undefined): ReadRange {\n\tconst offsetRaw = args?.offset;\n\tconst limitRaw = args?.limit;\n\tconst offset = typeof offsetRaw === \"number\" && Number.isFinite(offsetRaw) && offsetRaw > 0 ? offsetRaw : 1;\n\tconst end =\n\t\ttypeof limitRaw === \"number\" && Number.isFinite(limitRaw)\n\t\t\t? offset + Math.max(0, limitRaw)\n\t\t\t: Number.POSITIVE_INFINITY;\n\treturn { start: offset, end };\n}\n\n/** Whether two half-open line ranges intersect. */\nexport function rangesOverlap(a: ReadRange, b: ReadRange): boolean {\n\treturn a.start < b.end && b.start < a.end;\n}\n\n/** Whether `outer` fully contains `inner` (every line of inner lies within outer). */\nfunction rangeContains(outer: ReadRange, inner: ReadRange): boolean {\n\treturn outer.start <= inner.start && outer.end >= inner.end;\n}\n\n/**\n * Marker prefix for the at-call dedup pointer. Kept stable so the context GC can\n * recognise a pointer result and exclude it from supersession bookkeeping (a\n * pointer fetched no content, so it must not stub the read it points at).\n */\nexport const DEDUP_POINTER_PREFIX = \"[Already in context:\";\n\n/** Whether a tool-result text is an at-call dedup pointer. */\nexport function isDedupPointerText(text: string): boolean {\n\treturn text.trimStart().startsWith(DEDUP_POINTER_PREFIX);\n}\n\n/** A covering earlier read, described for the pointer message. */\nexport interface CoveringRead {\n\t/** The path as the earlier read spelled it (for a friendly pointer). */\n\tdisplay: string;\n\t/** Delivered range start (1-indexed line). */\n\tstart: number;\n\t/** Delivered range end (exclusive; Infinity for a whole-file read). */\n\tend: number;\n}\n\nexport interface FindCoveringReadOptions {\n\t/** Resolved absolute path of the current read. */\n\tresolvedPath: string;\n\t/** Range the current read is asking for. */\n\trequestedRange: ReadRange;\n\t/** Tool call id of the current read, excluded from candidate/supersession sets. */\n\tcurrentCallId: string;\n\t/** Resolve a raw read-arg path the same way the current read resolved its path. */\n\tresolvePath: (rawPath: string) => string;\n}\n\ninterface ContentBlock {\n\ttype?: string;\n\tid?: string;\n\tname?: string;\n\targuments?: Record<string, unknown>;\n\ttext?: string;\n}\n\ninterface MessageLike {\n\trole?: string;\n\tcontent?: ContentBlock[];\n\ttoolCallId?: string;\n\ttoolName?: string;\n\tisError?: boolean;\n}\n\nconst READ_TOOL = \"read\";\nconst MUTATE_TOOLS = new Set([\"edit\", \"write\"]);\n\n/** A session compaction entry — the boundary that trims the live context. */\nfunction isCompactionEntry(entry: unknown): boolean {\n\treturn !!entry && typeof entry === \"object\" && (entry as { type?: unknown }).type === \"compaction\";\n}\n\n/** Accept either raw `AgentMessage`s or session entries wrapping `.message`. */\nfunction toMessage(entry: unknown): MessageLike | null {\n\tif (!entry || typeof entry !== \"object\") return null;\n\tconst e = entry as { message?: unknown; role?: unknown };\n\tif (e.message && typeof e.message === \"object\") return e.message as MessageLike;\n\tif (typeof e.role === \"string\") return e as MessageLike;\n\treturn null;\n}\n\nfunction resultText(m: MessageLike): string {\n\treturn (m.content ?? [])\n\t\t.filter((c) => c.type === \"text\")\n\t\t.map((c) => c.text ?? \"\")\n\t\t.join(\"\");\n}\n\n/**\n * Recover the range a read result actually *delivered* from its text.\n *\n * A cap-truncated read announces `[Showing lines A-B of N ...]`, so it delivered\n * only `[A, B+1)` even though its args declared a wider range. A read whose first\n * line alone exceeded the byte cap delivered nothing. Any other (untruncated)\n * read delivered its full declared range. The user-`limit` early-stop notice\n * (`[N more lines in file ...]`) is *not* a cap truncation — the declared range\n * was delivered in full — so it falls through to `declared`.\n */\nfunction deliveredRange(text: string, declared: ReadRange): ReadRange {\n\t// The truncation notice is always the trailing `\\n\\n[Showing lines A-B of N ...]`\n\t// clause the read tool appends. Anchor to the end so a `[Showing lines ...]`\n\t// string that merely appears *inside* the file's content can't spoof it.\n\tconst showing = text.match(/\\n\\n\\[Showing lines (\\d+)-(\\d+) of \\d+[^\\]]*\\]\\s*$/);\n\tif (showing) {\n\t\tconst a = Number(showing[1]);\n\t\tconst b = Number(showing[2]);\n\t\tif (Number.isFinite(a) && Number.isFinite(b) && b >= a) return { start: a, end: b + 1 };\n\t}\n\t// First line alone exceeded the byte limit: the whole result *is* that notice,\n\t// so it must start the text. Nothing usable was delivered.\n\tif (/^\\[Line \\d+ is .+ exceeds .+ limit\\./.test(text)) return { start: 1, end: 1 };\n\treturn declared;\n}\n\n/**\n * Find the latest earlier read that (a) is for the same resolved path, (b)\n * actually delivered a range containing the requested range, and (c) is still\n * live in the outgoing context — i.e. the post-hoc GC will not have stubbed it,\n * because no later edit/write and no later overlapping content read supersede\n * it. Returns that read's delivered range for the pointer, or null when the\n * current read must actually run.\n *\n * Deliberately conservative: a truncated earlier read only covers what it\n * delivered, a whole-file read must have been delivered untruncated to count as\n * covering, and any pointer results (which fetched nothing) are ignored on both\n * the candidate and the supersession side.\n */\nexport function findCoveringRead(entries: readonly unknown[], opts: FindCoveringReadOptions): CoveringRead | null {\n\t// `declared` mirrors the range the GC uses for supersession (straight from the\n\t// call args); `delivered` is what the result text shows was actually returned,\n\t// used for coverage. The current call has no result yet, so it never appears.\n\tinterface PriorRead {\n\t\tindex: number;\n\t\tdeclared: ReadRange;\n\t\tdelivered: ReadRange;\n\t\tdisplay: string;\n\t}\n\n\t// Resolve each distinct raw path once — the resolver may hit the filesystem.\n\tconst resolveCache = new Map<string, string>();\n\tconst resolvePath = (raw: string): string => {\n\t\tconst hit = resolveCache.get(raw);\n\t\tif (hit !== undefined) return hit;\n\t\tconst resolved = opts.resolvePath(raw);\n\t\tresolveCache.set(raw, resolved);\n\t\treturn resolved;\n\t};\n\n\t// Single ordered pass. `readCall`/`mutateCallPath` map a call id to its path as\n\t// the call is seen (a toolCall always precedes its result); `reads` collects\n\t// the target path's reads and `lastMutateIndex` its last mutate. `order` gives\n\t// live-context position for the supersession/mutate comparisons.\n\tconst readCall = new Map<string, { resolved: string; display: string; declared: ReadRange }>();\n\tconst mutateCallPath = new Map<string, string>();\n\tconst reads: PriorRead[] = [];\n\tlet lastMutateIndex = -1;\n\tlet order = 0;\n\n\tfor (const entry of entries) {\n\t\t// Compaction boundary: everything before it is replaced by a summary in the\n\t\t// live context, so drop the state accumulated so far. Conservative — the\n\t\t// kept tail before the boundary is dropped too — which can only miss a\n\t\t// dedup, never point at content that is no longer in context.\n\t\tif (isCompactionEntry(entry)) {\n\t\t\treadCall.clear();\n\t\t\tmutateCallPath.clear();\n\t\t\treads.length = 0;\n\t\t\tlastMutateIndex = -1;\n\t\t\torder = 0;\n\t\t\tcontinue;\n\t\t}\n\t\tconst m = toMessage(entry);\n\t\tif (!m) continue;\n\t\tconst i = order++;\n\t\tif (m.role === \"assistant\" && Array.isArray(m.content)) {\n\t\t\tfor (const b of m.content) {\n\t\t\t\tif (b.type !== \"toolCall\" || !b.id) continue;\n\t\t\t\tconst raw = b.arguments?.path;\n\t\t\t\tif (typeof raw !== \"string\" || raw.length === 0) continue;\n\t\t\t\tconst resolved = resolvePath(raw);\n\t\t\t\tif (b.name === READ_TOOL) {\n\t\t\t\t\treadCall.set(b.id, { resolved, display: raw, declared: readRangeFromArgs(b.arguments) });\n\t\t\t\t} else if (b.name && MUTATE_TOOLS.has(b.name)) {\n\t\t\t\t\tmutateCallPath.set(b.id, resolved);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (m.role === \"toolResult\" && !m.isError && m.toolCallId) {\n\t\t\tif (m.toolName === READ_TOOL) {\n\t\t\t\tif (m.toolCallId === opts.currentCallId) continue;\n\t\t\t\tconst info = readCall.get(m.toolCallId);\n\t\t\t\tif (!info || info.resolved !== opts.resolvedPath) continue;\n\t\t\t\tconst text = resultText(m);\n\t\t\t\t// A pointer fetched nothing: the GC excludes it from supersession, so we\n\t\t\t\t// must too (both as a candidate and as a superseder).\n\t\t\t\tif (isDedupPointerText(text)) continue;\n\t\t\t\treads.push({\n\t\t\t\t\tindex: i,\n\t\t\t\t\tdeclared: info.declared,\n\t\t\t\t\tdelivered: deliveredRange(text, info.declared),\n\t\t\t\t\tdisplay: info.display,\n\t\t\t\t});\n\t\t\t} else if (m.toolName && MUTATE_TOOLS.has(m.toolName)) {\n\t\t\t\tif (mutateCallPath.get(m.toolCallId) === opts.resolvedPath) lastMutateIndex = i;\n\t\t\t}\n\t\t}\n\t}\n\n\t// A read survives the GC iff no later mutate and no later read overlaps its\n\t// *declared* range — exactly the GC's own test — so predict it the same way.\n\tconst survivesGc = (r: PriorRead): boolean =>\n\t\tlastMutateIndex <= r.index && !reads.some((o) => o.index > r.index && rangesOverlap(o.declared, r.declared));\n\n\tlet best: PriorRead | null = null;\n\tfor (const r of reads) {\n\t\t// Coverage uses the *delivered* range: a truncated read only holds what it returned.\n\t\tif (!rangeContains(r.delivered, opts.requestedRange)) continue;\n\t\tif (!survivesGc(r)) continue;\n\t\tif (!best || r.index > best.index) best = r;\n\t}\n\tif (!best) return null;\n\treturn { display: best.display, start: best.delivered.start, end: best.delivered.end };\n}\n\n/** Build the pointer text returned in place of a re-fetch. */\nexport function buildDedupPointerText(covering: CoveringRead): string {\n\tconst where =\n\t\tcovering.end === Number.POSITIVE_INFINITY\n\t\t\t? \"the entire file\"\n\t\t\t: covering.end - 1 > covering.start\n\t\t\t\t? `lines ${covering.start}-${covering.end - 1}`\n\t\t\t\t: `line ${covering.start}`;\n\treturn `${DEDUP_POINTER_PREFIX} ${covering.display} (${where}) was already read earlier in this session and has not changed since. Not re-fetched to save tokens — pass a different offset/limit, or edit the file, if you need other or newer content.]`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/core/tools/read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAMjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAQ5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAKtF,OAAO,EAAoD,KAAK,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AAEtH,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AA4BD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,qCAAqC;IACrC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,+CAA+C;IAC/C,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACnF;AAQD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAmJD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAuLhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import { basename, dirname, isAbsolute, relative, resolve as resolvePath, sep } from \"node:path\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { compressReadOutput } from \"@kolisachint/hoocode-agent-core\";\nimport type { Api, ImageContent, Model, TextContent } from \"@kolisachint/hoocode-ai\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { getReadmePath } from \"../../config.js\";\nimport { keyHint, keyText } from \"../../modes/interactive/components/keybinding-hints.js\";\nimport { renderReadOutput } from \"../../modes/interactive/components/read-output.js\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.js\";\nimport { formatDimensionNote, resizeImage } from \"../../utils/image-resize.js\";\nimport { detectSupportedImageMimeTypeFromFile } from \"../../utils/mime.js\";\nimport { formatPathRelativeToCwdOrAbsolute } from \"../../utils/paths.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { resolveReadPath } from \"./path-utils.js\";\nimport { buildDedupPointerText, findCoveringRead, readRangeFromArgs } from \"./read-dedup.js\";\nimport { getTextOutput, invalidArgText, replaceTabs, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from \"./truncate.js\";\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nexport type ReadToolInput = Static<typeof readSchema>;\n\nexport interface ReadToolDetails {\n\ttruncation?: TruncationResult;\n}\n\ninterface CompactReadClassification {\n\tkind: \"docs\" | \"resource\" | \"skill\";\n\tlabel: string;\n}\n\nconst COMPACT_RESOURCE_FILE_NAMES = new Set([\"AGENTS.md\", \"AGENTS.MD\", \"CLAUDE.md\", \"CLAUDE.MD\"]);\n\n/**\n * Structured/binary document formats that the filetools-backed DocRead tool can\n * project into editable JSON. These are container/binary formats (OOXML is a zip,\n * PDF is binary), so a plain utf-8 read yields garbage — read short-circuits with\n * a pointer to DocRead instead of dumping bytes.\n */\nconst DOC_FORMAT_EXTENSIONS = new Map<string, string>([\n\t[\".docx\", \"Word (OOXML)\"],\n\t[\".xlsx\", \"Excel (OOXML)\"],\n\t[\".pptx\", \"PowerPoint (OOXML)\"],\n\t[\".pdf\", \"PDF\"],\n]);\n\nfunction getDocFormatHint(absolutePath: string): string | undefined {\n\tconst dot = absolutePath.lastIndexOf(\".\");\n\tif (dot === -1) return undefined;\n\treturn DOC_FORMAT_EXTENSIONS.get(absolutePath.slice(dot).toLowerCase());\n}\n\n/**\n * Pluggable operations for the read tool.\n * Override these to delegate file reading to remote systems (for example SSH).\n */\nexport interface ReadOperations {\n\t/** Read file contents as a Buffer */\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\t/** Check if file is readable (throw if not) */\n\taccess: (absolutePath: string) => Promise<void>;\n\t/** Detect image MIME type, return null or undefined for non-images */\n\tdetectImageMimeType?: (absolutePath: string) => Promise<string | null | undefined>;\n}\n\nconst defaultReadOperations: ReadOperations = {\n\treadFile: (path) => fsReadFile(path),\n\taccess: (path) => fsAccess(path, constants.R_OK),\n\tdetectImageMimeType: detectSupportedImageMimeTypeFromFile,\n};\n\nexport interface ReadToolOptions {\n\t/** Whether to auto-resize images to 2000x2000 max. Default: true */\n\tautoResizeImages?: boolean;\n\t/** Custom operations for file reading. Default: local filesystem */\n\toperations?: ReadOperations;\n\t/** Byte cap on the returned text before truncation. Default: DEFAULT_MAX_BYTES. */\n\tmaxOutputBytes?: number;\n\t/** Line cap on the returned text before truncation. Default: DEFAULT_MAX_LINES. */\n\tmaxOutputLines?: number;\n\t/**\n\t * When true, a text read whose requested line range is already fully covered\n\t * by an earlier, still-live read in the current session short-circuits with a\n\t * pointer instead of re-fetching the file. Complements the post-hoc context\n\t * GC; gated on the same setting. Default: false.\n\t */\n\tdedupReads?: boolean;\n}\n\ntype ReadRenderArgs = { path?: string; file_path?: string; offset?: number; limit?: number };\n\nfunction formatReadLineRange(args: ReadRenderArgs | undefined, theme: Theme): string {\n\tif (args?.offset === undefined && args?.limit === undefined) return \"\";\n\tconst startLine = args.offset ?? 1;\n\tconst endLine = args.limit !== undefined ? startLine + args.limit - 1 : \"\";\n\treturn theme.fg(\"warning\", `:${startLine}${endLine ? `-${endLine}` : \"\"}`);\n}\n\nfunction formatReadCall(args: ReadRenderArgs | undefined, theme: Theme): string {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath) : null;\n\tconst invalidArg = invalidArgText(theme);\n\tconst pathDisplay = path === null ? invalidArg : path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\");\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"read\"))} ${pathDisplay}${formatReadLineRange(args, theme)}`;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction getNonVisionImageNote(model: Model<Api> | undefined): string | undefined {\n\tif (!model || model.input.includes(\"image\")) {\n\t\treturn undefined;\n\t}\n\treturn \"[Current model does not support images. The image will be omitted from this request.]\";\n}\n\nfunction toPosixPath(filePath: string): string {\n\treturn filePath.split(sep).join(\"/\");\n}\n\nfunction getHooCodeDocsClassification(absolutePath: string): CompactReadClassification | undefined {\n\tconst packageRoot = dirname(getReadmePath());\n\tconst relativePath = relative(resolvePath(packageRoot), resolvePath(absolutePath));\n\tif (\n\t\trelativePath === \"\" ||\n\t\trelativePath === \"..\" ||\n\t\trelativePath.startsWith(`..${sep}`) ||\n\t\tisAbsolute(relativePath)\n\t) {\n\t\treturn undefined;\n\t}\n\n\tconst label = toPosixPath(relativePath);\n\tif (label === \"README.md\" || label.startsWith(\"docs/\") || label.startsWith(\"examples/\")) {\n\t\treturn { kind: \"docs\", label };\n\t}\n\treturn undefined;\n}\n\nfunction getCompactReadClassification(\n\targs: ReadRenderArgs | undefined,\n\tcwd: string,\n): CompactReadClassification | undefined {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tif (!rawPath) return undefined;\n\n\tconst absolutePath = resolveReadPath(rawPath, cwd);\n\tconst fileName = basename(absolutePath);\n\tif (fileName === \"SKILL.md\") {\n\t\treturn { kind: \"skill\", label: basename(dirname(absolutePath)) || fileName };\n\t}\n\n\tconst docsClassification = getHooCodeDocsClassification(absolutePath);\n\tif (docsClassification) return docsClassification;\n\n\tif (COMPACT_RESOURCE_FILE_NAMES.has(fileName)) {\n\t\treturn { kind: \"resource\", label: formatPathRelativeToCwdOrAbsolute(absolutePath, cwd) };\n\t}\n\n\treturn undefined;\n}\n\nfunction formatCompactReadCall(\n\tclassification: CompactReadClassification,\n\targs: ReadRenderArgs | undefined,\n\ttheme: Theme,\n): string {\n\tconst expandHint = theme.fg(\"dim\", ` (${keyText(\"app.tools.expand\")} to expand)`);\n\tif (classification.kind === \"skill\") {\n\t\treturn (\n\t\t\ttheme.fg(\"customMessageLabel\", `\\x1b[1m[skill]\\x1b[22m `) +\n\t\t\ttheme.fg(\"customMessageText\", classification.label) +\n\t\t\tformatReadLineRange(args, theme) +\n\t\t\texpandHint\n\t\t);\n\t}\n\n\treturn (\n\t\ttheme.fg(\"toolTitle\", theme.bold(`read ${classification.kind}`)) +\n\t\t\" \" +\n\t\ttheme.fg(\"accent\", classification.label) +\n\t\tformatReadLineRange(args, theme) +\n\t\texpandHint\n\t);\n}\n\nfunction formatReadResult(\n\targs: ReadRenderArgs | undefined,\n\tresult: { content: (TextContent | ImageContent)[]; details?: ReadToolDetails },\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tshowImages: boolean,\n\tcwd: string,\n\tisError: boolean,\n): string {\n\tif (!options.expanded && !isError && getCompactReadClassification(args, cwd)) {\n\t\treturn \"\";\n\t}\n\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst output = getTextOutput(result, showImages);\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tconst renderedLines = lang ? highlightCode(replaceTabs(output), lang) : output.split(\"\\n\");\n\tconst lines = trimTrailingEmptyLines(renderedLines);\n\tconst maxLines = options.expanded ? lines.length : 10;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\t// Calculate starting line number from args\n\tconst startLine = (args?.offset as number) ?? 1;\n\t// Use the new renderReadOutput function for line numbers\n\tconst numberedLines = renderReadOutput(displayLines.join(\"\\n\"), { startLine });\n\tlet text = `\\n${numberedLines.join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t}\n\n\tconst truncation = result.details?.truncation;\n\tif (truncation?.truncated) {\n\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`)}`;\n\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`)}`;\n\t\t} else {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`)}`;\n\t\t}\n\t}\n\treturn text;\n}\n\nexport function createReadToolDefinition(\n\tcwd: string,\n\toptions?: ReadToolOptions,\n): ToolDefinition<typeof readSchema, ReadToolDetails | undefined> {\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tconst ops = options?.operations ?? defaultReadOperations;\n\tconst maxBytes = options?.maxOutputBytes ?? DEFAULT_MAX_BYTES;\n\tconst maxLines = options?.maxOutputLines ?? DEFAULT_MAX_LINES;\n\tconst dedupReads = options?.dedupReads ?? false;\n\treturn {\n\t\tname: \"read\",\n\t\tlabel: \"read\",\n\t\tdescription: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, output is truncated to ${maxLines} lines or ${Math.round(maxBytes / 1024)}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tpromptSnippet: \"Read file contents\",\n\t\tpromptGuidelines: [\"Use read to examine files instead of cat or sed.\"],\n\t\tparameters: readSchema,\n\t\tasync execute(\n\t\t\ttoolCallId,\n\t\t\t{ path, offset, limit }: { path: string; offset?: number; limit?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\tctx?,\n\t\t) {\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\treturn new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(\n\t\t\t\t(resolve, reject) => {\n\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet aborted = false;\n\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t};\n\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Check if file exists and is readable.\n\t\t\t\t\t\t\tawait ops.access(absolutePath);\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tconst mimeType = ops.detectImageMimeType ? await ops.detectImageMimeType(absolutePath) : undefined;\n\t\t\t\t\t\t\tlet content: (TextContent | ImageContent)[];\n\t\t\t\t\t\t\tlet details: ReadToolDetails | undefined;\n\t\t\t\t\t\t\tconst nonVisionImageNote = getNonVisionImageNote(ctx?.model);\n\t\t\t\t\t\t\t// At-call dedup: for plain-text reads, if an earlier still-live read\n\t\t\t\t\t\t\t// in this session already covers the requested range, point at it\n\t\t\t\t\t\t\t// instead of re-fetching. Skipped for images and structured docs.\n\t\t\t\t\t\t\tconst dedupCovering =\n\t\t\t\t\t\t\t\tdedupReads && !mimeType && !getDocFormatHint(absolutePath) && ctx?.sessionManager\n\t\t\t\t\t\t\t\t\t? findCoveringRead(ctx.sessionManager.getBranch(), {\n\t\t\t\t\t\t\t\t\t\t\tresolvedPath: absolutePath,\n\t\t\t\t\t\t\t\t\t\t\trequestedRange: readRangeFromArgs({ offset, limit }),\n\t\t\t\t\t\t\t\t\t\t\tcurrentCallId: toolCallId,\n\t\t\t\t\t\t\t\t\t\t\tresolvePath: (raw) => resolveReadPath(raw, cwd),\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t: null;\n\t\t\t\t\t\t\tif (dedupCovering) {\n\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: buildDedupPointerText(dedupCovering) }];\n\t\t\t\t\t\t\t} else if (mimeType) {\n\t\t\t\t\t\t\t\t// Read image as binary.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst base64 = buffer.toString(\"base64\");\n\t\t\t\t\t\t\t\tif (autoResizeImages) {\n\t\t\t\t\t\t\t\t\t// Resize image if needed before sending it back to the model.\n\t\t\t\t\t\t\t\t\tconst resized = await resizeImage({ type: \"image\", data: base64, mimeType });\n\t\t\t\t\t\t\t\t\tif (!resized) {\n\t\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${mimeType}]\\n[Image omitted: could not be resized below the inline image size limit.]`;\n\t\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: textNote }];\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst dimensionNote = formatDimensionNote(resized);\n\t\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${resized.mimeType}]`;\n\t\t\t\t\t\t\t\t\t\tif (dimensionNote) textNote += `\\n${dimensionNote}`;\n\t\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\t\tcontent = [\n\t\t\t\t\t\t\t\t\t\t\t{ type: \"text\", text: textNote },\n\t\t\t\t\t\t\t\t\t\t\t{ type: \"image\", data: resized.data, mimeType: resized.mimeType },\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} else {\n\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${mimeType}]`;\n\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\tcontent = [\n\t\t\t\t\t\t\t\t\t\t{ type: \"text\", text: textNote },\n\t\t\t\t\t\t\t\t\t\t{ type: \"image\", data: base64, mimeType },\n\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (getDocFormatHint(absolutePath)) {\n\t\t\t\t\t\t\t\t// Structured/binary document: a utf-8 read is meaningless. Point the\n\t\t\t\t\t\t\t\t// model at DocRead, which extracts it to editable, id-addressed JSON.\n\t\t\t\t\t\t\t\tconst label = getDocFormatHint(absolutePath);\n\t\t\t\t\t\t\t\tcontent = [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `[${label} document — not plain text. Use DocRead on ${path} to extract editable, id-addressed structure (then DocEdit/DocWrite to modify it losslessly). DocRead requires --enable-filetools.]`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Read text content.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst textContent = buffer.toString(\"utf-8\");\n\t\t\t\t\t\t\t\tconst allLines = textContent.split(\"\\n\");\n\t\t\t\t\t\t\t\tconst totalFileLines = allLines.length;\n\t\t\t\t\t\t\t\t// Apply offset if specified. Convert from 1-indexed input to 0-indexed array access.\n\t\t\t\t\t\t\t\tconst startLine = offset ? Math.max(0, offset - 1) : 0;\n\t\t\t\t\t\t\t\tconst startLineDisplay = startLine + 1;\n\t\t\t\t\t\t\t\t// Check if offset is out of bounds.\n\t\t\t\t\t\t\t\tif (startLine >= allLines.length) {\n\t\t\t\t\t\t\t\t\tthrow new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet selectedContent: string;\n\t\t\t\t\t\t\t\tlet userLimitedLines: number | undefined;\n\t\t\t\t\t\t\t\t// If limit is specified by the user, honor it first. Otherwise truncateHead decides.\n\t\t\t\t\t\t\t\tif (limit !== undefined) {\n\t\t\t\t\t\t\t\t\tconst endLine = Math.min(startLine + limit, allLines.length);\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine, endLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t\tuserLimitedLines = endLine - startLine;\n\t\t\t\t\t\t\t\t} else if (startLine === 0) {\n\t\t\t\t\t\t\t\t\t// Whole-file read (common case): reuse the original string instead of\n\t\t\t\t\t\t\t\t\t// rebuilding it from the split lines.\n\t\t\t\t\t\t\t\t\tselectedContent = textContent;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Apply truncation, respecting both line and byte limits.\n\t\t\t\t\t\t\t\t// Apply lossless compression then truncation.\n\t\t\t\t\t\t\t\tconst compressedContent = compressReadOutput(selectedContent);\n\t\t\t\t\t\t\t\tconst truncation = truncateHead(compressedContent, { maxBytes, maxLines });\n\t\t\t\t\t\t\t\tlet outputText: string;\n\t\t\t\t\t\t\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\t\t\t\t\t\t\t// First line alone exceeds the byte limit. Point the model at a bash fallback.\n\t\t\t\t\t\t\t\t\tconst firstLineSize = formatSize(Buffer.byteLength(allLines[startLine], \"utf-8\"));\n\t\t\t\t\t\t\t\t\toutputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(maxBytes)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${maxBytes}]`;\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (truncation.truncated) {\n\t\t\t\t\t\t\t\t\t// Truncation occurred. Build an actionable continuation notice.\n\t\t\t\t\t\t\t\t\tconst endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n\t\t\t\t\t\t\t\t\tconst nextOffset = endLineDisplay + 1;\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(maxBytes)} limit). Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {\n\t\t\t\t\t\t\t\t\t// User-specified limit stopped early, but the file still has more content.\n\t\t\t\t\t\t\t\t\tconst remaining = allLines.length - (startLine + userLimitedLines);\n\t\t\t\t\t\t\t\t\tconst nextOffset = startLine + userLimitedLines + 1;\n\t\t\t\t\t\t\t\t\toutputText = `${truncation.content}\\n\\n[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// No truncation and no remaining user-limited content.\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: outputText }];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tresolve({ content, details });\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tif (!aborted) reject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\tconst classification = !context.expanded ? getCompactReadClassification(args, context.cwd) : undefined;\n\t\t\ttext.setText(\n\t\t\t\tclassification ? formatCompactReadCall(classification, args, theme) : formatReadCall(args, theme),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(\n\t\t\t\tformatReadResult(context.args, result, options, theme, context.showImages, context.cwd, context.isError),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createReadTool(cwd: string, options?: ReadToolOptions): AgentTool<typeof readSchema> {\n\treturn wrapToolDefinition(createReadToolDefinition(cwd, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/core/tools/read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAMjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAQ5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAKtF,OAAO,EAAoD,KAAK,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AAEtH,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AA2BD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,qCAAqC;IACrC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,+CAA+C;IAC/C,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACnF;AAQD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAmJD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CA0LhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import { basename, dirname, isAbsolute, relative, resolve as resolvePath, sep } from \"node:path\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { compressReadOutput } from \"@kolisachint/hoocode-agent-core\";\nimport type { Api, ImageContent, Model, TextContent } from \"@kolisachint/hoocode-ai\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { getReadmePath } from \"../../config.js\";\nimport { keyHint, keyText } from \"../../modes/interactive/components/keybinding-hints.js\";\nimport { renderReadOutput } from \"../../modes/interactive/components/read-output.js\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.js\";\nimport { formatDimensionNote, resizeImage } from \"../../utils/image-resize.js\";\nimport { detectSupportedImageMimeTypeFromFile } from \"../../utils/mime.js\";\nimport { formatPathRelativeToCwdOrAbsolute } from \"../../utils/paths.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { resolveReadPath } from \"./path-utils.js\";\nimport { buildDedupPointerText, findCoveringRead, readRangeFromArgs } from \"./read-dedup.js\";\nimport { getTextOutput, invalidArgText, replaceTabs, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from \"./truncate.js\";\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nexport type ReadToolInput = Static<typeof readSchema>;\n\nexport interface ReadToolDetails {\n\ttruncation?: TruncationResult;\n}\n\ninterface CompactReadClassification {\n\tkind: \"docs\" | \"resource\" | \"skill\";\n\tlabel: string;\n}\n\nconst COMPACT_RESOURCE_FILE_NAMES = new Set([\"AGENTS.md\", \"AGENTS.MD\", \"CLAUDE.md\", \"CLAUDE.MD\"]);\n\n/**\n * Structured/binary document formats. These are container/binary formats (OOXML\n * is a zip, PDF is binary), so a plain utf-8 read yields garbage — read\n * short-circuits with a note instead of dumping bytes into the context.\n */\nconst DOC_FORMAT_EXTENSIONS = new Map<string, string>([\n\t[\".docx\", \"Word (OOXML)\"],\n\t[\".xlsx\", \"Excel (OOXML)\"],\n\t[\".pptx\", \"PowerPoint (OOXML)\"],\n\t[\".pdf\", \"PDF\"],\n]);\n\nfunction getDocFormatHint(absolutePath: string): string | undefined {\n\tconst dot = absolutePath.lastIndexOf(\".\");\n\tif (dot === -1) return undefined;\n\treturn DOC_FORMAT_EXTENSIONS.get(absolutePath.slice(dot).toLowerCase());\n}\n\n/**\n * Pluggable operations for the read tool.\n * Override these to delegate file reading to remote systems (for example SSH).\n */\nexport interface ReadOperations {\n\t/** Read file contents as a Buffer */\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\t/** Check if file is readable (throw if not) */\n\taccess: (absolutePath: string) => Promise<void>;\n\t/** Detect image MIME type, return null or undefined for non-images */\n\tdetectImageMimeType?: (absolutePath: string) => Promise<string | null | undefined>;\n}\n\nconst defaultReadOperations: ReadOperations = {\n\treadFile: (path) => fsReadFile(path),\n\taccess: (path) => fsAccess(path, constants.R_OK),\n\tdetectImageMimeType: detectSupportedImageMimeTypeFromFile,\n};\n\nexport interface ReadToolOptions {\n\t/** Whether to auto-resize images to 2000x2000 max. Default: true */\n\tautoResizeImages?: boolean;\n\t/** Custom operations for file reading. Default: local filesystem */\n\toperations?: ReadOperations;\n\t/** Byte cap on the returned text before truncation. Default: DEFAULT_MAX_BYTES. */\n\tmaxOutputBytes?: number;\n\t/** Line cap on the returned text before truncation. Default: DEFAULT_MAX_LINES. */\n\tmaxOutputLines?: number;\n\t/**\n\t * When true, a text read whose requested line range is already fully covered\n\t * by an earlier, still-live read in the current session short-circuits with a\n\t * pointer instead of re-fetching the file. Complements the post-hoc context\n\t * GC; gated on the same setting. Default: false.\n\t */\n\tdedupReads?: boolean;\n}\n\ntype ReadRenderArgs = { path?: string; file_path?: string; offset?: number; limit?: number };\n\nfunction formatReadLineRange(args: ReadRenderArgs | undefined, theme: Theme): string {\n\tif (args?.offset === undefined && args?.limit === undefined) return \"\";\n\tconst startLine = args.offset ?? 1;\n\tconst endLine = args.limit !== undefined ? startLine + args.limit - 1 : \"\";\n\treturn theme.fg(\"warning\", `:${startLine}${endLine ? `-${endLine}` : \"\"}`);\n}\n\nfunction formatReadCall(args: ReadRenderArgs | undefined, theme: Theme): string {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath) : null;\n\tconst invalidArg = invalidArgText(theme);\n\tconst pathDisplay = path === null ? invalidArg : path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\");\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"read\"))} ${pathDisplay}${formatReadLineRange(args, theme)}`;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction getNonVisionImageNote(model: Model<Api> | undefined): string | undefined {\n\tif (!model || model.input.includes(\"image\")) {\n\t\treturn undefined;\n\t}\n\treturn \"[Current model does not support images. The image will be omitted from this request.]\";\n}\n\nfunction toPosixPath(filePath: string): string {\n\treturn filePath.split(sep).join(\"/\");\n}\n\nfunction getHooCodeDocsClassification(absolutePath: string): CompactReadClassification | undefined {\n\tconst packageRoot = dirname(getReadmePath());\n\tconst relativePath = relative(resolvePath(packageRoot), resolvePath(absolutePath));\n\tif (\n\t\trelativePath === \"\" ||\n\t\trelativePath === \"..\" ||\n\t\trelativePath.startsWith(`..${sep}`) ||\n\t\tisAbsolute(relativePath)\n\t) {\n\t\treturn undefined;\n\t}\n\n\tconst label = toPosixPath(relativePath);\n\tif (label === \"README.md\" || label.startsWith(\"docs/\") || label.startsWith(\"examples/\")) {\n\t\treturn { kind: \"docs\", label };\n\t}\n\treturn undefined;\n}\n\nfunction getCompactReadClassification(\n\targs: ReadRenderArgs | undefined,\n\tcwd: string,\n): CompactReadClassification | undefined {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tif (!rawPath) return undefined;\n\n\tconst absolutePath = resolveReadPath(rawPath, cwd);\n\tconst fileName = basename(absolutePath);\n\tif (fileName === \"SKILL.md\") {\n\t\treturn { kind: \"skill\", label: basename(dirname(absolutePath)) || fileName };\n\t}\n\n\tconst docsClassification = getHooCodeDocsClassification(absolutePath);\n\tif (docsClassification) return docsClassification;\n\n\tif (COMPACT_RESOURCE_FILE_NAMES.has(fileName)) {\n\t\treturn { kind: \"resource\", label: formatPathRelativeToCwdOrAbsolute(absolutePath, cwd) };\n\t}\n\n\treturn undefined;\n}\n\nfunction formatCompactReadCall(\n\tclassification: CompactReadClassification,\n\targs: ReadRenderArgs | undefined,\n\ttheme: Theme,\n): string {\n\tconst expandHint = theme.fg(\"dim\", ` (${keyText(\"app.tools.expand\")} to expand)`);\n\tif (classification.kind === \"skill\") {\n\t\treturn (\n\t\t\ttheme.fg(\"customMessageLabel\", `\\x1b[1m[skill]\\x1b[22m `) +\n\t\t\ttheme.fg(\"customMessageText\", classification.label) +\n\t\t\tformatReadLineRange(args, theme) +\n\t\t\texpandHint\n\t\t);\n\t}\n\n\treturn (\n\t\ttheme.fg(\"toolTitle\", theme.bold(`read ${classification.kind}`)) +\n\t\t\" \" +\n\t\ttheme.fg(\"accent\", classification.label) +\n\t\tformatReadLineRange(args, theme) +\n\t\texpandHint\n\t);\n}\n\nfunction formatReadResult(\n\targs: ReadRenderArgs | undefined,\n\tresult: { content: (TextContent | ImageContent)[]; details?: ReadToolDetails },\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tshowImages: boolean,\n\tcwd: string,\n\tisError: boolean,\n): string {\n\tif (!options.expanded && !isError && getCompactReadClassification(args, cwd)) {\n\t\treturn \"\";\n\t}\n\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst output = getTextOutput(result, showImages);\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tconst renderedLines = lang ? highlightCode(replaceTabs(output), lang) : output.split(\"\\n\");\n\tconst lines = trimTrailingEmptyLines(renderedLines);\n\tconst maxLines = options.expanded ? lines.length : 10;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\t// Calculate starting line number from args\n\tconst startLine = (args?.offset as number) ?? 1;\n\t// Use the new renderReadOutput function for line numbers\n\tconst numberedLines = renderReadOutput(displayLines.join(\"\\n\"), { startLine });\n\tlet text = `\\n${numberedLines.join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t}\n\n\tconst truncation = result.details?.truncation;\n\tif (truncation?.truncated) {\n\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`)}`;\n\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`)}`;\n\t\t} else {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`)}`;\n\t\t}\n\t}\n\treturn text;\n}\n\nexport function createReadToolDefinition(\n\tcwd: string,\n\toptions?: ReadToolOptions,\n): ToolDefinition<typeof readSchema, ReadToolDetails | undefined> {\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tconst ops = options?.operations ?? defaultReadOperations;\n\tconst maxBytes = options?.maxOutputBytes ?? DEFAULT_MAX_BYTES;\n\tconst maxLines = options?.maxOutputLines ?? DEFAULT_MAX_LINES;\n\tconst dedupReads = options?.dedupReads ?? false;\n\treturn {\n\t\tname: \"read\",\n\t\tlabel: \"read\",\n\t\tdescription: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, output is truncated to ${maxLines} lines or ${Math.round(maxBytes / 1024)}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tpromptSnippet: \"Read file contents\",\n\t\t// No promptGuidelines: \"use read instead of cat/sed\" is already covered by the\n\t\t// file-exploration guideline buildSystemPrompt emits, and again by bash's own\n\t\t// snippet. Three copies of one rule.\n\t\tparameters: readSchema,\n\t\tasync execute(\n\t\t\ttoolCallId,\n\t\t\t{ path, offset, limit }: { path: string; offset?: number; limit?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\tctx?,\n\t\t) {\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\treturn new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(\n\t\t\t\t(resolve, reject) => {\n\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet aborted = false;\n\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t};\n\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Check if file exists and is readable.\n\t\t\t\t\t\t\tawait ops.access(absolutePath);\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tconst mimeType = ops.detectImageMimeType ? await ops.detectImageMimeType(absolutePath) : undefined;\n\t\t\t\t\t\t\tlet content: (TextContent | ImageContent)[];\n\t\t\t\t\t\t\tlet details: ReadToolDetails | undefined;\n\t\t\t\t\t\t\tconst nonVisionImageNote = getNonVisionImageNote(ctx?.model);\n\t\t\t\t\t\t\t// At-call dedup: for plain-text reads, if an earlier still-live read\n\t\t\t\t\t\t\t// in this session already covers the requested range, point at it\n\t\t\t\t\t\t\t// instead of re-fetching. Skipped for images and structured docs.\n\t\t\t\t\t\t\tconst dedupCovering =\n\t\t\t\t\t\t\t\tdedupReads && !mimeType && !getDocFormatHint(absolutePath) && ctx?.sessionManager\n\t\t\t\t\t\t\t\t\t? findCoveringRead(ctx.sessionManager.getBranch(), {\n\t\t\t\t\t\t\t\t\t\t\tresolvedPath: absolutePath,\n\t\t\t\t\t\t\t\t\t\t\trequestedRange: readRangeFromArgs({ offset, limit }),\n\t\t\t\t\t\t\t\t\t\t\tcurrentCallId: toolCallId,\n\t\t\t\t\t\t\t\t\t\t\tresolvePath: (raw) => resolveReadPath(raw, cwd),\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t: null;\n\t\t\t\t\t\t\tif (dedupCovering) {\n\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: buildDedupPointerText(dedupCovering) }];\n\t\t\t\t\t\t\t} else if (mimeType) {\n\t\t\t\t\t\t\t\t// Read image as binary.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst base64 = buffer.toString(\"base64\");\n\t\t\t\t\t\t\t\tif (autoResizeImages) {\n\t\t\t\t\t\t\t\t\t// Resize image if needed before sending it back to the model.\n\t\t\t\t\t\t\t\t\tconst resized = await resizeImage({ type: \"image\", data: base64, mimeType });\n\t\t\t\t\t\t\t\t\tif (!resized) {\n\t\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${mimeType}]\\n[Image omitted: could not be resized below the inline image size limit.]`;\n\t\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: textNote }];\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst dimensionNote = formatDimensionNote(resized);\n\t\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${resized.mimeType}]`;\n\t\t\t\t\t\t\t\t\t\tif (dimensionNote) textNote += `\\n${dimensionNote}`;\n\t\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\t\tcontent = [\n\t\t\t\t\t\t\t\t\t\t\t{ type: \"text\", text: textNote },\n\t\t\t\t\t\t\t\t\t\t\t{ type: \"image\", data: resized.data, mimeType: resized.mimeType },\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} else {\n\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${mimeType}]`;\n\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\tcontent = [\n\t\t\t\t\t\t\t\t\t\t{ type: \"text\", text: textNote },\n\t\t\t\t\t\t\t\t\t\t{ type: \"image\", data: base64, mimeType },\n\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (getDocFormatHint(absolutePath)) {\n\t\t\t\t\t\t\t\t// Structured/binary document: a utf-8 read is meaningless, and dumping\n\t\t\t\t\t\t\t\t// the bytes would burn context for nothing. Stop here and let the model\n\t\t\t\t\t\t\t\t// choose an extraction path (bash + an appropriate converter).\n\t\t\t\t\t\t\t\tconst label = getDocFormatHint(absolutePath);\n\t\t\t\t\t\t\t\tcontent = [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `[${label} document — not plain text; read cannot show it. Extract it with a converter via bash if you need its contents.]`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Read text content.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst textContent = buffer.toString(\"utf-8\");\n\t\t\t\t\t\t\t\tconst allLines = textContent.split(\"\\n\");\n\t\t\t\t\t\t\t\tconst totalFileLines = allLines.length;\n\t\t\t\t\t\t\t\t// Apply offset if specified. Convert from 1-indexed input to 0-indexed array access.\n\t\t\t\t\t\t\t\tconst startLine = offset ? Math.max(0, offset - 1) : 0;\n\t\t\t\t\t\t\t\tconst startLineDisplay = startLine + 1;\n\t\t\t\t\t\t\t\t// Check if offset is out of bounds.\n\t\t\t\t\t\t\t\tif (startLine >= allLines.length) {\n\t\t\t\t\t\t\t\t\tthrow new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet selectedContent: string;\n\t\t\t\t\t\t\t\tlet userLimitedLines: number | undefined;\n\t\t\t\t\t\t\t\t// If limit is specified by the user, honor it first. Otherwise truncateHead decides.\n\t\t\t\t\t\t\t\tif (limit !== undefined) {\n\t\t\t\t\t\t\t\t\tconst endLine = Math.min(startLine + limit, allLines.length);\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine, endLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t\tuserLimitedLines = endLine - startLine;\n\t\t\t\t\t\t\t\t} else if (startLine === 0) {\n\t\t\t\t\t\t\t\t\t// Whole-file read (common case): reuse the original string instead of\n\t\t\t\t\t\t\t\t\t// rebuilding it from the split lines.\n\t\t\t\t\t\t\t\t\tselectedContent = textContent;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Apply truncation, respecting both line and byte limits.\n\t\t\t\t\t\t\t\t// Apply lossless compression then truncation.\n\t\t\t\t\t\t\t\tconst compressedContent = compressReadOutput(selectedContent);\n\t\t\t\t\t\t\t\tconst truncation = truncateHead(compressedContent, { maxBytes, maxLines });\n\t\t\t\t\t\t\t\tlet outputText: string;\n\t\t\t\t\t\t\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\t\t\t\t\t\t\t// First line alone exceeds the byte limit. Point the model at a bash fallback.\n\t\t\t\t\t\t\t\t\tconst firstLineSize = formatSize(Buffer.byteLength(allLines[startLine], \"utf-8\"));\n\t\t\t\t\t\t\t\t\toutputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(maxBytes)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${maxBytes}]`;\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (truncation.truncated) {\n\t\t\t\t\t\t\t\t\t// Truncation occurred. Build an actionable continuation notice.\n\t\t\t\t\t\t\t\t\tconst endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n\t\t\t\t\t\t\t\t\tconst nextOffset = endLineDisplay + 1;\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(maxBytes)} limit). Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {\n\t\t\t\t\t\t\t\t\t// User-specified limit stopped early, but the file still has more content.\n\t\t\t\t\t\t\t\t\tconst remaining = allLines.length - (startLine + userLimitedLines);\n\t\t\t\t\t\t\t\t\tconst nextOffset = startLine + userLimitedLines + 1;\n\t\t\t\t\t\t\t\t\toutputText = `${truncation.content}\\n\\n[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// No truncation and no remaining user-limited content.\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: outputText }];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tresolve({ content, details });\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tif (!aborted) reject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\tconst classification = !context.expanded ? getCompactReadClassification(args, context.cwd) : undefined;\n\t\t\ttext.setText(\n\t\t\t\tclassification ? formatCompactReadCall(classification, args, theme) : formatReadCall(args, theme),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(\n\t\t\t\tformatReadResult(context.args, result, options, theme, context.showImages, context.cwd, context.isError),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createReadTool(cwd: string, options?: ReadToolOptions): AgentTool<typeof readSchema> {\n\treturn wrapToolDefinition(createReadToolDefinition(cwd, options));\n}\n"]}
|
package/dist/core/tools/read.js
CHANGED
|
@@ -23,10 +23,9 @@ const readSchema = Type.Object({
|
|
|
23
23
|
});
|
|
24
24
|
const COMPACT_RESOURCE_FILE_NAMES = new Set(["AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"]);
|
|
25
25
|
/**
|
|
26
|
-
* Structured/binary document formats
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* a pointer to DocRead instead of dumping bytes.
|
|
26
|
+
* Structured/binary document formats. These are container/binary formats (OOXML
|
|
27
|
+
* is a zip, PDF is binary), so a plain utf-8 read yields garbage — read
|
|
28
|
+
* short-circuits with a note instead of dumping bytes into the context.
|
|
30
29
|
*/
|
|
31
30
|
const DOC_FORMAT_EXTENSIONS = new Map([
|
|
32
31
|
[".docx", "Word (OOXML)"],
|
|
@@ -166,7 +165,9 @@ export function createReadToolDefinition(cwd, options) {
|
|
|
166
165
|
label: "read",
|
|
167
166
|
description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, output is truncated to ${maxLines} lines or ${Math.round(maxBytes / 1024)}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,
|
|
168
167
|
promptSnippet: "Read file contents",
|
|
169
|
-
promptGuidelines:
|
|
168
|
+
// No promptGuidelines: "use read instead of cat/sed" is already covered by the
|
|
169
|
+
// file-exploration guideline buildSystemPrompt emits, and again by bash's own
|
|
170
|
+
// snippet. Three copies of one rule.
|
|
170
171
|
parameters: readSchema,
|
|
171
172
|
async execute(toolCallId, { path, offset, limit }, signal, _onUpdate, ctx) {
|
|
172
173
|
const absolutePath = resolveReadPath(path, cwd);
|
|
@@ -242,13 +243,14 @@ export function createReadToolDefinition(cwd, options) {
|
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
else if (getDocFormatHint(absolutePath)) {
|
|
245
|
-
// Structured/binary document: a utf-8 read is meaningless
|
|
246
|
-
//
|
|
246
|
+
// Structured/binary document: a utf-8 read is meaningless, and dumping
|
|
247
|
+
// the bytes would burn context for nothing. Stop here and let the model
|
|
248
|
+
// choose an extraction path (bash + an appropriate converter).
|
|
247
249
|
const label = getDocFormatHint(absolutePath);
|
|
248
250
|
content = [
|
|
249
251
|
{
|
|
250
252
|
type: "text",
|
|
251
|
-
text: `[${label} document — not plain text
|
|
253
|
+
text: `[${label} document — not plain text; read cannot show it. Extract it with a converter via bash if you need its contents.]`,
|
|
252
254
|
},
|
|
253
255
|
];
|
|
254
256
|
}
|