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
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
* Everything here is pure and paint-injected so it unit-tests with an identity
|
|
8
8
|
* paint. `paint(color, text)` matches `safeThemePaint`; in tests it returns the
|
|
9
9
|
* text unchanged, so assertions see the plain line. Inclusion decisions use
|
|
10
|
-
* plain
|
|
11
|
-
*
|
|
10
|
+
* the plain text's terminal columns (`visibleWidth`, never `.length`, so a CJK
|
|
11
|
+
* path or branch counts two columns a glyph), so the painted result (ANSI adds
|
|
12
|
+
* zero display width) always fits the width it was built for.
|
|
12
13
|
*/
|
|
13
14
|
|
|
15
|
+
import { cutPlainText } from "../lib/tui-render.ts";
|
|
16
|
+
import { tailColumns, visibleWidth } from "../lib/text-width.ts";
|
|
14
17
|
import { costOf, usageEntryCost } from "../lib/usage-bus.ts";
|
|
15
18
|
import type { WorktreeLocation } from "../lib/worktree-channel.ts";
|
|
16
19
|
|
|
@@ -21,8 +24,12 @@ export interface FooterData {
|
|
|
21
24
|
cwd: string;
|
|
22
25
|
home: string;
|
|
23
26
|
branch?: string;
|
|
24
|
-
/**
|
|
25
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Context tokens in use and the model's window. Tokens are unknown right
|
|
29
|
+
* after a compaction until the next reply; the fill then reads `?/window`,
|
|
30
|
+
* as pi's own footer shows it.
|
|
31
|
+
*/
|
|
32
|
+
contextTokens?: number | null;
|
|
26
33
|
contextWindow?: number;
|
|
27
34
|
/** Context percentage, shown in parens after the fill; `null`/undefined omits it
|
|
28
35
|
* (e.g. right after compaction, unknown until the next reply). */
|
|
@@ -73,12 +80,13 @@ function tilde(cwd: string, home: string): string {
|
|
|
73
80
|
|
|
74
81
|
/**
|
|
75
82
|
* Fit a path into `max` columns, keeping the tail (the deepest, most specific
|
|
76
|
-
* folders) with a leading `…`. `max <= 1` collapses to just `…`.
|
|
83
|
+
* folders) with a leading `…`. `max <= 1` collapses to just `…`. Cut by
|
|
84
|
+
* columns on a grapheme boundary, so a wide glyph or an emoji stays whole.
|
|
77
85
|
*/
|
|
78
86
|
function truncatePath(path: string, max: number): string {
|
|
79
|
-
if (path
|
|
87
|
+
if (visibleWidth(path) <= max) return path;
|
|
80
88
|
if (max <= 1) return "…";
|
|
81
|
-
return `…${
|
|
89
|
+
return `…${tailColumns(path, max - 1).text}`;
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
interface Part {
|
|
@@ -86,6 +94,8 @@ interface Part {
|
|
|
86
94
|
painted: string;
|
|
87
95
|
/** Higher drops first when the line is too narrow; 0 = never dropped. */
|
|
88
96
|
dropPriority: number;
|
|
97
|
+
/** The model name: the one core part cut (with an ellipsis) when even the core overflows. */
|
|
98
|
+
model: boolean;
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
const SEP = " · ";
|
|
@@ -103,17 +113,19 @@ export function buildFooterLines(data: FooterData, width: number, paint: Paint):
|
|
|
103
113
|
// A labelled metric: the label is muted, the value takes `color`. The plain
|
|
104
114
|
// form (label + value) drives the width/drop math; the painted form colours
|
|
105
115
|
// the two halves separately.
|
|
106
|
-
const push = (label: string, value: string, color: string, dropPriority = 0) => {
|
|
116
|
+
const push = (label: string, value: string, color: string, dropPriority = 0, model = false) => {
|
|
107
117
|
if (!value) return;
|
|
108
118
|
const plain = label ? `${label} ${value}` : value;
|
|
109
119
|
const painted = label ? `${paint("muted", label)} ${paint(color, value)}` : paint(color, value);
|
|
110
|
-
parts.push({ plain, painted, dropPriority });
|
|
120
|
+
parts.push({ plain, painted, dropPriority, model });
|
|
111
121
|
};
|
|
112
122
|
|
|
113
|
-
// Context fill as "usage: tokens/window (pct%)"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
// Context fill as "usage: tokens/window (pct%)", or "?/window" while the
|
|
124
|
+
// tokens are unknown (right after a compaction).
|
|
125
|
+
if (typeof data.contextWindow === "number" && data.contextWindow > 0) {
|
|
126
|
+
const tokens = typeof data.contextTokens === "number" ? formatTokens(data.contextTokens) : "?";
|
|
127
|
+
const pct = typeof data.contextTokens === "number" && typeof data.contextPercent === "number" ? ` (${data.contextPercent.toFixed(0)}%)` : "";
|
|
128
|
+
push("usage:", `${tokens}/${formatTokens(data.contextWindow)}${pct}`, "dim");
|
|
117
129
|
}
|
|
118
130
|
// Total cost, always shown once known.
|
|
119
131
|
push("cost:", formatCost(data.cost), "dim");
|
|
@@ -123,7 +135,7 @@ export function buildFooterLines(data: FooterData, width: number, paint: Paint):
|
|
|
123
135
|
push("cache-hit:", `${data.cacheHitPercent.toFixed(0)}%`, color, 2);
|
|
124
136
|
}
|
|
125
137
|
// Model, then effort — no labels; both self-describing.
|
|
126
|
-
if (data.model) push("", data.model, "dim");
|
|
138
|
+
if (data.model) push("", data.model, "dim", 0, true);
|
|
127
139
|
if (data.effort) push("", data.effort, "accent");
|
|
128
140
|
|
|
129
141
|
// Left chunk: path + branch, with the open PR attached to the branch as
|
|
@@ -132,7 +144,7 @@ export function buildFooterLines(data: FooterData, width: number, paint: Paint):
|
|
|
132
144
|
// branch (bare-branch fallback), and only when there is no room even for the
|
|
133
145
|
// bare branch does the whole chunk go, leaving metrics alone. The three
|
|
134
146
|
// segments paint separately — path dim, branch in accent, PR in the link
|
|
135
|
-
// colour — but every width/gap decision uses their plain
|
|
147
|
+
// colour — but every width/gap decision uses their plain columns.
|
|
136
148
|
const path = tilde(data.cwd, data.home);
|
|
137
149
|
const branchSeg = data.branch ? ` ⎇ ${data.branch}` : "";
|
|
138
150
|
const prSeg = data.branch && typeof data.pr === "number" ? ` ← PR #${data.pr}` : "";
|
|
@@ -140,7 +152,7 @@ export function buildFooterLines(data: FooterData, width: number, paint: Paint):
|
|
|
140
152
|
// Drop optional right parts (highest priority first) until the metrics fit.
|
|
141
153
|
let kept = parts.slice();
|
|
142
154
|
const rightPlain = () => kept.map((p) => p.plain).join(SEP);
|
|
143
|
-
while (rightPlain()
|
|
155
|
+
while (visibleWidth(rightPlain()) > width) {
|
|
144
156
|
const droppable = kept
|
|
145
157
|
.map((p, i) => ({ p, i }))
|
|
146
158
|
.filter((x) => x.p.dropPriority > 0)
|
|
@@ -149,25 +161,34 @@ export function buildFooterLines(data: FooterData, width: number, paint: Paint):
|
|
|
149
161
|
kept = kept.filter((_, i) => i !== droppable.i);
|
|
150
162
|
}
|
|
151
163
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
164
|
+
// Even the core overflows: cut the model name with an ellipsis so the
|
|
165
|
+
// effort after it stays; when that is not enough, cut the whole line.
|
|
166
|
+
const overflow = visibleWidth(rightPlain()) - width;
|
|
167
|
+
if (overflow > 0) {
|
|
168
|
+
const model = kept.find((p) => p.model);
|
|
169
|
+
const room = model ? visibleWidth(model.plain) - overflow : 0;
|
|
170
|
+
if (model && room >= 4) {
|
|
171
|
+
const shortened = cutPlainText(model.plain, room);
|
|
172
|
+
kept = kept.map((p) => (p === model ? { ...p, plain: shortened, painted: paint("dim", shortened) } : p));
|
|
173
|
+
} else {
|
|
174
|
+
return [paint("dim", cutPlainText(rightPlain(), width))];
|
|
175
|
+
}
|
|
157
176
|
}
|
|
158
177
|
|
|
178
|
+
const rightLen = visibleWidth(rightPlain());
|
|
179
|
+
|
|
159
180
|
const rightPaintedStr = kept.map((p) => p.painted).join(paint("dim", SEP));
|
|
160
181
|
|
|
161
182
|
// Room for the left chunk after the metrics and at least one space of gap.
|
|
162
183
|
// Keep the PR only if the branch+PR both fit; otherwise drop it and keep the
|
|
163
184
|
// bare branch.
|
|
164
185
|
const room = width - rightLen - 1;
|
|
165
|
-
const keptPr = room >= branchSeg
|
|
166
|
-
const suffixLen = branchSeg
|
|
186
|
+
const keptPr = room >= visibleWidth(branchSeg) + visibleWidth(prSeg) + 1 ? prSeg : "";
|
|
187
|
+
const suffixLen = visibleWidth(branchSeg) + visibleWidth(keptPr);
|
|
167
188
|
if (room >= suffixLen + 1) {
|
|
168
189
|
const shownPath = truncatePath(path, room - suffixLen);
|
|
169
190
|
const leftPlain = `${shownPath}${branchSeg}${keptPr}`;
|
|
170
|
-
const gap = " ".repeat(width - leftPlain
|
|
191
|
+
const gap = " ".repeat(Math.max(0, width - visibleWidth(leftPlain) - rightLen));
|
|
171
192
|
const leftPainted = `${paint("dim", shownPath)}${paint("accent", branchSeg)}${paint("mdLink", keptPr)}`;
|
|
172
193
|
return [`${leftPainted}${gap}${rightPaintedStr}`];
|
|
173
194
|
}
|
|
@@ -24,7 +24,7 @@ import { ULTRACODE_STATUS_KEY } from "../effort/slider.ts";
|
|
|
24
24
|
import { USAGE_CHANNEL } from "../lib/usage-bus.ts";
|
|
25
25
|
import { buildFooterLines, computeMainUsage, footerLocation, type FooterData } from "./footer-line.ts";
|
|
26
26
|
import { WORKTREE_CHANNEL, type WorktreeLocation } from "../lib/worktree-channel.ts";
|
|
27
|
-
import { fetchPrNumber } from "./pr.ts";
|
|
27
|
+
import { fetchPrNumber, nextPrPollDelay, PR_SLOW_LOOKUP_MS } from "./pr.ts";
|
|
28
28
|
|
|
29
29
|
export default function footerExtension(pi: ExtensionAPI) {
|
|
30
30
|
if (process.env.CC_FOOTER === "0") return;
|
|
@@ -38,6 +38,27 @@ export default function footerExtension(pi: ExtensionAPI) {
|
|
|
38
38
|
let pr: number | undefined;
|
|
39
39
|
/** Bumped per branch change so a slow gh lookup for an old branch is ignored. */
|
|
40
40
|
let prToken = 0;
|
|
41
|
+
/**
|
|
42
|
+
* The PR is looked up again once a minute while the session is in use, so a
|
|
43
|
+
* PR merged or closed mid-session disappears (pr.ts nextPrPollDelay). A slow
|
|
44
|
+
* or missing `gh` turns polling off; branch changes still look up.
|
|
45
|
+
*/
|
|
46
|
+
const prPoll = { lastLookupAt: 0, lastInputAt: Date.now(), disabled: false };
|
|
47
|
+
let prPollTimer: ReturnType<typeof setTimeout> | undefined;
|
|
48
|
+
const stopPrPoll = () => {
|
|
49
|
+
clearTimeout(prPollTimer);
|
|
50
|
+
prPollTimer = undefined;
|
|
51
|
+
};
|
|
52
|
+
const schedulePrPoll = () => {
|
|
53
|
+
stopPrPoll();
|
|
54
|
+
const delay = nextPrPollDelay(prPoll, Date.now());
|
|
55
|
+
if (delay === undefined) return;
|
|
56
|
+
prPollTimer = setTimeout(() => {
|
|
57
|
+
prPollTimer = undefined;
|
|
58
|
+
refreshLocation();
|
|
59
|
+
}, delay);
|
|
60
|
+
prPollTimer.unref?.();
|
|
61
|
+
};
|
|
41
62
|
|
|
42
63
|
/** Invalidate the memoized line and repaint; set once the footer mounts. */
|
|
43
64
|
let repaint = () => {};
|
|
@@ -71,20 +92,43 @@ export default function footerExtension(pi: ExtensionAPI) {
|
|
|
71
92
|
|
|
72
93
|
const refreshPr = (cwd: string, branch: string | null) => {
|
|
73
94
|
const token = ++prToken;
|
|
95
|
+
stopPrPoll();
|
|
74
96
|
if (!branch) {
|
|
75
97
|
pr = undefined;
|
|
76
98
|
repaint();
|
|
77
99
|
return;
|
|
78
100
|
}
|
|
101
|
+
const startedAt = Date.now();
|
|
102
|
+
prPoll.lastLookupAt = startedAt;
|
|
79
103
|
void fetchPrNumber(cwd, branch).then((found) => {
|
|
80
104
|
if (token !== prToken) return; // a newer branch superseded this lookup
|
|
81
|
-
|
|
105
|
+
if (found.ghMissing || Date.now() - startedAt > PR_SLOW_LOOKUP_MS) prPoll.disabled = true;
|
|
106
|
+
pr = found.pr;
|
|
82
107
|
repaint();
|
|
108
|
+
schedulePrPoll();
|
|
83
109
|
});
|
|
84
110
|
};
|
|
85
111
|
|
|
112
|
+
// Input keeps polling alive; after an hour without any, the next input
|
|
113
|
+
// resumes it. A turn boundary asks again, never sooner than a minute after
|
|
114
|
+
// the last lookup.
|
|
115
|
+
pi.on("input", () => {
|
|
116
|
+
prPoll.lastInputAt = Date.now();
|
|
117
|
+
if (!prPollTimer) schedulePrPoll();
|
|
118
|
+
});
|
|
119
|
+
pi.on("agent_settled", () => {
|
|
120
|
+
if (!prPollTimer) schedulePrPoll();
|
|
121
|
+
});
|
|
122
|
+
pi.on("session_shutdown", () => {
|
|
123
|
+
stopPrPoll();
|
|
124
|
+
prToken++;
|
|
125
|
+
});
|
|
126
|
+
|
|
86
127
|
pi.on("session_start", (_event, ctx) => {
|
|
87
128
|
recomputeMain(ctx);
|
|
129
|
+
// A slow or missing gh turns polling off for one session, not the process.
|
|
130
|
+
prPoll.disabled = false;
|
|
131
|
+
prPoll.lastInputAt = Date.now();
|
|
88
132
|
if (!ctx.hasUI) return;
|
|
89
133
|
|
|
90
134
|
ctx.ui.setFooter((tui: unknown, theme: unknown, footerData: unknown) => {
|
|
@@ -144,6 +188,7 @@ export default function footerExtension(pi: ExtensionAPI) {
|
|
|
144
188
|
return Object.assign(component, {
|
|
145
189
|
dispose: () => {
|
|
146
190
|
stopFollowingBranch();
|
|
191
|
+
stopPrPoll();
|
|
147
192
|
repaint = () => {};
|
|
148
193
|
refreshLocation = () => {};
|
|
149
194
|
},
|
package/extensions/footer/pr.ts
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* a PR whose head lives in the base repo, so it misses the common case of a PR
|
|
9
9
|
* opened from a fork (`isCrossRepository`), even though `list` on the base repo
|
|
10
10
|
* knows about it. `--state open` keeps a merged/closed PR for the same branch
|
|
11
|
-
* name from showing.
|
|
11
|
+
* name from showing. The footer repeats the lookup once a minute while the
|
|
12
|
+
* session is in use (`nextPrPollDelay`), so a PR merged, closed or opened
|
|
13
|
+
* mid-session shows within a minute.
|
|
12
14
|
*
|
|
13
15
|
* The exec lives here but the parse is split out so it unit-tests without a
|
|
14
16
|
* subprocess.
|
|
@@ -35,19 +37,46 @@ export function parsePrNumber(stdout: string): number | undefined {
|
|
|
35
37
|
|
|
36
38
|
const LOOKUP_TIMEOUT_MS = 4000;
|
|
37
39
|
|
|
40
|
+
/** A lookup's outcome: the open PR, if any, and whether `gh` is not installed at all. */
|
|
41
|
+
export interface PrLookup {
|
|
42
|
+
pr: number | undefined;
|
|
43
|
+
ghMissing: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
/**
|
|
39
|
-
* Resolve the open PR number for `branch` in `cwd
|
|
40
|
-
*
|
|
47
|
+
* Resolve the open PR number for `branch` in `cwd`. Never rejects: any failure
|
|
48
|
+
* (no gh, no PR, detached HEAD, timeout) has `pr` undefined, and a `gh` that
|
|
49
|
+
* is not on PATH says so, so the footer stops polling for it.
|
|
41
50
|
*/
|
|
42
|
-
export function fetchPrNumber(cwd: string, branch: string): Promise<
|
|
51
|
+
export function fetchPrNumber(cwd: string, branch: string): Promise<PrLookup> {
|
|
43
52
|
return new Promise((resolve) => {
|
|
44
53
|
execFile(
|
|
45
54
|
"gh",
|
|
46
55
|
["pr", "list", "--head", branch, "--state", "open", "--json", "number"],
|
|
47
56
|
{ cwd, timeout: LOOKUP_TIMEOUT_MS, encoding: "utf8" },
|
|
48
57
|
(error, stdout) => {
|
|
49
|
-
|
|
58
|
+
const ghMissing = (error as NodeJS.ErrnoException | null)?.code === "ENOENT";
|
|
59
|
+
resolve({ pr: error ? undefined : parsePrNumber(stdout), ghMissing });
|
|
50
60
|
},
|
|
51
61
|
);
|
|
52
62
|
});
|
|
53
63
|
}
|
|
64
|
+
|
|
65
|
+
/** Claude Code's PR status cadence (its `usePrStatus`): one lookup a minute while the session is in use. */
|
|
66
|
+
export const PR_POLL_INTERVAL_MS = 60_000;
|
|
67
|
+
/** Polling stops after this long without user input, and resumes on the next input. */
|
|
68
|
+
export const PR_IDLE_STOP_MS = 60 * 60_000;
|
|
69
|
+
/** A lookup slower than this turns polling off for the session (branch changes still look up). */
|
|
70
|
+
export const PR_SLOW_LOOKUP_MS = 4_000;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* When the next PR lookup is due, relative to the last one: never sooner than
|
|
74
|
+
* PR_POLL_INTERVAL_MS after it, so a turn boundary asking for a refresh does
|
|
75
|
+
* not spawn `gh` more than once a minute. Undefined when polling should stop:
|
|
76
|
+
* turned off by a slow lookup, or no input for PR_IDLE_STOP_MS.
|
|
77
|
+
*/
|
|
78
|
+
export function nextPrPollDelay(state: { lastLookupAt: number; lastInputAt: number; disabled: boolean }, now: number): number | undefined {
|
|
79
|
+
if (state.disabled) return undefined;
|
|
80
|
+
if (now - state.lastInputAt >= PR_IDLE_STOP_MS) return undefined;
|
|
81
|
+
return Math.max(0, state.lastLookupAt + PR_POLL_INTERVAL_MS - now);
|
|
82
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* pi.exec is deliberately not used: it hardcodes shell:false and gives the
|
|
8
8
|
* child no stdin, while Claude Code hooks are shell command strings that read
|
|
9
9
|
* a JSON payload from stdin. (Ported from pi-code's runHookCommand, MIT — see
|
|
10
|
-
* docs/decisions.md.)
|
|
10
|
+
* working-docs/decisions.md.)
|
|
11
11
|
*
|
|
12
12
|
* The interpreter is Claude Code's: bash (Git Bash on Windows, honouring
|
|
13
13
|
* `CLAUDE_CODE_GIT_BASH_PATH`) unless the hook says `shell: "powershell"`, or
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
* otherwise `close` never fires and the promise hangs past the timeout
|
|
23
23
|
* (Windows: `taskkill /T /F` on the leader's pid does the same)
|
|
24
24
|
* - setEncoding("utf8") so multi-byte characters can't be split across chunks
|
|
25
|
-
* -
|
|
25
|
+
* - a hook exceeding 1MB per stream is killed and reported as an output-limit
|
|
26
|
+
* failure, never parsed as a silently truncated JSON verdict
|
|
26
27
|
* - stdin errors ignored (a hook that exits without reading stdin — `exit 2`
|
|
27
28
|
* — would otherwise EPIPE-crash the write)
|
|
28
29
|
* - stdin JSON is newline-terminated: without a trailing "\n" a hook doing
|
|
@@ -41,13 +42,14 @@
|
|
|
41
42
|
* SIGCHLD-driven exit callback; with the process handle unref'd the loop
|
|
42
43
|
* counted itself empty and node exited 0 mid-tool, silently, before the
|
|
43
44
|
* hook's `close` ever fired — measured at ~55% of runs with a trivial
|
|
44
|
-
* `exit 0` hook (docs/one-shot-lsp-event-loop-drain.md has the same
|
|
45
|
+
* `exit 0` hook (working-docs/one-shot-lsp-event-loop-drain.md has the same
|
|
45
46
|
* mechanism for the LSP client). A ref'd child holds the loop until `close`.
|
|
46
47
|
*/
|
|
47
48
|
|
|
48
49
|
import type { ChildProcess } from "node:child_process";
|
|
49
50
|
import { detachedSpawnOptions, killProcessTree, waitForChildExit } from "../lib/process-tree.ts";
|
|
50
51
|
import { bashSpawn, POWERSHELL_UTF8_PREFIX, powerShellSpawn, type ShellSpawn, spawnShellCommand } from "../lib/shell-spawn.ts";
|
|
52
|
+
import type { FinishedRun } from "./protocol.ts";
|
|
51
53
|
import type { HookShell } from "./settings.ts";
|
|
52
54
|
|
|
53
55
|
/**
|
|
@@ -76,29 +78,12 @@ export function hookShellSpawn(shell: HookShell | undefined): { spec?: ShellSpaw
|
|
|
76
78
|
return { spec: resolved.spawn, shell: want };
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* null when the process was killed (timeout) or never spawned. Normalized on
|
|
82
|
-
* the timeout path rather than taken from `waitpid`: a group SIGKILL is not
|
|
83
|
-
* atomic, so the shell can be scheduled after its foreground child is killed
|
|
84
|
-
* and before its own signal lands, reap the child and exit(128+9) itself —
|
|
85
|
-
* `close` then reports a normal exit of 137 instead of death by signal
|
|
86
|
-
* (roughly 1% of timeouts under load, and never when the shell had exec'd
|
|
87
|
-
* away, leaving no shell to reap; findings §10.20). Callers should still
|
|
88
|
-
* prefer `timedOut`, which says what happened rather than what it looked
|
|
89
|
-
* like, but they no longer have to.
|
|
90
|
-
*/
|
|
91
|
-
exitCode: number | null;
|
|
92
|
-
timedOut: boolean;
|
|
93
|
-
/** Set when the child could not be spawned at all. */
|
|
94
|
-
spawnError?: string;
|
|
95
|
-
stdout: string;
|
|
96
|
-
stderr: string;
|
|
97
|
-
durationMs: number;
|
|
98
|
-
}
|
|
81
|
+
/** A finished run (`protocol.ts FinishedRun`, field docs there) and how long it took. */
|
|
82
|
+
export type HookRunResult = FinishedRun & { durationMs: number };
|
|
99
83
|
|
|
100
84
|
export interface HookRunOptions {
|
|
101
85
|
cwd: string;
|
|
86
|
+
signal?: AbortSignal;
|
|
102
87
|
/** Seconds, Claude Code convention. Clamped to [1, MAX_TIMEOUT_S]. */
|
|
103
88
|
timeoutSeconds?: number;
|
|
104
89
|
/** Exposed to the hook as CLAUDE_PROJECT_DIR; defaults to cwd. */
|
|
@@ -129,6 +114,10 @@ export function runHookCommand(command: string, stdinJson: string, opts: HookRun
|
|
|
129
114
|
const started = Date.now();
|
|
130
115
|
|
|
131
116
|
return new Promise((resolve) => {
|
|
117
|
+
if (opts.signal?.aborted) {
|
|
118
|
+
resolve({ exitCode: null, timedOut: false, aborted: true, stdout: "", stderr: "", durationMs: Date.now() - started });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
132
121
|
let child: ChildProcess;
|
|
133
122
|
const failed = (spawnError: string) =>
|
|
134
123
|
resolve({ exitCode: null, timedOut: false, spawnError, stdout: "", stderr: "", durationMs: Date.now() - started });
|
|
@@ -175,14 +164,26 @@ export function runHookCommand(command: string, stdinJson: string, opts: HookRun
|
|
|
175
164
|
let stdout = "";
|
|
176
165
|
let stderr = "";
|
|
177
166
|
let timedOut = false;
|
|
167
|
+
let aborted = false;
|
|
168
|
+
let outputLimitExceeded = false;
|
|
169
|
+
const outputBytes = { stdout: 0, stderr: 0 };
|
|
178
170
|
let settled = false;
|
|
171
|
+
const onAbort = () => {
|
|
172
|
+
aborted = true;
|
|
173
|
+
killProcessTree(child, "SIGKILL");
|
|
174
|
+
};
|
|
175
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
179
176
|
|
|
180
177
|
const capture = (sink: "stdout" | "stderr") => (chunk: string) => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
178
|
+
if (outputLimitExceeded) return;
|
|
179
|
+
outputBytes[sink] += Buffer.byteLength(chunk, "utf8");
|
|
180
|
+
if (outputBytes[sink] > MAX_OUTPUT_BYTES) {
|
|
181
|
+
outputLimitExceeded = true;
|
|
182
|
+
killProcessTree(child, "SIGKILL");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (sink === "stdout") stdout += chunk;
|
|
186
|
+
else stderr += chunk;
|
|
186
187
|
};
|
|
187
188
|
child.stdout?.setEncoding("utf8");
|
|
188
189
|
child.stderr?.setEncoding("utf8");
|
|
@@ -200,7 +201,8 @@ export function runHookCommand(command: string, stdinJson: string, opts: HookRun
|
|
|
200
201
|
if (settled) return;
|
|
201
202
|
settled = true;
|
|
202
203
|
clearTimeout(timer);
|
|
203
|
-
|
|
204
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
205
|
+
resolve({ ...result, ...(aborted ? { aborted: true } : {}), ...(outputLimitExceeded ? { outputLimitExceeded: true } : {}), durationMs: Date.now() - started });
|
|
204
206
|
};
|
|
205
207
|
|
|
206
208
|
// Settles on exit plus a short stdio grace, not on `close`: a grandchild
|
|
@@ -211,7 +213,7 @@ export function runHookCommand(command: string, stdinJson: string, opts: HookRun
|
|
|
211
213
|
// keeps "we killed it" from ever looking like an ordinary non-zero exit,
|
|
212
214
|
// which fails OPEN downstream.
|
|
213
215
|
waitForChildExit(child).then(
|
|
214
|
-
({ code }) => finish({ exitCode: timedOut ? null : code, timedOut, stdout, stderr }),
|
|
216
|
+
({ code }) => finish({ exitCode: timedOut || aborted || outputLimitExceeded ? null : code, timedOut, stdout, stderr }),
|
|
215
217
|
(error: Error) => finish({ exitCode: null, timedOut, spawnError: error.message, stdout, stderr }),
|
|
216
218
|
);
|
|
217
219
|
|