one-code-extension 0.4.1 → 0.6.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 +16 -5
- 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 +40 -28
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +26 -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 +58 -31
- 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 +124 -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 +642 -0
- package/extensions/background/index.ts +614 -248
- package/extensions/background/loop-fire.ts +173 -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 +56 -0
- package/extensions/background/wakeup.ts +178 -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 +66 -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 +250 -78
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/cache-marks/index.ts +42 -0
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +436 -63
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +219 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/budget.ts +16 -2
- package/extensions/context-budget/index.ts +35 -15
- package/extensions/context-management/index.ts +36 -28
- 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 +141 -25
- 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 +420 -42
- package/extensions/file-tracker/replay.ts +137 -24
- package/extensions/file-tracker/restore.ts +66 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +45 -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 +209 -58
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/prompt-context.ts +59 -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 +26 -12
- 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/ledger/index.ts +46 -0
- package/extensions/ledger/wire.ts +154 -0
- package/extensions/lib/agent-cron.ts +146 -0
- package/extensions/lib/agent-loader.ts +18 -3
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-auth.ts +98 -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 +52 -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 +97 -0
- package/extensions/lib/covered-messages.ts +23 -0
- package/extensions/lib/deferred.ts +359 -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/ledger/fold-memo.ts +49 -0
- package/extensions/lib/ledger/fold.ts +265 -0
- package/extensions/lib/ledger/layout.ts +72 -0
- package/extensions/lib/ledger/plan.ts +102 -0
- package/extensions/lib/ledger/records.ts +276 -0
- package/extensions/lib/ledger/state.ts +47 -0
- package/extensions/lib/ledger/validate.ts +141 -0
- package/extensions/lib/ledger/wire.ts +632 -0
- package/extensions/lib/local-command.ts +71 -19
- 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 +178 -0
- package/extensions/lib/model-catalog/models-dev.json +18533 -0
- package/extensions/lib/model-catalog/openrouter.json +5050 -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 +357 -73
- 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 +38 -2
- package/extensions/lib/plan-mode-channels.ts +42 -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/previous-mark.ts +101 -0
- package/extensions/lib/process-state.ts +48 -0
- package/extensions/lib/process-tree.ts +132 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +370 -358
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +547 -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 +24 -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 +112 -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 +129 -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 +159 -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 +76 -12
- 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 +90 -43
- 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 +220 -121
- 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 +164 -73
- 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/model-default/index.ts +86 -19
- 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 +1426 -398
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +11 -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 +276 -92
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/run-state.ts +26 -0
- 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 +64 -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 +14 -7
- package/extensions/recap/index.ts +73 -71
- 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/announce.ts +30 -0
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +469 -132
- package/extensions/skill/invoke.ts +118 -75
- 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 +146 -31
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +82 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1594 -852
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +5 -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/parent-mode.ts +50 -0
- package/extensions/subagents/pending-claim.ts +18 -1
- package/extensions/subagents/runner.ts +135 -80
- package/extensions/subagents/runs.ts +44 -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 +165 -47
- package/extensions/system-prompt/template.ts +34 -51
- 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 +204 -70
- 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/index.ts +366 -144
- 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 +108 -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 +157 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +33 -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 +162 -44
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +21 -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
- package/extensions/tool-search/announce.ts +0 -42
|
@@ -10,19 +10,75 @@
|
|
|
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 { leadingToolsItem, toolsItemFor } from "./ledger/wire.ts";
|
|
18
|
+
import { RESPONSES_APIS } from "./system-role.ts";
|
|
19
|
+
import { ADDENDUM_HEAD } from "./tool-additions.ts";
|
|
16
20
|
import { normalizeToolName } from "../permissions/matcher.ts";
|
|
17
21
|
|
|
18
22
|
export const DEFER_CHANNEL = "one-code:defer-tool";
|
|
19
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Take a deferred tool out of this session's surface (`{ name }`): it leaves the
|
|
26
|
+
* deferred registry and the active set, so no request carries it and
|
|
27
|
+
* `tool_search` cannot load it. An extension withholds its own tool when the
|
|
28
|
+
* session model must not have it (the task tools on frontier models) and
|
|
29
|
+
* re-emits DEFER_CHANNEL to bring it back after a model change. Before the
|
|
30
|
+
* first request the standing listing is rewritten without it; after that the
|
|
31
|
+
* listing stays frozen and a load reports "not found" (tool-search/announce.ts).
|
|
32
|
+
*/
|
|
33
|
+
export const WITHHOLD_CHANNEL = "one-code:withhold-tool";
|
|
34
|
+
|
|
20
35
|
export interface DeferRequest {
|
|
21
36
|
name: string;
|
|
22
37
|
/** Extra search terms beyond the tool's name and description. */
|
|
23
38
|
keywords?: string[];
|
|
24
39
|
}
|
|
25
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Ask the session's tool-search which tools it defers: emit a `DeferredQuery`
|
|
43
|
+
* and it adds one request per deferred tool. A subagent defers the same tools
|
|
44
|
+
* its parent injects into it (MCP, cron, SendMessage), whose owners run only
|
|
45
|
+
* in the parent.
|
|
46
|
+
*/
|
|
47
|
+
export const DEFERRED_QUERY_CHANNEL = "one-code:deferred-query";
|
|
48
|
+
|
|
49
|
+
export interface DeferredQuery {
|
|
50
|
+
tools: DeferRequest[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The tools this session defers, as its tool-search answers DEFERRED_QUERY_CHANNEL (none without one). */
|
|
54
|
+
export function queryDeferred(events: { emit(channel: string, data: unknown): void }): DeferRequest[] {
|
|
55
|
+
const query: DeferredQuery = { tools: [] };
|
|
56
|
+
events.emit(DEFERRED_QUERY_CHANNEL, query);
|
|
57
|
+
return query.tools;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The tools a child gets from its parent rather than its own extensions: the
|
|
62
|
+
* shared MCP tools (`mcp__*`, which no child extension owns) and `extra`, the
|
|
63
|
+
* ones the caller injects by name (the spawn and cron tools).
|
|
64
|
+
*/
|
|
65
|
+
export function injectedByParent(extra: readonly { name: string }[]): (name: string) => boolean {
|
|
66
|
+
const names = new Set(extra.map((tool) => tool.name));
|
|
67
|
+
return (name) => name.startsWith("mcp__") || names.has(name);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* An inline child extension that defers, at load, the parent's deferral of each
|
|
72
|
+
* tool `injected` names, whose owners run only in the parent. A tool the
|
|
73
|
+
* child's own extensions own stays theirs to defer or withhold per the child's
|
|
74
|
+
* model.
|
|
75
|
+
*/
|
|
76
|
+
export function deferLikeParentExtension(parentDeferred: readonly DeferRequest[], injected: (name: string) => boolean) {
|
|
77
|
+
return (pi: { events: { emit(channel: string, data: unknown): void } }) => {
|
|
78
|
+
for (const request of parentDeferred) if (injected(request.name)) pi.events.emit(DEFER_CHANNEL, request);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
26
82
|
export interface SearchableTool {
|
|
27
83
|
name: string;
|
|
28
84
|
description: string;
|
|
@@ -49,6 +105,10 @@ export class DeferredRegistry {
|
|
|
49
105
|
has(name: string): boolean {
|
|
50
106
|
return this.entries.has(name);
|
|
51
107
|
}
|
|
108
|
+
|
|
109
|
+
remove(name: string): boolean {
|
|
110
|
+
return this.entries.delete(name);
|
|
111
|
+
}
|
|
52
112
|
}
|
|
53
113
|
|
|
54
114
|
// The one registry instance is constructed by its owner (tool-search/index.ts):
|
|
@@ -80,25 +140,50 @@ export function deferredReminderText(tools: Array<Pick<SearchableTool, "name"> &
|
|
|
80
140
|
* this rides the tail instead, so the cached prefix holds.
|
|
81
141
|
*/
|
|
82
142
|
export function deferredAddendumText(added: readonly string[]): string {
|
|
83
|
-
return [
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
143
|
+
return [ADDENDUM_HEAD, ...added].join("\n");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The one-shot that says deferred tools the conversation was told about are
|
|
148
|
+
* gone (their MCP server disconnected or was removed, a resume without it).
|
|
149
|
+
* The earlier listing or addendum stays in the history as sent, and so may
|
|
150
|
+
* the tool's declaration, so without this the model calls it and gets "Tool
|
|
151
|
+
* not found".
|
|
152
|
+
*/
|
|
153
|
+
export function deferredRemovedText(removed: readonly string[]): string {
|
|
154
|
+
return ["The following deferred tools are no longer available. tool_search cannot load them and calls to them fail; do not use them:", ...removed].join("\n");
|
|
87
155
|
}
|
|
88
156
|
|
|
157
|
+
/** The loader every deferred tool sits behind. */
|
|
158
|
+
export const TOOL_SEARCH = "tool_search";
|
|
159
|
+
|
|
89
160
|
/**
|
|
90
|
-
* Whether
|
|
91
|
-
*
|
|
92
|
-
*
|
|
161
|
+
* Whether deferral applies at all: `tool_search` is among pi's registered
|
|
162
|
+
* tools. A `--tools` allowlist that leaves it out (or `--exclude-tools
|
|
163
|
+
* tool_search`) unregisters it, and since pi 1.0.4 the allowlist keeps the
|
|
164
|
+
* `mcp__*` tools registered unless an entry names one (findings §66). Without
|
|
165
|
+
* the loader nothing can be loaded, so no tool is deferred or listed: the
|
|
166
|
+
* tools the allowlist kept stay as pi activated them.
|
|
167
|
+
*/
|
|
168
|
+
export function deferralEnabled(tools: readonly { name: string }[]): boolean {
|
|
169
|
+
return tools.some((tool) => tool.name === TOOL_SEARCH);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Whether Anthropic accepts client-side `tool_reference` blocks for this model,
|
|
174
|
+
* with the model's own compat flag winning: first-party Anthropic, Claude 4.5
|
|
175
|
+
* or newer, Haiku 4.5 and later Haikus included (they take references with no
|
|
176
|
+
* beta, and an eager append there rewrites the whole cache: findings §55 P4
|
|
177
|
+
* for Haiku 4.5, §67 for Haiku 5.5). Older Haikus stay out.
|
|
93
178
|
*/
|
|
94
179
|
export function supportsToolReferences(
|
|
95
180
|
model: { provider?: string; id?: string; compat?: { supportsToolReferences?: boolean } } | undefined,
|
|
96
181
|
): boolean {
|
|
97
182
|
if (!model) return false;
|
|
98
183
|
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));
|
|
184
|
+
if (model.provider !== "anthropic" || !model.id) return false;
|
|
185
|
+
const version = parseClaudeId(model.id);
|
|
186
|
+
return version !== undefined && (version.major > 4 || (version.major === 4 && (version.minor ?? 0) >= 5));
|
|
102
187
|
}
|
|
103
188
|
|
|
104
189
|
/** The slice of pi's ToolInfo the deferred definitions need. */
|
|
@@ -127,7 +212,74 @@ export function toolSearchLoads(branch: readonly SessionEntry[]): Map<string, st
|
|
|
127
212
|
}
|
|
128
213
|
return loads;
|
|
129
214
|
}
|
|
130
|
-
const TOOL_SEARCH_NAMES: ReadonlySet<string> = new Set([
|
|
215
|
+
const TOOL_SEARCH_NAMES: ReadonlySet<string> = new Set([TOOL_SEARCH]);
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The tools `entries` use: loaded by a `tool_search` result in them (`loads`,
|
|
219
|
+
* their `toolSearchLoads`), or called by an assistant message in them. A call
|
|
220
|
+
* the dispatcher failed as "Tool <name> not found" does not count: the tool
|
|
221
|
+
* was not loaded, and the miss steers the model to `tool_search` to load it.
|
|
222
|
+
*/
|
|
223
|
+
export function toolsInUse(entries: readonly SessionEntry[], loads: ToolSearchLoads = toolSearchLoads(entries)): Set<string> {
|
|
224
|
+
const inUse = new Set([...loads.values()].flat());
|
|
225
|
+
const missed = new Set<string>();
|
|
226
|
+
for (const entry of entries) {
|
|
227
|
+
const message = entry.type === "message" ? (entry.message as { role?: string; toolCallId?: unknown; isError?: unknown }) : undefined;
|
|
228
|
+
if (message?.role === "toolResult" && message.isError === true && typeof message.toolCallId === "string" && toolNotFoundName(resultText(message))) missed.add(message.toolCallId);
|
|
229
|
+
}
|
|
230
|
+
for (const entry of entries) {
|
|
231
|
+
const message = entry.type === "message" ? (entry.message as { role?: string; content?: unknown }) : undefined;
|
|
232
|
+
if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
233
|
+
for (const block of message.content as Array<{ type?: string; id?: unknown; name?: unknown }>) {
|
|
234
|
+
if (block?.type === "toolCall" && typeof block.name === "string" && !(typeof block.id === "string" && missed.has(block.id))) inUse.add(block.name);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return inUse;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Why a deferred tool is callable without a fresh `tool_search` load. */
|
|
241
|
+
export interface CallableReasons {
|
|
242
|
+
/** Loaded by a `tool_search` result in context, or called in context: its history names it. */
|
|
243
|
+
inUse: ReadonlySet<string>;
|
|
244
|
+
/** Announced callable by a `tool_addition` in the current epoch (the ledger's told facts). */
|
|
245
|
+
toldCallable: ReadonlySet<string>;
|
|
246
|
+
/** Declared eagerly by the recorded head the next request starts from: it stays declared, so it stays callable. */
|
|
247
|
+
declaredEager: ReadonlySet<string>;
|
|
248
|
+
/** Announced callable by the next request itself: loadable and untold inside an epoch, on a model that takes `tool_addition`. */
|
|
249
|
+
announcing: ReadonlySet<string>;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* The deferred tools that are callable now, derived from the branch, the model
|
|
254
|
+
* and the registry (lifecycle-grid-hunt design.md section 6): every other
|
|
255
|
+
* deferred tool is inactive, whoever activated it. One function decides, so a
|
|
256
|
+
* resume, `/reload`, `/tree`, a compaction, a model switch and a late defer
|
|
257
|
+
* cannot disagree with what the wire declares and what the model was told.
|
|
258
|
+
*/
|
|
259
|
+
export function callableDeferredTools(deferred: Iterable<string>, reasons: CallableReasons): Set<string> {
|
|
260
|
+
const callable = new Set<string>();
|
|
261
|
+
for (const name of deferred) {
|
|
262
|
+
if (reasons.inUse.has(name) || reasons.toldCallable.has(name) || reasons.declaredEager.has(name) || reasons.announcing.has(name)) callable.add(name);
|
|
263
|
+
}
|
|
264
|
+
return callable;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* pi's active set with exactly the `callable` deferred tools active (those
|
|
269
|
+
* pi has registered) and no `withheld` tool; every other tool as it was. The
|
|
270
|
+
* input itself when nothing changes.
|
|
271
|
+
*/
|
|
272
|
+
export function reconcileActive(
|
|
273
|
+
active: readonly string[],
|
|
274
|
+
deferred: ReadonlySet<string>,
|
|
275
|
+
callable: ReadonlySet<string>,
|
|
276
|
+
withheld: ReadonlySet<string>,
|
|
277
|
+
registered: ReadonlySet<string>,
|
|
278
|
+
): readonly string[] {
|
|
279
|
+
const kept = active.filter((name) => !withheld.has(name) && (!deferred.has(name) || callable.has(name)));
|
|
280
|
+
const added = [...callable].filter((name) => registered.has(name) && !withheld.has(name) && !kept.includes(name));
|
|
281
|
+
return kept.length === active.length && added.length === 0 ? active : [...kept, ...added];
|
|
282
|
+
}
|
|
131
283
|
|
|
132
284
|
type WireTool = Record<string, unknown> & { name: string };
|
|
133
285
|
type WireBlock = Record<string, unknown> & { type?: unknown };
|
|
@@ -157,7 +309,7 @@ function withBreakpointOnLast<T extends Record<string, unknown>>(items: T[], cac
|
|
|
157
309
|
* An Anthropic request whose `tools` array is byte-identical to request 1's,
|
|
158
310
|
* whatever `tool_search` has loaded since — the client side of Anthropic's
|
|
159
311
|
* deferred-tool contract, which pi 0.86 no longer supplies for One Code
|
|
160
|
-
* (rationale and measurements: docs/decisions/caching.md "Loaded deferred
|
|
312
|
+
* (rationale and measurements: working-docs/decisions/caching.md "Loaded deferred
|
|
161
313
|
* tools stay deferred on the wire", findings §7).
|
|
162
314
|
*
|
|
163
315
|
* - Every registry tool `isDeferred` that is not eager on the wire is appended
|
|
@@ -171,6 +323,9 @@ function withBreakpointOnLast<T extends Record<string, unknown>>(items: T[], cac
|
|
|
171
323
|
* ordinary result content). Demotion happens only when that result is on the
|
|
172
324
|
* wire and precedes every `tool_use` of the tool; otherwise the tool stays
|
|
173
325
|
* eager (one accepted cache miss, never a request Anthropic rejects).
|
|
326
|
+
* - A tool the request adds mid-session by `tool_addition` (`additions`,
|
|
327
|
+
* lib/tool-additions.ts) is demoted the same way: its addition block
|
|
328
|
+
* references it, so it needs no `tool_search` result.
|
|
174
329
|
* - pi's own deferred entries that are not registry tools (its
|
|
175
330
|
* `__pi_deferred_placeholder__`) stay in place.
|
|
176
331
|
*
|
|
@@ -184,6 +339,7 @@ export function stabilizeDeferredTools(
|
|
|
184
339
|
tools: readonly DeferrableToolInfo[],
|
|
185
340
|
isDeferred: (name: string) => boolean,
|
|
186
341
|
loads: ToolSearchLoads = new Map(),
|
|
342
|
+
additions: ReadonlySet<string> = new Set(),
|
|
187
343
|
): Record<string, unknown> | undefined {
|
|
188
344
|
const existing = payload.tools;
|
|
189
345
|
if (!Array.isArray(existing) || existing.length === 0) return undefined;
|
|
@@ -220,16 +376,7 @@ export function stabilizeDeferredTools(
|
|
|
220
376
|
}
|
|
221
377
|
}
|
|
222
378
|
});
|
|
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
|
-
}
|
|
379
|
+
const loaderOf = earliestLoads(loads, (callId) => resultAt.get(callId));
|
|
233
380
|
for (const tool of eager) {
|
|
234
381
|
if (!isDeferred(tool.name)) continue;
|
|
235
382
|
const loader = loaderOf.get(tool.name);
|
|
@@ -239,8 +386,15 @@ export function stabilizeDeferredTools(
|
|
|
239
386
|
demoted.add(tool.name);
|
|
240
387
|
referencesAt.set(loader.callId, [...(referencesAt.get(loader.callId) ?? []), tool.name]);
|
|
241
388
|
}
|
|
389
|
+
// References in the order the query named them, as Claude Code lists them.
|
|
390
|
+
for (const [callId, names] of referencesAt) {
|
|
391
|
+
const order = loads.get(callId) ?? [];
|
|
392
|
+
referencesAt.set(callId, [...names].sort((a, b) => order.indexOf(a) - order.indexOf(b)));
|
|
393
|
+
}
|
|
242
394
|
}
|
|
243
395
|
|
|
396
|
+
for (const tool of eager) if (additions.has(tool.name) && isDeferred(tool.name)) demoted.add(tool.name);
|
|
397
|
+
|
|
244
398
|
// pi puts the tools breakpoint on the last eager tool; if that one is demoted
|
|
245
399
|
// the breakpoint moves to the new last one, otherwise nothing moves.
|
|
246
400
|
const last = eager.at(-1);
|
|
@@ -285,7 +439,7 @@ function withToolReferences(message: WireMessage, referencesAt: ReadonlyMap<stri
|
|
|
285
439
|
if (cache_control !== undefined) movedBreakpoint = cache_control;
|
|
286
440
|
blocks.push({ ...rest, content: names.map((name) => ({ type: "tool_reference", tool_name: name })) });
|
|
287
441
|
if (typeof original === "string") {
|
|
288
|
-
if (original.trim().length > 0) siblings.push({ type: "text", text: original });
|
|
442
|
+
if (original.trim().length > 0) siblings.push({ type: "text", text: withClaudeCodeLoadText(original) });
|
|
289
443
|
} else if (Array.isArray(original)) {
|
|
290
444
|
siblings.push(...(original as WireBlock[]));
|
|
291
445
|
}
|
|
@@ -293,6 +447,148 @@ function withToolReferences(message: WireMessage, referencesAt: ReadonlyMap<stri
|
|
|
293
447
|
return { ...message, content: withBreakpointOnLast([...blocks, ...siblings], movedBreakpoint) };
|
|
294
448
|
}
|
|
295
449
|
|
|
450
|
+
/** `tool_search`'s plain success sentence, which the references stand in for. */
|
|
451
|
+
const LOADED_SENTENCE = /^Loaded [^\n]*\. These tools are now callable\.(?=\n|$)/;
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* The text beside the references: Claude Code's "Tool loaded." in place of the
|
|
455
|
+
* plain success sentence, which names what the references already carry. A
|
|
456
|
+
* sentence with a not-found note, and anything after it, stays as it was.
|
|
457
|
+
*/
|
|
458
|
+
function withClaudeCodeLoadText(text: string): string {
|
|
459
|
+
return text.replace(LOADED_SENTENCE, "Tool loaded.");
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* The earliest on-wire load of each tool: the `tool_search` call whose result
|
|
464
|
+
* sits first in the conversation (`resultAt` is its index, undefined when it is
|
|
465
|
+
* not on the wire).
|
|
466
|
+
*/
|
|
467
|
+
function earliestLoads(loads: ToolSearchLoads, resultAt: (callId: string) => number | undefined): Map<string, { callId: string; at: number }> {
|
|
468
|
+
const loaderOf = new Map<string, { callId: string; at: number }>();
|
|
469
|
+
for (const [callId, names] of loads) {
|
|
470
|
+
const at = resultAt(callId);
|
|
471
|
+
if (at === undefined) continue;
|
|
472
|
+
for (const name of names) {
|
|
473
|
+
const current = loaderOf.get(name);
|
|
474
|
+
if (current === undefined || at < current.at) loaderOf.set(name, { callId, at });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return loaderOf;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
/** How a Responses model takes a mid-conversation tool load (pi-ai's model `compat` fields). */
|
|
482
|
+
export interface ToolLoadCompat {
|
|
483
|
+
supportsAdditionalTools?: boolean;
|
|
484
|
+
supportsToolSearch?: boolean;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** The model fields that say whether, and how, a Responses request can add a tool mid-conversation. */
|
|
488
|
+
export interface ResponsesToolLoadModel {
|
|
489
|
+
api?: string;
|
|
490
|
+
compat?: ToolLoadCompat;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* The model's tool-load compat when it takes a mid-conversation tool load in
|
|
495
|
+
* pi-ai's native Responses shape, else undefined.
|
|
496
|
+
*/
|
|
497
|
+
export function responsesToolLoadCompat(model: ResponsesToolLoadModel | undefined): ToolLoadCompat | undefined {
|
|
498
|
+
if (!model?.api || !RESPONSES_APIS.has(model.api)) return undefined;
|
|
499
|
+
const compat = model.compat;
|
|
500
|
+
return compat?.supportsAdditionalTools === true || compat?.supportsToolSearch === true ? compat : undefined;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
type WireItem = Record<string, unknown>;
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* A Responses request whose starting tools stay request 1's whatever
|
|
507
|
+
* `tool_search` has loaded since: the OpenAI side of
|
|
508
|
+
* {@link stabilizeDeferredTools}. pi 0.86 and later put a loaded tool into
|
|
509
|
+
* `tools` for One Code (its forced system prompt collapses tool changes into
|
|
510
|
+
* the head, findings §7), so every load re-cached the whole conversation (0
|
|
511
|
+
* cached tokens on the request after a load on `openai-codex`, findings §45).
|
|
512
|
+
*
|
|
513
|
+
* The starting tools are a top-level `tools` array (pi 1.0 to 1.3, and
|
|
514
|
+
* models pi does not flag) or, from pi 1.4.0, an `additional_tools` item at
|
|
515
|
+
* the head of the input (findings §78); this reads which from the payload.
|
|
516
|
+
* A loaded registry tool leaves that list and enters the input right before the
|
|
517
|
+
* `function_call_output` of the `tool_search` call that loaded it, in pi-ai's
|
|
518
|
+
* own native shape (`convertResponsesMessages` `appendSystemToolAdditions`):
|
|
519
|
+
* an `additional_tools` developer item when the request already declares in
|
|
520
|
+
* items or the model supports one, else a `tool_search_call` +
|
|
521
|
+
* `tool_search_output` pair whose tools carry `defer_loading: true`. A tool
|
|
522
|
+
* the model called before that output, or whose loading output is not on the
|
|
523
|
+
* wire, stays where pi declared it.
|
|
524
|
+
*
|
|
525
|
+
* Returns undefined (leave the payload alone) when nothing moves. Never
|
|
526
|
+
* mutates its input.
|
|
527
|
+
*/
|
|
528
|
+
export function stabilizeResponsesToolLoads(
|
|
529
|
+
payload: Record<string, unknown>,
|
|
530
|
+
isDeferred: (name: string) => boolean,
|
|
531
|
+
loads: ToolSearchLoads,
|
|
532
|
+
compat: ToolLoadCompat,
|
|
533
|
+
): Record<string, unknown> | undefined {
|
|
534
|
+
const input = payload.input;
|
|
535
|
+
if (!Array.isArray(input) || loads.size === 0) return undefined;
|
|
536
|
+
// Where the request declares its starting tools: a top-level array (pi 1.0
|
|
537
|
+
// to 1.3, and models pi does not flag), or an `additional_tools` item at the
|
|
538
|
+
// head of the input (pi 1.4.0 and later, findings §78). Read from the bytes.
|
|
539
|
+
const headItem = leadingToolsItem(payload.tools, input);
|
|
540
|
+
const tools = headItem ? headItem.tools : payload.tools;
|
|
541
|
+
if (!Array.isArray(tools)) return undefined;
|
|
542
|
+
|
|
543
|
+
// Input index of each tool output and of each tool's first call. pi sends a
|
|
544
|
+
// call id as `<call_id>|<item_id>`; the wire's `call_id` is the first part.
|
|
545
|
+
const outputAt = new Map<string, number>();
|
|
546
|
+
const firstUse = new Map<string, number>();
|
|
547
|
+
(input as WireItem[]).forEach((item, index) => {
|
|
548
|
+
if (item?.type === "function_call_output" && typeof item.call_id === "string") outputAt.set(item.call_id, index);
|
|
549
|
+
else if ((item?.type === "function_call" || item?.type === "custom_tool_call") && typeof item.name === "string" && !firstUse.has(item.name)) {
|
|
550
|
+
firstUse.set(item.name, index);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
const loaderOf = earliestLoads(loads, (callId) => outputAt.get(callId.split("|")[0]));
|
|
554
|
+
|
|
555
|
+
const movedAt = new Map<number, WireItem[]>();
|
|
556
|
+
const kept: unknown[] = [];
|
|
557
|
+
for (const raw of tools) {
|
|
558
|
+
const tool = raw as WireItem | null;
|
|
559
|
+
const name = typeof tool?.name === "string" ? tool.name : undefined;
|
|
560
|
+
const at = name !== undefined && isDeferred(name) ? loaderOf.get(name)?.at : undefined;
|
|
561
|
+
const use = name !== undefined ? firstUse.get(name) : undefined;
|
|
562
|
+
if (at === undefined || (use !== undefined && use < at)) {
|
|
563
|
+
kept.push(raw);
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
movedAt.set(at, [...(movedAt.get(at) ?? []), tool as WireItem]);
|
|
567
|
+
}
|
|
568
|
+
if (movedAt.size === 0) return undefined;
|
|
569
|
+
|
|
570
|
+
// A request that declares in items takes another item, shaped like its
|
|
571
|
+
// head; the top-level shape takes what the model's compat supports.
|
|
572
|
+
const nextInput: unknown[] = [];
|
|
573
|
+
(input as WireItem[]).forEach((item, index) => {
|
|
574
|
+
const moved = movedAt.get(index);
|
|
575
|
+
if (moved) nextInput.push(...(headItem ? [toolsItemFor(moved, headItem)] : toolLoadItems(moved, String(item.call_id), compat)));
|
|
576
|
+
nextInput.push(headItem && index === 0 ? { ...headItem, tools: kept } : item);
|
|
577
|
+
});
|
|
578
|
+
return headItem ? { ...payload, input: nextInput } : { ...payload, tools: kept, input: nextInput };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** pi-ai's native items for tools added at one point of a Responses conversation. */
|
|
582
|
+
function toolLoadItems(tools: WireItem[], loaderCallId: string, compat: ToolLoadCompat): WireItem[] {
|
|
583
|
+
if (compat.supportsAdditionalTools) return [toolsItemFor(tools)];
|
|
584
|
+
const names = tools.map((tool) => String(tool.name));
|
|
585
|
+
const callId = `pi_tool_load_${createHash("sha256").update(`${loaderCallId}:${names.join(",")}`).digest("hex").slice(0, 16)}`;
|
|
586
|
+
return [
|
|
587
|
+
{ type: "tool_search_call", call_id: callId, execution: "client", status: "completed", arguments: { query: names.join(" "), limit: names.length } },
|
|
588
|
+
{ type: "tool_search_output", call_id: callId, execution: "client", status: "completed", tools: tools.map((tool) => ({ ...tool, defer_loading: true })) },
|
|
589
|
+
];
|
|
590
|
+
}
|
|
591
|
+
|
|
296
592
|
/**
|
|
297
593
|
* The bare error pi's core dispatcher returns when the model calls a tool whose
|
|
298
594
|
* schema isn't in the active set — including every deferred tool before
|
|
@@ -326,6 +622,35 @@ export function deferredMissReminderText(name: string): string {
|
|
|
326
622
|
].join(" ");
|
|
327
623
|
}
|
|
328
624
|
|
|
625
|
+
/**
|
|
626
|
+
* The notice for a resumed session whose first message still lists deferred
|
|
627
|
+
* tools (that message keeps its bytes) while this run's tool list leaves
|
|
628
|
+
* tool_search out (`deferralEnabled`): nothing can load them now.
|
|
629
|
+
*/
|
|
630
|
+
export function loaderUnavailableReminderText(): string {
|
|
631
|
+
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.";
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* The correction for a tool WITHHOLD_CHANNEL took away (the task tools on a
|
|
636
|
+
* frontier or Sonnet 5+ model): unlike an ordinary deferred miss, `tool_search`
|
|
637
|
+
* cannot load it — the name is gone from the registry, not merely inactive —
|
|
638
|
+
* so the steer must NOT repeat `deferredMissReminderText`'s "load it with
|
|
639
|
+
* tool_search" advice, which would just fail again.
|
|
640
|
+
*/
|
|
641
|
+
export function withheldMissReminderText(name: string): string {
|
|
642
|
+
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.`;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* A call to a tool the conversation still declares (the epoch's recorded
|
|
647
|
+
* tools stay declared, decisions/caching.md "record once") that this session
|
|
648
|
+
* no longer runs: a setting turned it off, or an extension stopped offering it.
|
|
649
|
+
*/
|
|
650
|
+
export function unavailableToolReminderText(name: string): string {
|
|
651
|
+
return `\`${name}\` failed with "Tool ${name} not found" because it is no longer available in this session, although it is still declared. Do not retry it; continue without it.`;
|
|
652
|
+
}
|
|
653
|
+
|
|
329
654
|
export interface SearchMatch {
|
|
330
655
|
name: string;
|
|
331
656
|
score: number;
|
|
@@ -340,6 +665,15 @@ export interface SearchMatch {
|
|
|
340
665
|
* requested names matched nothing instead of silently dropping them.
|
|
341
666
|
*/
|
|
342
667
|
export function selectedNames(query: string): string[] | undefined {
|
|
668
|
+
return selectedRequests(query)?.map((request) => request.name);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* {@link selectedNames} with each name as the model wrote it, so a "not found"
|
|
673
|
+
* note quotes the model's spelling: `select:find,grep,glob` maps `glob` to
|
|
674
|
+
* `find`, and quoting the mapped names read "find, grep, find".
|
|
675
|
+
*/
|
|
676
|
+
export function selectedRequests(query: string): { name: string; asWritten: string }[] | undefined {
|
|
343
677
|
const trimmed = query.trim();
|
|
344
678
|
if (!trimmed.toLowerCase().startsWith("select:")) return undefined;
|
|
345
679
|
return trimmed
|
|
@@ -350,7 +684,7 @@ export function selectedNames(query: string): string[] | undefined {
|
|
|
350
684
|
// normalizeToolName lowercases ordinary names but PRESERVES case for
|
|
351
685
|
// `mcp__…` names; the trailing toLowerCase keeps select: case-insensitive
|
|
352
686
|
// for those too. Don't drop it as "redundant".
|
|
353
|
-
.map((
|
|
687
|
+
.map((asWritten) => ({ name: normalizeToolName(asWritten).toLowerCase(), asWritten }));
|
|
354
688
|
}
|
|
355
689
|
|
|
356
690
|
/**
|
|
@@ -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
|
+
}
|