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
|
@@ -13,24 +13,34 @@
|
|
|
13
13
|
|
|
14
14
|
import { mkdirSync } from "node:fs";
|
|
15
15
|
import { join } from "node:path";
|
|
16
|
-
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import type { ExtensionAPI, ExtensionContext, ExtensionToolContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
17
17
|
import type { Static, TObject } from "typebox";
|
|
18
18
|
import { generateTaskId, TASK_REGISTER_CHANNEL } from "../background/registry.ts";
|
|
19
19
|
import { type BashFinishSummary, runBackgroundBashBlocking, startBackgroundBash, tailCap } from "../bash/background.ts";
|
|
20
20
|
import { createTaskNotifier, oneShotNote, sessionOutlivesTurn, shellSummary, type TaskStatus, taskNotification } from "./notifications.ts";
|
|
21
21
|
import { commandToEvaluate, trackOriginalCommands } from "./original-command.ts";
|
|
22
|
-
import { persistIfLarge, sessionResultsDir } from "./persisted-output.ts";
|
|
22
|
+
import { persistedFilePreview, persistIfLarge, sessionResultsDir } from "./persisted-output.ts";
|
|
23
|
+
import { withClaudeCodeShellText } from "./shell-result.ts";
|
|
24
|
+
import { bashParserReady } from "./bash-parser.ts";
|
|
23
25
|
import type { ShellSpawn } from "./shell-spawn.ts";
|
|
26
|
+
import { keepSpillReadable } from "./spill-file.ts";
|
|
24
27
|
import { ccWrapBuiltinRenderers, linesComponent, resultLines } from "./tui-render.ts";
|
|
28
|
+
import { registerVariantTool } from "./tool-variants.ts";
|
|
25
29
|
|
|
26
30
|
// The completion notification carries status + exit code + where the output is,
|
|
27
31
|
// plus only a short tail: a finished build used to push 30 KB (~8k tokens) into
|
|
28
32
|
// context unasked (review T7). The full tail stays behind task_output / the log.
|
|
29
33
|
export const NOTIFY_OUTPUT_CAP = 2_000;
|
|
30
|
-
/**
|
|
31
|
-
export const
|
|
34
|
+
/** Claude Code's inline cap for a shell's output; past it the output is persisted (file + head preview), never cut. */
|
|
35
|
+
export const INLINE_OUTPUT_CAP = 30_000;
|
|
32
36
|
/** Claude Code's cap; the tools' `timeout` is milliseconds, as CC's is. */
|
|
33
37
|
export const MAX_TIMEOUT_MS = 600_000;
|
|
38
|
+
/**
|
|
39
|
+
* Claude Code's default for a command that names no `timeout`, which both
|
|
40
|
+
* tool descriptions promise. pi's executor has no default, so without this a
|
|
41
|
+
* foreground `npm run dev` ran until Esc, and forever in a `-p` run.
|
|
42
|
+
*/
|
|
43
|
+
export const DEFAULT_TIMEOUT_MS = 120_000;
|
|
34
44
|
|
|
35
45
|
/** The parameter shape both shell tools share (descriptions differ per tool). */
|
|
36
46
|
export interface ShellToolParams {
|
|
@@ -53,7 +63,7 @@ export interface ShellToolBase {
|
|
|
53
63
|
|
|
54
64
|
/** pi's shell tool executor: `execute(toolCallId, { command, timeout: seconds }, signal, onUpdate, ctx)`. */
|
|
55
65
|
export interface ShellToolExecutor {
|
|
56
|
-
execute: (toolCallId: string, params: { command: string; timeout?: number }, signal: AbortSignal | undefined, onUpdate: any, ctx:
|
|
66
|
+
execute: (toolCallId: string, params: { command: string; timeout?: number }, signal: AbortSignal | undefined, onUpdate: any, ctx: ExtensionToolContext) => Promise<any>;
|
|
57
67
|
}
|
|
58
68
|
|
|
59
69
|
/** What one shell tool's definition supplies; everything else is shared. */
|
|
@@ -98,10 +108,20 @@ export function finishLine(summary: BashFinishSummary, timeoutSeconds?: number):
|
|
|
98
108
|
return `failed (${detail ?? `exit code ${summary.exitCode}`})`;
|
|
99
109
|
}
|
|
100
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Why a one-shot `run_in_background` call was stopped: a one-shot session
|
|
113
|
+
* cannot keep a command running past the run, so the call is held to the
|
|
114
|
+
* foreground deadline.
|
|
115
|
+
*/
|
|
116
|
+
export function oneShotDeadlineNote(timeoutMs: number, explicit: boolean): string {
|
|
117
|
+
const which = explicit ? `its ${timeoutMs} ms timeout` : `the default ${timeoutMs} ms timeout (no \`timeout\` was given)`;
|
|
118
|
+
return `It was stopped at ${which}: a one-shot session cannot keep a background command running after the run ends, so a server or watcher started this way stops at the deadline. Pass a longer \`timeout\` (max ${MAX_TIMEOUT_MS}) for a long build.`;
|
|
119
|
+
}
|
|
120
|
+
|
|
101
121
|
/** `<sessionDir>/bash/<taskId>/output.log` — one spool location for every shell tool (the shell panel reads it). */
|
|
102
122
|
export function taskLogPath(ctx: ExtensionContext, taskId: string): string | undefined {
|
|
103
123
|
try {
|
|
104
|
-
const dir = join(ctx
|
|
124
|
+
const dir = join(sessionResultsDir(ctx), "bash", taskId);
|
|
105
125
|
mkdirSync(dir, { recursive: true });
|
|
106
126
|
return join(dir, "output.log");
|
|
107
127
|
} catch {
|
|
@@ -109,7 +129,13 @@ export function taskLogPath(ctx: ExtensionContext, taskId: string): string | und
|
|
|
109
129
|
}
|
|
110
130
|
}
|
|
111
131
|
|
|
112
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Register the shell tool; the returned setter registers it again with a new
|
|
134
|
+
* description (a no-op when the text is unchanged), for a description that
|
|
135
|
+
* follows the session (lib/tool-variants.ts). Everything else — the notifier,
|
|
136
|
+
* the original-command tracker, execute — is built once.
|
|
137
|
+
*/
|
|
138
|
+
export function registerShellTool<P extends TObject>(pi: ExtensionAPI, spec: ShellToolSpec<P>): (description: string) => void {
|
|
113
139
|
// A completion whose output task_output just returned is withdrawn (CC's
|
|
114
140
|
// delivered_as_tool_result) — the model already has it.
|
|
115
141
|
const notifyTask = createTaskNotifier(pi, { withdrawOnDelivery: true });
|
|
@@ -124,7 +150,7 @@ export function registerShellTool<P extends TObject>(pi: ExtensionAPI, spec: She
|
|
|
124
150
|
// "Took Ns" that counted permission-prompt wait disappears.
|
|
125
151
|
const wrapped = ccWrapBuiltinRenderers<{ command?: string }>(spec.ccLabel, spec.base, { title: (a) => a?.command });
|
|
126
152
|
|
|
127
|
-
|
|
153
|
+
const definition = {
|
|
128
154
|
name: spec.name,
|
|
129
155
|
label: spec.base.label,
|
|
130
156
|
description: spec.description,
|
|
@@ -162,14 +188,24 @@ export function registerShellTool<P extends TObject>(pi: ExtensionAPI, spec: She
|
|
|
162
188
|
if (guardReason) return { content: [{ type: "text" as const, text: guardReason }], isError: true, details: {} };
|
|
163
189
|
// pi's executor takes seconds; the model-facing unit is milliseconds
|
|
164
190
|
// (Claude Code's), so a habitual `timeout: 120000` is 2 minutes, not
|
|
165
|
-
// 33 hours (review T11).
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
: undefined;
|
|
191
|
+
// 33 hours (review T11). A command that names none gets Claude Code's
|
|
192
|
+
// default, which is what the descriptions promise.
|
|
193
|
+
const explicitTimeout = params.timeout !== undefined && Number.isFinite(params.timeout) && params.timeout > 0;
|
|
194
|
+
const timeoutSeconds = (explicitTimeout ? Math.min(params.timeout!, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS) / 1000;
|
|
170
195
|
|
|
171
196
|
if (!params.run_in_background) {
|
|
172
|
-
|
|
197
|
+
// pi spills a long output to os.tmpdir(), outside every readable root;
|
|
198
|
+
// move it where the model's read of it is a working-space read, and
|
|
199
|
+
// cut the output Claude Code's way (its head, not pi's tail). Then
|
|
200
|
+
// Claude Code's result text (`Exit code N` first, trimmed output).
|
|
201
|
+
const result = await keepSpillReadable(
|
|
202
|
+
() => spec.foreground(ctx.cwd).execute(toolCallId, { command: params.command, timeout: timeoutSeconds }, signal, onUpdate, ctx),
|
|
203
|
+
sessionResultsDir(ctx),
|
|
204
|
+
{ inlineMax: INLINE_OUTPUT_CAP, id: `${spec.name}-${toolCallId}` },
|
|
205
|
+
);
|
|
206
|
+
// The no-match check parses the command; only bash results need the grammar.
|
|
207
|
+
if (spec.name === "bash") await bashParserReady();
|
|
208
|
+
return withClaudeCodeShellText(result, spec.name, originalCommand);
|
|
173
209
|
}
|
|
174
210
|
|
|
175
211
|
const shell = spec.backgroundShell();
|
|
@@ -189,48 +225,61 @@ export function registerShellTool<P extends TObject>(pi: ExtensionAPI, spec: She
|
|
|
189
225
|
// detached task would be orphaned, and its completion callback would
|
|
190
226
|
// call sendMessage on the disposed session and crash pi (measured,
|
|
191
227
|
// STEERING-REVIEW-2026-09-05 H3). Run blocking there — the same rule the
|
|
192
|
-
// Agent tool applies — and return the output in the result.
|
|
228
|
+
// Agent tool applies — and return the output in the result. The call
|
|
229
|
+
// holds the turn like a foreground command, so it gets the foreground
|
|
230
|
+
// deadline (the model's `timeout`, else the default): with none, a
|
|
231
|
+
// server started this way hung the run forever. A detached shell below
|
|
232
|
+
// keeps having no deadline (findings §31).
|
|
193
233
|
if (!sessionOutlivesTurn(ctx.mode)) {
|
|
194
234
|
const summary = await runBackgroundBashBlocking({ id, command, description, cwd: ctx.cwd, timeoutSeconds, logPath, shell }, signal);
|
|
195
|
-
const
|
|
235
|
+
const spool = summary.logPath && Buffer.byteLength(summary.output, "utf8") > INLINE_OUTPUT_CAP
|
|
236
|
+
? persistedFilePreview(summary.logPath)
|
|
237
|
+
: undefined;
|
|
238
|
+
const output = spool ?? persistIfLarge(summary.output, { dir: sessionResultsDir(ctx), id: `${spec.name}-${id}`, maxBytes: INLINE_OUTPUT_CAP });
|
|
239
|
+
const deadline = summary.timedOut ? ` ${oneShotDeadlineNote(timeoutSeconds * 1000, explicitTimeout)}` : "";
|
|
196
240
|
return {
|
|
197
241
|
content: [
|
|
198
242
|
{
|
|
199
243
|
type: "text" as const,
|
|
200
|
-
text: `${spec.ccLabel} task ${id} (${description}) ${finishLine(summary, timeoutSeconds)}. ${oneShotNote("command")}${logPath ? ` Log: ${logPath}.` : ""}\n\n${output}`,
|
|
244
|
+
text: `${spec.ccLabel} task ${id} (${description}) ${finishLine(summary, timeoutSeconds)}. ${oneShotNote("command")}${deadline}${summary.logPath ? ` Log: ${summary.logPath}.` : ""}\n\n${output}`,
|
|
201
245
|
},
|
|
202
246
|
],
|
|
203
247
|
// No taskId: nothing was registered behind task_output/task_stop, and
|
|
204
248
|
// its presence is what renderResult reads as "running in the background".
|
|
205
|
-
details: { logPath },
|
|
206
|
-
isError: summary.
|
|
249
|
+
details: { logPath: summary.logPath },
|
|
250
|
+
isError: shellFinish(summary, timeoutSeconds).status === "failed",
|
|
207
251
|
};
|
|
208
252
|
}
|
|
209
253
|
|
|
254
|
+
// No timeout here: Claude Code clears a command's timeout the
|
|
255
|
+
// moment it goes to the background, so a detached shell has no
|
|
256
|
+
// deadline (findings §31). Models habitually send `timeout: 600000`
|
|
257
|
+
// next to `run_in_background`, which used to kill a dev server at
|
|
258
|
+
// exactly 10 minutes. The one-shot path above keeps the timeout
|
|
259
|
+
// because there the command holds the turn.
|
|
210
260
|
const task = startBackgroundBash({
|
|
211
261
|
id,
|
|
212
262
|
command,
|
|
213
263
|
description,
|
|
214
264
|
cwd: ctx.cwd,
|
|
215
|
-
timeoutSeconds,
|
|
216
265
|
logPath,
|
|
217
266
|
shell,
|
|
218
267
|
onFinished: (finishedTask, summary) => {
|
|
219
268
|
// Claude Code's shell completion names the output file and nothing
|
|
220
269
|
// else — the model reads it (or task_output) when it needs the text.
|
|
221
270
|
// Without a spool file the tail rides `<result>` so nothing is lost.
|
|
222
|
-
const { status, detail } = shellFinish(summary
|
|
271
|
+
const { status, detail } = shellFinish(summary);
|
|
223
272
|
notify(
|
|
224
273
|
taskNotification({
|
|
225
274
|
kind: "shell",
|
|
226
275
|
taskId: id,
|
|
227
276
|
toolUseId: toolCallId,
|
|
228
|
-
outputFile: logPath,
|
|
277
|
+
outputFile: finishedTask.logPath,
|
|
229
278
|
status,
|
|
230
279
|
summary: shellSummary(description, status, summary.exitCode, detail),
|
|
231
|
-
result: logPath ? undefined : tailCap(summary.output, NOTIFY_OUTPUT_CAP).trim() || undefined,
|
|
280
|
+
result: finishedTask.logPath ? undefined : tailCap(summary.output, NOTIFY_OUTPUT_CAP).trim() || undefined,
|
|
232
281
|
}),
|
|
233
|
-
{ taskId: id, status: finishedTask.status, exitCode: summary.exitCode, logPath },
|
|
282
|
+
{ taskId: id, status: finishedTask.status, exitCode: summary.exitCode, logPath: finishedTask.logPath },
|
|
234
283
|
);
|
|
235
284
|
},
|
|
236
285
|
});
|
|
@@ -248,5 +297,6 @@ export function registerShellTool<P extends TObject>(pi: ExtensionAPI, spec: She
|
|
|
248
297
|
details: { taskId: id, logPath },
|
|
249
298
|
};
|
|
250
299
|
},
|
|
251
|
-
} as ToolDefinition<P
|
|
300
|
+
} as ToolDefinition<P>;
|
|
301
|
+
return registerVariantTool(pi, spec.description, (description) => ({ ...definition, description }) as unknown as ToolDefinition);
|
|
252
302
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic cache placement for standalone side calls. pi-ai marks the final
|
|
3
|
+
* user question, which means every different question writes a fresh cache
|
|
4
|
+
* entry. Move that marker onto the preceding conversation message instead,
|
|
5
|
+
* without changing any message text. Call this from an `anthropic-messages`
|
|
6
|
+
* payload hook, or an `openai-completions` one: Chat Completions carries the
|
|
7
|
+
* same `cache_control` markers when the model takes Anthropic's format
|
|
8
|
+
* (OpenRouter's `anthropic/*`), and has none to move otherwise.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** The APIs whose standalone side-call payload this placement applies to. */
|
|
12
|
+
export const SIDE_CALL_CACHE_APIS: ReadonlySet<string> = new Set(["anthropic-messages", "openai-completions"]);
|
|
13
|
+
|
|
14
|
+
/** Anthropic permits at most four cache-control markers per request. */
|
|
15
|
+
export const MAX_SIDE_CALL_CACHE_MARKERS = 4;
|
|
16
|
+
|
|
17
|
+
type WireRecord = Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Move pi-ai's final-question cache marker to the last preceding message and
|
|
21
|
+
* remove the earliest markers past Anthropic's four-marker limit. Unsupported
|
|
22
|
+
* payload shapes are left unchanged rather than being normalised.
|
|
23
|
+
*/
|
|
24
|
+
export function cacheSideCallConversation(payload: unknown): void {
|
|
25
|
+
if (!isRecord(payload) || !Array.isArray(payload.messages)) return;
|
|
26
|
+
const messages = payload.messages;
|
|
27
|
+
const questionIndex = messages.length - 1;
|
|
28
|
+
const question = asMessage(messages[questionIndex]);
|
|
29
|
+
if (!question || question.role !== "user") return;
|
|
30
|
+
const questionBlocks = blocks(question.content);
|
|
31
|
+
const sourceIndex = questionBlocks.findLastIndex((block) => block.cache_control !== undefined);
|
|
32
|
+
if (sourceIndex === -1) return;
|
|
33
|
+
|
|
34
|
+
const target = precedingCacheableBlock(messages, questionIndex);
|
|
35
|
+
if (!target) return;
|
|
36
|
+
const marker = questionBlocks[sourceIndex].cache_control;
|
|
37
|
+
const nextQuestionBlocks = [...questionBlocks];
|
|
38
|
+
const { cache_control: _, ...unmarked } = questionBlocks[sourceIndex];
|
|
39
|
+
nextQuestionBlocks[sourceIndex] = unmarked;
|
|
40
|
+
const nextTargetBlocks = [...target.blocks];
|
|
41
|
+
nextTargetBlocks[target.blockIndex] = { ...target.blocks[target.blockIndex], cache_control: marker };
|
|
42
|
+
const nextMessages = [...messages];
|
|
43
|
+
nextMessages[questionIndex] = { ...question, content: nextQuestionBlocks };
|
|
44
|
+
nextMessages[target.messageIndex] = { ...target.message, content: nextTargetBlocks };
|
|
45
|
+
payload.messages = nextMessages;
|
|
46
|
+
capMarkers(payload);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface Target {
|
|
50
|
+
messageIndex: number;
|
|
51
|
+
message: WireRecord;
|
|
52
|
+
blocks: WireRecord[];
|
|
53
|
+
blockIndex: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The last prior wire block Anthropic accepts as a message cache breakpoint. */
|
|
57
|
+
function precedingCacheableBlock(messages: unknown[], questionIndex: number): Target | undefined {
|
|
58
|
+
for (let messageIndex = questionIndex - 1; messageIndex >= 0; messageIndex--) {
|
|
59
|
+
const message = asMessage(messages[messageIndex]);
|
|
60
|
+
if (!message) continue;
|
|
61
|
+
// pi-ai keeps earlier user text as a string, only converting the final
|
|
62
|
+
// marked message to blocks. Convert this target exactly as pi-ai does so
|
|
63
|
+
// the newest conversation turn, rather than an older block message, gets
|
|
64
|
+
// the cache boundary. The text itself remains byte-for-byte unchanged.
|
|
65
|
+
// Chat Completions also sends assistant and tool text as strings; an
|
|
66
|
+
// empty one (a tool-call-only reply) cannot carry a marker, as in pi-ai.
|
|
67
|
+
if (typeof message.content === "string") {
|
|
68
|
+
if (message.content.length === 0) continue;
|
|
69
|
+
return { messageIndex, message, blocks: [{ type: "text", text: message.content }], blockIndex: 0 };
|
|
70
|
+
}
|
|
71
|
+
const content = blocks(message.content);
|
|
72
|
+
const blockIndex = content.findLastIndex(isCacheableBlock);
|
|
73
|
+
if (blockIndex !== -1) return { messageIndex, message, blocks: content, blockIndex };
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function asMessage(value: unknown): WireRecord | undefined {
|
|
79
|
+
return isRecord(value) && typeof value.role === "string" ? value : undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function blocks(value: unknown): WireRecord[] {
|
|
83
|
+
return Array.isArray(value) && value.every(isRecord) ? value : [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** This is pi-ai's own list from its Anthropic message converter. */
|
|
87
|
+
function isCacheableBlock(block: WireRecord): boolean {
|
|
88
|
+
return (
|
|
89
|
+
block.type === "text" ||
|
|
90
|
+
block.type === "image" ||
|
|
91
|
+
block.type === "tool_result" ||
|
|
92
|
+
block.type === "tool_addition" ||
|
|
93
|
+
block.type === "tool_removal"
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Drop the earliest actual Anthropic markers in wire order: tools, system, messages. */
|
|
98
|
+
function capMarkers(payload: WireRecord): void {
|
|
99
|
+
const markers = [
|
|
100
|
+
...topLevelMarkers(payload.tools),
|
|
101
|
+
...topLevelMarkers(payload.system),
|
|
102
|
+
...messageMarkers(payload.messages),
|
|
103
|
+
];
|
|
104
|
+
for (const marker of markers.slice(0, Math.max(0, markers.length - MAX_SIDE_CALL_CACHE_MARKERS))) delete marker.cache_control;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Tool definitions and top-level system blocks can each carry one marker. */
|
|
108
|
+
function topLevelMarkers(value: unknown): WireRecord[] {
|
|
109
|
+
return Array.isArray(value) ? value.filter((entry): entry is WireRecord => isRecord(entry) && entry.cache_control !== undefined) : [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Only a message content block is an Anthropic conversation marker. Never inspect block payloads. */
|
|
113
|
+
function messageMarkers(value: unknown): WireRecord[] {
|
|
114
|
+
if (!Array.isArray(value)) return [];
|
|
115
|
+
return value.flatMap((message) => (isRecord(message) ? blocks(message.content).filter((block) => block.cache_control !== undefined) : []));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isRecord(value: unknown): value is WireRecord {
|
|
119
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
/** The conversation cache placement a side call passes to `runSideCall` (`side-call-run.ts`). */
|
|
124
|
+
export const CONVERSATION_CACHE_PLACEMENT = { apis: SIDE_CALL_CACHE_APIS, place: cacheSideCallConversation } as const;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One standalone side call: a single `completeSimple` request outside the main
|
|
3
|
+
* agent loop, shared by `/btw`, the recap and the web_fetch reader. The
|
|
4
|
+
* auto-mode classifier has its own transport and stays out of this.
|
|
5
|
+
*
|
|
6
|
+
* Every side call takes the same steps: splice the provider's base URL from the
|
|
7
|
+
* resolved auth into the model, combine the caller's abort signal with a
|
|
8
|
+
* per-attempt timeout, key the request to the session (`<session id>:<kind>`,
|
|
9
|
+
* stable across calls, so the provider's prompt cache is reused), apply the
|
|
10
|
+
* caller's cache placement on the APIs it covers, log the attempt
|
|
11
|
+
* (`CC_SIDE_CALL_LOG`), retry with thinking on when the model cannot disable
|
|
12
|
+
* it, and report usage for the footer cost. The request on the wire is exactly
|
|
13
|
+
* what each extension built inline before (`side-call-run-wire.test.ts`).
|
|
14
|
+
*
|
|
15
|
+
* Kept apart from `side-call.ts`, which stays free of runtime pi imports for
|
|
16
|
+
* the pure prompt modules that use it.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { type Api, type AssistantMessage, type Context, cleanupSessionResources, type Model, type SimpleStreamOptions, type ThinkingLevel } from "@earendil-works/pi-ai";
|
|
20
|
+
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
21
|
+
import { withReasoningFallback } from "./model-policy.ts";
|
|
22
|
+
import { SIDE_CALL_KINDS, type SideCallLogContext, logSideCallUsage } from "./side-call-usage.ts";
|
|
23
|
+
|
|
24
|
+
/** A payload hook that places cache breakpoints, and the APIs whose payload shape it handles. */
|
|
25
|
+
export interface SideCallCachePlacement {
|
|
26
|
+
apis: ReadonlySet<string>;
|
|
27
|
+
place: (payload: unknown) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SideCallRun {
|
|
31
|
+
/** Names the call in the cache key and the side-call log. */
|
|
32
|
+
kind: SideCallLogContext["kind"];
|
|
33
|
+
model: Model<Api>;
|
|
34
|
+
/** The model registry's resolved auth for `model` (the caller handles a failed lookup). */
|
|
35
|
+
auth: { apiKey?: string; headers?: SimpleStreamOptions["headers"]; env?: Record<string, string>; baseUrl?: string };
|
|
36
|
+
/** The session's id; the call's cache key is `<sessionId>:<kind>`. */
|
|
37
|
+
sessionId: string;
|
|
38
|
+
context: Context;
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
/** Applies to each attempt on its own, so a reasoning retry gets a full timeout. */
|
|
41
|
+
timeoutMs: number;
|
|
42
|
+
maxTokens: number;
|
|
43
|
+
/** Cache placement for the payload; none leaves pi-ai's own markers. */
|
|
44
|
+
cache?: SideCallCachePlacement;
|
|
45
|
+
/** Per-session memo of a model's required thinking level (`withReasoningFallback`). */
|
|
46
|
+
learnedReasoning?: Map<string, ThinkingLevel>;
|
|
47
|
+
/** Each attempt's usage, for the footer's all-in cost. */
|
|
48
|
+
onUsage: (usage: unknown) => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type SideCallKind = SideCallLogContext["kind"];
|
|
52
|
+
|
|
53
|
+
/** A side call's cache key, which pi-ai also takes as its `sessionId`. */
|
|
54
|
+
function sideCallKey(sessionId: string, kind: SideCallKind): string {
|
|
55
|
+
return `${sessionId}:${kind}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Close what pi-ai holds open under a session's side-call keys, every kind's
|
|
60
|
+
* (a key never used costs nothing). On openai-codex it keeps one WebSocket per
|
|
61
|
+
* `sessionId` with a 5-minute idle timer holding the process, and pi's own
|
|
62
|
+
* dispose closes only the session's id, so a `pi -p` run waited out the timer
|
|
63
|
+
* after its answer (findings §59). Every extension that runs a side call calls
|
|
64
|
+
* it as the session ends, so a new kind is released with no further change.
|
|
65
|
+
*/
|
|
66
|
+
export function releaseSideCalls(sessionId: string): void {
|
|
67
|
+
for (const kind of SIDE_CALL_KINDS) cleanupSessionResources(sideCallKey(sessionId, kind));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Run the side call and return the final reply; a provider failure is in its `stopReason`, as with `completeSimple`. */
|
|
71
|
+
export async function runSideCall(run: SideCallRun): Promise<AssistantMessage> {
|
|
72
|
+
const { kind, model, auth, context } = run;
|
|
73
|
+
const sessionId = sideCallKey(run.sessionId, kind);
|
|
74
|
+
const target = auth.baseUrl ? ({ ...model, baseUrl: auth.baseUrl } as Model<Api>) : model;
|
|
75
|
+
const onPayload = run.cache?.apis.has(model.api) ? run.cache.place : undefined;
|
|
76
|
+
return withReasoningFallback(
|
|
77
|
+
model,
|
|
78
|
+
async (reasoning) => {
|
|
79
|
+
const timeout = AbortSignal.timeout(run.timeoutMs);
|
|
80
|
+
const reply = await completeSimple(target, context, {
|
|
81
|
+
apiKey: auth.apiKey,
|
|
82
|
+
headers: auth.headers,
|
|
83
|
+
env: auth.env,
|
|
84
|
+
signal: run.signal ? AbortSignal.any([run.signal, timeout]) : timeout,
|
|
85
|
+
maxTokens: run.maxTokens,
|
|
86
|
+
sessionId,
|
|
87
|
+
...(onPayload ? { onPayload } : {}),
|
|
88
|
+
...(reasoning ? { reasoning } : {}),
|
|
89
|
+
});
|
|
90
|
+
logSideCallUsage({ kind, model, sessionId, system: context.systemPrompt ?? "", messages: context.messages }, reply);
|
|
91
|
+
return reply;
|
|
92
|
+
},
|
|
93
|
+
run.learnedReasoning,
|
|
94
|
+
run.onUsage,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Opt-in side-call cache evidence, independent of the provider's transport. */
|
|
2
|
+
import { appendFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
/** Every side-call kind; releaseSideCalls closes each one's pi-ai key. */
|
|
5
|
+
export const SIDE_CALL_KINDS = ["reader", "btw", "recap"] as const;
|
|
6
|
+
|
|
7
|
+
export interface SideCallLogContext {
|
|
8
|
+
kind: (typeof SIDE_CALL_KINDS)[number];
|
|
9
|
+
model: { provider: string; id: string; api: string };
|
|
10
|
+
sessionId: string;
|
|
11
|
+
system: string;
|
|
12
|
+
messages: readonly unknown[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* CC_SIDE_CALL_LOG names a JSONL file containing prompt text and normalized
|
|
17
|
+
* usage, never credentials or response text. Like CC_AUTO_MODE_LOG, this is
|
|
18
|
+
* explicitly opt-in and never read back. Log every completed provider attempt,
|
|
19
|
+
* including reasoning retries; a probe must not mistake an error for a hit.
|
|
20
|
+
*/
|
|
21
|
+
export function logSideCallUsage(call: SideCallLogContext, reply: { usage?: unknown; stopReason: string }): void {
|
|
22
|
+
const path = process.env.CC_SIDE_CALL_LOG;
|
|
23
|
+
if (!path) return;
|
|
24
|
+
try {
|
|
25
|
+
appendFileSync(path, `${JSON.stringify({
|
|
26
|
+
kind: call.kind,
|
|
27
|
+
model: `${call.model.provider}/${call.model.id}`,
|
|
28
|
+
api: call.model.api,
|
|
29
|
+
sessionId: call.sessionId,
|
|
30
|
+
system: call.system,
|
|
31
|
+
messages: call.messages,
|
|
32
|
+
usage: reply.usage,
|
|
33
|
+
stopReason: reply.stopReason,
|
|
34
|
+
})}\n`);
|
|
35
|
+
} catch {
|
|
36
|
+
// A diagnostic log that cannot be written must not fail a side call.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -26,6 +26,17 @@ export function toolStubs(names: readonly string[], reason = "Unavailable for th
|
|
|
26
26
|
);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* The messages the current branch holds, as pi builds a request from them
|
|
31
|
+
* (compaction-aware), read when the side call runs. A copy kept from the last
|
|
32
|
+
* `context` event goes stale at a `/tree` or a compaction, which fire no
|
|
33
|
+
* `context` event, and the fallback then answered from the abandoned branch or
|
|
34
|
+
* the whole pre-compaction history.
|
|
35
|
+
*/
|
|
36
|
+
export function branchMessages<M>(ctx: { sessionManager: { buildSessionProjection(): { messages: M[] } } }): M[] {
|
|
37
|
+
return ctx.sessionManager.buildSessionProjection().messages;
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
/**
|
|
30
41
|
* Trim a message history forward to the first clean turn boundary — a `user`
|
|
31
42
|
* prompt or an `assistant` reply. A raw history can begin mid-exchange with a
|
|
@@ -40,6 +51,18 @@ export function trimToTurnBoundary<T extends { role: string }>(messages: readonl
|
|
|
40
51
|
return start === -1 ? [] : messages.slice(start);
|
|
41
52
|
}
|
|
42
53
|
|
|
54
|
+
/**
|
|
55
|
+
* The conversation without pi's `system` transcript messages, for a capture of
|
|
56
|
+
* the `context` event. pi 0.86 hands `context` handlers a leading system message
|
|
57
|
+
* holding pi's own default prompt sections and every full tool schema; a side
|
|
58
|
+
* call that forwarded it had pi-ai replay those into its system prompt and tools
|
|
59
|
+
* (over the stubs), and it shifted every role-by-index comparison. pi 0.87 strips
|
|
60
|
+
* system messages before `context` handlers run, so there this is a no-op.
|
|
61
|
+
*/
|
|
62
|
+
export function withoutSystemMessages<T extends { role: string }>(messages: readonly T[]): T[] {
|
|
63
|
+
return messages.filter((message) => message.role !== "system");
|
|
64
|
+
}
|
|
65
|
+
|
|
43
66
|
/** The placeholder left in a message whose only content was an image we removed. */
|
|
44
67
|
export const IMAGE_OMITTED_TEXT = "[image omitted]";
|
|
45
68
|
|
|
@@ -55,7 +78,7 @@ export const IMAGE_OMITTED_TEXT = "[image omitted]";
|
|
|
55
78
|
* placeholder text block, so it stays a valid turn on strict providers. Purely
|
|
56
79
|
* structural (any `{ role, content }` message) to stay free of runtime pi
|
|
57
80
|
* imports; assistant messages carry no images and pass through untouched.
|
|
58
|
-
* See `docs/decisions/model-policy.md`.
|
|
81
|
+
* See `working-docs/decisions/model-policy.md`.
|
|
59
82
|
*/
|
|
60
83
|
export function stripImageBlocks<M extends { role: string; content: unknown }>(messages: readonly M[]): M[] {
|
|
61
84
|
return messages.map((message) => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One in-flight run per key (pure): a call while a run for its key is pending
|
|
3
|
+
* shares that run's promise instead of starting another; once it settles, the
|
|
4
|
+
* next call starts fresh. Used where two overlapping callers would otherwise
|
|
5
|
+
* both pass an "is it there yet?" check across an await and duplicate work
|
|
6
|
+
* that must happen once (a language server spawn, a consent dialog).
|
|
7
|
+
*/
|
|
8
|
+
export function singleFlight<T>(): (key: string, run: () => Promise<T>) => Promise<T> {
|
|
9
|
+
const pending = new Map<string, Promise<T>>();
|
|
10
|
+
return (key, run) => {
|
|
11
|
+
let current = pending.get(key);
|
|
12
|
+
if (!current) {
|
|
13
|
+
current = run().finally(() => pending.delete(key));
|
|
14
|
+
pending.set(key, current);
|
|
15
|
+
}
|
|
16
|
+
return current;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills whose body is generated, not read from their SKILL.md: Claude Code's
|
|
3
|
+
* `/loop` builds a different prompt per argument shape (findings §21). The
|
|
4
|
+
* skill extension emits a `SkillBodyQuery` on SKILL_BODY_CHANNEL before it
|
|
5
|
+
* delivers a skill; the extension that owns the skill fills `body`
|
|
6
|
+
* synchronously, and the SKILL.md body is the fallback.
|
|
7
|
+
*
|
|
8
|
+
* A fired scheduled prompt that is a slash command (`/loop check the deploy`,
|
|
9
|
+
* `/babysit-prs`) runs the way Claude Code runs it, as if typed: the
|
|
10
|
+
* background extension emits a `SlashExpandQuery` on SLASH_EXPAND_CHANNEL
|
|
11
|
+
* and the skill extension fills `expanded` with the skill's block.
|
|
12
|
+
*
|
|
13
|
+
* Pure: no pi imports.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const SKILL_BODY_CHANNEL = "one-code:skill-body";
|
|
17
|
+
export const SLASH_EXPAND_CHANNEL = "one-code:slash-expand";
|
|
18
|
+
|
|
19
|
+
export interface SkillBodyQuery {
|
|
20
|
+
/** The skill's name. */
|
|
21
|
+
skill: string;
|
|
22
|
+
/** Its SKILL.md, so a provider answers only for its own file. */
|
|
23
|
+
path: string;
|
|
24
|
+
args: string;
|
|
25
|
+
cwd: string;
|
|
26
|
+
/** Set by the provider; the arguments are then part of it. */
|
|
27
|
+
body?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SlashExpandQuery {
|
|
31
|
+
/** The fired prompt, starting with `/`. */
|
|
32
|
+
text: string;
|
|
33
|
+
cwd: string;
|
|
34
|
+
/** Set by the skill extension when the command is a skill. */
|
|
35
|
+
expanded?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** `/name rest` split, or undefined when `text` is not a slash command. */
|
|
39
|
+
export function parseSlashCommand(text: string): { name: string; args: string } | undefined {
|
|
40
|
+
const match = /^\/([^\s/][^\s]*)(?:\s+([\s\S]*))?$/.exec(text.trim());
|
|
41
|
+
return match ? { name: match[1], args: (match[2] ?? "").trim() } : undefined;
|
|
42
|
+
}
|