one-code-extension 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -5
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +40 -28
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +26 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +58 -31
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +124 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +642 -0
- package/extensions/background/index.ts +614 -248
- package/extensions/background/loop-fire.ts +173 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +56 -0
- package/extensions/background/wakeup.ts +178 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +66 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +250 -78
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/cache-marks/index.ts +42 -0
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +436 -63
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +219 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/budget.ts +16 -2
- package/extensions/context-budget/index.ts +35 -15
- package/extensions/context-management/index.ts +36 -28
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +141 -25
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +420 -42
- package/extensions/file-tracker/replay.ts +137 -24
- package/extensions/file-tracker/restore.ts +66 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +45 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +209 -58
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/prompt-context.ts +59 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +26 -12
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/ledger/index.ts +46 -0
- package/extensions/ledger/wire.ts +154 -0
- package/extensions/lib/agent-cron.ts +146 -0
- package/extensions/lib/agent-loader.ts +18 -3
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-auth.ts +98 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +52 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +97 -0
- package/extensions/lib/covered-messages.ts +23 -0
- package/extensions/lib/deferred.ts +359 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/ledger/fold-memo.ts +49 -0
- package/extensions/lib/ledger/fold.ts +265 -0
- package/extensions/lib/ledger/layout.ts +72 -0
- package/extensions/lib/ledger/plan.ts +102 -0
- package/extensions/lib/ledger/records.ts +276 -0
- package/extensions/lib/ledger/state.ts +47 -0
- package/extensions/lib/ledger/validate.ts +141 -0
- package/extensions/lib/ledger/wire.ts +632 -0
- package/extensions/lib/local-command.ts +71 -19
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +178 -0
- package/extensions/lib/model-catalog/models-dev.json +18533 -0
- package/extensions/lib/model-catalog/openrouter.json +5050 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +357 -73
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +38 -2
- package/extensions/lib/plan-mode-channels.ts +42 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/previous-mark.ts +101 -0
- package/extensions/lib/process-state.ts +48 -0
- package/extensions/lib/process-tree.ts +132 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +370 -358
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +547 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +24 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +112 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +129 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +159 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +76 -12
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +90 -43
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +220 -121
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +164 -73
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/model-default/index.ts +86 -19
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1426 -398
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +11 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +276 -92
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/run-state.ts +26 -0
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +64 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +14 -7
- package/extensions/recap/index.ts +73 -71
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/announce.ts +30 -0
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +469 -132
- package/extensions/skill/invoke.ts +118 -75
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +146 -31
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +82 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1594 -852
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +5 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/parent-mode.ts +50 -0
- package/extensions/subagents/pending-claim.ts +18 -1
- package/extensions/subagents/runner.ts +135 -80
- package/extensions/subagents/runs.ts +44 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +165 -47
- package/extensions/system-prompt/template.ts +34 -51
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +204 -70
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/index.ts +366 -144
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +108 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +157 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +33 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +162 -44
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +21 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
- package/extensions/tool-search/announce.ts +0 -42
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a session switch or quit would stop (pure). Running background tasks and
|
|
3
|
+
* scheduled jobs belong to the session: `/clear`, `/new`, `/resume`, a fork and
|
|
4
|
+
* quitting stop them. A `/reload` stops the running tasks only: it keeps the
|
|
5
|
+
* conversation, so the scheduled jobs come back from the branch
|
|
6
|
+
* (background/index.ts `armFromBranch`). pi lets an extension cancel a switch
|
|
7
|
+
* (`session_before_switch`, `session_before_fork`), so a switch asks first;
|
|
8
|
+
* reloading and quitting have no such hook, so the standing widget line says
|
|
9
|
+
* it up front.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** A running task or scheduled job, as the dialog and widget name it. */
|
|
13
|
+
export interface SessionWork {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const SWITCH_CANCEL = "Cancel";
|
|
19
|
+
export const SWITCH_STOP = "Stop them and continue";
|
|
20
|
+
|
|
21
|
+
/** At most this many items are named in the dialog; the rest are counted. */
|
|
22
|
+
const MAX_NAMED = 6;
|
|
23
|
+
|
|
24
|
+
const plural = (n: number, word: string) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
25
|
+
|
|
26
|
+
function counts(tasks: number, jobs: number): string {
|
|
27
|
+
const parts = [tasks > 0 ? plural(tasks, "background task") : "", jobs > 0 ? plural(jobs, "scheduled job") : ""].filter(Boolean);
|
|
28
|
+
return parts.join(" and ");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The confirm dialog's title, or undefined when nothing would be stopped.
|
|
33
|
+
* `action` names the switch ("Starting a new session"): pi reports /clear and
|
|
34
|
+
* /new alike, so the dialog cannot name the command.
|
|
35
|
+
*/
|
|
36
|
+
export function switchWarning(tasks: readonly SessionWork[], jobs: readonly SessionWork[], action: string): string | undefined {
|
|
37
|
+
if (tasks.length === 0 && jobs.length === 0) return undefined;
|
|
38
|
+
const items = [...tasks, ...jobs];
|
|
39
|
+
const named = items.slice(0, MAX_NAMED).map((item) => ` ${item.id} ${item.label}`);
|
|
40
|
+
if (items.length > MAX_NAMED) named.push(` … and ${items.length - MAX_NAMED} more`);
|
|
41
|
+
const one = items.length === 1;
|
|
42
|
+
const them = one ? "it" : "them";
|
|
43
|
+
return [
|
|
44
|
+
`${counts(tasks.length, jobs.length)} ${one ? "is" : "are"} still active. ${action} stops ${them}:`,
|
|
45
|
+
named.join("\n"),
|
|
46
|
+
tasks.length > 0 ? `Cancel to keep ${them} running; /tasks opens or stops a task.` : `Cancel to keep ${them}.`,
|
|
47
|
+
].join("\n\n");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The standing widget line while anything runs, or undefined when nothing does. */
|
|
51
|
+
export function workWidgetLine(tasks: number, jobs: number): string | undefined {
|
|
52
|
+
if (tasks === 0 && jobs === 0) return undefined;
|
|
53
|
+
// A /reload stops only the running tasks; the jobs re-arm from the branch.
|
|
54
|
+
const stoppedBy = jobs === 0 ? "/clear, /reload or quitting" : tasks === 0 ? "/clear or quitting" : "/clear or quitting, the tasks by /reload too";
|
|
55
|
+
return ` ${counts(tasks, jobs)} · stopped by ${stoppedBy}${tasks > 0 ? " · /tasks to manage" : ""}`;
|
|
56
|
+
}
|
|
@@ -1,80 +1,203 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* schedule_wakeup
|
|
2
|
+
* schedule_wakeup (pure): Claude Code's self-paced `/loop` timer, 2.1.282
|
|
3
|
+
* (findings §21). A wakeup is a one-shot job in the session's CronStore
|
|
4
|
+
* (source "wakeup"), so cron_list shows it and
|
|
5
|
+
* one timer fires everything. Its cron names the target minute
|
|
6
|
+
* (`M H * * *`); the fire itself is the exact target time.
|
|
3
7
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* `DynamicLoop` is Claude Code's loop core: one pending wakeup (a new one
|
|
9
|
+
* supersedes the rest), a 7-day age per prompt, `stop` cancelling every
|
|
10
|
+
* wakeup, and the keepalive: a turn that ran a wakeup and ended without
|
|
11
|
+
* scheduling the next gets one 1200 s fallback, and a second miss ends the
|
|
12
|
+
* loop. Texts are Claude Code's with our tool names.
|
|
9
13
|
*/
|
|
10
14
|
|
|
15
|
+
import { type CronStore, RECURRING_MAX_AGE_MS } from "./cron.ts";
|
|
16
|
+
|
|
11
17
|
export const MIN_DELAY_SECONDS = 60;
|
|
12
18
|
export const MAX_DELAY_SECONDS = 3600;
|
|
19
|
+
/** Claude Code's keepalive fallback delay and budget. */
|
|
20
|
+
export const KEEPALIVE_DELAY_SECONDS = 1200;
|
|
21
|
+
export const KEEPALIVE_BUDGET = 1;
|
|
13
22
|
|
|
14
23
|
export function clampDelaySeconds(delay: number): number {
|
|
15
24
|
if (!Number.isFinite(delay)) return MIN_DELAY_SECONDS;
|
|
16
25
|
return Math.min(MAX_DELAY_SECONDS, Math.max(MIN_DELAY_SECONDS, Math.round(delay)));
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/** Nothing changed this tick (matches CC's /loop noop flag). Recorded for callers; no effect on scheduling. */
|
|
24
|
-
noop?: boolean;
|
|
28
|
+
/** An env flag as Claude Code reads a boolean: unset is `fallback`, a false-like value is off. */
|
|
29
|
+
export function envFlag(value: string | undefined, fallback: boolean): boolean {
|
|
30
|
+
if (value === undefined) return fallback;
|
|
31
|
+
return !["", "0", "false", "no", "off"].includes(value.trim().toLowerCase());
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
task: string;
|
|
34
|
+
/** Keepalive is on unless `CLAUDE_CODE_LOOP_KEEPALIVE` turns it off. */
|
|
35
|
+
export function keepaliveEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
36
|
+
return envFlag(env.CLAUDE_CODE_LOOP_KEEPALIVE, true);
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const n = Number(m[1]);
|
|
42
|
-
const unit = m[2].toLowerCase();
|
|
43
|
-
const seconds = unit === "h" ? n * 3600 : unit === "m" ? n * 60 : n;
|
|
44
|
-
return { intervalSeconds: seconds, task: m[3].trim() };
|
|
45
|
-
}
|
|
46
|
-
return { task: raw.trim() };
|
|
39
|
+
export interface WakeupSchedule {
|
|
40
|
+
scheduledFor: number;
|
|
41
|
+
clampedDelaySeconds: number;
|
|
42
|
+
wasClamped: boolean;
|
|
43
|
+
/** The store's job id, so a cron_delete of it is matched when the branch is replayed. */
|
|
44
|
+
jobId?: string;
|
|
45
|
+
/** When this prompt's loop started: its age carries across a /reload. */
|
|
46
|
+
startedAt: number;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
interface PromptAge {
|
|
50
|
+
startedAt: number;
|
|
51
|
+
lastScheduledFor: number;
|
|
52
|
+
agedOut?: boolean;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
export class DynamicLoop {
|
|
56
|
+
/** The wakeup prompt whose turn is running, for the keepalive (Claude Code's in-flight tick). */
|
|
57
|
+
inFlight: string | null = null;
|
|
58
|
+
private misses = 0;
|
|
59
|
+
private readonly ages = new Map<string, PromptAge>();
|
|
60
|
+
|
|
61
|
+
constructor(
|
|
62
|
+
private readonly store: CronStore,
|
|
63
|
+
private readonly maxAgeMs = RECURRING_MAX_AGE_MS,
|
|
64
|
+
) {}
|
|
65
|
+
|
|
66
|
+
/** When the loop for `prompt` started, if it is running. */
|
|
67
|
+
chainStart(prompt: string): number | undefined {
|
|
68
|
+
return this.ages.get(prompt)?.startedAt;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A pending wakeup exists. */
|
|
72
|
+
hasPending(): boolean {
|
|
73
|
+
return this.store.list().some((job) => job.source === "wakeup");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Claude Code's shared scheduling step: null when the loop for this prompt
|
|
78
|
+
* is past its maximum age, which ends it.
|
|
79
|
+
*/
|
|
80
|
+
schedule(delaySeconds: number, prompt: string, now: number, viaKeepalive = false): WakeupSchedule | null {
|
|
81
|
+
if (!viaKeepalive) this.misses = 0;
|
|
82
|
+
this.store.deleteWhere((job) => job.source === "wakeup");
|
|
83
|
+
const age = this.ages.get(prompt);
|
|
84
|
+
const stale = age !== undefined && now > age.lastScheduledFor + MAX_DELAY_SECONDS * 1000;
|
|
85
|
+
const startedAt = age === undefined || stale ? now : age.startedAt;
|
|
86
|
+
if (this.maxAgeMs > 0 && now - startedAt >= this.maxAgeMs) {
|
|
87
|
+
if (!age?.agedOut) this.ages.set(prompt, { startedAt, lastScheduledFor: now - (MAX_DELAY_SECONDS - MIN_DELAY_SECONDS) * 1000, agedOut: true });
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const clamped = clampDelaySeconds(delaySeconds);
|
|
91
|
+
const wasClamped = !(delaySeconds >= MIN_DELAY_SECONDS && delaySeconds <= MAX_DELAY_SECONDS);
|
|
92
|
+
const scheduledFor = now + clamped * 1000;
|
|
93
|
+
const target = new Date(scheduledFor);
|
|
94
|
+
const created = this.store.create({ cron: `${target.getMinutes()} ${target.getHours()} * * *`, prompt, recurring: false, source: "wakeup", fireAt: scheduledFor }, now);
|
|
95
|
+
this.ages.set(prompt, { startedAt, lastScheduledFor: scheduledFor });
|
|
96
|
+
if (viaKeepalive) this.misses++;
|
|
97
|
+
return { scheduledFor, clampedDelaySeconds: clamped, wasClamped, startedAt, ...(created.ok ? { jobId: created.job.id } : {}) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Take back a pending wakeup's loop state from the branch (a /reload, a
|
|
102
|
+
* /tree switch): its age, and whether the keepalive armed it, which spends
|
|
103
|
+
* the loop's one fallback.
|
|
104
|
+
*/
|
|
105
|
+
restoreChain(prompt: string, startedAt: number, scheduledFor: number, viaKeepalive: boolean): void {
|
|
106
|
+
this.ages.set(prompt, { startedAt, lastScheduledFor: scheduledFor });
|
|
107
|
+
this.misses = viaKeepalive ? 1 : 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* `stop: true`: cancel every pending wakeup; the count is the result's.
|
|
112
|
+
* The cancelled prompts and the one in flight forget their age, so a
|
|
113
|
+
* restarted loop gets a fresh 7 days (Claude Code's `forgetChainStart`).
|
|
114
|
+
*/
|
|
115
|
+
stop(): number {
|
|
116
|
+
const cancelled = this.store.deleteWhere((job) => job.source === "wakeup");
|
|
117
|
+
for (const job of cancelled) this.ages.delete(job.prompt);
|
|
118
|
+
if (this.inFlight !== null) this.ages.delete(this.inFlight);
|
|
119
|
+
this.inFlight = null;
|
|
120
|
+
this.misses = 0;
|
|
121
|
+
return cancelled.length;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A turn ended. When it ran a wakeup and left none pending, re-arm once at
|
|
126
|
+
* the keepalive delay; past the budget the loop ends.
|
|
127
|
+
*/
|
|
128
|
+
settle(now: number, keepalive = keepaliveEnabled()): "none" | "armed" | "ended" {
|
|
129
|
+
const prompt = this.inFlight;
|
|
130
|
+
this.inFlight = null;
|
|
131
|
+
if (prompt === null || !keepalive || this.hasPending()) return "none";
|
|
132
|
+
if (this.misses >= KEEPALIVE_BUDGET) return "ended";
|
|
133
|
+
return this.schedule(KEEPALIVE_DELAY_SECONDS, prompt, now, true) ? "armed" : "ended";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
clear(): void {
|
|
137
|
+
this.inFlight = null;
|
|
138
|
+
this.misses = 0;
|
|
139
|
+
this.ages.clear();
|
|
140
|
+
}
|
|
65
141
|
}
|
|
66
142
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
143
|
+
// ── Model-facing text (Claude Code 2.1.282, our tool names) ────────────────
|
|
144
|
+
|
|
145
|
+
export function formatScheduled(result: WakeupSchedule, now: number): string {
|
|
146
|
+
const at = new Date(result.scheduledFor).toTimeString().slice(0, 8);
|
|
147
|
+
const inSeconds = Math.max(0, Math.round((result.scheduledFor - now) / 1000));
|
|
148
|
+
const clamped = result.wasClamped ? ` (clamped to ${result.clampedDelaySeconds}s from your requested value)` : "";
|
|
149
|
+
return `Next wakeup scheduled for ${at} (in ${inSeconds}s)${clamped}. Nothing more to do this turn — the harness re-invokes you when the wakeup fires or a task-notification arrives.`;
|
|
70
150
|
}
|
|
71
151
|
|
|
72
|
-
export
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return `
|
|
152
|
+
export const AGED_OUT_RESULT = "Wakeup not scheduled. The loop reached its maximum duration — the loop has ended; do not re-issue.";
|
|
153
|
+
|
|
154
|
+
export function formatStopped(cancelled: number): string {
|
|
155
|
+
const monitor = "If you armed a monitor for this loop, task_stop it now; otherwise nothing more to do this turn.";
|
|
156
|
+
if (cancelled === 0) {
|
|
157
|
+
return `Loop stopped — any dynamic loop in this session is ended; there was no pending wakeup to cancel. If you are running a fixed-interval /loop (a recurring cron), it is NOT stopped by this call — cancel it with cron_delete. ${monitor}`;
|
|
158
|
+
}
|
|
159
|
+
return `Loop stopped — cancelled ${cancelled} pending wakeup(s); no further dynamic-loop wakeups scheduled. ${monitor}`;
|
|
80
160
|
}
|
|
161
|
+
|
|
162
|
+
export const WAKEUP_ERRORS = {
|
|
163
|
+
delayAndReason: "`delaySeconds` and `reason` are required when `stop` is not true.",
|
|
164
|
+
prompt: "`prompt` is required when `stop` is not true.",
|
|
165
|
+
noop: "`noop` is required when `stop` is not true.",
|
|
166
|
+
} as const;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Claude Code's description for a session whose prompt-cache TTL it cannot
|
|
170
|
+
* name (its third branch): One Code runs on any provider, so the 1-hour and
|
|
171
|
+
* 5-minute branches, which assume Anthropic billing, never apply as such.
|
|
172
|
+
*/
|
|
173
|
+
export const SCHEDULE_WAKEUP_DESCRIPTION = `Schedule when to resume work in /loop dynamic mode — the user invoked /loop without an interval, asking you to self-pace iterations of a specific task.
|
|
174
|
+
|
|
175
|
+
Do NOT schedule a short-interval wakeup to poll for background work you started — when harness-tracked work finishes, you are re-invoked automatically, so polling is wasted. Instead schedule a long fallback (1200s+) so the loop survives if the work hangs or never notifies. The exception is external work the harness cannot track (a CI run, a deploy, a remote queue) — there, pick a delay matched to how fast that state actually changes.
|
|
176
|
+
|
|
177
|
+
Pass the same /loop prompt back via \`prompt\` each turn so the next firing repeats the task. For an autonomous /loop (no user prompt), pass the literal sentinel \`<<autonomous-loop-dynamic>>\` as \`prompt\` instead — the runtime resolves it back to the autonomous-loop instructions at fire time. (There is a similar \`<<autonomous-loop>>\` sentinel for cron_create-based autonomous loops; do not confuse the two — schedule_wakeup always uses the \`-dynamic\` variant.) To end the loop, call this tool with \`stop: true\` (omit every other field) — the loop ends immediately and no further wakeups fire.
|
|
178
|
+
|
|
179
|
+
Set \`noop: true\` if nothing changed — you checked and there's nothing to report ("no change", "still waiting", "quiet hold"). Set \`noop: false\` if something happened worth keeping — you edited a file, posted a message, advanced state, or surfaced a finding. Consecutive \`noop: true\` ticks are collapsed in the user's terminal view and tracked as a streak, so long quiet holds stay legible to the user without scrolling. Omit \`noop\` when stopping (\`stop: true\`).
|
|
180
|
+
|
|
181
|
+
## Picking delaySeconds
|
|
182
|
+
|
|
183
|
+
The Anthropic prompt cache decides how expensive a wake-up is: waking inside the cache TTL re-reads your conversation context cached (fast, cheap); waking past it re-reads everything uncached. The TTL depends on how the session is billed: Claude subscriber sessions get a 1-hour TTL (dropping to 5 minutes during usage overage), while API-key, Bedrock, and Vertex sessions default to 5 minutes.
|
|
184
|
+
|
|
185
|
+
In either regime: never schedule extra wakeups just to keep the cache warm — they cost more than the cache miss they avoid. Match the delay to what you're actually waiting for: when actively polling external state the harness can't notify you about (a CI run, a deploy, a remote queue), pick the delay from how fast that state actually changes; for idle ticks with no specific signal to watch, default to **1200s–1800s** (20–30 min) — the user can always interrupt if they need you sooner.
|
|
186
|
+
|
|
187
|
+
On a 5-minute TTL only, two refinements: under 300s (60s–270s) the cache stays warm, so prefer 270s over 300s when actively polling (300s is the worst-of-both — you pay the miss without amortizing it); and commit to 1200s+ rather than repeated ~300s waits, so one cache miss buys a long wait.
|
|
188
|
+
|
|
189
|
+
The runtime clamps to [60, 3600], so you don't need to clamp yourself.
|
|
190
|
+
|
|
191
|
+
## The reason field
|
|
192
|
+
|
|
193
|
+
One short sentence on what you chose and why. Goes to telemetry and is shown back to the user. "watching CI run" beats "waiting." The user reads this to understand what you're doing without having to predict your cadence in advance — make it specific.
|
|
194
|
+
`;
|
|
195
|
+
|
|
196
|
+
export const SCHEDULE_WAKEUP_PARAMS = {
|
|
197
|
+
delaySeconds: "Seconds from now to wake up. Clamped to [60, 3600] by the runtime. Required unless `stop` is true.",
|
|
198
|
+
reason: "One short sentence explaining the chosen delay. Goes to telemetry and is shown to the user. Be specific. Required unless `stop` is true.",
|
|
199
|
+
prompt:
|
|
200
|
+
"The /loop input to fire on wake-up. Pass the same /loop input verbatim each turn so the next firing re-enters the skill and continues the loop. For autonomous /loop (no user prompt), pass the literal sentinel `<<autonomous-loop-dynamic>>` instead (the dynamic-pacing variant, not the cron_create-mode `<<autonomous-loop>>`). Required unless `stop` is true.",
|
|
201
|
+
stop: "Set to true to end the dynamic loop immediately instead of scheduling another wakeup. When true, all other fields are ignored and no further wakeups fire.",
|
|
202
|
+
noop: "true = nothing changed (you checked and there is nothing to report). false = something happened worth keeping (edited a file, posted a message, advanced state, surfaced a finding). Consecutive noop:true ticks are collapsed in the user's terminal view and tracked as a streak. Required unless `stop` is true.",
|
|
203
|
+
} as const;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* A background command spawns detached in its own process group, spools
|
|
6
6
|
* stdout+stderr to memory (tail-capped) and to a log file when one is given,
|
|
7
7
|
* and satisfies the BackgroundTask contract so task_output/task_stop work
|
|
8
|
-
* unchanged. Robustness rules from docs/features/tools/records/background-bash.md: `output()`
|
|
8
|
+
* unchanged. Robustness rules from working-docs/features/tools/records/background-bash.md: `output()`
|
|
9
9
|
* never returns an empty body for a finished task (an explicitly-marked
|
|
10
10
|
* "(no output)" beats an ambiguous blank a weak model reads as failure), and
|
|
11
11
|
* the completion callback receives exactly what `output()` returns, so the
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { createWriteStream } from "node:fs";
|
|
16
|
+
import { StringDecoder } from "node:string_decoder";
|
|
16
17
|
import type { BackgroundTask } from "../background/registry.ts";
|
|
17
18
|
import { whenAborted } from "../lib/abort.ts";
|
|
18
|
-
import { detachedSpawnOptions, KILL_GRACE_MS, stopProcessTree, waitForChildExit } from "../lib/process-tree.ts";
|
|
19
|
+
import { detachedSpawnOptions, KILL_GRACE_MS, rememberProcessGroup, stopProcessTree, waitForChildExit } from "../lib/process-tree.ts";
|
|
19
20
|
import { bashSpawnOrThrow, type ShellSpawn, spawnShellCommand } from "../lib/shell-spawn.ts";
|
|
20
21
|
|
|
21
22
|
export const STORED_OUTPUT_CAP = 200_000;
|
|
@@ -35,6 +36,8 @@ export interface BashFinishSummary {
|
|
|
35
36
|
timedOut: boolean;
|
|
36
37
|
/** Exactly what task.output() returns — never empty. */
|
|
37
38
|
output: string;
|
|
39
|
+
/** The flushed spool, absent if writing it failed. */
|
|
40
|
+
logPath?: string;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
export interface StartBackgroundBashOptions {
|
|
@@ -61,23 +64,33 @@ export function startBackgroundBash(options: StartBackgroundBashOptions): Backgr
|
|
|
61
64
|
...detachedSpawnOptions(),
|
|
62
65
|
stdio: ["ignore", "pipe", "pipe"],
|
|
63
66
|
});
|
|
67
|
+
rememberProcessGroup(child);
|
|
64
68
|
|
|
65
69
|
let stored = "";
|
|
70
|
+
let overflowed = false;
|
|
71
|
+
let spoolFailed = false;
|
|
66
72
|
let ended = false;
|
|
67
73
|
let stopRequested = false;
|
|
68
74
|
let timedOut = false;
|
|
69
75
|
const log = options.logPath ? createWriteStream(options.logPath, { flags: "a" }) : undefined;
|
|
70
76
|
log?.on("error", () => {
|
|
71
|
-
//
|
|
77
|
+
// Never name a missing or partial file as the full output.
|
|
78
|
+
spoolFailed = true;
|
|
79
|
+
task.logPath = undefined;
|
|
72
80
|
});
|
|
73
81
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
// One streaming decoder per stream: a UTF-8 character split across two
|
|
83
|
+
// reads must not become two U+FFFD. The log gets the raw bytes.
|
|
84
|
+
const stdoutText = new StringDecoder("utf8");
|
|
85
|
+
const stderrText = new StringDecoder("utf8");
|
|
86
|
+
const appender = (decoder: StringDecoder) => (chunk: Buffer) => {
|
|
87
|
+
const text = stored + decoder.write(chunk);
|
|
88
|
+
overflowed ||= text.length > STORED_OUTPUT_CAP;
|
|
89
|
+
stored = tailCap(text, STORED_OUTPUT_CAP);
|
|
90
|
+
if (!spoolFailed) log?.write(chunk);
|
|
78
91
|
};
|
|
79
|
-
child.stdout?.on("data",
|
|
80
|
-
child.stderr?.on("data",
|
|
92
|
+
child.stdout?.on("data", appender(stdoutText));
|
|
93
|
+
child.stderr?.on("data", appender(stderrText));
|
|
81
94
|
|
|
82
95
|
let finish!: () => void;
|
|
83
96
|
const finished = new Promise<void>((resolve) => {
|
|
@@ -94,7 +107,9 @@ export function startBackgroundBash(options: StartBackgroundBashOptions): Backgr
|
|
|
94
107
|
startedAt: Date.now(),
|
|
95
108
|
logPath: options.logPath,
|
|
96
109
|
ownUI: true, // rendered live by the subagents panel's shell manager
|
|
97
|
-
output: () =>
|
|
110
|
+
output: () => overflowed && !task.logPath
|
|
111
|
+
? `[The command's spool file could not be written, so only the last ${STORED_OUTPUT_CAP.toLocaleString("en-US")} characters of its output were kept.]\n${stored}`
|
|
112
|
+
: stored || (task.status === "running" ? "" : EMPTY_OUTPUT_MARKER),
|
|
98
113
|
stop: () => {
|
|
99
114
|
stopRequested = true;
|
|
100
115
|
// SIGTERM the tree, SIGKILL after the grace: a command that traps TERM
|
|
@@ -117,18 +132,39 @@ export function startBackgroundBash(options: StartBackgroundBashOptions): Backgr
|
|
|
117
132
|
if (ended) return;
|
|
118
133
|
ended = true;
|
|
119
134
|
if (timer) clearTimeout(timer);
|
|
120
|
-
|
|
121
|
-
|
|
135
|
+
// An incomplete sequence at the very end decodes as U+FFFD, as it would have.
|
|
136
|
+
const rest = stdoutText.end() + stderrText.end();
|
|
137
|
+
if (rest) stored = tailCap(stored + rest, STORED_OUTPUT_CAP);
|
|
138
|
+
let completed = false;
|
|
122
139
|
const complete = () => {
|
|
140
|
+
if (completed) return;
|
|
141
|
+
completed = true;
|
|
142
|
+
task.status = status;
|
|
143
|
+
task.finishedAt = Date.now();
|
|
123
144
|
finish();
|
|
124
|
-
options.onFinished(task, { exitCode, signal, stopped: stopRequested, timedOut, output: task.output() });
|
|
145
|
+
options.onFinished(task, { exitCode, signal, stopped: stopRequested, timedOut, output: task.output(), logPath: task.logPath });
|
|
125
146
|
};
|
|
126
147
|
// end() flushes asynchronously; `finished` must not resolve while the
|
|
127
148
|
// log file is still short of what output() returns, or a reader sent to
|
|
128
|
-
// logPath by the completion notification can see a truncated file.
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
149
|
+
// logPath by the completion notification can see a truncated file.
|
|
150
|
+
// `close` follows a clean end and an error alike; a stuck stream (a
|
|
151
|
+
// write that never calls back) must not keep the task running, so after
|
|
152
|
+
// a second it is dropped and the partial file is no longer named.
|
|
153
|
+
if (log && !log.closed) {
|
|
154
|
+
const fallback = setTimeout(() => {
|
|
155
|
+
spoolFailed = true;
|
|
156
|
+
task.logPath = undefined;
|
|
157
|
+
log.destroy();
|
|
158
|
+
complete();
|
|
159
|
+
}, 1_000);
|
|
160
|
+
log.once("close", () => {
|
|
161
|
+
clearTimeout(fallback);
|
|
162
|
+
complete();
|
|
163
|
+
});
|
|
164
|
+
log.end();
|
|
165
|
+
} else {
|
|
166
|
+
complete();
|
|
167
|
+
}
|
|
132
168
|
};
|
|
133
169
|
|
|
134
170
|
// Exit plus a short stdio grace, not `close`: a descendant the stop missed
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** One Code's bash tool for an in-process child session (lib/child-extensions.ts): the same tool, prints nothing. */
|
|
2
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { bashTool } from "./index.ts";
|
|
4
|
+
|
|
5
|
+
export default function childBashExtension(pi: ExtensionAPI) {
|
|
6
|
+
bashTool(pi, { announce: false });
|
|
7
|
+
}
|