one-code-extension 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -219
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +32 -23
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +27 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +66 -34
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +123 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +515 -0
- package/extensions/background/index.ts +514 -236
- package/extensions/background/loop-fire.ts +136 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +52 -0
- package/extensions/background/wakeup.ts +159 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +88 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +229 -71
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +446 -58
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +216 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/index.ts +24 -5
- package/extensions/context-management/index.ts +25 -0
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +72 -19
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +337 -26
- package/extensions/file-tracker/replay.ts +96 -10
- package/extensions/file-tracker/restore.ts +63 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +32 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +198 -31
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +13 -5
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/lib/agent-cron.ts +130 -0
- package/extensions/lib/agent-loader.ts +15 -1
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +42 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +78 -0
- package/extensions/lib/context-stack.ts +81 -0
- package/extensions/lib/deferred.ts +219 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/local-command.ts +47 -13
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +174 -0
- package/extensions/lib/model-catalog/models-dev.json +18297 -0
- package/extensions/lib/model-catalog/openrouter.json +5252 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +308 -64
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +21 -2
- package/extensions/lib/plan-mode-channels.ts +20 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/process-tree.ts +131 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +364 -58
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +439 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +13 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +113 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +149 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +143 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +70 -4
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +87 -5
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +209 -71
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +151 -67
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1300 -381
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +6 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +156 -69
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +61 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +12 -7
- package/extensions/recap/index.ts +65 -60
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +421 -101
- package/extensions/skill/invoke.ts +128 -18
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +116 -23
- package/extensions/subagents/announce.ts +69 -0
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +76 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1493 -824
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +2 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/runner.ts +108 -80
- package/extensions/subagents/runs.ts +39 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +131 -47
- package/extensions/system-prompt/template.ts +24 -50
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +175 -13
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/announce.ts +16 -0
- package/extensions/tool-search/index.ts +189 -41
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +105 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +145 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +29 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +121 -39
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +19 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expanding a plugin command body: arguments, then each `` !`command` ``
|
|
3
|
+
* placeholder replaced with that command's output.
|
|
4
|
+
*
|
|
5
|
+
* Two rules keep a typed argument from becoming shell syntax. Placeholders
|
|
6
|
+
* are found in the body as written, before any argument goes in, and an
|
|
7
|
+
* argument substituted into a placeholder's command goes in as a
|
|
8
|
+
* single-quoted word (`template.ts`), so `$(…)`, `;` or a backtick pair in
|
|
9
|
+
* what the user typed stays text. And the command runs in the bash every
|
|
10
|
+
* other shell path uses (`lib/shell-spawn.ts`: Git Bash on Windows, never a
|
|
11
|
+
* bare `/bin/sh` or `cmd.exe`), as Claude Code runs these in its bash:
|
|
12
|
+
* Node's `shell: true` used `/bin/sh` (dash on Debian and Ubuntu) and
|
|
13
|
+
* `cmd.exe`, where `[[ ]]`, `head` and process substitution fail and the
|
|
14
|
+
* error text became the placeholder's output.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { ChildProcess } from "node:child_process";
|
|
18
|
+
import { detachedSpawnOptions, killProcessTree, waitForChildExit } from "../lib/process-tree.ts";
|
|
19
|
+
import { bashSpawn, type ShellSpawn, spawnShellCommand } from "../lib/shell-spawn.ts";
|
|
20
|
+
import { shellQuote } from "../lib/shell-quote.ts";
|
|
21
|
+
import { splitShellPlaceholders, substituteArguments } from "./template.ts";
|
|
22
|
+
|
|
23
|
+
export const SHELL_TIMEOUT_MS = 30_000;
|
|
24
|
+
/** A placeholder output past this many characters stops being collected, and says so. */
|
|
25
|
+
const MAX_OUTPUT_CHARS = 2 * 1024 * 1024;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Run one placeholder command in bash and return its stdout (stderr when
|
|
29
|
+
* stdout is empty), trimmed. A failure returns its error text, as before:
|
|
30
|
+
* the placeholder shows what went wrong rather than vanishing.
|
|
31
|
+
*/
|
|
32
|
+
export async function runPlaceholderCommand(
|
|
33
|
+
command: string,
|
|
34
|
+
cwd: string,
|
|
35
|
+
opts: { spec?: ShellSpawn; timeoutMs?: number; maxOutputChars?: number } = {},
|
|
36
|
+
): Promise<string> {
|
|
37
|
+
const spec = opts.spec ?? bashSpawn().spawn;
|
|
38
|
+
if (!spec) return `command failed: ${bashSpawn().error ?? "no bash shell found"}`;
|
|
39
|
+
let child: ChildProcess;
|
|
40
|
+
try {
|
|
41
|
+
child = spawnShellCommand(spec, command, { cwd, stdio: ["ignore", "pipe", "pipe"], ...detachedSpawnOptions() });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return `command failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
44
|
+
}
|
|
45
|
+
const maxChars = opts.maxOutputChars ?? MAX_OUTPUT_CHARS;
|
|
46
|
+
let stdout = "";
|
|
47
|
+
let stderr = "";
|
|
48
|
+
const cut = { stdout: false, stderr: false };
|
|
49
|
+
const capture = (sink: "stdout" | "stderr") => (chunk: string) => {
|
|
50
|
+
const current = sink === "stdout" ? stdout : stderr;
|
|
51
|
+
if (current.length + chunk.length > maxChars) cut[sink] = true;
|
|
52
|
+
if (current.length >= maxChars) return;
|
|
53
|
+
const next = current + chunk.slice(0, maxChars - current.length);
|
|
54
|
+
if (sink === "stdout") stdout = next;
|
|
55
|
+
else stderr = next;
|
|
56
|
+
};
|
|
57
|
+
child.stdout?.setEncoding("utf8");
|
|
58
|
+
child.stderr?.setEncoding("utf8");
|
|
59
|
+
child.stdout?.on("data", capture("stdout"));
|
|
60
|
+
child.stderr?.on("data", capture("stderr"));
|
|
61
|
+
let timedOut = false;
|
|
62
|
+
const timeoutMs = opts.timeoutMs ?? SHELL_TIMEOUT_MS;
|
|
63
|
+
const timer = setTimeout(() => {
|
|
64
|
+
timedOut = true;
|
|
65
|
+
killProcessTree(child, "SIGKILL");
|
|
66
|
+
}, timeoutMs);
|
|
67
|
+
timer.unref();
|
|
68
|
+
try {
|
|
69
|
+
await waitForChildExit(child);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return `command failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
72
|
+
} finally {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
}
|
|
75
|
+
// The model must know an output is incomplete (a `git diff` cut mid-file).
|
|
76
|
+
const shown = stdout ? "stdout" : "stderr";
|
|
77
|
+
const text = `${(stdout || stderr).trim()}${cut[shown] ? `\n[output cut after ${maxChars} characters]` : ""}`;
|
|
78
|
+
if (timedOut) return `${text ? `${text}\n` : ""}command timed out after ${Math.round(timeoutMs / 1000)} seconds`;
|
|
79
|
+
return text;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Expand a command body: every placeholder's command runs once (with the
|
|
84
|
+
* arguments quoted in), text pieces get the arguments as typed, and an
|
|
85
|
+
* output is never scanned for arguments or placeholders again. `persist`
|
|
86
|
+
* bounds each output (a large `git diff`) before it lands in the prompt.
|
|
87
|
+
*/
|
|
88
|
+
export async function expandTemplate(
|
|
89
|
+
body: string,
|
|
90
|
+
args: string,
|
|
91
|
+
cwd: string,
|
|
92
|
+
opts: { persist?: (text: string, index: number) => string; run?: (command: string, cwd: string) => Promise<string> } = {},
|
|
93
|
+
): Promise<string> {
|
|
94
|
+
const pieces = splitShellPlaceholders(body);
|
|
95
|
+
const commands = [...new Set(pieces.flatMap((piece) => (piece.kind === "shell" ? [substituteArguments(piece.command, args, shellQuote)] : [])))];
|
|
96
|
+
const run = opts.run ?? ((command: string, dir: string) => runPlaceholderCommand(command, dir));
|
|
97
|
+
const outputs = new Map<string, string>();
|
|
98
|
+
await Promise.all(
|
|
99
|
+
commands.map(async (command, index) => {
|
|
100
|
+
const text = await run(command, cwd);
|
|
101
|
+
outputs.set(command, opts.persist ? opts.persist(text, index) : text);
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
return pieces
|
|
105
|
+
.map((piece) => (piece.kind === "text" ? substituteArguments(piece.text, args) : (outputs.get(substituteArguments(piece.command, args, shellQuote)) ?? "")))
|
|
106
|
+
.join("");
|
|
107
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude Code command-template expansion (pure
|
|
2
|
+
* Claude Code command-template expansion (pure; `shell-expand.ts` runs the commands).
|
|
3
3
|
*
|
|
4
4
|
* A command markdown file has frontmatter (`description`, `allowed-tools`,
|
|
5
5
|
* `argument-hint`) and a body that may contain:
|
|
@@ -17,26 +17,43 @@ export interface CommandTemplate {
|
|
|
17
17
|
body: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Replace `$ARGUMENTS`, `$@` and `$1`… with the invocation's arguments. With
|
|
22
|
+
* `quote`, each argument word is passed through it (a shell placeholder's
|
|
23
|
+
* command gets them as quoted words), so `$ARGUMENTS` stays one word per
|
|
24
|
+
* argument and no argument can add shell syntax.
|
|
25
|
+
*/
|
|
26
|
+
export function substituteArguments(body: string, args: string, quote?: (word: string) => string): string {
|
|
21
27
|
const parts = args.trim().length > 0 ? args.trim().split(/\s+/) : [];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
const all = quote ? parts.map(quote).join(" ") : args.trim();
|
|
29
|
+
// One pass: a second pass would substitute inside argument text the first
|
|
30
|
+
// one inserted (an argument holding `$1`), unquoting it.
|
|
31
|
+
return body.replace(/\$ARGUMENTS\b|\$@|\$(\d+)/g, (_match, index: string | undefined) => {
|
|
32
|
+
if (index === undefined) return all;
|
|
33
|
+
const part = parts[Number(index) - 1];
|
|
34
|
+
return quote ? quote(part ?? "") : (part ?? "");
|
|
35
|
+
});
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
export type TemplatePiece = { kind: "text"; text: string } | { kind: "shell"; command: string };
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A template body split into text and `` !`command` `` placeholders, found in
|
|
42
|
+
* the body as written: an argument substituted later can never become a
|
|
43
|
+
* placeholder. Until 2026-09-27 the arguments went in first, so an argument
|
|
44
|
+
* holding `` !`…` `` ran as a command of its own.
|
|
45
|
+
*/
|
|
46
|
+
export function splitShellPlaceholders(body: string): TemplatePiece[] {
|
|
47
|
+
const pieces: TemplatePiece[] = [];
|
|
31
48
|
const pattern = /!`([^`]+)`/g;
|
|
49
|
+
let last = 0;
|
|
32
50
|
let match = pattern.exec(body);
|
|
33
51
|
while (match) {
|
|
34
|
-
|
|
52
|
+
if (match.index > last) pieces.push({ kind: "text", text: body.slice(last, match.index) });
|
|
53
|
+
pieces.push({ kind: "shell", command: match[1] });
|
|
54
|
+
last = match.index + match[0].length;
|
|
35
55
|
match = pattern.exec(body);
|
|
36
56
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
export function replaceShellPlaceholders(body: string, outputs: Map<string, string>): string {
|
|
41
|
-
return body.replace(/!`([^`]+)`/g, (_match, command: string) => outputs.get(command) ?? "");
|
|
57
|
+
if (last < body.length) pieces.push({ kind: "text", text: body.slice(last) });
|
|
58
|
+
return pieces;
|
|
42
59
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** One Code's PowerShell tool for an in-process child session (lib/child-extensions.ts): the same tool and activation, prints nothing. */
|
|
2
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { powershellTool } from "./index.ts";
|
|
4
|
+
|
|
5
|
+
export default function childPowershellExtension(pi: ExtensionAPI) {
|
|
6
|
+
powershellTool(pi, { announce: false });
|
|
7
|
+
}
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
* (identical in the reconstructed `PowerShellTool/prompt.ts`). The
|
|
11
11
|
* "Developer tools verified on this machine's PATH" list Claude Code can add
|
|
12
12
|
* sits behind a remote flag that was off in the capture, so it is not
|
|
13
|
-
* reproduced.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* reproduced. The shipped text corrects the captured working-directory
|
|
14
|
+
* sentence (`CC_CWD_SENTENCE`), drops the Glob/Grep lines
|
|
15
|
+
* (`CC_SEARCH_LINES`) and appends One
|
|
16
|
+
* Code's own short note about the deferred task tools and the `Start-Sleep`
|
|
17
|
+
* guard, after the captured text, the way the bash tool does.
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
20
|
export type PowerShellEditionName = "core" | "desktop" | "unknown";
|
|
@@ -126,14 +128,44 @@ Usage notes:
|
|
|
126
128
|
|
|
127
129
|
/** One Code's addendum: where the harness differs from Claude Code's runtime (task tools are deferred, the sleep guard). */
|
|
128
130
|
export const ONE_CODE_ADDENDUM =
|
|
129
|
-
" A background run returns a task id immediately; completion arrives as a task notification, and the output is retrievable with task_output / stoppable with task_stop (both deferred — load them with tool_search; in a one-shot print/json session the call runs to completion and returns the output directly). A foreground command that LEADS with `Start-Sleep` is blocked; to wait on a condition use the monitor tool (deferred — load it with tool_search select:monitor).";
|
|
131
|
+
" A background run returns a task id immediately; completion arrives as a task notification, and the output is retrievable with task_output / stoppable with task_stop (both deferred — load them with tool_search; in a one-shot print/json session the call runs to completion within the foreground timeout and returns the output directly). A foreground command that LEADS with `Start-Sleep` is blocked; to wait on a condition use the monitor tool (deferred — load it with tool_search select:monitor).";
|
|
130
132
|
|
|
131
133
|
/** Claude Code's description for the detected edition, verbatim, without One Code's addendum. */
|
|
132
134
|
export function claudeCodePowerShellDescription(edition: PowerShellEditionName): string {
|
|
133
135
|
return TEMPLATE.replace("{{EDITION}}", EDITION_SECTIONS[edition]);
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Claude Code's shell keeps its working directory between calls; pi's
|
|
140
|
+
* PowerShell tool spawns a fresh process per call in the session's directory,
|
|
141
|
+
* so a `Set-Location` ends with its command. A model that trusts the captured
|
|
142
|
+
* sentence would `cd` in one call and resolve relative paths against the
|
|
143
|
+
* wrong folder in the next, so the shipped text swaps it.
|
|
144
|
+
*/
|
|
145
|
+
export const CC_CWD_SENTENCE = "Working directory persists between commands; shell state (variables, functions) does not.";
|
|
146
|
+
export const ONE_CODE_CWD_SENTENCE =
|
|
147
|
+
"Each command starts in the session's working directory; neither a directory change (`cd`, `Set-Location`) nor shell state (variables, functions) carries over to the next command.";
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Claude Code's PowerShell prompt names Glob and Grep even in its native
|
|
151
|
+
* builds, which ship neither (their Bash prompt drops the same two lines).
|
|
152
|
+
* One Code ships no search tools outside the tiny tier, so the lines would
|
|
153
|
+
* forbid the only search route left (`Get-ChildItem -Recurse`,
|
|
154
|
+
* `Select-String`); they are dropped on every tier. A tiny model still sees
|
|
155
|
+
* `find` and `grep` in its tool list.
|
|
156
|
+
*/
|
|
157
|
+
export const CC_SEARCH_LINES =
|
|
158
|
+
" - File search: Use Glob (NOT Get-ChildItem -Recurse)\n - Content search: Use Grep (NOT Select-String)\n";
|
|
159
|
+
|
|
160
|
+
/** The same reason takes searching out of the file operations the tool is told to leave alone. */
|
|
161
|
+
export const CC_FILE_OPERATIONS = "(reading, writing, editing, searching, finding files)";
|
|
162
|
+
export const ONE_CODE_FILE_OPERATIONS = "(reading, writing, editing files)";
|
|
163
|
+
|
|
164
|
+
/** The description the `powershell` tool ships: Claude Code's text with the corrections above, plus One Code's addendum. */
|
|
137
165
|
export function powerShellToolDescription(edition: PowerShellEditionName): string {
|
|
138
|
-
|
|
166
|
+
const text = claudeCodePowerShellDescription(edition)
|
|
167
|
+
.replace(CC_CWD_SENTENCE, ONE_CODE_CWD_SENTENCE)
|
|
168
|
+
.replace(CC_SEARCH_LINES, "")
|
|
169
|
+
.replace(CC_FILE_OPERATIONS, ONE_CODE_FILE_OPERATIONS);
|
|
170
|
+
return `${text}${ONE_CODE_ADDENDUM}`;
|
|
139
171
|
}
|
|
@@ -64,7 +64,7 @@ export function powershellGuardReason(command: string, opts: { background: boole
|
|
|
64
64
|
// finds "rebase" instead of misreading the flag's value as the
|
|
65
65
|
// subcommand and missing the interactive-editor guard entirely.
|
|
66
66
|
const words = statement.trim().split(/\s+/).slice(1);
|
|
67
|
-
const { sub, rest } = gitSubcommand(words.map((value) => ({ value
|
|
67
|
+
const { sub, rest } = gitSubcommand(words.map((value) => ({ value })));
|
|
68
68
|
const flags = new Set(rest.map((t) => t.value));
|
|
69
69
|
if (sub === "rebase" && (flags.has("-i") || flags.has("--interactive"))) {
|
|
70
70
|
return (
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* powershell extension — Claude Code's PowerShell tool on top of pi's own
|
|
3
|
-
* `powershell` built-in (docs/decisions/windows.md, findings §22).
|
|
3
|
+
* `powershell` built-in (working-docs/decisions/windows.md, findings §22).
|
|
4
4
|
*
|
|
5
5
|
* Registering a tool named `powershell` overrides pi's built-in (findings §2),
|
|
6
6
|
* exactly as `extensions/bash` overrides `bash`, and the shared body in
|
|
7
7
|
* lib/shell-tool.ts supplies the `run_in_background` orchestration for both.
|
|
8
8
|
* The foreground path delegates to pi's real definition
|
|
9
|
-
* (`createPowerShellToolDefinition`) so schema, cwd handling
|
|
10
|
-
*
|
|
9
|
+
* (`createPowerShellToolDefinition`) so schema, cwd handling and streaming
|
|
10
|
+
* stay upstream's (a long output is cut Claude Code's way, as for bash) — with One Code's own `operations`
|
|
11
11
|
* (lib/shell-spawn.ts), because pi's local operations throw off Windows and
|
|
12
12
|
* this tool is developed against a `pwsh` on this Mac. The description is
|
|
13
13
|
* Claude Code's captured text (description.ts); the guards are the PowerShell
|
|
@@ -25,7 +25,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
|
|
|
25
25
|
import { createPowerShellToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
26
26
|
import { Type } from "typebox";
|
|
27
27
|
import { perCwd } from "../lib/per-cwd.ts";
|
|
28
|
-
import { bashSpawn, createPowerShellOperations,
|
|
28
|
+
import { bashSpawn, createPowerShellOperations, POWERSHELL_TOOL_PREFIX, powerShellEdition, powerShellSpawn } from "../lib/shell-spawn.ts";
|
|
29
29
|
import { registerShellTool } from "../lib/shell-tool.ts";
|
|
30
30
|
import { powerShellToolDescription } from "./description.ts";
|
|
31
31
|
import { powershellGuardReason } from "./guards.ts";
|
|
@@ -40,6 +40,11 @@ const PowerShellParams = Type.Object({
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
export default function powershellExtension(pi: ExtensionAPI) {
|
|
43
|
+
powershellTool(pi, { announce: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The tool itself; `announce: false` is the in-process child's shape (powershell/child.ts), which prints no notices. */
|
|
47
|
+
export function powershellTool(pi: ExtensionAPI, { announce }: { announce: boolean }) {
|
|
43
48
|
const spec = powerShellSpawn();
|
|
44
49
|
const operations = createPowerShellOperations(() => spec);
|
|
45
50
|
const base = createPowerShellToolDefinition(process.cwd(), { operations });
|
|
@@ -66,8 +71,8 @@ export default function powershellExtension(pi: ExtensionAPI) {
|
|
|
66
71
|
foreground: perCwd((cwd: string) => createPowerShellToolDefinition(cwd, { operations })),
|
|
67
72
|
guard: powershellGuardReason,
|
|
68
73
|
backgroundShell: () => spec,
|
|
69
|
-
// The foreground path prepends
|
|
70
|
-
wrapBackgroundCommand: (command) => `${
|
|
74
|
+
// The foreground path prepends the tool's prefix inside the operations; the background spawn needs it too.
|
|
75
|
+
wrapBackgroundCommand: (command) => `${POWERSHELL_TOOL_PREFIX}${command}`,
|
|
71
76
|
});
|
|
72
77
|
|
|
73
78
|
let noticesShown = false;
|
|
@@ -76,7 +81,7 @@ export default function powershellExtension(pi: ExtensionAPI) {
|
|
|
76
81
|
const active = pi.getActiveTools();
|
|
77
82
|
const next = withShellTools(active, current);
|
|
78
83
|
if (next.join(" ") !== active.join(" ")) pi.setActiveTools(next);
|
|
79
|
-
if (noticesShown || current.notices.length === 0) return;
|
|
84
|
+
if (!announce || noticesShown || current.notices.length === 0) return;
|
|
80
85
|
noticesShown = true;
|
|
81
86
|
for (const notice of current.notices) {
|
|
82
87
|
if (ctx?.hasUI) ctx.ui.notify(notice, "warning");
|
|
@@ -8,30 +8,34 @@
|
|
|
8
8
|
* most one recap per user turn (firedSinceTurn), matching CC's
|
|
9
9
|
* hasSummarySinceLastUserTurn guard.
|
|
10
10
|
*
|
|
11
|
-
* Generation
|
|
12
|
-
*
|
|
13
|
-
* prompt,
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* Generation is Claude Code 2.1.261's: the session's last provider request
|
|
12
|
+
* replayed unchanged on the session model, the reply that answered it, then
|
|
13
|
+
* the verbatim prompt, so the call reads the cache the session wrote and
|
|
14
|
+
* writes the reply for the user's next turn (lib/request-replay.ts; the
|
|
15
|
+
* compaction extension publishes the capture). Without a capture for the
|
|
16
|
+
* session model, or on a provider API the replay does not cover, it falls back
|
|
17
|
+
* to a small standalone call on a cheap same-containment model
|
|
18
|
+
* (pickEconomicalContainedModel): the last 30 messages and name-only stubs of
|
|
19
|
+
* the active tools (some providers reject a history carrying tool_use blocks
|
|
20
|
+
* with no tools declared). The result is a display-only entry (appendEntry,
|
|
16
21
|
* not in LLM context). Best-effort throughout: any failure just shows nothing.
|
|
17
22
|
*
|
|
18
|
-
* Deviations from CC, logged in docs/decisions: the session-memory block is
|
|
19
|
-
* omitted (decoupling)
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* the model answering in text rather than calling one. A failed recap does not
|
|
23
|
-
* retry until the next turn (one attempt per turn, success or not). CC_RECAP=0
|
|
24
|
-
* opts out; CC_RECAP_IDLE_MS overrides the 5-minute delay.
|
|
23
|
+
* Deviations from CC, logged in working-docs/decisions: the session-memory block is
|
|
24
|
+
* omitted (decoupling). A failed recap does not retry until the next turn (one
|
|
25
|
+
* attempt per turn, success or not). CC_RECAP=0 opts out; CC_RECAP_IDLE_MS
|
|
26
|
+
* overrides the 5-minute delay.
|
|
25
27
|
*/
|
|
26
28
|
|
|
27
29
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
28
30
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
29
|
-
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
30
31
|
import { convertToLlm, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
31
|
-
import {
|
|
32
|
+
import { followLastExchange, replaySideCall } from "../lib/replay-call.ts";
|
|
32
33
|
import { recordUsage } from "../lib/usage-bus.ts";
|
|
33
34
|
import { pickEconomicalContainedModel } from "../lib/model-tier.ts";
|
|
34
|
-
import {
|
|
35
|
+
import { CONVERSATION_CACHE_PLACEMENT } from "../lib/side-call-cache.ts";
|
|
36
|
+
import { releaseSideCalls, runSideCall } from "../lib/side-call-run.ts";
|
|
37
|
+
import { answerText, stripImageBlocks, toolStubs, withoutSystemMessages } from "../lib/side-call.ts";
|
|
38
|
+
import { unrefTimers } from "../lib/timer-ops.ts";
|
|
35
39
|
import { dimMarkedLine } from "../lib/tui-render.ts";
|
|
36
40
|
import { RECAP_PROMPT, recapLine, recentForRecap, REFERENCE_MARK } from "./prompt.ts";
|
|
37
41
|
import { RecapScheduler } from "./scheduler.ts";
|
|
@@ -66,6 +70,8 @@ export default function recapExtension(pi: ExtensionAPI) {
|
|
|
66
70
|
pi.on("context", (event) => {
|
|
67
71
|
capturedMessages = event.messages;
|
|
68
72
|
});
|
|
73
|
+
// The same request as it went on the wire, and the reply that answered it.
|
|
74
|
+
const exchange = followLastExchange(pi);
|
|
69
75
|
|
|
70
76
|
let lastCtx: ExtensionContext | undefined;
|
|
71
77
|
let inFlight: AbortController | undefined;
|
|
@@ -77,19 +83,47 @@ export default function recapExtension(pi: ExtensionAPI) {
|
|
|
77
83
|
};
|
|
78
84
|
|
|
79
85
|
const scheduler = new RecapScheduler(
|
|
80
|
-
|
|
81
|
-
set: (cb, ms) => {
|
|
82
|
-
const handle = setTimeout(cb, ms);
|
|
83
|
-
handle.unref?.();
|
|
84
|
-
return handle;
|
|
85
|
-
},
|
|
86
|
-
clear: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
|
|
87
|
-
},
|
|
86
|
+
unrefTimers,
|
|
88
87
|
idleMs,
|
|
89
88
|
() => process.env.CC_RECAP !== "0",
|
|
90
89
|
() => void generate(),
|
|
91
90
|
);
|
|
92
91
|
|
|
92
|
+
/** The fallback: a small standalone call on a cheap same-containment model. */
|
|
93
|
+
async function standaloneRecap(ctx: ExtensionContext, model: Model<Api>, messages: AgentMessage[], signal: AbortSignal): Promise<string> {
|
|
94
|
+
const choice = pickEconomicalContainedModel(ctx.modelRegistry.getAvailable(), model);
|
|
95
|
+
if (!choice) return "";
|
|
96
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(choice.model);
|
|
97
|
+
if (!auth.ok) return "";
|
|
98
|
+
// Name-only stubs of the active tools, so a history carrying tool_use
|
|
99
|
+
// blocks stays valid on strict providers (see the header note) without
|
|
100
|
+
// shipping every full schema (~6k tokens) to a call that must answer
|
|
101
|
+
// in text.
|
|
102
|
+
const tools = toolStubs(pi.getActiveTools(), STUB_REASON);
|
|
103
|
+
|
|
104
|
+
const recent = recentForRecap(withoutSystemMessages(messages));
|
|
105
|
+
// The cheap reader may be a text-only model, and the recap does not need
|
|
106
|
+
// images — strip them so a pasted image or one a Read returned cannot break
|
|
107
|
+
// the call (working-docs/decisions/model-policy.md).
|
|
108
|
+
const recapMessages = [...stripImageBlocks(convertToLlm(recent)), { role: "user" as const, content: RECAP_PROMPT, timestamp: Date.now() }];
|
|
109
|
+
// Thinking off unless the model cannot disable it; withReasoningFallback
|
|
110
|
+
// sends a level up front for catalog-marked models and retries on the 400
|
|
111
|
+
// for the rest. Fires on a 5-min idle timer, so no cross-call memo.
|
|
112
|
+
const result = await runSideCall({
|
|
113
|
+
kind: "recap",
|
|
114
|
+
model: choice.model,
|
|
115
|
+
auth,
|
|
116
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
117
|
+
context: { systemPrompt: "", messages: recapMessages, tools },
|
|
118
|
+
signal,
|
|
119
|
+
timeoutMs: RECAP_TIMEOUT_MS,
|
|
120
|
+
maxTokens: RECAP_MAX_TOKENS,
|
|
121
|
+
cache: CONVERSATION_CACHE_PLACEMENT,
|
|
122
|
+
onUsage: (usage) => recordUsage(pi, "recap", usage),
|
|
123
|
+
});
|
|
124
|
+
return answerText(result.content);
|
|
125
|
+
}
|
|
126
|
+
|
|
93
127
|
async function generate() {
|
|
94
128
|
const ctx = lastCtx;
|
|
95
129
|
const messages = capturedMessages;
|
|
@@ -100,43 +134,13 @@ export default function recapExtension(pi: ExtensionAPI) {
|
|
|
100
134
|
try {
|
|
101
135
|
const model = ctx.model;
|
|
102
136
|
if (!model) return;
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// Name-only stubs of the active tools, so a history carrying tool_use
|
|
110
|
-
// blocks stays valid on strict providers (see the header note) without
|
|
111
|
-
// shipping every full schema (~6k tokens) to a call that must answer
|
|
112
|
-
// in text.
|
|
113
|
-
const tools = toolStubs(pi.getActiveTools(), STUB_REASON);
|
|
114
|
-
|
|
115
|
-
const recent = recentForRecap(messages);
|
|
116
|
-
// The cheap reader may be a text-only model, and the recap does not need
|
|
117
|
-
// images — strip them so a pasted image or one a Read returned cannot break
|
|
118
|
-
// the call (docs/decisions/model-policy.md).
|
|
119
|
-
const recapMessages = [...stripImageBlocks(convertToLlm(recent)), { role: "user" as const, content: RECAP_PROMPT, timestamp: Date.now() }];
|
|
120
|
-
// Thinking off unless the model cannot disable it; withReasoningFallback
|
|
121
|
-
// sends a level up front for catalog-marked models and retries on the 400
|
|
122
|
-
// for the rest. Fires on a 5-min idle timer, so no cross-call memo.
|
|
123
|
-
const result = await withReasoningFallback(choice.model, (reasoning) => {
|
|
124
|
-
const timeout = AbortSignal.timeout(RECAP_TIMEOUT_MS);
|
|
125
|
-
return completeSimple(
|
|
126
|
-
baseUrl ? ({ ...choice.model, baseUrl } as Model<Api>) : choice.model,
|
|
127
|
-
{ systemPrompt: "", messages: recapMessages, tools },
|
|
128
|
-
{
|
|
129
|
-
apiKey: auth.apiKey,
|
|
130
|
-
headers: auth.headers,
|
|
131
|
-
env: auth.env,
|
|
132
|
-
signal: AbortSignal.any([controller.signal, timeout]),
|
|
133
|
-
maxTokens: RECAP_MAX_TOKENS,
|
|
134
|
-
...(reasoning ? { reasoning } : {}),
|
|
135
|
-
},
|
|
136
|
-
);
|
|
137
|
-
}, undefined, (usage) => recordUsage(pi, "recap", usage));
|
|
137
|
+
const replayed = await replaySideCall(ctx, model as Model<Api>, exchange, RECAP_PROMPT, {
|
|
138
|
+
signal: controller.signal,
|
|
139
|
+
timeoutMs: RECAP_TIMEOUT_MS,
|
|
140
|
+
onUsage: (usage) => recordUsage(pi, "recap", usage),
|
|
141
|
+
});
|
|
142
|
+
const content = replayed ?? (await standaloneRecap(ctx, model as Model<Api>, messages, controller.signal));
|
|
138
143
|
if (controller.signal.aborted) return;
|
|
139
|
-
const content = answerText(result.content);
|
|
140
144
|
if (!content) return;
|
|
141
145
|
scheduler.markFired();
|
|
142
146
|
pi.appendEntry<RecapData>(ENTRY_TYPE, { content });
|
|
@@ -194,8 +198,9 @@ export default function recapExtension(pi: ExtensionAPI) {
|
|
|
194
198
|
scheduler.turnEnded();
|
|
195
199
|
});
|
|
196
200
|
|
|
197
|
-
pi.on("session_shutdown", () => {
|
|
201
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
198
202
|
scheduler.reset();
|
|
199
203
|
abortInFlight();
|
|
204
|
+
releaseSideCalls(ctx.sessionManager.getSessionId());
|
|
200
205
|
});
|
|
201
206
|
}
|
|
@@ -18,9 +18,9 @@ export const REFERENCE_MARK = "※";
|
|
|
18
18
|
*/
|
|
19
19
|
export const RECENT_MESSAGE_WINDOW = 30;
|
|
20
20
|
|
|
21
|
-
/** The away-summary instruction, verbatim from
|
|
21
|
+
/** The away-summary instruction, verbatim from Claude Code 2.1.261's request (no memory block). */
|
|
22
22
|
export const RECAP_PROMPT =
|
|
23
|
-
"The user stepped away and is coming back.
|
|
23
|
+
"The user stepped away and is coming back. Recap in under 40 words, 1-2 plain sentences, no markdown. Lead with the overall goal and current task, then the one next action. Skip root-cause narrative, fix internals, secondary to-dos, and em-dash tangents.";
|
|
24
24
|
|
|
25
25
|
/** The transcript text after the ※ mark, matching CC's shipped "recap: …" line. */
|
|
26
26
|
export function recapLine(content: string): string {
|
|
@@ -14,11 +14,9 @@
|
|
|
14
14
|
* async model call it triggers stays in the extension.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
clear(handle: unknown): void;
|
|
21
|
-
}
|
|
17
|
+
import type { TimerOps } from "../lib/timer-ops.ts";
|
|
18
|
+
|
|
19
|
+
export type { TimerOps };
|
|
22
20
|
|
|
23
21
|
export class RecapScheduler {
|
|
24
22
|
private turnRunning = false;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi's grep tool prints one context window per match, so with `context` set,
|
|
3
|
+
* nearby matches repeat the lines their windows share (a line once as context,
|
|
4
|
+
* again as context, again as the next match). Merged here (pure): each file
|
|
5
|
+
* line appears once, in the order first seen, and a line that is a match in
|
|
6
|
+
* any window shows as the match. Rows are `path:N: text` (match) and
|
|
7
|
+
* `path-N- text` (context); two rows are the same line only when path, number
|
|
8
|
+
* and text all agree, so lines of different files are never merged. Anything
|
|
9
|
+
* else (pi's notices) passes through.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const MATCH_ROW = /^(.+?):(\d+): (.*)$/s;
|
|
13
|
+
const CONTEXT_ROW = /^(.+?)-(\d+)- (.*)$/s;
|
|
14
|
+
|
|
15
|
+
export function mergeGrepWindows(text: string): string {
|
|
16
|
+
const out: string[] = [];
|
|
17
|
+
/** Each line's context form → its index in `out`. */
|
|
18
|
+
const at = new Map<string, number>();
|
|
19
|
+
for (const line of text.split("\n")) {
|
|
20
|
+
// The separator that comes first decides: a row's text may itself hold
|
|
21
|
+
// `:N: ` or `-N- ` (`a.ts-12- k:3: v` is a context row).
|
|
22
|
+
const asMatch = MATCH_ROW.exec(line);
|
|
23
|
+
const asContext = CONTEXT_ROW.exec(line);
|
|
24
|
+
const match = asMatch && (!asContext || asMatch[1].length < asContext[1].length) ? asMatch : null;
|
|
25
|
+
const key = match ? `${match[1]}-${match[2]}- ${match[3]}` : asContext ? line : undefined;
|
|
26
|
+
if (key === undefined) {
|
|
27
|
+
out.push(line);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const index = at.get(key);
|
|
31
|
+
if (index === undefined) {
|
|
32
|
+
at.set(key, out.length);
|
|
33
|
+
out.push(line);
|
|
34
|
+
} else if (match) {
|
|
35
|
+
out[index] = line;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out.join("\n");
|
|
39
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code's "send now" chord, `ctrl+x ctrl+s` (its `chat:sendNow` action),
|
|
3
|
+
* read from raw terminal input. Pure: the extension feeds it every key.
|
|
4
|
+
*
|
|
5
|
+
* Keys arrive in any of three encodings: the legacy control byte (`\x18`,
|
|
6
|
+
* `\x13`), the kitty keyboard protocol's CSI u form, which pi-tui turns on
|
|
7
|
+
* with flags 7 (disambiguate, event types, alternate keys) where the terminal
|
|
8
|
+
* supports it, or xterm's modifyOtherKeys form (`\x1b[27;5;120~`), which
|
|
9
|
+
* pi-tui turns on otherwise and tmux sends with `extended-keys on`.
|
|
10
|
+
* `lib/key-input.ts` names all three alike. With event types on, a release
|
|
11
|
+
* follows every press, so releases never change the chord's state.
|
|
12
|
+
*
|
|
13
|
+
* pi binds `ctrl+x` alone to "copy the last assistant message". The chord
|
|
14
|
+
* takes a `ctrl+x` only while send now applies (a turn runs with something to
|
|
15
|
+
* send), where it is the chord's prefix as in Claude Code: one the chord does
|
|
16
|
+
* not complete (another key, or the extension's timeout) is dropped. At every
|
|
17
|
+
* other time `ctrl+x` reaches pi and copies.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { isKeyRelease, keyId } from "../lib/key-input.ts";
|
|
21
|
+
|
|
22
|
+
export type ChordKey = "ctrl+x" | "ctrl+s" | "release" | "other";
|
|
23
|
+
|
|
24
|
+
export function classifyKey(data: string): ChordKey {
|
|
25
|
+
if (isKeyRelease(data)) return "release";
|
|
26
|
+
const id = keyId(data);
|
|
27
|
+
if (id === "ctrl+x" || id === "ctrl+s") return id;
|
|
28
|
+
return "other";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* What the extension does with a key: let it through, hold it (a `ctrl+x`
|
|
33
|
+
* that may start the chord, consumed), or send now (the `ctrl+s`, consumed).
|
|
34
|
+
*/
|
|
35
|
+
export type ChordAction = "pass" | "hold" | "send";
|
|
36
|
+
|
|
37
|
+
export class SendNowChord {
|
|
38
|
+
private armed = false;
|
|
39
|
+
|
|
40
|
+
/** Feed one key; `active` is whether send now applies (a turn is running with something to send). */
|
|
41
|
+
feed(data: string, active: boolean): ChordAction {
|
|
42
|
+
const key = classifyKey(data);
|
|
43
|
+
if (key === "release") return "pass";
|
|
44
|
+
const armed = this.armed;
|
|
45
|
+
this.armed = false;
|
|
46
|
+
// A second ctrl+x, or the kitty protocol's auto-repeat of a held one, keeps the chord armed.
|
|
47
|
+
if (key === "ctrl+x" && active) {
|
|
48
|
+
this.armed = true;
|
|
49
|
+
return "hold";
|
|
50
|
+
}
|
|
51
|
+
if (armed && key === "ctrl+s" && active) return "send";
|
|
52
|
+
return "pass";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The chord timed out: its `ctrl+x` is dropped. */
|
|
56
|
+
expire(): void {
|
|
57
|
+
this.armed = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The hint under the queued messages, Claude Code's wording. */
|
|
62
|
+
export const SEND_NOW_HINT = "ctrl+x ctrl+s to send now";
|