one-code-extension 0.4.0 → 0.5.0
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/README.md +219 -219
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +32 -23
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +27 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +66 -34
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +123 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +515 -0
- package/extensions/background/index.ts +514 -236
- package/extensions/background/loop-fire.ts +136 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +52 -0
- package/extensions/background/wakeup.ts +159 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +88 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +229 -71
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +446 -58
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +216 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/index.ts +24 -5
- package/extensions/context-management/index.ts +25 -0
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +72 -19
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +337 -26
- package/extensions/file-tracker/replay.ts +96 -10
- package/extensions/file-tracker/restore.ts +63 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +32 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +198 -31
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +13 -5
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/lib/agent-cron.ts +130 -0
- package/extensions/lib/agent-loader.ts +15 -1
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +42 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +78 -0
- package/extensions/lib/context-stack.ts +81 -0
- package/extensions/lib/deferred.ts +219 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/local-command.ts +47 -13
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +174 -0
- package/extensions/lib/model-catalog/models-dev.json +18297 -0
- package/extensions/lib/model-catalog/openrouter.json +5252 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +308 -64
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +21 -2
- package/extensions/lib/plan-mode-channels.ts +20 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/process-tree.ts +131 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +364 -58
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +439 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +13 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +113 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +149 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +143 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +70 -4
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +87 -5
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +209 -71
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +151 -67
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1300 -381
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +6 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +156 -69
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +61 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +12 -7
- package/extensions/recap/index.ts +65 -60
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +421 -101
- package/extensions/skill/invoke.ts +128 -18
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +116 -23
- package/extensions/subagents/announce.ts +69 -0
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +76 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1493 -824
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +2 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/runner.ts +108 -80
- package/extensions/subagents/runs.ts +39 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +131 -47
- package/extensions/system-prompt/template.ts +24 -50
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +175 -13
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/announce.ts +16 -0
- package/extensions/tool-search/index.ts +189 -41
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +105 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +145 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +29 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +121 -39
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +19 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
|
@@ -10,13 +10,27 @@
|
|
|
10
10
|
* `{ name, keywords? }` while extensions are loading (before session_start).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
13
14
|
import type { SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
14
15
|
import { toolResultsOnBranch } from "./branch-restore.ts";
|
|
15
|
-
import {
|
|
16
|
+
import { parseClaudeId } from "./claude-id.ts";
|
|
17
|
+
import { RESPONSES_APIS } from "./system-role.ts";
|
|
18
|
+
import { ADDENDUM_HEAD } from "./tool-additions.ts";
|
|
16
19
|
import { normalizeToolName } from "../permissions/matcher.ts";
|
|
17
20
|
|
|
18
21
|
export const DEFER_CHANNEL = "one-code:defer-tool";
|
|
19
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Take a deferred tool out of this session's surface (`{ name }`): it leaves the
|
|
25
|
+
* deferred registry and the active set, so no request carries it and
|
|
26
|
+
* `tool_search` cannot load it. An extension withholds its own tool when the
|
|
27
|
+
* session model must not have it (the task tools on frontier models) and
|
|
28
|
+
* re-emits DEFER_CHANNEL to bring it back after a model change. Before the
|
|
29
|
+
* first request the standing listing is rewritten without it; after that the
|
|
30
|
+
* listing stays frozen and a load reports "not found" (tool-search/announce.ts).
|
|
31
|
+
*/
|
|
32
|
+
export const WITHHOLD_CHANNEL = "one-code:withhold-tool";
|
|
33
|
+
|
|
20
34
|
export interface DeferRequest {
|
|
21
35
|
name: string;
|
|
22
36
|
/** Extra search terms beyond the tool's name and description. */
|
|
@@ -49,6 +63,10 @@ export class DeferredRegistry {
|
|
|
49
63
|
has(name: string): boolean {
|
|
50
64
|
return this.entries.has(name);
|
|
51
65
|
}
|
|
66
|
+
|
|
67
|
+
remove(name: string): boolean {
|
|
68
|
+
return this.entries.delete(name);
|
|
69
|
+
}
|
|
52
70
|
}
|
|
53
71
|
|
|
54
72
|
// The one registry instance is constructed by its owner (tool-search/index.ts):
|
|
@@ -80,25 +98,39 @@ export function deferredReminderText(tools: Array<Pick<SearchableTool, "name"> &
|
|
|
80
98
|
* this rides the tail instead, so the cached prefix holds.
|
|
81
99
|
*/
|
|
82
100
|
export function deferredAddendumText(added: readonly string[]): string {
|
|
83
|
-
return [
|
|
84
|
-
"Additional deferred tools became available via tool_search since this conversation started (they registered after the first request). Same rules: load with tool_search \"select:<name>\" before calling.",
|
|
85
|
-
...added,
|
|
86
|
-
].join("\n");
|
|
101
|
+
return [ADDENDUM_HEAD, ...added].join("\n");
|
|
87
102
|
}
|
|
88
103
|
|
|
104
|
+
/** The loader every deferred tool sits behind. */
|
|
105
|
+
export const TOOL_SEARCH = "tool_search";
|
|
106
|
+
|
|
89
107
|
/**
|
|
90
|
-
* Whether
|
|
91
|
-
*
|
|
92
|
-
*
|
|
108
|
+
* Whether deferral applies at all: `tool_search` is among pi's registered
|
|
109
|
+
* tools. A `--tools` allowlist that leaves it out (or `--exclude-tools
|
|
110
|
+
* tool_search`) unregisters it, and since pi 1.0.4 the allowlist keeps the
|
|
111
|
+
* `mcp__*` tools registered unless an entry names one (findings §66). Without
|
|
112
|
+
* the loader nothing can be loaded, so no tool is deferred or listed: the
|
|
113
|
+
* tools the allowlist kept stay as pi activated them.
|
|
114
|
+
*/
|
|
115
|
+
export function deferralEnabled(tools: readonly { name: string }[]): boolean {
|
|
116
|
+
return tools.some((tool) => tool.name === TOOL_SEARCH);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Whether Anthropic accepts client-side `tool_reference` blocks for this model,
|
|
121
|
+
* with the model's own compat flag winning: first-party Anthropic, Claude 4.5
|
|
122
|
+
* or newer, Haiku 4.5 and later Haikus included (they take references with no
|
|
123
|
+
* beta, and an eager append there rewrites the whole cache: findings §55 P4
|
|
124
|
+
* for Haiku 4.5, §67 for Haiku 5.5). Older Haikus stay out.
|
|
93
125
|
*/
|
|
94
126
|
export function supportsToolReferences(
|
|
95
127
|
model: { provider?: string; id?: string; compat?: { supportsToolReferences?: boolean } } | undefined,
|
|
96
128
|
): boolean {
|
|
97
129
|
if (!model) return false;
|
|
98
130
|
if (typeof model.compat?.supportsToolReferences === "boolean") return model.compat.supportsToolReferences;
|
|
99
|
-
if (model.provider !== "anthropic" || !model.id
|
|
100
|
-
const version =
|
|
101
|
-
return version !== undefined && (version.major > 4 || (version.major === 4 && version.minor >= 5));
|
|
131
|
+
if (model.provider !== "anthropic" || !model.id) return false;
|
|
132
|
+
const version = parseClaudeId(model.id);
|
|
133
|
+
return version !== undefined && (version.major > 4 || (version.major === 4 && (version.minor ?? 0) >= 5));
|
|
102
134
|
}
|
|
103
135
|
|
|
104
136
|
/** The slice of pi's ToolInfo the deferred definitions need. */
|
|
@@ -127,7 +159,7 @@ export function toolSearchLoads(branch: readonly SessionEntry[]): Map<string, st
|
|
|
127
159
|
}
|
|
128
160
|
return loads;
|
|
129
161
|
}
|
|
130
|
-
const TOOL_SEARCH_NAMES: ReadonlySet<string> = new Set([
|
|
162
|
+
const TOOL_SEARCH_NAMES: ReadonlySet<string> = new Set([TOOL_SEARCH]);
|
|
131
163
|
|
|
132
164
|
type WireTool = Record<string, unknown> & { name: string };
|
|
133
165
|
type WireBlock = Record<string, unknown> & { type?: unknown };
|
|
@@ -157,7 +189,7 @@ function withBreakpointOnLast<T extends Record<string, unknown>>(items: T[], cac
|
|
|
157
189
|
* An Anthropic request whose `tools` array is byte-identical to request 1's,
|
|
158
190
|
* whatever `tool_search` has loaded since — the client side of Anthropic's
|
|
159
191
|
* deferred-tool contract, which pi 0.86 no longer supplies for One Code
|
|
160
|
-
* (rationale and measurements: docs/decisions/caching.md "Loaded deferred
|
|
192
|
+
* (rationale and measurements: working-docs/decisions/caching.md "Loaded deferred
|
|
161
193
|
* tools stay deferred on the wire", findings §7).
|
|
162
194
|
*
|
|
163
195
|
* - Every registry tool `isDeferred` that is not eager on the wire is appended
|
|
@@ -171,6 +203,9 @@ function withBreakpointOnLast<T extends Record<string, unknown>>(items: T[], cac
|
|
|
171
203
|
* ordinary result content). Demotion happens only when that result is on the
|
|
172
204
|
* wire and precedes every `tool_use` of the tool; otherwise the tool stays
|
|
173
205
|
* eager (one accepted cache miss, never a request Anthropic rejects).
|
|
206
|
+
* - A tool the request adds mid-session by `tool_addition` (`additions`,
|
|
207
|
+
* lib/tool-additions.ts) is demoted the same way: its addition block
|
|
208
|
+
* references it, so it needs no `tool_search` result.
|
|
174
209
|
* - pi's own deferred entries that are not registry tools (its
|
|
175
210
|
* `__pi_deferred_placeholder__`) stay in place.
|
|
176
211
|
*
|
|
@@ -184,6 +219,7 @@ export function stabilizeDeferredTools(
|
|
|
184
219
|
tools: readonly DeferrableToolInfo[],
|
|
185
220
|
isDeferred: (name: string) => boolean,
|
|
186
221
|
loads: ToolSearchLoads = new Map(),
|
|
222
|
+
additions: ReadonlySet<string> = new Set(),
|
|
187
223
|
): Record<string, unknown> | undefined {
|
|
188
224
|
const existing = payload.tools;
|
|
189
225
|
if (!Array.isArray(existing) || existing.length === 0) return undefined;
|
|
@@ -220,16 +256,7 @@ export function stabilizeDeferredTools(
|
|
|
220
256
|
}
|
|
221
257
|
}
|
|
222
258
|
});
|
|
223
|
-
|
|
224
|
-
const loaderOf = new Map<string, { callId: string; at: number }>();
|
|
225
|
-
for (const [callId, names] of loads) {
|
|
226
|
-
const at = resultAt.get(callId);
|
|
227
|
-
if (at === undefined) continue;
|
|
228
|
-
for (const name of names) {
|
|
229
|
-
const current = loaderOf.get(name);
|
|
230
|
-
if (current === undefined || at < current.at) loaderOf.set(name, { callId, at });
|
|
231
|
-
}
|
|
232
|
-
}
|
|
259
|
+
const loaderOf = earliestLoads(loads, (callId) => resultAt.get(callId));
|
|
233
260
|
for (const tool of eager) {
|
|
234
261
|
if (!isDeferred(tool.name)) continue;
|
|
235
262
|
const loader = loaderOf.get(tool.name);
|
|
@@ -239,8 +266,15 @@ export function stabilizeDeferredTools(
|
|
|
239
266
|
demoted.add(tool.name);
|
|
240
267
|
referencesAt.set(loader.callId, [...(referencesAt.get(loader.callId) ?? []), tool.name]);
|
|
241
268
|
}
|
|
269
|
+
// References in the order the query named them, as Claude Code lists them.
|
|
270
|
+
for (const [callId, names] of referencesAt) {
|
|
271
|
+
const order = loads.get(callId) ?? [];
|
|
272
|
+
referencesAt.set(callId, [...names].sort((a, b) => order.indexOf(a) - order.indexOf(b)));
|
|
273
|
+
}
|
|
242
274
|
}
|
|
243
275
|
|
|
276
|
+
for (const tool of eager) if (additions.has(tool.name) && isDeferred(tool.name)) demoted.add(tool.name);
|
|
277
|
+
|
|
244
278
|
// pi puts the tools breakpoint on the last eager tool; if that one is demoted
|
|
245
279
|
// the breakpoint moves to the new last one, otherwise nothing moves.
|
|
246
280
|
const last = eager.at(-1);
|
|
@@ -285,7 +319,7 @@ function withToolReferences(message: WireMessage, referencesAt: ReadonlyMap<stri
|
|
|
285
319
|
if (cache_control !== undefined) movedBreakpoint = cache_control;
|
|
286
320
|
blocks.push({ ...rest, content: names.map((name) => ({ type: "tool_reference", tool_name: name })) });
|
|
287
321
|
if (typeof original === "string") {
|
|
288
|
-
if (original.trim().length > 0) siblings.push({ type: "text", text: original });
|
|
322
|
+
if (original.trim().length > 0) siblings.push({ type: "text", text: withClaudeCodeLoadText(original) });
|
|
289
323
|
} else if (Array.isArray(original)) {
|
|
290
324
|
siblings.push(...(original as WireBlock[]));
|
|
291
325
|
}
|
|
@@ -293,6 +327,137 @@ function withToolReferences(message: WireMessage, referencesAt: ReadonlyMap<stri
|
|
|
293
327
|
return { ...message, content: withBreakpointOnLast([...blocks, ...siblings], movedBreakpoint) };
|
|
294
328
|
}
|
|
295
329
|
|
|
330
|
+
/** `tool_search`'s plain success sentence, which the references stand in for. */
|
|
331
|
+
const LOADED_SENTENCE = /^Loaded [^\n]*\. These tools are now callable\.(?=\n|$)/;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* The text beside the references: Claude Code's "Tool loaded." in place of the
|
|
335
|
+
* plain success sentence, which names what the references already carry. A
|
|
336
|
+
* sentence with a not-found note, and anything after it, stays as it was.
|
|
337
|
+
*/
|
|
338
|
+
function withClaudeCodeLoadText(text: string): string {
|
|
339
|
+
return text.replace(LOADED_SENTENCE, "Tool loaded.");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* The earliest on-wire load of each tool: the `tool_search` call whose result
|
|
344
|
+
* sits first in the conversation (`resultAt` is its index, undefined when it is
|
|
345
|
+
* not on the wire).
|
|
346
|
+
*/
|
|
347
|
+
function earliestLoads(loads: ToolSearchLoads, resultAt: (callId: string) => number | undefined): Map<string, { callId: string; at: number }> {
|
|
348
|
+
const loaderOf = new Map<string, { callId: string; at: number }>();
|
|
349
|
+
for (const [callId, names] of loads) {
|
|
350
|
+
const at = resultAt(callId);
|
|
351
|
+
if (at === undefined) continue;
|
|
352
|
+
for (const name of names) {
|
|
353
|
+
const current = loaderOf.get(name);
|
|
354
|
+
if (current === undefined || at < current.at) loaderOf.set(name, { callId, at });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return loaderOf;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
/** How a Responses model takes a mid-conversation tool load (pi-ai's model `compat` fields). */
|
|
362
|
+
export interface ToolLoadCompat {
|
|
363
|
+
supportsAdditionalTools?: boolean;
|
|
364
|
+
supportsToolSearch?: boolean;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** The model fields that say whether, and how, a Responses request can add a tool mid-conversation. */
|
|
368
|
+
export interface ResponsesToolLoadModel {
|
|
369
|
+
api?: string;
|
|
370
|
+
compat?: ToolLoadCompat;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* The model's tool-load compat when it takes a mid-conversation tool load in
|
|
375
|
+
* pi-ai's native Responses shape, else undefined.
|
|
376
|
+
*/
|
|
377
|
+
export function responsesToolLoadCompat(model: ResponsesToolLoadModel | undefined): ToolLoadCompat | undefined {
|
|
378
|
+
if (!model?.api || !RESPONSES_APIS.has(model.api)) return undefined;
|
|
379
|
+
const compat = model.compat;
|
|
380
|
+
return compat?.supportsAdditionalTools === true || compat?.supportsToolSearch === true ? compat : undefined;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
type WireItem = Record<string, unknown>;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* A Responses request whose `tools` array stays request 1's whatever
|
|
387
|
+
* `tool_search` has loaded since: the OpenAI side of
|
|
388
|
+
* {@link stabilizeDeferredTools}. pi 0.86 and later put a loaded tool into
|
|
389
|
+
* `tools` for One Code (its forced system prompt collapses tool changes into
|
|
390
|
+
* the head, findings §7), so every load re-cached the whole conversation (0
|
|
391
|
+
* cached tokens on the request after a load on `openai-codex`, findings §45).
|
|
392
|
+
*
|
|
393
|
+
* A loaded registry tool leaves `tools` and enters the input right before the
|
|
394
|
+
* `function_call_output` of the `tool_search` call that loaded it, in pi-ai's
|
|
395
|
+
* own native shape (`convertResponsesMessages` `appendSystemToolAdditions`):
|
|
396
|
+
* an `additional_tools` developer item when the model supports one, else a
|
|
397
|
+
* `tool_search_call` + `tool_search_output` pair whose tools carry
|
|
398
|
+
* `defer_loading: true`. A tool the model called before that output, or whose
|
|
399
|
+
* loading output is not on the wire, stays in `tools`.
|
|
400
|
+
*
|
|
401
|
+
* Returns undefined (leave the payload alone) when nothing moves. Never
|
|
402
|
+
* mutates its input.
|
|
403
|
+
*/
|
|
404
|
+
export function stabilizeResponsesToolLoads(
|
|
405
|
+
payload: Record<string, unknown>,
|
|
406
|
+
isDeferred: (name: string) => boolean,
|
|
407
|
+
loads: ToolSearchLoads,
|
|
408
|
+
compat: ToolLoadCompat,
|
|
409
|
+
): Record<string, unknown> | undefined {
|
|
410
|
+
const tools = payload.tools;
|
|
411
|
+
const input = payload.input;
|
|
412
|
+
if (!Array.isArray(tools) || !Array.isArray(input) || loads.size === 0) return undefined;
|
|
413
|
+
|
|
414
|
+
// Input index of each tool output and of each tool's first call. pi sends a
|
|
415
|
+
// call id as `<call_id>|<item_id>`; the wire's `call_id` is the first part.
|
|
416
|
+
const outputAt = new Map<string, number>();
|
|
417
|
+
const firstUse = new Map<string, number>();
|
|
418
|
+
(input as WireItem[]).forEach((item, index) => {
|
|
419
|
+
if (item?.type === "function_call_output" && typeof item.call_id === "string") outputAt.set(item.call_id, index);
|
|
420
|
+
else if ((item?.type === "function_call" || item?.type === "custom_tool_call") && typeof item.name === "string" && !firstUse.has(item.name)) {
|
|
421
|
+
firstUse.set(item.name, index);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
const loaderOf = earliestLoads(loads, (callId) => outputAt.get(callId.split("|")[0]));
|
|
425
|
+
|
|
426
|
+
const movedAt = new Map<number, WireItem[]>();
|
|
427
|
+
const kept: unknown[] = [];
|
|
428
|
+
for (const raw of tools) {
|
|
429
|
+
const tool = raw as WireItem | null;
|
|
430
|
+
const name = typeof tool?.name === "string" ? tool.name : undefined;
|
|
431
|
+
const at = name !== undefined && isDeferred(name) ? loaderOf.get(name)?.at : undefined;
|
|
432
|
+
const use = name !== undefined ? firstUse.get(name) : undefined;
|
|
433
|
+
if (at === undefined || (use !== undefined && use < at)) {
|
|
434
|
+
kept.push(raw);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
movedAt.set(at, [...(movedAt.get(at) ?? []), tool as WireItem]);
|
|
438
|
+
}
|
|
439
|
+
if (movedAt.size === 0) return undefined;
|
|
440
|
+
|
|
441
|
+
const nextInput: unknown[] = [];
|
|
442
|
+
(input as WireItem[]).forEach((item, index) => {
|
|
443
|
+
const moved = movedAt.get(index);
|
|
444
|
+
if (moved) nextInput.push(...toolLoadItems(moved, String(item.call_id), compat));
|
|
445
|
+
nextInput.push(item);
|
|
446
|
+
});
|
|
447
|
+
return { ...payload, tools: kept, input: nextInput };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** pi-ai's native items for tools added at one point of a Responses conversation. */
|
|
451
|
+
function toolLoadItems(tools: WireItem[], loaderCallId: string, compat: ToolLoadCompat): WireItem[] {
|
|
452
|
+
if (compat.supportsAdditionalTools) return [{ type: "additional_tools", role: "developer", tools }];
|
|
453
|
+
const names = tools.map((tool) => String(tool.name));
|
|
454
|
+
const callId = `pi_tool_load_${createHash("sha256").update(`${loaderCallId}:${names.join(",")}`).digest("hex").slice(0, 16)}`;
|
|
455
|
+
return [
|
|
456
|
+
{ type: "tool_search_call", call_id: callId, execution: "client", status: "completed", arguments: { query: names.join(" "), limit: names.length } },
|
|
457
|
+
{ type: "tool_search_output", call_id: callId, execution: "client", status: "completed", tools: tools.map((tool) => ({ ...tool, defer_loading: true })) },
|
|
458
|
+
];
|
|
459
|
+
}
|
|
460
|
+
|
|
296
461
|
/**
|
|
297
462
|
* The bare error pi's core dispatcher returns when the model calls a tool whose
|
|
298
463
|
* schema isn't in the active set — including every deferred tool before
|
|
@@ -326,6 +491,26 @@ export function deferredMissReminderText(name: string): string {
|
|
|
326
491
|
].join(" ");
|
|
327
492
|
}
|
|
328
493
|
|
|
494
|
+
/**
|
|
495
|
+
* The notice for a resumed session whose first message still lists deferred
|
|
496
|
+
* tools (that message keeps its bytes) while this run's tool list leaves
|
|
497
|
+
* tool_search out (`deferralEnabled`): nothing can load them now.
|
|
498
|
+
*/
|
|
499
|
+
export function loaderUnavailableReminderText(): string {
|
|
500
|
+
return "tool_search is not available in this session, so it cannot load the deferred tools listed earlier in this conversation. Use only the tools defined for this session.";
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* The correction for a tool WITHHOLD_CHANNEL took away (the task tools on a
|
|
505
|
+
* frontier or Sonnet 5+ model): unlike an ordinary deferred miss, `tool_search`
|
|
506
|
+
* cannot load it — the name is gone from the registry, not merely inactive —
|
|
507
|
+
* so the steer must NOT repeat `deferredMissReminderText`'s "load it with
|
|
508
|
+
* tool_search" advice, which would just fail again.
|
|
509
|
+
*/
|
|
510
|
+
export function withheldMissReminderText(name: string): string {
|
|
511
|
+
return `\`${name}\` failed with "Tool ${name} not found" because it was withdrawn for the current model — it is not a deferred tool you can load with tool_search. Do not retry it; continue without it.`;
|
|
512
|
+
}
|
|
513
|
+
|
|
329
514
|
export interface SearchMatch {
|
|
330
515
|
name: string;
|
|
331
516
|
score: number;
|
|
@@ -340,6 +525,15 @@ export interface SearchMatch {
|
|
|
340
525
|
* requested names matched nothing instead of silently dropping them.
|
|
341
526
|
*/
|
|
342
527
|
export function selectedNames(query: string): string[] | undefined {
|
|
528
|
+
return selectedRequests(query)?.map((request) => request.name);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* {@link selectedNames} with each name as the model wrote it, so a "not found"
|
|
533
|
+
* note quotes the model's spelling: `select:find,grep,glob` maps `glob` to
|
|
534
|
+
* `find`, and quoting the mapped names read "find, grep, find".
|
|
535
|
+
*/
|
|
536
|
+
export function selectedRequests(query: string): { name: string; asWritten: string }[] | undefined {
|
|
343
537
|
const trimmed = query.trim();
|
|
344
538
|
if (!trimmed.toLowerCase().startsWith("select:")) return undefined;
|
|
345
539
|
return trimmed
|
|
@@ -350,7 +544,7 @@ export function selectedNames(query: string): string[] | undefined {
|
|
|
350
544
|
// normalizeToolName lowercases ordinary names but PRESERVES case for
|
|
351
545
|
// `mcp__…` names; the trailing toLowerCase keeps select: case-insensitive
|
|
352
546
|
// for those too. Don't drop it as "redundant".
|
|
353
|
-
.map((
|
|
547
|
+
.map((asWritten) => ({ name: normalizeToolName(asWritten).toLowerCase(), asWritten }));
|
|
354
548
|
}
|
|
355
549
|
|
|
356
550
|
/**
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rows each extension paints below the editor (the "dock": the subagent strip
|
|
3
|
+
* and shells chip, the workflow status rows), shared over `pi.events` (pure:
|
|
4
|
+
* no pi imports). A panel that takes the editor's place sizes itself to what
|
|
5
|
+
* the dock leaves: pi cuts a component taller than its share from the bottom,
|
|
6
|
+
* which took the viewer's key-hint row when only its own strip was counted.
|
|
7
|
+
*
|
|
8
|
+
* Extensions are isolated (jiti), so each one keeps its own `DockRows` fed from
|
|
9
|
+
* the channel; a source reports through `dockRowsReporter`, which emits only
|
|
10
|
+
* on a change.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const DOCK_ROWS_CHANNEL = "one-code:dock-rows";
|
|
14
|
+
|
|
15
|
+
export interface DockRowsEvent {
|
|
16
|
+
/** The reporting strip, e.g. "subagents" or "workflow". */
|
|
17
|
+
source: string;
|
|
18
|
+
/** Rows it paints now; 0 when it shows nothing. */
|
|
19
|
+
rows: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Emitter {
|
|
23
|
+
emit(channel: string, data: unknown): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A source's reporter: call it with the rows painted on each render; it emits only when the count changes. */
|
|
27
|
+
export function dockRowsReporter(events: Emitter, source: string): (rows: number) => void {
|
|
28
|
+
let last = 0;
|
|
29
|
+
return (rows) => {
|
|
30
|
+
if (rows === last) return;
|
|
31
|
+
last = rows;
|
|
32
|
+
events.emit(DOCK_ROWS_CHANNEL, { source, rows } satisfies DockRowsEvent);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A `DockRows` fed from `events`, for an extension that sizes a view to the dock. */
|
|
37
|
+
export function trackDockRows(events: { on(channel: string, handler: (data: unknown) => void): unknown }): DockRows {
|
|
38
|
+
const dock = new DockRows();
|
|
39
|
+
events.on(DOCK_ROWS_CHANNEL, (data) => dock.note(data));
|
|
40
|
+
return dock;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The dock's rows as reported, by source. */
|
|
44
|
+
export class DockRows {
|
|
45
|
+
private readonly rows = new Map<string, number>();
|
|
46
|
+
|
|
47
|
+
note(data: unknown): void {
|
|
48
|
+
const event = data as Partial<DockRowsEvent> | undefined;
|
|
49
|
+
if (typeof event?.source !== "string" || typeof event.rows !== "number") return;
|
|
50
|
+
if (event.rows > 0) this.rows.set(event.source, event.rows);
|
|
51
|
+
else this.rows.delete(event.source);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Rows every source other than `self` paints. */
|
|
55
|
+
others(self: string): number {
|
|
56
|
+
let total = 0;
|
|
57
|
+
for (const [source, rows] of this.rows) if (source !== self) total += rows;
|
|
58
|
+
return total;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Claude Code's empty tool_result guard and marker (toolResultStorage). */
|
|
2
|
+
import { ccToolName } from "../hooks/matcher.ts";
|
|
3
|
+
|
|
4
|
+
export function emptyToolResultText(toolName: string): string {
|
|
5
|
+
return `(${ccToolName(toolName)} completed with no output)`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Images and other non-text blocks are results, even without accompanying text. */
|
|
9
|
+
function isEmptyToolResultContent(content: unknown): boolean {
|
|
10
|
+
if (!content) return true;
|
|
11
|
+
if (typeof content === "string") return content.trim() === "";
|
|
12
|
+
if (!Array.isArray(content)) return false;
|
|
13
|
+
return content.every((block) => block !== null && typeof block === "object" && block.type === "text" &&
|
|
14
|
+
"text" in block && (typeof block.text !== "string" || block.text.trim() === ""));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Replacement content, or undefined to retain the tool's own result verbatim. */
|
|
18
|
+
export function emptyToolResultContent(content: unknown, toolName: string): Array<{ type: "text"; text: string }> | undefined {
|
|
19
|
+
const only = Array.isArray(content) && content.length === 1 && content[0]?.type === "text" && typeof content[0].text === "string" ? content[0] : undefined;
|
|
20
|
+
// pi Read returns raw file bytes, not Claude Code's numbered lines. Leave
|
|
21
|
+
// blank selections alone; file-tracker supplies the genuine empty-file warning.
|
|
22
|
+
if (toolName === "read" && only) return undefined;
|
|
23
|
+
if (isEmptyToolResultContent(content)) return [{ type: "text", text: emptyToolResultText(toolName) }];
|
|
24
|
+
// pi's find is Claude Code's Glob; Grep already uses Claude Code's no-match text.
|
|
25
|
+
if (toolName === "find" && only?.text === "No files found matching pattern") return [{ ...only, text: "No files found" }];
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code's environment block and model line (pure): the first two parts of
|
|
3
|
+
* its mid-conversation system message, and the first two blocks of the first
|
|
4
|
+
* user message on a model without one (lib/system-role.ts). They left the
|
|
5
|
+
* system prompt, which keeps only what never varies per session.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { parseClaudeId } from "./model-tier.ts";
|
|
9
|
+
|
|
10
|
+
export interface EnvironmentFacts {
|
|
11
|
+
cwd: string;
|
|
12
|
+
isGitRepo: boolean;
|
|
13
|
+
platform: string;
|
|
14
|
+
shell: string;
|
|
15
|
+
osVersion: string;
|
|
16
|
+
/** The session's private scratchpad, when one could be made (lib/scratchpad.ts). */
|
|
17
|
+
scratchpadDir?: string;
|
|
18
|
+
/** The workspace directories as the session started; listed only when there are some. */
|
|
19
|
+
workspaceDirs?: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Claude Code's `# Environment` block, byte for byte; the trailing space after "environment:" is Claude Code's. */
|
|
23
|
+
export function environmentBlock(env: EnvironmentFacts): string {
|
|
24
|
+
const lines = [
|
|
25
|
+
"# Environment",
|
|
26
|
+
"You have been invoked in the following environment: ",
|
|
27
|
+
` - Primary working directory: ${env.cwd}`,
|
|
28
|
+
` - Is a git repository: ${env.isGitRepo}`,
|
|
29
|
+
];
|
|
30
|
+
if (env.workspaceDirs?.length) lines.push(" - Additional working directories:", ...env.workspaceDirs.map((dir) => ` - ${dir}`));
|
|
31
|
+
lines.push(` - Platform: ${env.platform}`, ` - Shell: ${env.shell}`, ` - OS Version: ${env.osVersion}`);
|
|
32
|
+
if (env.scratchpadDir) {
|
|
33
|
+
lines.push(
|
|
34
|
+
` - Scratchpad directory: ${env.scratchpadDir} — always use it for temporary files (intermediate results, scripts, outputs that don't belong in the project) instead of \`/tmp\` or other system temp directories; it is session-specific, isolated from the project, and can generally be used without permission prompts. Only use \`/tmp\` if the user explicitly asks.`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return lines.join("\n");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Knowledge cutoffs Claude Code states, keyed by family and version. */
|
|
41
|
+
const CLAUDE_CUTOFFS: Record<string, string> = {
|
|
42
|
+
"opus 4.5": "May 2025",
|
|
43
|
+
"opus 4.6": "May 2025",
|
|
44
|
+
"opus 4.7": "January 2026",
|
|
45
|
+
"opus 4.8": "January 2026",
|
|
46
|
+
"opus 5": "May 2026",
|
|
47
|
+
"opus 5.5": "June 2026",
|
|
48
|
+
"sonnet 4.5": "January 2025",
|
|
49
|
+
"sonnet 4.6": "August 2025",
|
|
50
|
+
"sonnet 5": "January 2026",
|
|
51
|
+
"sonnet 5.5": "June 2026",
|
|
52
|
+
"haiku 4.5": "February 2025",
|
|
53
|
+
"haiku 5.5": "June 2026",
|
|
54
|
+
"fable 5.1": "June 2026",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** A Claude model's name as Claude Code says it ("Opus 5.5", "Haiku 4.5"), from an id in any provider's spelling. */
|
|
58
|
+
export function claudeDisplayName(id: string): string | undefined {
|
|
59
|
+
const claude = parseClaudeId(id);
|
|
60
|
+
if (!claude) return undefined;
|
|
61
|
+
const family = claude.family[0].toUpperCase() + claude.family.slice(1);
|
|
62
|
+
return claude.minor === undefined ? `${family} ${claude.major}` : `${family} ${claude.major}.${claude.minor}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Claude Code's model sentence. A Claude model gets its name and, where
|
|
67
|
+
* Claude Code states one, its knowledge cutoff; any other model gets the
|
|
68
|
+
* catalog's name and no cutoff clause, since the cutoff is a per-model fact.
|
|
69
|
+
*/
|
|
70
|
+
export function modelLine(model: { id: string; name?: string }): string {
|
|
71
|
+
const claude = claudeDisplayName(model.id);
|
|
72
|
+
const name = claude ?? (model.name || model.id);
|
|
73
|
+
const cutoff = claude ? CLAUDE_CUTOFFS[claude.toLowerCase()] : undefined;
|
|
74
|
+
const base = `You are powered by the model named ${name}. The exact model ID is ${model.id}.`;
|
|
75
|
+
return cutoff ? `${base} Assistant knowledge cutoff is ${cutoff}.` : base;
|
|
76
|
+
}
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* fetch
|
|
3
|
-
*
|
|
2
|
+
* fetch and read the response under one hard deadline (pure, no pi imports):
|
|
3
|
+
* the timer runs until `read` settles, so a body that stalls after the headers
|
|
4
|
+
* is cut off too, whatever `fetchImpl` does with the signal. `signal` aborts
|
|
5
|
+
* early (a session shutting down). Shared by the plugin-marketplace fetches and
|
|
6
|
+
* the model-catalog refresh; `fetchImpl` is the test seam, given the same signal.
|
|
4
7
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
export async function fetchWithTimeout<T>(
|
|
9
|
+
url: string,
|
|
10
|
+
timeoutMs: number,
|
|
11
|
+
read: (response: Response) => Promise<T>,
|
|
12
|
+
options: { init?: RequestInit; signal?: AbortSignal; fetchImpl?: typeof fetch } = {},
|
|
13
|
+
): Promise<T> {
|
|
7
14
|
const controller = new AbortController();
|
|
8
|
-
|
|
15
|
+
// Parsed up front: a malformed URL fails the call, never the timer callback.
|
|
16
|
+
const host = new URL(url).host;
|
|
17
|
+
const timer = setTimeout(() => controller.abort(new Error(`${host} did not answer within ${timeoutMs} ms`)), timeoutMs);
|
|
9
18
|
timer.unref?.();
|
|
19
|
+
const signal = options.signal ? AbortSignal.any([controller.signal, options.signal]) : controller.signal;
|
|
20
|
+
const aborted = new Promise<never>((_, reject) => {
|
|
21
|
+
const fail = () => reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? "aborted")));
|
|
22
|
+
if (signal.aborted) fail();
|
|
23
|
+
else signal.addEventListener("abort", fail, { once: true });
|
|
24
|
+
});
|
|
10
25
|
try {
|
|
11
|
-
|
|
26
|
+
const work = (async () => read(await (options.fetchImpl ?? fetch)(url, { ...options.init, signal })))();
|
|
27
|
+
// Lost to the deadline, the work may still reject later: not unhandled.
|
|
28
|
+
work.catch(() => {});
|
|
29
|
+
return await Promise.race([work, aborted]);
|
|
12
30
|
} finally {
|
|
13
31
|
clearTimeout(timer);
|
|
14
32
|
}
|