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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control characters in text that reaches the terminal (pure).
|
|
3
|
+
*
|
|
4
|
+
* Tool results, tool arguments, notification bodies, MCP results, fetched
|
|
5
|
+
* pages and the session title all carry text One Code did not write. Sent to
|
|
6
|
+
* the terminal raw, an escape sequence in it acts: OSC 0 retitles the
|
|
7
|
+
* terminal, OSC 52 rewrites the clipboard in kitty, WezTerm, Alacritty and
|
|
8
|
+
* Windows Terminal, cursor movement rewrites the rows above, SGR conceal hides
|
|
9
|
+
* text, and a carriage return overwrites the start of the row.
|
|
10
|
+
*
|
|
11
|
+
* `sanitizeDisplayText` applies pi's own rule for a tool result
|
|
12
|
+
* (`render-utils.js getTextOutput`: `stripAnsi`, then `sanitizeBinaryOutput`,
|
|
13
|
+
* then drop `\r`), and also drops C1 controls, which some terminals act on.
|
|
14
|
+
* It keeps `\n` and `\t` and never touches styling One Code adds afterwards,
|
|
15
|
+
* because it runs on the text before painting. `escapeControlText` shows each
|
|
16
|
+
* control character as a visible `\xHH` escape instead, for consent dialogs,
|
|
17
|
+
* where the user must see exactly what would run. `sanitizeTitle` is for the
|
|
18
|
+
* one-line terminal title.
|
|
19
|
+
*
|
|
20
|
+
* Display only: the model-facing text is never passed through these.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// pi's `ansiRegex` (utils/ansi.js, from chalk's ansi-regex) matches an OSC up
|
|
24
|
+
// to the first ST, else CSI and the related ESC forms. Its OSC branch is a lazy
|
|
25
|
+
// scan to the end of the text from every `ESC ]`, quadratic on many
|
|
26
|
+
// unterminated ones (50,000 took 1.8 s), so `stripAnsi` walks the text once
|
|
27
|
+
// with the same alternation and remembers where no terminator is left.
|
|
28
|
+
const CSI_AT = /[\u001B\u009B][[\]()#;?]*(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]/y;
|
|
29
|
+
|
|
30
|
+
/** The index just past the first string terminator (BEL, `ESC \`, or C1 ST) at or after `from`, else -1. */
|
|
31
|
+
function stringTerminatorEnd(text: string, from: number): number {
|
|
32
|
+
for (let i = from; i < text.length; i++) {
|
|
33
|
+
const ch = text.charCodeAt(i);
|
|
34
|
+
if (ch === 0x07 || ch === 0x9c) return i + 1;
|
|
35
|
+
if (ch === 0x1b && text.charCodeAt(i + 1) === 0x5c) return i + 2;
|
|
36
|
+
}
|
|
37
|
+
return -1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** `text.replace(ansiRegex, "")` in linear time: the same matches, left to right. */
|
|
41
|
+
function stripAnsi(text: string): string {
|
|
42
|
+
let out = "";
|
|
43
|
+
let from = 0;
|
|
44
|
+
// No string terminator exists at or after this index (a failed search holds for every later start).
|
|
45
|
+
let noTerminatorFrom = Number.POSITIVE_INFINITY;
|
|
46
|
+
let i = 0;
|
|
47
|
+
while (i < text.length) {
|
|
48
|
+
const ch = text.charCodeAt(i);
|
|
49
|
+
if (ch !== 0x1b && ch !== 0x9b) {
|
|
50
|
+
i++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (ch === 0x1b && text.charCodeAt(i + 1) === 0x5d && i + 2 < noTerminatorFrom) {
|
|
54
|
+
const end = stringTerminatorEnd(text, i + 2);
|
|
55
|
+
if (end !== -1) {
|
|
56
|
+
out += text.slice(from, i);
|
|
57
|
+
from = i = end;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
noTerminatorFrom = i + 2;
|
|
61
|
+
}
|
|
62
|
+
CSI_AT.lastIndex = i;
|
|
63
|
+
const match = CSI_AT.exec(text);
|
|
64
|
+
if (match) {
|
|
65
|
+
out += text.slice(from, i);
|
|
66
|
+
from = i = i + match[0].length;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
return out + text.slice(from);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** C0 controls other than tab and newline, DEL, C1 controls, and U+FFF9 to U+FFFB (interlinear annotation). */
|
|
75
|
+
const UNSAFE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f-]/g;
|
|
76
|
+
const HAS_UNSAFE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f-]/;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Text safe to write to the terminal: escape sequences removed, then every
|
|
80
|
+
* control character other than `\n` and `\t` (carriage returns included, as
|
|
81
|
+
* pi does). Plain text comes back unchanged (same string).
|
|
82
|
+
*/
|
|
83
|
+
export function sanitizeDisplayText(text: string): string {
|
|
84
|
+
if (!HAS_UNSAFE.test(text)) return text;
|
|
85
|
+
return stripAnsi(text).replace(UNSAFE, "");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* `label` as an OSC 8 hyperlink to `url` (pi-tui's `hyperlink`). Terminals
|
|
90
|
+
* without OSC 8, and tmux without its `hyperlinks` feature, show the label
|
|
91
|
+
* alone, so the label must read on its own. Both parts are sanitized, so
|
|
92
|
+
* neither can end the sequence early.
|
|
93
|
+
*/
|
|
94
|
+
export function terminalLink(label: string, url: string): string {
|
|
95
|
+
return `\x1b]8;;${sanitizeDisplayText(url)}\x1b\\${sanitizeDisplayText(label)}\x1b]8;;\x1b\\`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Every control character other than `\n` and `\t` shown as a visible
|
|
100
|
+
* `\xHH` escape (`\x1b[8m` for an SGR conceal), so nothing in the text can
|
|
101
|
+
* act on the terminal or hide part of itself.
|
|
102
|
+
*/
|
|
103
|
+
export function escapeControlText(text: string): string {
|
|
104
|
+
if (!HAS_UNSAFE.test(text)) return text;
|
|
105
|
+
return text.replace(UNSAFE, (ch) => {
|
|
106
|
+
const code = ch.charCodeAt(0);
|
|
107
|
+
return code > 0xff ? `\\u${code.toString(16).padStart(4, "0")}` : `\\x${code.toString(16).padStart(2, "0")}`;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** One line for the terminal title: escape sequences and every control character removed, whitespace collapsed. */
|
|
112
|
+
export function sanitizeTitle(text: string): string {
|
|
113
|
+
return sanitizeDisplayText(text).replace(/\s+/g, " ").trim();
|
|
114
|
+
}
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
* 2-vs-1 decision is byte-identical to pi's without importing pi or adding a
|
|
18
18
|
* package. Grapheme segmentation and the mark/emoji classification use V8's
|
|
19
19
|
* built-in `Intl.Segmenter` and Unicode property regexes.
|
|
20
|
+
*
|
|
21
|
+
* Synced with pi-tui 0.99.2. `text-width.test.ts` compares this measure with
|
|
22
|
+
* pi-tui's over every code point to U+3FFFF and over random strings with
|
|
23
|
+
* escapes, so a pi bump that changes pi-tui's measure fails the suite.
|
|
20
24
|
*/
|
|
21
25
|
|
|
22
26
|
// --- East Asian wide/fullwidth lookup (get-east-asian-width v1.6.0, Unicode 16) ---
|
|
@@ -127,6 +131,7 @@ export function graphemeWidth(segment: string): number {
|
|
|
127
131
|
} else if (!nonPrintingCharRegex.test(char)) {
|
|
128
132
|
const c = char.codePointAt(0)!;
|
|
129
133
|
if (followsMark || (c >= 0xff00 && c <= 0xffef)) width += eastAsianWidth(c);
|
|
134
|
+
else if (c === 0x0e33 || c === 0x0eb3) width += 1; // Thai SARA AM, Lao AM
|
|
130
135
|
followsMark = false;
|
|
131
136
|
}
|
|
132
137
|
}
|
|
@@ -134,36 +139,35 @@ export function graphemeWidth(segment: string): number {
|
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
// --- ANSI / OSC / APC escape handling ---
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
142
|
+
// The length of the terminal control sequence starting at index `i`, or 0.
|
|
143
|
+
// Mirrors pi-tui's `extractAnsiCode` exactly, because pi-tui validates a line
|
|
144
|
+
// with it: a CSI runs to the first `m`, `G`, `K`, `H` or `J`; an OSC or APC
|
|
145
|
+
// runs to BEL or ST; an unterminated sequence, or any other ESC form, is not
|
|
146
|
+
// a sequence, so its bytes count as text. A broader rule here measured such
|
|
147
|
+
// text narrower than pi-tui and could emit a line pi-tui rejects. Display text
|
|
148
|
+
// never carries those forms once `sanitizeDisplayText` has run.
|
|
140
149
|
function escapeAt(s: string, i: number): number {
|
|
141
150
|
if (s.charCodeAt(i) !== 0x1b) return 0;
|
|
142
151
|
const next = s[i + 1];
|
|
143
152
|
if (next === "[") {
|
|
144
|
-
// CSI: ESC [ ... final byte 0x40–0x7E
|
|
145
153
|
let j = i + 2;
|
|
146
|
-
while (j < s.length)
|
|
147
|
-
|
|
148
|
-
j++;
|
|
149
|
-
if (c >= 0x40 && c <= 0x7e) return j - i;
|
|
150
|
-
}
|
|
151
|
-
return s.length - i;
|
|
154
|
+
while (j < s.length && !CSI_END.test(s[j])) j++;
|
|
155
|
+
return j < s.length ? j + 1 - i : 0;
|
|
152
156
|
}
|
|
153
|
-
if (next === "]" || next === "_"
|
|
154
|
-
// OSC / APC / DCS / PM: ESC <intro> ... terminated by BEL or ST (ESC \)
|
|
157
|
+
if (next === "]" || next === "_") {
|
|
155
158
|
let j = i + 2;
|
|
156
159
|
while (j < s.length) {
|
|
157
160
|
if (s.charCodeAt(j) === 0x07) return j + 1 - i; // BEL
|
|
158
161
|
if (s.charCodeAt(j) === 0x1b && s[j + 1] === "\\") return j + 2 - i; // ST
|
|
159
162
|
j++;
|
|
160
163
|
}
|
|
161
|
-
return
|
|
164
|
+
return 0;
|
|
162
165
|
}
|
|
163
|
-
|
|
164
|
-
return next ? 2 : 1;
|
|
166
|
+
return 0;
|
|
165
167
|
}
|
|
166
168
|
|
|
169
|
+
const CSI_END = /[mGKHJ]/;
|
|
170
|
+
|
|
167
171
|
const PRINTABLE_ASCII = /^[\x20-\x7e]*$/;
|
|
168
172
|
|
|
169
173
|
/**
|
|
@@ -174,6 +178,9 @@ const PRINTABLE_ASCII = /^[\x20-\x7e]*$/;
|
|
|
174
178
|
export function visibleWidth(text: string): number {
|
|
175
179
|
if (text.length === 0) return 0;
|
|
176
180
|
if (PRINTABLE_ASCII.test(text)) return text.length;
|
|
181
|
+
// pi-tui expands tabs before segmenting, so a mark after a tab joins the
|
|
182
|
+
// last space's cluster; doing the same keeps the two measures identical.
|
|
183
|
+
if (text.includes("\t")) text = text.replace(/\t/g, " ");
|
|
177
184
|
let clean = "";
|
|
178
185
|
for (let i = 0; i < text.length; ) {
|
|
179
186
|
const esc = escapeAt(text, i);
|
|
@@ -204,6 +211,29 @@ export function sliceColumns(text: string, maxCols: number): { text: string; wid
|
|
|
204
211
|
return fitPainted(text, maxCols);
|
|
205
212
|
}
|
|
206
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Longest suffix of PLAIN (escape-free) text whose visible width is ≤
|
|
216
|
+
* `maxCols`, cut on a grapheme boundary: the counterpart of `sliceColumns` for
|
|
217
|
+
* text that keeps its tail, such as a path whose deepest folders matter most.
|
|
218
|
+
*/
|
|
219
|
+
export function tailColumns(text: string, maxCols: number): { text: string; width: number } {
|
|
220
|
+
if (maxCols <= 0) return { text: "", width: 0 };
|
|
221
|
+
if (PRINTABLE_ASCII.test(text)) {
|
|
222
|
+
const tail = text.slice(Math.max(0, text.length - maxCols));
|
|
223
|
+
return { text: tail, width: tail.length };
|
|
224
|
+
}
|
|
225
|
+
const graphemes = [...graphemeSegmenter.segment(text)].map((s) => s.segment);
|
|
226
|
+
let width = 0;
|
|
227
|
+
let start = graphemes.length;
|
|
228
|
+
while (start > 0) {
|
|
229
|
+
const w = graphemeWidth(graphemes[start - 1]);
|
|
230
|
+
if (width + w > maxCols) break;
|
|
231
|
+
width += w;
|
|
232
|
+
start--;
|
|
233
|
+
}
|
|
234
|
+
return { text: graphemes.slice(start).join(""), width };
|
|
235
|
+
}
|
|
236
|
+
|
|
207
237
|
/**
|
|
208
238
|
* Hard-wrap text into chunks each ≤ `columns` visible columns, on grapheme
|
|
209
239
|
* boundaries. A single glyph wider than the whole budget occupies its own chunk
|
|
@@ -253,7 +283,8 @@ export function fitPainted(line: string, maxCols: number): { text: string; width
|
|
|
253
283
|
continue;
|
|
254
284
|
}
|
|
255
285
|
// Consume the visible run up to the next escape, segment it into graphemes.
|
|
256
|
-
|
|
286
|
+
// An ESC that starts no sequence is text (width 0), so the run starts past it.
|
|
287
|
+
let end = line.charCodeAt(i) === 0x1b ? i + 1 : i;
|
|
257
288
|
while (end < line.length && line.charCodeAt(end) !== 0x1b) end++;
|
|
258
289
|
for (const { segment } of graphemeSegmenter.segment(line.slice(i, end))) {
|
|
259
290
|
const w = graphemeWidth(segment);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An injectable timer (pure), so an idle-timer state machine is unit-tested
|
|
3
|
+
* with fake timers, and the real adapter both recap and idle-compact run on.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface TimerOps {
|
|
7
|
+
/** Schedule `cb` after `ms`; return a handle for `clear`. */
|
|
8
|
+
set(cb: () => void, ms: number): unknown;
|
|
9
|
+
clear(handle: unknown): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** `setTimeout` with the handle unref'd, so a pending idle timer never keeps a one-shot process alive. */
|
|
13
|
+
export const unrefTimers: TimerOps = {
|
|
14
|
+
set: (cb, ms) => {
|
|
15
|
+
const handle = setTimeout(cb, ms);
|
|
16
|
+
handle.unref?.();
|
|
17
|
+
return handle;
|
|
18
|
+
},
|
|
19
|
+
clear: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
|
|
20
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code's `tool_addition` for tools that arrive mid-session (pure). One
|
|
3
|
+
* Code renders the arrival as the deferred-tool addendum on the request's
|
|
4
|
+
* tail (lib/deferred.ts deferredAddendumText), which reads correctly on any model.
|
|
5
|
+
* On a first-party model that takes mid-conversation tool changes, the
|
|
6
|
+
* addendum leaves the message that carries it for a system message right
|
|
7
|
+
* after it: Claude Code's sentence and names, then one reference-form
|
|
8
|
+
* `tool_addition` block per tool, each declared deferred in `tools`. The model
|
|
9
|
+
* can then call the tool without a `tool_search` round trip (findings §55
|
|
10
|
+
* P1). Elsewhere the addendum stays as it is.
|
|
11
|
+
*
|
|
12
|
+
* An addendum is matched as a whole block, never inside other text: in a tool
|
|
13
|
+
* result at `context` time, while the result's blocks are still separate and
|
|
14
|
+
* keyed by its call id (`liftAddenda`), and on a user message as a whole part
|
|
15
|
+
* (a one-shot pinned there).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { MID_CONVERSATION_SYSTEM_BETA, MID_CONVERSATION_TOOL_CHANGES_BETA, reseatMessageMark } from "./anthropic-payload.ts";
|
|
19
|
+
import { byWireCallId, systemRoleLayout, type WireMessage, type WirePart, withoutParts } from "./system-role.ts";
|
|
20
|
+
|
|
21
|
+
/** The first line of the stored addendum; the names follow, one per line. */
|
|
22
|
+
export const ADDENDUM_HEAD =
|
|
23
|
+
"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.";
|
|
24
|
+
|
|
25
|
+
/** Claude Code's sentence before the names, in its tool-addition message. */
|
|
26
|
+
export const TOOLS_AVAILABLE = "The following tools just became available and are ready to use:";
|
|
27
|
+
|
|
28
|
+
/** Claude Code's betas for a reference-form tool addition in a system message. */
|
|
29
|
+
export const TOOL_ADDITION_BETAS = [MID_CONVERSATION_SYSTEM_BETA, MID_CONVERSATION_TOOL_CHANGES_BETA];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether the model takes Claude Code's reference-form `tool_addition`:
|
|
33
|
+
* first-party Anthropic on the system-role layout, with pi's catalog flag for
|
|
34
|
+
* mid-conversation tool changes (Opus 5.5, Sonnet 5.5 and Fable 5.1 among them).
|
|
35
|
+
*/
|
|
36
|
+
export function supportsToolAdditions(model: { id?: string; provider?: string; api?: string; compat?: unknown } | undefined): boolean {
|
|
37
|
+
if (!model || model.provider !== "anthropic" || systemRoleLayout({ id: model.id ?? "", api: model.api ?? "", compat: model.compat }) !== "anthropic") return false;
|
|
38
|
+
return (model.compat as { supportsMidConvoToolChanges?: boolean } | undefined)?.supportsMidConvoToolChanges === true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const OPEN = `<system-reminder>\n${ADDENDUM_HEAD}\n`;
|
|
42
|
+
const CLOSE = "\n</system-reminder>";
|
|
43
|
+
|
|
44
|
+
/** The names a whole framed addendum block lists; undefined for any other text. */
|
|
45
|
+
export function addendumNames(text: string): string[] | undefined {
|
|
46
|
+
if (!text.startsWith(OPEN) || !text.endsWith(CLOSE)) return undefined;
|
|
47
|
+
return text
|
|
48
|
+
.slice(OPEN.length, -CLOSE.length)
|
|
49
|
+
.split("\n")
|
|
50
|
+
.filter((line) => line.length > 0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type StoredBlock = { type?: string; text?: string };
|
|
54
|
+
type StoredMessage = { role: string; toolCallId?: string; content?: unknown };
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The context-time step: each tool result's addendum block leaves the result,
|
|
58
|
+
* its names kept under the call id, for the payload step to send as an
|
|
59
|
+
* addition after that result. Returns the input when nothing changed.
|
|
60
|
+
*/
|
|
61
|
+
export function liftAddenda<T extends StoredMessage>(messages: T[]): { messages: T[]; byCall: Map<string, string[]> } {
|
|
62
|
+
const byCall = new Map<string, string[]>();
|
|
63
|
+
let changed = false;
|
|
64
|
+
const out = messages.map((message) => {
|
|
65
|
+
if (message.role !== "toolResult" || !Array.isArray(message.content) || !message.toolCallId) return message;
|
|
66
|
+
const names: string[] = [];
|
|
67
|
+
const kept = (message.content as StoredBlock[]).filter((block) => {
|
|
68
|
+
const found = block?.type === "text" && typeof block.text === "string" ? addendumNames(block.text) : undefined;
|
|
69
|
+
if (found) names.push(...found);
|
|
70
|
+
return !found;
|
|
71
|
+
});
|
|
72
|
+
if (names.length === 0) return message;
|
|
73
|
+
changed = true;
|
|
74
|
+
byCall.set(message.toolCallId, names);
|
|
75
|
+
return { ...message, content: kept };
|
|
76
|
+
});
|
|
77
|
+
return { messages: changed ? out : messages, byCall };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The Anthropic payload with a tool-addition system message after each user
|
|
82
|
+
* message that answers a call `liftAddenda` lifted an addendum from, or that
|
|
83
|
+
* carries a pinned addendum part (removed). Also returns every name added: they
|
|
84
|
+
* must be declared deferred in `tools`. With `references: false` the message
|
|
85
|
+
* is Claude Code's sentence and names alone, for a request whose tools cannot
|
|
86
|
+
* be referenced (OAuth renames them): the tools are active, so the model can
|
|
87
|
+
* still call them. Undefined when there is none.
|
|
88
|
+
*/
|
|
89
|
+
export function withToolAdditions(
|
|
90
|
+
payload: Record<string, unknown>,
|
|
91
|
+
byCall: ReadonlyMap<string, readonly string[]>,
|
|
92
|
+
references = true,
|
|
93
|
+
): { payload: Record<string, unknown>; names: string[] } | undefined {
|
|
94
|
+
const messages = payload.messages;
|
|
95
|
+
if (!Array.isArray(messages)) return undefined;
|
|
96
|
+
const out: WireMessage[] = [];
|
|
97
|
+
const all = new Set<string>();
|
|
98
|
+
for (const message of messages as WireMessage[]) {
|
|
99
|
+
if (message?.role !== "user" || !Array.isArray(message.content)) {
|
|
100
|
+
out.push(message);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const names: string[] = [];
|
|
104
|
+
const parts = message.content as WirePart[];
|
|
105
|
+
for (const part of parts) {
|
|
106
|
+
const called = part?.type === "tool_result" && typeof part.tool_use_id === "string" ? byWireCallId(byCall, part.tool_use_id) : undefined;
|
|
107
|
+
if (called) names.push(...called);
|
|
108
|
+
}
|
|
109
|
+
const pinnedNames = (part: WirePart) => (part?.type === "text" && typeof part.text === "string" ? addendumNames(part.text) : undefined);
|
|
110
|
+
// A pinned addendum part goes, keeping pi's cache mark; a message it would empty keeps it.
|
|
111
|
+
const kept = withoutParts(parts, (part) => pinnedNames(part) !== undefined) ?? parts;
|
|
112
|
+
if (kept !== parts) for (const part of parts) names.push(...(pinnedNames(part) ?? []));
|
|
113
|
+
if (names.length === 0) {
|
|
114
|
+
out.push(message);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const unique = [...new Set(names)];
|
|
118
|
+
for (const name of unique) all.add(name);
|
|
119
|
+
out.push(kept.length === parts.length ? message : { ...message, content: kept }, {
|
|
120
|
+
role: "system",
|
|
121
|
+
content: [
|
|
122
|
+
{ type: "text", text: [TOOLS_AVAILABLE, ...unique].join("\n") },
|
|
123
|
+
...(references ? unique.map((name) => ({ type: "tool_addition", tool: { type: "tool_reference", name } })) : []),
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (all.size === 0) return undefined;
|
|
128
|
+
return { payload: { ...payload, messages: reseatMessageMark(out) }, names: [...all] };
|
|
129
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A streamed tool call's damage. `corrupt` is certain (not one JSON value, a
|
|
3
|
+
* shell command ending inside an open double quote): the call is blocked and
|
|
4
|
+
* the turn stopped. `suspect` has the shape of a cut but also of a valid
|
|
5
|
+
* command (an option followed by one trailing blank): the call goes back to the
|
|
6
|
+
* model once, and a repeat in the same run counts as corrupt.
|
|
7
|
+
*/
|
|
8
|
+
export interface ToolCallDamage {
|
|
9
|
+
kind: "corrupt" | "suspect";
|
|
10
|
+
reason: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// The last word is an option followed by one blank, or a long option with an
|
|
14
|
+
// empty `=` value: `grep -rn "x"` and `--include="*.py"` cut at the escaped
|
|
15
|
+
// quote (findings §61, Morph). A bare `FOO=` is a valid assignment.
|
|
16
|
+
const CUT_AFTER_OPTION = /(?:^|\s)(-{1,2}[A-Za-z][\w-]*)[ \t]$|(?:^|\s)(--[A-Za-z][\w-]*=)$/;
|
|
17
|
+
// Heredocs and here-strings (bash `<<`, `<<<`; PowerShell `@"…"@`, `@'…'@`)
|
|
18
|
+
// carry quotes the scan below cannot pair, so it does not judge them.
|
|
19
|
+
const HERE_DOCUMENT = /<<|@["']/;
|
|
20
|
+
|
|
21
|
+
/** Inspect one tool call's raw streamed arguments before pi's tolerant parsing hides damage. */
|
|
22
|
+
export function toolCallCorruptionReason(toolName: string, rawArguments: string): ToolCallDamage | undefined {
|
|
23
|
+
// A tool with no parameters may stream no argument text at all; pi reads it as {}.
|
|
24
|
+
if (rawArguments.trim() === "") return;
|
|
25
|
+
let args: unknown;
|
|
26
|
+
try {
|
|
27
|
+
args = JSON.parse(rawArguments);
|
|
28
|
+
} catch {
|
|
29
|
+
return { kind: "corrupt", reason: "The raw tool-call arguments are not exactly one JSON value." };
|
|
30
|
+
}
|
|
31
|
+
if (toolName !== "bash" && toolName !== "powershell" && toolName !== "monitor") return;
|
|
32
|
+
if (!args || typeof args !== "object" || !("command" in args) || typeof args.command !== "string") return;
|
|
33
|
+
const cut = CUT_AFTER_OPTION.exec(args.command);
|
|
34
|
+
if (cut) {
|
|
35
|
+
return {
|
|
36
|
+
kind: "suspect",
|
|
37
|
+
reason: `The shell command ends with "${cut[1] ?? cut[2]}" and nothing after it, the shape of arguments cut off before a quoted string.`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const command = args.command.trimEnd();
|
|
41
|
+
if (!command.endsWith('"') || HERE_DOCUMENT.test(command)) return;
|
|
42
|
+
|
|
43
|
+
let singleQuoted = false;
|
|
44
|
+
let doubleQuoted = false;
|
|
45
|
+
const escape = toolName === "powershell" ? "`" : "\\";
|
|
46
|
+
for (let i = 0; i < command.length; i++) {
|
|
47
|
+
const char = command[i];
|
|
48
|
+
if (singleQuoted) {
|
|
49
|
+
if (char === "'") singleQuoted = false;
|
|
50
|
+
} else if (char === escape) {
|
|
51
|
+
i++;
|
|
52
|
+
} else if (char === "'" && !doubleQuoted) {
|
|
53
|
+
singleQuoted = true;
|
|
54
|
+
} else if (char === '"') {
|
|
55
|
+
doubleQuoted = !doubleQuoted;
|
|
56
|
+
} else if (char === "#" && !doubleQuoted && (i === 0 || /[\s;&|(]/.test(command[i - 1]))) {
|
|
57
|
+
// A comment runs to the end of its line.
|
|
58
|
+
const newline = command.indexOf("\n", i);
|
|
59
|
+
if (newline === -1) break;
|
|
60
|
+
i = newline;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (doubleQuoted) return { kind: "corrupt", reason: "The shell command ends with an unbalanced double quote, indicating truncated tool-call arguments." };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// GLM's native tool-call tags, which a broken upstream parser leaks into the JSON arguments.
|
|
67
|
+
const TOOL_CALL_MARKUP = /<\/arg_value>\s{0,8}<arg_key>|<\/arg_key>\s{0,8}<arg_value>|<\/tool_call>\s{0,8}<tool_call>/g;
|
|
68
|
+
const LONGEST_MARKUP = 40;
|
|
69
|
+
|
|
70
|
+
/** The number of markup matches that marks a streaming call as leaked. */
|
|
71
|
+
export const TOOL_CALL_MARKUP_LIMIT = 2;
|
|
72
|
+
/**
|
|
73
|
+
* The same inside file content (write, edit, notebook_edit), which can quote
|
|
74
|
+
* the markup legitimately (this repo's tests do); a leak there repeats without
|
|
75
|
+
* end and passes it within a few thousand characters.
|
|
76
|
+
*/
|
|
77
|
+
export const CONTENT_MARKUP_LIMIT = 50;
|
|
78
|
+
|
|
79
|
+
// A string value under one of these keys is file content.
|
|
80
|
+
const CONTENT_KEY = /"(?:content|oldText|newText|new_source)"\s*:\s*$/;
|
|
81
|
+
|
|
82
|
+
/** Whether `index` sits in a string value under a content key: the nearest unescaped quote before it opens that value. */
|
|
83
|
+
function inContentValue(text: string, index: number): boolean {
|
|
84
|
+
// lastIndexOf clamps a negative start to 0, so stop before it can repeat a quote at 0.
|
|
85
|
+
for (let quote = index; quote > 0; ) {
|
|
86
|
+
quote = text.lastIndexOf('"', quote - 1);
|
|
87
|
+
if (quote < 0) break;
|
|
88
|
+
let slashes = 0;
|
|
89
|
+
while (text[quote - 1 - slashes] === "\\") slashes++;
|
|
90
|
+
if (slashes % 2 === 0) return CONTENT_KEY.test(text.slice(Math.max(0, quote - 32), quote));
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Count native tool-call markup in `text` from `from` on, for a call whose
|
|
97
|
+
* arguments are still streaming, apart (`contentCount`) inside file content;
|
|
98
|
+
* `next` is where the following scan resumes, so each delta is scanned once.
|
|
99
|
+
*/
|
|
100
|
+
export function scanToolCallMarkup(text: string, from: number): { count: number; contentCount: number; next: number } {
|
|
101
|
+
TOOL_CALL_MARKUP.lastIndex = from;
|
|
102
|
+
let count = 0;
|
|
103
|
+
let contentCount = 0;
|
|
104
|
+
let next = from;
|
|
105
|
+
for (let match = TOOL_CALL_MARKUP.exec(text); match; match = TOOL_CALL_MARKUP.exec(text)) {
|
|
106
|
+
if (inContentValue(text, match.index)) contentCount++;
|
|
107
|
+
else count++;
|
|
108
|
+
next = TOOL_CALL_MARKUP.lastIndex;
|
|
109
|
+
}
|
|
110
|
+
return { count, contentCount, next: Math.max(next, text.length - LONGEST_MARKUP) };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Blank space a tool call may stream after its last argument character before
|
|
115
|
+
* the stream counts as runaway. Only a run holding a raw tab or newline
|
|
116
|
+
* counts: inside a JSON string those are escaped, so such a run is padding
|
|
117
|
+
* between tokens, never argument content (a string may hold any run of spaces).
|
|
118
|
+
*/
|
|
119
|
+
export const RUNAWAY_PADDING_LIMIT = 10_000;
|
|
120
|
+
|
|
121
|
+
export interface TrailingPadding {
|
|
122
|
+
run: number;
|
|
123
|
+
structural: boolean;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The trailing blank run after one more streamed delta. */
|
|
127
|
+
export function trailingPadding(previous: TrailingPadding | undefined, delta: string): TrailingPadding {
|
|
128
|
+
const last = delta.search(/\S\s*$/);
|
|
129
|
+
if (last < 0) {
|
|
130
|
+
return { run: (previous?.run ?? 0) + delta.length, structural: (previous?.structural ?? false) || /[\t\n\r]/.test(delta) };
|
|
131
|
+
}
|
|
132
|
+
const tail = delta.slice(last + 1);
|
|
133
|
+
return { run: tail.length, structural: /[\t\n\r]/.test(tail) };
|
|
134
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a file tool's `path` argument exactly as pi's own `read`, `edit` and
|
|
3
|
+
* `write` do (`resolveToCwd` in pi-coding-agent's `core/tools/path-utils.js`,
|
|
4
|
+
* over `utils/paths.js normalizePath`), vendored because the package does not
|
|
5
|
+
* export it. Anything that reasons about the file a pi tool will touch (the
|
|
6
|
+
* file tracker's read-before-write and stale-edit guards, our notebook tool,
|
|
7
|
+
* the worktree rewrite of relative paths) must land on the same file, or a spelling pi accepts (`~/x`, `@x`,
|
|
8
|
+
* `file:///…`, a non-breaking space, Git Bash's `/c/…` on Windows) slips past
|
|
9
|
+
* the guard (TOOLS-REVIEW-2026-09-26 M1). Pure: no pi imports.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors pi 0.87.1 to 0.99.2: Unicode spaces fold to a plain space, one
|
|
12
|
+
* leading `@` is dropped, on Windows a Git Bash/MSYS/Cygwin/WSL drive path
|
|
13
|
+
* becomes `C:\…`, `~` and `~/` expand to the home directory (and `~\` on
|
|
14
|
+
* Windows), a `file://` URL becomes its path, and a relative result resolves
|
|
15
|
+
* against `cwd`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { homedir } from "node:os";
|
|
19
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
|
|
22
|
+
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
23
|
+
|
|
24
|
+
/** pi's `normalizeWindowsShellPath`: `/c/x`, `/mnt/c/x`, `/cygdrive/c/x` to `C:\x`. */
|
|
25
|
+
function windowsShellPath(path: string): string {
|
|
26
|
+
if (!path.startsWith("/") || path.startsWith("//") || path.includes("\\")) return path;
|
|
27
|
+
const match = path.match(/^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i);
|
|
28
|
+
if (!match) return path;
|
|
29
|
+
return `${match[1].toUpperCase()}:\\${match[2]?.replaceAll("/", "\\") ?? ""}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ToolPathOptions {
|
|
33
|
+
home?: string;
|
|
34
|
+
platform?: NodeJS.Platform;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* `raw` as pi's file tools read it before resolving it against a cwd: the
|
|
39
|
+
* steps above except the last. Callers that must tell a relative path from an
|
|
40
|
+
* absolute one the way pi will (the worktree rewrite) judge this, not `raw`.
|
|
41
|
+
*/
|
|
42
|
+
export function normalizeToolPath(raw: string, options: ToolPathOptions = {}): string {
|
|
43
|
+
const platform = options.platform ?? process.platform;
|
|
44
|
+
const home = options.home ?? homedir();
|
|
45
|
+
let path = raw.replace(UNICODE_SPACES, " ");
|
|
46
|
+
if (path.startsWith("@")) path = path.slice(1);
|
|
47
|
+
if (platform === "win32") path = windowsShellPath(path);
|
|
48
|
+
if (path === "~") return home;
|
|
49
|
+
if (path.startsWith("~/") || (platform === "win32" && path.startsWith("~\\"))) return join(home, path.slice(2));
|
|
50
|
+
if (/^file:\/\//.test(path)) {
|
|
51
|
+
// A URL this platform cannot map (`file:///x` on Windows has no drive)
|
|
52
|
+
// stays as written: pi's tool then fails on it, and a guard must not throw.
|
|
53
|
+
try {
|
|
54
|
+
return fileURLToPath(path);
|
|
55
|
+
} catch {
|
|
56
|
+
return path;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return path;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The absolute path a pi file tool resolves `raw` to from `cwd`. */
|
|
63
|
+
export function resolveToolPath(raw: string, cwd: string, options: ToolPathOptions = {}): string {
|
|
64
|
+
const path = normalizeToolPath(raw, options);
|
|
65
|
+
return isAbsolute(path) ? resolve(path) : resolve(cwd, path);
|
|
66
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool descriptions that follow the session, as Claude Code's do.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code sends its strongest models short tool descriptions and its
|
|
5
|
+
* weakest the long ones it wrote for Haiku 4.5; its Bash text also changes
|
|
6
|
+
* with the permission mode. One Code keys the same split on the model's tier
|
|
7
|
+
* (`working-docs/decisions/system-prompt.md`, "Four registers"): frontier and
|
|
8
|
+
* workhorse get the short forms, cheap and tiny the long ones, on every
|
|
9
|
+
* provider.
|
|
10
|
+
*
|
|
11
|
+
* pi copies a tool's description when the tool is registered, so a tool whose
|
|
12
|
+
* text depends on the session is registered again when that text changes
|
|
13
|
+
* (`registerVariantTool`). This can also move a tool in pi's active tool
|
|
14
|
+
* order, so only a model-tier change may re-register after the first request.
|
|
15
|
+
* The tier changes with the model, which starts a new provider cache anyway.
|
|
16
|
+
* Permission-mode variants freeze at the first request (bash/index.ts); live
|
|
17
|
+
* changes ride reminders and the permission gate, never the cached tool list.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
21
|
+
import type { PromptTier } from "./model-tier.ts";
|
|
22
|
+
import { sessionModelTier } from "./session-model-tier.ts";
|
|
23
|
+
|
|
24
|
+
/** Which of Claude Code's description forms a tool carries. */
|
|
25
|
+
export type DescriptionForm = "short" | "long";
|
|
26
|
+
|
|
27
|
+
/** Claude Code's short forms on frontier and workhorse; its long forms on cheap and tiny. */
|
|
28
|
+
export function descriptionForm(tier: PromptTier): DescriptionForm {
|
|
29
|
+
return tier === "cheap" || tier === "tiny" ? "long" : "short";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Every tool definition shape registerTool accepts; the concrete generics differ per tool.
|
|
33
|
+
type AnyToolDefinition = ToolDefinition<any, any, any>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Register `define(initial)` now, and return a setter that registers the tool
|
|
37
|
+
* again with `define(next)` when `next` differs from the variant in force.
|
|
38
|
+
* Repeated calls with the same variant do nothing, so callers can apply the
|
|
39
|
+
* session's facts on every event that might change them.
|
|
40
|
+
*/
|
|
41
|
+
export function registerVariantTool<V>(pi: ExtensionAPI, initial: V, define: (variant: V) => AnyToolDefinition): (variant: V) => void {
|
|
42
|
+
let current = initial;
|
|
43
|
+
pi.registerTool(define(initial));
|
|
44
|
+
return (next: V) => {
|
|
45
|
+
if (next === current) return;
|
|
46
|
+
current = next;
|
|
47
|
+
pi.registerTool(define(next));
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Register `tool` with the description `describe` gives the session model's
|
|
53
|
+
* form, following it through the session: the one call each tool with a
|
|
54
|
+
* short and a long form makes.
|
|
55
|
+
*/
|
|
56
|
+
export function registerFormTool(pi: ExtensionAPI, tool: AnyToolDefinition, describe: (form: DescriptionForm) => string): void {
|
|
57
|
+
followDescriptionForm(pi, registerVariantTool<DescriptionForm>(pi, "short", (form) => ({ ...tool, description: describe(form) })));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Apply the frozen session tier's form (and the tier it came from) only at session and model boundaries. */
|
|
61
|
+
export function followDescriptionForm(pi: ExtensionAPI, apply: (form: DescriptionForm, tier: PromptTier) => void): void {
|
|
62
|
+
const requestTier = sessionModelTier(pi);
|
|
63
|
+
const follow = () => {
|
|
64
|
+
const tier = requestTier();
|
|
65
|
+
apply(descriptionForm(tier), tier);
|
|
66
|
+
};
|
|
67
|
+
pi.on("session_start", follow);
|
|
68
|
+
pi.on("model_select", follow);
|
|
69
|
+
}
|