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
|
@@ -28,6 +28,23 @@ export interface PendingRun {
|
|
|
28
28
|
taskId: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/** The runs by name and task id, comma-separated. */
|
|
32
|
+
function namedRuns(runs: readonly PendingRun[]): string {
|
|
33
|
+
return runs.map((run) => `${run.name} (task ${run.taskId})`).join(", ");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The background agents still working when a compaction summarized the calls
|
|
38
|
+
* that started them: the new epoch's history no longer shows them, so a model
|
|
39
|
+
* that is not told could start the same work again or report a result it
|
|
40
|
+
* never got (lifecycle-grid-hunt round 3, R3-M27; Claude Code re-attaches its
|
|
41
|
+
* task status after a compaction).
|
|
42
|
+
*/
|
|
43
|
+
export function runningAfterCompactionReminder(runs: readonly PendingRun[]): string | undefined {
|
|
44
|
+
if (runs.length === 0) return undefined;
|
|
45
|
+
return `Background agents you started before this conversation was compacted are still running: ${namedRuns(runs)}. Each one notifies you when it finishes; do not start the same work again. Use task_output to check on one, or task_stop to end it.`;
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
/**
|
|
32
49
|
* The one-shot for a turn that ended with runs still pending, or undefined when
|
|
33
50
|
* there is nothing to say. Named runs, so a model with several in flight knows
|
|
@@ -35,7 +52,7 @@ export interface PendingRun {
|
|
|
35
52
|
*/
|
|
36
53
|
export function pendingClaimReminder(runs: readonly PendingRun[]): string | undefined {
|
|
37
54
|
if (runs.length === 0) return undefined;
|
|
38
|
-
const named = runs
|
|
55
|
+
const named = namedRuns(runs);
|
|
39
56
|
const subject = runs.length === 1 ? "It has" : "They have";
|
|
40
57
|
return (
|
|
41
58
|
`You ended that turn while ${named} ${runs.length === 1 ? "was" : "were"} still running. ${subject} not reported yet, ` +
|
|
@@ -12,19 +12,25 @@
|
|
|
12
12
|
* `<sessionDir>/subagents/<taskId>/`, so a finished run can be resumed from disk.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { childWriteWatcher } from "../lib/child-writes.ts";
|
|
15
16
|
import { createHash } from "node:crypto";
|
|
16
|
-
import {
|
|
17
|
-
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
18
|
import { type AgentSession, type ExtensionError, getAgentDir, SessionManager, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
19
|
-
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
19
|
+
import type { Api, Message, Model } from "@earendil-works/pi-ai";
|
|
20
20
|
import { whenAborted } from "../lib/abort.ts";
|
|
21
|
-
import { type AgentLoaderOptions, buildAgentLoader, createSharedModelRuntime, openChildSession } from "../lib/agent-loader.ts";
|
|
21
|
+
import { type AgentLoaderOptions, buildAgentLoader, createSharedModelRuntime, disposeChildSession, openChildSession } from "../lib/agent-loader.ts";
|
|
22
22
|
import { agentPromptIdentity, PrefixWarmGate, prefixWarmKey, type Release } from "../lib/prefix-warm-gate.ts";
|
|
23
23
|
import type { PermissionBridge } from "../permissions/subagent-gate.ts";
|
|
24
24
|
import type { HookBridge } from "../hooks/subagent-bridge.ts";
|
|
25
25
|
import { findConfigured, modelSpec } from "../lib/model-policy.ts";
|
|
26
26
|
import { isModelUnavailableError } from "../auto-mode/model-select.ts";
|
|
27
|
-
import { type AgentDefinition,
|
|
27
|
+
import { type AgentDefinition, agentToolOptions, unusableAllowlistError } from "./agents.ts";
|
|
28
|
+
import { CHILD_EXTENSION_PATHS } from "../lib/child-extensions.ts";
|
|
29
|
+
import { type HandbackSlot, SUBAGENT_HANDBACK, subagentHandbackExtension } from "../lib/subagent-handback.ts";
|
|
30
|
+
import type { RequestCapture } from "../lib/request-replay.ts";
|
|
31
|
+
import { forkCacheExtension } from "./fork-cache.ts";
|
|
32
|
+
import { parentModeExtension } from "./parent-mode.ts";
|
|
33
|
+
import { type DeferRequest, deferLikeParentExtension, injectedByParent } from "../lib/deferred.ts";
|
|
28
34
|
import type { ChildHandle, ChildOutcome, RpcChildHandle } from "./outcome.ts";
|
|
29
35
|
import { sendToMainTool } from "./send-to-main-tool.ts";
|
|
30
36
|
import { SessionTurnTracker } from "./session-turns.ts";
|
|
@@ -53,34 +59,12 @@ const WALL_CLOCK_CAP_MS = 30 * 60 * 1000;
|
|
|
53
59
|
|
|
54
60
|
/**
|
|
55
61
|
* Tools the runtime injects into a child; the permission gate must never gate
|
|
56
|
-
* them. `
|
|
62
|
+
* them. `SubagentHandback` only returns the child's report to its caller.
|
|
63
|
+
* `Agent` is the injected child-spawn tool (index.ts) — the spawn itself
|
|
57
64
|
* is free (matching the main conversation's auto-allowed Agent tool); every
|
|
58
65
|
* tool call the spawned grandchild makes still runs through the real gate.
|
|
59
66
|
*/
|
|
60
|
-
const NEVER_GATE = new Set(["structured_output", "SendMessage", "Agent"]);
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* The curated extensions loaded into a subagent session (via additionalExtensionPaths,
|
|
64
|
-
* which the loader loads even under noExtensions). Broadly Claude Code's model — a
|
|
65
|
-
* subagent gets project context + freshness + a working toolset, but NOT the frontier
|
|
66
|
-
* chrome (banner/spinner/recap) or the orchestration EXTENSIONS. Nested spawning
|
|
67
|
-
* (CC parity: subagents spawn subagents) is instead an injected `Agent` custom tool
|
|
68
|
-
* (index.ts childAgentTool, passed via extraTools) that delegates to the PARENT
|
|
69
|
-
* extension's runtime — one registry, one panel, one permission gate — and is
|
|
70
|
-
* depth-capped there.
|
|
71
|
-
*
|
|
72
|
-
* `lsp` is deliberately NOT here (matching CC, findings §17.3): a child session is torn
|
|
73
|
-
* down with the raw AgentSession.dispose(), which never fires session_shutdown, so lsp's
|
|
74
|
-
* cleanup would never run and any language server it started would leak for the life of
|
|
75
|
-
* the parent session. MCP is also not listed: its
|
|
76
|
-
* tools are shared in from the parent as customTools (getMcpTools), not reconnected.
|
|
77
|
-
*
|
|
78
|
-
* Order mirrors the package's load order: reminder/deferral sinks (system-reminder,
|
|
79
|
-
* tool-search) first, before anything emitting on their channels (the bus does not replay).
|
|
80
|
-
*/
|
|
81
|
-
const EXTENSIONS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
82
|
-
const CHILD_EXTENSIONS = ["system-reminder", "tool-search", "claude-context", "file-tracker", "search-tools", "skill", "web", "web-fetch", "notebook"];
|
|
83
|
-
const CHILD_EXTENSION_PATHS = CHILD_EXTENSIONS.map((name) => join(EXTENSIONS_DIR, name, "index.ts"));
|
|
67
|
+
const NEVER_GATE = new Set(["structured_output", "SendMessage", SUBAGENT_HANDBACK, "Agent"]);
|
|
84
68
|
|
|
85
69
|
type Session = AgentSession;
|
|
86
70
|
|
|
@@ -92,8 +76,15 @@ interface ChildSessionSpec {
|
|
|
92
76
|
agent?: AgentDefinition;
|
|
93
77
|
/** Parent session file — present for a fork run (inherit the parent transcript). */
|
|
94
78
|
forkFrom?: string;
|
|
79
|
+
/** Messages appended to a fork's transcript before its task (e.g. `/btw`'s side exchange). */
|
|
80
|
+
forkMessages?: Message[];
|
|
95
81
|
/** The parent's current system prompt, applied to a fork so it continues as the parent. */
|
|
96
82
|
parentSystemPrompt?: string;
|
|
83
|
+
/**
|
|
84
|
+
* The parent's last provider request, for a fork: every fork request is
|
|
85
|
+
* built from it so the fork reads the parent's prompt cache (fork-cache.ts).
|
|
86
|
+
*/
|
|
87
|
+
parentRequest?: RequestCapture;
|
|
97
88
|
/** Existing persisted session to resume (SendMessage to a finished agent). */
|
|
98
89
|
sessionFile?: string;
|
|
99
90
|
/** Where a new run's persisted session lands; falls back to an in-memory session. */
|
|
@@ -111,6 +102,12 @@ interface ChildSessionSpec {
|
|
|
111
102
|
onMessageToMain?: (message: string, summary?: string) => void;
|
|
112
103
|
/** Extra injected tools (e.g. the child-spawn Agent tool for depth-0 children). */
|
|
113
104
|
extraTools?: ToolDefinition[];
|
|
105
|
+
/**
|
|
106
|
+
* The main session's tool call that started the run's current turn, read
|
|
107
|
+
* per gated call (a SendMessage can start a later turn). The permission
|
|
108
|
+
* bridge passes it on as `ChildToolCall.parentToolCallId`.
|
|
109
|
+
*/
|
|
110
|
+
mainToolCallId?: () => string | undefined;
|
|
114
111
|
}
|
|
115
112
|
|
|
116
113
|
export interface SubagentRunOptions extends ChildSessionSpec {
|
|
@@ -125,11 +122,33 @@ export interface ResidentRunOptions extends Omit<ChildSessionSpec, "sessionFile"
|
|
|
125
122
|
onProgress: (toolCalls: number, lastText: string, usage: UsageTotals) => void;
|
|
126
123
|
/** Fires at the end of EVERY turn (initial task and later messages alike). */
|
|
127
124
|
onTurnEnd: (outcome: ChildOutcome) => void;
|
|
128
|
-
|
|
125
|
+
/** Fires once when the resident exits; a returned promise (worktree cleanup) is part of kill()'s settle. */
|
|
126
|
+
onExit?: () => void | Promise<unknown>;
|
|
129
127
|
/** Optional live sink for the subagent panel (activity + transcript blocks). */
|
|
130
128
|
sink?: LiveSink;
|
|
131
129
|
}
|
|
132
130
|
|
|
131
|
+
/**
|
|
132
|
+
* A child's session manager: a resume reopens its file; otherwise a fork
|
|
133
|
+
* clones the parent transcript (when the parent has written one) and anything
|
|
134
|
+
* else starts empty, and `forkMessages` are appended so the child sees them before
|
|
135
|
+
* its task and a later resume finds them in its file. Exported for tests.
|
|
136
|
+
*/
|
|
137
|
+
export function newChildSessionManager(spec: Pick<ChildSessionSpec, "cwd" | "forkFrom" | "forkMessages" | "sessionFile" | "sessionDir">): SessionManager {
|
|
138
|
+
// The resume cwd is passed explicitly: a worktree run's persisted cwd may
|
|
139
|
+
// be gone by the time it is messaged (index.ts substitutes the parent cwd).
|
|
140
|
+
if (spec.sessionFile) return SessionManager.open(spec.sessionFile, undefined, spec.cwd);
|
|
141
|
+
// pi writes a session file with its first assistant reply, so a parent with
|
|
142
|
+
// no transcript yet (or run with --no-session) has nothing to clone.
|
|
143
|
+
const manager = spec.forkFrom && existsSync(spec.forkFrom)
|
|
144
|
+
? SessionManager.forkFrom(spec.forkFrom, spec.cwd, spec.sessionDir)
|
|
145
|
+
: spec.sessionDir
|
|
146
|
+
? SessionManager.create(spec.cwd, spec.sessionDir)
|
|
147
|
+
: SessionManager.inMemory(spec.cwd);
|
|
148
|
+
for (const message of spec.forkMessages ?? []) manager.appendMessage(message);
|
|
149
|
+
return manager;
|
|
150
|
+
}
|
|
151
|
+
|
|
133
152
|
/** Prepend a one-time spawn note (e.g. a model fallback) to an outcome's output. */
|
|
134
153
|
function withNote(outcome: ChildOutcome, note: string | undefined): ChildOutcome {
|
|
135
154
|
return note ? { ...outcome, output: `${note}\n\n${outcome.output}` } : outcome;
|
|
@@ -145,6 +164,10 @@ export class SubagentRuntime {
|
|
|
145
164
|
* still-connecting (possibly empty) one.
|
|
146
165
|
*/
|
|
147
166
|
private readonly getMcpTools: () => ToolDefinition[] | Promise<ToolDefinition[]>;
|
|
167
|
+
/** The tools the parent session defers (lib/deferred.ts queryDeferred): a child defers the ones injected into it the same way. */
|
|
168
|
+
private readonly getParentDeferred: () => DeferRequest[];
|
|
169
|
+
/** The main session's live permission mode, for a fork: told to it when it differs from what it was told (parent-mode.ts). */
|
|
170
|
+
private readonly getParentMode: (() => string) | undefined;
|
|
148
171
|
/**
|
|
149
172
|
* The parent's permission decision closure (undefined until permissions
|
|
150
173
|
* publishes it), wrapped so every call carries the asking run's name: the
|
|
@@ -158,10 +181,14 @@ export class SubagentRuntime {
|
|
|
158
181
|
/** A child extension handler threw (pi swallows it otherwise); surfaced to the user by index.ts. */
|
|
159
182
|
private readonly onExtensionError: (runName: string | undefined, error: ExtensionError) => void;
|
|
160
183
|
private readonly onModelUnusable: (model: string, reason: string) => void;
|
|
184
|
+
/** A child's edit or write changed a file (lib/child-writes.ts); index.ts tells the parent's file-tracker. */
|
|
185
|
+
private readonly onChildWrite: (path: string, runName: string | undefined) => void;
|
|
161
186
|
/** Child session id → run name, for the bridge wrapper above. */
|
|
162
187
|
private readonly runNames = new Map<string, string>();
|
|
163
188
|
/** Child session id → agent type (`explore`, …), for the hook payload's `agent_type`. */
|
|
164
189
|
private readonly agentTypes = new Map<string, string>();
|
|
190
|
+
/** Child session id → the main-session call that started its turn, for the bridge wrapper. */
|
|
191
|
+
private readonly mainToolCalls = new Map<string, () => string | undefined>();
|
|
165
192
|
/**
|
|
166
193
|
* Parallel children with the same request prefix (same agent prompt and
|
|
167
194
|
* model; forks share the parent's) let the first one start streaming before
|
|
@@ -178,8 +205,14 @@ export class SubagentRuntime {
|
|
|
178
205
|
getHookBridge: () => HookBridge | undefined,
|
|
179
206
|
onExtensionError: (runName: string | undefined, error: ExtensionError) => void,
|
|
180
207
|
onModelUnusable: (model: string, reason: string) => void,
|
|
208
|
+
onChildWrite: (path: string, runName: string | undefined) => void,
|
|
209
|
+
getParentDeferred: () => DeferRequest[],
|
|
210
|
+
getParentMode: (() => string) | undefined,
|
|
181
211
|
) {
|
|
182
212
|
this.modelRuntime = modelRuntime;
|
|
213
|
+
this.getParentDeferred = getParentDeferred;
|
|
214
|
+
this.getParentMode = getParentMode;
|
|
215
|
+
this.onChildWrite = onChildWrite;
|
|
183
216
|
this.baseCwd = baseCwd;
|
|
184
217
|
this.getMcpTools = getMcpTools;
|
|
185
218
|
this.getHookBridge = getHookBridge;
|
|
@@ -191,7 +224,11 @@ export class SubagentRuntime {
|
|
|
191
224
|
const namedBridge: PermissionBridge = (call) => {
|
|
192
225
|
const bridge = getPermissionBridge();
|
|
193
226
|
if (!bridge) throw new Error("the parent's permission bridge is no longer available");
|
|
194
|
-
return bridge({
|
|
227
|
+
return bridge({
|
|
228
|
+
...call,
|
|
229
|
+
agent: call.agent ?? (call.sessionId ? this.runNames.get(call.sessionId) : undefined),
|
|
230
|
+
parentToolCallId: call.parentToolCallId ?? (call.sessionId ? this.mainToolCalls.get(call.sessionId)?.() : undefined),
|
|
231
|
+
});
|
|
195
232
|
};
|
|
196
233
|
this.getPermissionBridge = () => (getPermissionBridge() ? namedBridge : undefined);
|
|
197
234
|
}
|
|
@@ -203,11 +240,14 @@ export class SubagentRuntime {
|
|
|
203
240
|
getHookBridge: () => HookBridge | undefined = () => undefined,
|
|
204
241
|
onExtensionError: (runName: string | undefined, error: ExtensionError) => void = () => {},
|
|
205
242
|
onModelUnusable: (model: string, reason: string) => void = () => {},
|
|
243
|
+
onChildWrite: (path: string, runName: string | undefined) => void = () => {},
|
|
244
|
+
getParentDeferred: () => DeferRequest[] = () => [],
|
|
245
|
+
getParentMode?: () => string,
|
|
206
246
|
): Promise<SubagentRuntime> {
|
|
207
247
|
const modelRuntime = await createSharedModelRuntime(getAgentDir());
|
|
208
248
|
// Prime the catalog once; spawns read the live snapshot (see resolveModel).
|
|
209
249
|
await modelRuntime.getAvailable();
|
|
210
|
-
return new SubagentRuntime(modelRuntime, cwd, getMcpTools, getPermissionBridge, getHookBridge, onExtensionError, onModelUnusable);
|
|
250
|
+
return new SubagentRuntime(modelRuntime, cwd, getMcpTools, getPermissionBridge, getHookBridge, onExtensionError, onModelUnusable, onChildWrite, getParentDeferred, getParentMode);
|
|
211
251
|
}
|
|
212
252
|
|
|
213
253
|
/**
|
|
@@ -234,15 +274,27 @@ export class SubagentRuntime {
|
|
|
234
274
|
* prompt and default tools, review S6); a named agent carries its own; a
|
|
235
275
|
* plain run gets pi's base prompt. Built fresh per session (agent-loader.ts).
|
|
236
276
|
*/
|
|
237
|
-
private childLoaderOptions(spec: ChildSessionSpec): AgentLoaderOptions {
|
|
238
|
-
const
|
|
277
|
+
private childLoaderOptions(spec: ChildSessionSpec, handback: HandbackSlot): AgentLoaderOptions {
|
|
278
|
+
const fork = SubagentRuntime.isFork(spec);
|
|
279
|
+
const systemPrompt = fork ? spec.parentSystemPrompt : spec.agent?.systemPrompt;
|
|
239
280
|
return {
|
|
240
281
|
cwd: this.baseCwd,
|
|
241
282
|
agentDir: getAgentDir(),
|
|
242
283
|
systemPrompt,
|
|
243
284
|
neverGate: NEVER_GATE,
|
|
244
285
|
extraExtensionPaths: CHILD_EXTENSION_PATHS,
|
|
245
|
-
//
|
|
286
|
+
// Every child hands back through SubagentHandback (deferred in a fork,
|
|
287
|
+
// lib/subagent-handback.ts). Inline extensions load after the paths
|
|
288
|
+
// above, so the fork-cache one, last, sees the final body.
|
|
289
|
+
extraFactories: [
|
|
290
|
+
// The tools injected below (the spawn and cron tools, the shared MCP
|
|
291
|
+
// tools) are deferred as the parent defers them: their owners run there.
|
|
292
|
+
deferLikeParentExtension(this.getParentDeferred(), injectedByParent(spec.extraTools ?? [])),
|
|
293
|
+
subagentHandbackExtension(handback, { deferred: fork }),
|
|
294
|
+
...(fork && this.getParentMode ? [parentModeExtension(this.getParentMode)] : []),
|
|
295
|
+
...(fork && spec.parentRequest ? [forkCacheExtension(spec.parentRequest)] : []),
|
|
296
|
+
],
|
|
297
|
+
// claude-context (above) injects the CLAUDE.md instructions on the child's session_start;
|
|
246
298
|
// pi must not append the same files to the system prompt as well.
|
|
247
299
|
noContextFiles: true,
|
|
248
300
|
getPermissionBridge: this.getPermissionBridge,
|
|
@@ -271,7 +323,7 @@ export class SubagentRuntime {
|
|
|
271
323
|
/**
|
|
272
324
|
* Hold a run until a sibling with the same request prefix has started
|
|
273
325
|
* streaming (see `warmGate`): same prompt identity, same cwd (the child's
|
|
274
|
-
*
|
|
326
|
+
* instructions block names its paths) and same model. Resolves to the release
|
|
275
327
|
* for the caller's `finally`.
|
|
276
328
|
*/
|
|
277
329
|
private admitPrefix(spec: Pick<ChildSessionSpec, "cwd" | "forkFrom" | "parentSystemPrompt" | "agent">, session: Session): Promise<Release> {
|
|
@@ -288,9 +340,12 @@ export class SubagentRuntime {
|
|
|
288
340
|
onSettled?: () => void,
|
|
289
341
|
sink?: LiveSink,
|
|
290
342
|
): () => void {
|
|
343
|
+
const childWrote = childWriteWatcher(session.sessionManager?.getCwd?.() ?? this.baseCwd);
|
|
291
344
|
return session.subscribe((event) => {
|
|
292
345
|
try {
|
|
293
346
|
const settled = tracker.process(event as never);
|
|
347
|
+
const wrote = childWrote(event as never);
|
|
348
|
+
if (wrote) this.onChildWrite(wrote, this.runNames.get(session.sessionId));
|
|
294
349
|
const e = event as { type?: string; toolName?: string; args?: unknown; isError?: boolean; result?: unknown; message?: { role?: string; content?: unknown } };
|
|
295
350
|
if (e.type === "tool_execution_start") {
|
|
296
351
|
sink?.onBlock?.({ kind: "call", tool: e.toolName ?? "tool", text: summarizeArgs(e.args) });
|
|
@@ -350,20 +405,14 @@ export class SubagentRuntime {
|
|
|
350
405
|
* inherits the parent transcript + system prompt; a fresh named run gets the
|
|
351
406
|
* agent's own prompt and toolset.
|
|
352
407
|
*/
|
|
353
|
-
private async buildChildSession(spec: ChildSessionSpec): Promise<{ session: Session; note?: string }> {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
? SessionManager.forkFrom(spec.forkFrom, spec.cwd, spec.sessionDir)
|
|
362
|
-
: spec.sessionDir
|
|
363
|
-
? SessionManager.create(spec.cwd, spec.sessionDir)
|
|
364
|
-
: SessionManager.inMemory(spec.cwd);
|
|
365
|
-
// A fork keeps the parent's toolset; only a named agent carries an allowlist.
|
|
366
|
-
const allowlist = spec.forkFrom ? undefined : spec.agent?.tools;
|
|
408
|
+
private async buildChildSession(spec: ChildSessionSpec, handback: HandbackSlot): Promise<{ session: Session; note?: string }> {
|
|
409
|
+
// The MCP wait first: the loader takes the parent's deferrals, which a server
|
|
410
|
+
// connecting during the wait adds to (it is instant once the connects settled).
|
|
411
|
+
const mcpTools = await this.getMcpTools();
|
|
412
|
+
const loader = await buildAgentLoader(this.childLoaderOptions(spec, handback));
|
|
413
|
+
const newSessionManager = () => newChildSessionManager(spec);
|
|
414
|
+
// A fork keeps the parent's toolset; only a named agent carries tool lists.
|
|
415
|
+
const agent = spec.forkFrom ? undefined : spec.agent;
|
|
367
416
|
const make = async (model: string | undefined): Promise<Session> => {
|
|
368
417
|
const resolvedModel = model ? await this.resolveModel(model) : undefined;
|
|
369
418
|
const session = await openChildSession({
|
|
@@ -374,10 +423,9 @@ export class SubagentRuntime {
|
|
|
374
423
|
modelRuntime: this.modelRuntime,
|
|
375
424
|
model: resolvedModel as never,
|
|
376
425
|
thinkingLevel: spec.thinking as never,
|
|
377
|
-
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
excludeTools: spec.forkFrom ? undefined : spec.agent?.excludeTools,
|
|
426
|
+
// The allowlist with the plumbing re-added, and the denylist (CC's "All
|
|
427
|
+
// tools except …" shape), which filters injected customTools too.
|
|
428
|
+
...agentToolOptions(agent),
|
|
381
429
|
customTools: [sendToMainTool((m, s) => spec.onMessageToMain?.(m, s)), ...(spec.extraTools ?? []), ...mcpTools],
|
|
382
430
|
sessionManager: newSessionManager(),
|
|
383
431
|
},
|
|
@@ -386,21 +434,17 @@ export class SubagentRuntime {
|
|
|
386
434
|
});
|
|
387
435
|
// An allowlist that matched nothing real would run a tool-less agent
|
|
388
436
|
// (pi drops unknown names silently). Fail loud with the fix named.
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
).length === 0
|
|
395
|
-
) {
|
|
437
|
+
const unusable = unusableAllowlistError(
|
|
438
|
+
agent,
|
|
439
|
+
session.getAllTools().map((t) => t.name),
|
|
440
|
+
);
|
|
441
|
+
if (unusable) {
|
|
396
442
|
this.discard(session);
|
|
397
|
-
throw new Error(
|
|
398
|
-
`Agent "${spec.agent?.name}" lists tools (${allowlist.join(", ")}) that match no available tool. ` +
|
|
399
|
-
"Use Claude Code names (Read, Edit, Write, Bash, Grep, Glob, WebFetch, WebSearch, NotebookEdit, Skill, Agent, SendMessage) or pi names (read, edit, write, bash, grep, find, ls, …) in the agent file's `tools` list.",
|
|
400
|
-
);
|
|
443
|
+
throw new Error(unusable);
|
|
401
444
|
}
|
|
402
445
|
if (spec.name) this.runNames.set(session.sessionManager.getSessionId(), spec.name);
|
|
403
446
|
if (spec.agent) this.agentTypes.set(session.sessionManager.getSessionId(), spec.agent.name);
|
|
447
|
+
if (spec.mainToolCallId) this.mainToolCalls.set(session.sessionManager.getSessionId(), spec.mainToolCallId);
|
|
404
448
|
return session;
|
|
405
449
|
};
|
|
406
450
|
try {
|
|
@@ -421,7 +465,8 @@ export class SubagentRuntime {
|
|
|
421
465
|
private discard(session: Session): void {
|
|
422
466
|
this.runNames.delete(session.sessionManager.getSessionId());
|
|
423
467
|
this.agentTypes.delete(session.sessionManager.getSessionId());
|
|
424
|
-
session.
|
|
468
|
+
this.mainToolCalls.delete(session.sessionManager.getSessionId());
|
|
469
|
+
disposeChildSession(session);
|
|
425
470
|
}
|
|
426
471
|
|
|
427
472
|
/**
|
|
@@ -452,17 +497,17 @@ export class SubagentRuntime {
|
|
|
452
497
|
// corrupt session file, loader error) must resolve to a failed
|
|
453
498
|
// outcome, never reject handle.result — call sites like the
|
|
454
499
|
// SendMessage resume path consume it with a bare .then().
|
|
455
|
-
const built = await this.buildChildSession(options);
|
|
500
|
+
const built = await this.buildChildSession(options, tracker);
|
|
456
501
|
session = built.session;
|
|
457
502
|
spawnNote = built.note;
|
|
458
503
|
unsubscribe = this.wireTracker(session, tracker, options.onProgress, undefined, options.sink);
|
|
459
504
|
// A resume continues a unique history; only a fresh run shares a prefix.
|
|
460
505
|
const releasePrefix = options.sessionFile ? undefined : await this.admitPrefix(options, session);
|
|
461
506
|
try {
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
if (
|
|
507
|
+
// abort() is a no-op before the prompt starts. Every cancellation
|
|
508
|
+
// source (signal, kill, wall-clock cap) marks the tracker first,
|
|
509
|
+
// including one that landed during construction or the prefix wait.
|
|
510
|
+
if (tracker.aborted) throw new Error("cancelled before the first request was sent");
|
|
466
511
|
// No template/command expansion: a task starting with "/" is text, not
|
|
467
512
|
// a command lookup (pi's own sendUserMessage does the same — L2).
|
|
468
513
|
await session.prompt(options.task, { expandPromptTemplates: false });
|
|
@@ -504,11 +549,12 @@ export class SubagentRuntime {
|
|
|
504
549
|
* while an idle resident lives until task_stop / session shutdown.
|
|
505
550
|
*/
|
|
506
551
|
async runResident(options: ResidentRunOptions): Promise<RpcChildHandle> {
|
|
507
|
-
const
|
|
552
|
+
const tracker = new SessionTurnTracker();
|
|
553
|
+
const built = await this.buildChildSession(options, tracker);
|
|
508
554
|
const session = built.session;
|
|
509
555
|
let spawnNote = built.note;
|
|
510
|
-
const tracker = new SessionTurnTracker();
|
|
511
556
|
let exited = false;
|
|
557
|
+
let exitPromise: Promise<void> | undefined;
|
|
512
558
|
let turnActive = false;
|
|
513
559
|
let firstTurn = true;
|
|
514
560
|
let turnTimer: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -632,10 +678,18 @@ export class SubagentRuntime {
|
|
|
632
678
|
await session.steer(message);
|
|
633
679
|
return "steered";
|
|
634
680
|
},
|
|
681
|
+
// The background extension is not loaded in a child, so the parent's
|
|
682
|
+
// cron fires record what they sent straight into the child's session.
|
|
683
|
+
conversation: {
|
|
684
|
+
getBranch: () => session.sessionManager.getBranch(),
|
|
685
|
+
appendEntry: (customType, data) => {
|
|
686
|
+
session.sessionManager.appendCustomEntry(customType, data);
|
|
687
|
+
},
|
|
688
|
+
},
|
|
635
689
|
busy: () => turnActive || !session.isIdle,
|
|
636
690
|
exited: () => exited,
|
|
637
691
|
kill: () => {
|
|
638
|
-
if (exited) return Promise.resolve();
|
|
692
|
+
if (exited) return exitPromise ?? Promise.resolve();
|
|
639
693
|
exited = true;
|
|
640
694
|
pendingSend = undefined;
|
|
641
695
|
// Report an in-flight turn as terminated, not as a normal completion:
|
|
@@ -645,14 +699,15 @@ export class SubagentRuntime {
|
|
|
645
699
|
finishTurn(tracker.turnOutcome());
|
|
646
700
|
// onExit only after the abort settles: it triggers worktree cleanup,
|
|
647
701
|
// which must not race an aborting session still writing files. The
|
|
648
|
-
// returned promise
|
|
649
|
-
|
|
702
|
+
// returned promise settles after that cleanup too, so session_shutdown
|
|
703
|
+
// waits for exactly that.
|
|
704
|
+
return (exitPromise = session
|
|
650
705
|
.abort()
|
|
651
706
|
.catch(() => undefined)
|
|
652
|
-
.
|
|
707
|
+
.then(async () => {
|
|
653
708
|
discard();
|
|
654
|
-
options.onExit?.();
|
|
655
|
-
});
|
|
709
|
+
await Promise.resolve(options.onExit?.()).catch(() => undefined);
|
|
710
|
+
}));
|
|
656
711
|
},
|
|
657
712
|
release: () => {
|
|
658
713
|
if (exited || turnActive || !session.isIdle) return;
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
import { readdirSync, statSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
|
|
13
|
+
/** The synthetic agent type of a run that clones the calling conversation. */
|
|
14
|
+
export const FORK_AGENT = "fork";
|
|
15
|
+
/** The agent a run that names none starts, as in Claude Code. */
|
|
16
|
+
export const DEFAULT_AGENT = "general-purpose";
|
|
17
|
+
|
|
13
18
|
export interface AgentRunRecord {
|
|
14
19
|
name: string;
|
|
15
20
|
agent: string;
|
|
@@ -23,6 +28,12 @@ export interface AgentRunRecord {
|
|
|
23
28
|
worktree?: boolean;
|
|
24
29
|
model?: string;
|
|
25
30
|
thinking?: string;
|
|
31
|
+
/**
|
|
32
|
+
* The run's short title, Claude Code's `description`: the Agent call's
|
|
33
|
+
* `description`, or a `/btw` fork's question. The strip, `/tasks` and the
|
|
34
|
+
* notification summary show it (findings §40); absent, they show the name.
|
|
35
|
+
*/
|
|
36
|
+
description?: string;
|
|
26
37
|
/**
|
|
27
38
|
* Nesting level: 0 = spawned by main, 1 = spawned by a child's own Agent
|
|
28
39
|
* tool. Set once at creation and read by every spawn-tool handout (the
|
|
@@ -38,13 +49,23 @@ export interface AgentRunRecord {
|
|
|
38
49
|
inline?: boolean;
|
|
39
50
|
}
|
|
40
51
|
|
|
52
|
+
/** `<base>-<n>` with the lowest n ≥ `from` not in `taken`. */
|
|
53
|
+
function firstFreeSuffixed(taken: ReadonlySet<string>, base: string, from: number): string {
|
|
54
|
+
for (let n = from; ; n++) {
|
|
55
|
+
const candidate = `${base}-${n}`;
|
|
56
|
+
if (!taken.has(candidate)) return candidate;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
41
60
|
/** `<agent>-<n>` with the lowest n not already taken. */
|
|
42
61
|
export function nextRunName(existing: Iterable<string>, agent: string): string {
|
|
62
|
+
return firstFreeSuffixed(new Set(existing), agent, 1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** `base` when free, else `<base>-<n>` with the lowest n ≥ 2 not taken (Claude Code's fork naming). */
|
|
66
|
+
export function freeRunName(existing: Iterable<string>, base: string): string {
|
|
43
67
|
const taken = new Set(existing);
|
|
44
|
-
|
|
45
|
-
const candidate = `${agent}-${n}`;
|
|
46
|
-
if (!taken.has(candidate)) return candidate;
|
|
47
|
-
}
|
|
68
|
+
return taken.has(base) ? firstFreeSuffixed(taken, base, 2) : base;
|
|
48
69
|
}
|
|
49
70
|
|
|
50
71
|
/**
|
|
@@ -119,11 +140,21 @@ export class RunRegistry {
|
|
|
119
140
|
this.reserved.clear();
|
|
120
141
|
}
|
|
121
142
|
|
|
122
|
-
/**
|
|
143
|
+
/** Keep only the records `keep` accepts; names held for runs being spawned stay held. */
|
|
144
|
+
retain(keep: (record: AgentRunRecord) => boolean): void {
|
|
145
|
+
for (const record of [...this.byId.values()]) if (!keep(record)) this.remove(record.taskId);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Hold a name until its record is added. */
|
|
123
149
|
reserve(name: string): void {
|
|
124
150
|
this.reserved.add(name);
|
|
125
151
|
}
|
|
126
152
|
|
|
153
|
+
/** Free a held name whose spawn failed validation, so a corrected retry gets it. */
|
|
154
|
+
release(name: string): void {
|
|
155
|
+
this.reserved.delete(name);
|
|
156
|
+
}
|
|
157
|
+
|
|
127
158
|
/** Latest wins per name, matching Claude Code's semantics. */
|
|
128
159
|
add(record: AgentRunRecord): void {
|
|
129
160
|
this.reserved.delete(record.name);
|
|
@@ -131,6 +162,14 @@ export class RunRegistry {
|
|
|
131
162
|
this.byId.set(record.taskId, record);
|
|
132
163
|
}
|
|
133
164
|
|
|
165
|
+
/** Forget a run that never started, so neither list_agents nor a resume offers it. */
|
|
166
|
+
remove(taskId: string): void {
|
|
167
|
+
const record = this.byId.get(taskId);
|
|
168
|
+
if (!record) return;
|
|
169
|
+
this.byId.delete(taskId);
|
|
170
|
+
if (this.byName.get(record.name) === record) this.byName.delete(record.name);
|
|
171
|
+
}
|
|
172
|
+
|
|
134
173
|
/** Every name in use: recorded runs plus names reserved for runs being spawned. */
|
|
135
174
|
names(): string[] {
|
|
136
175
|
return [...new Set([...this.byName.keys(), ...this.reserved])];
|
|
@@ -27,7 +27,7 @@ export function sendToMainTool(onMessage: (message: string, summary?: string) =>
|
|
|
27
27
|
}) as never,
|
|
28
28
|
async execute(_toolCallId: string, params: unknown) {
|
|
29
29
|
const p = (params ?? {}) as { to?: unknown; message?: unknown; summary?: unknown };
|
|
30
|
-
// Fail loud (docs/decisions/tools.md): a wrong recipient or a missing
|
|
30
|
+
// Fail loud (working-docs/decisions/tools.md): a wrong recipient or a missing
|
|
31
31
|
// message returns isError with the fix named, never a silent success.
|
|
32
32
|
if (p.to !== undefined && p.to !== "main") {
|
|
33
33
|
return {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { recordAction, type ChildAction } from "../auto-mode/actions.ts";
|
|
15
|
-
import
|
|
15
|
+
import type { HandbackSlot } from "../lib/subagent-handback.ts";
|
|
16
|
+
import type { ChildOutcome } from "./outcome.ts";
|
|
16
17
|
import { addUsage, emptyUsage, type UsageTotals } from "./usage.ts";
|
|
17
18
|
|
|
18
19
|
/** The subset of AgentSession events this tracker reads. */
|
|
@@ -23,7 +24,12 @@ interface TrackedEvent {
|
|
|
23
24
|
message?: { role?: string; content?: unknown; usage?: unknown; stopReason?: string; errorMessage?: string };
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
/**
|
|
28
|
+
* The tracker is also the run's `HandbackSlot`: a `SubagentHandback` call
|
|
29
|
+
* records the turn's report here, and the turn's outcome is that report, or
|
|
30
|
+
* the turn's final assistant text when the child never called the tool.
|
|
31
|
+
*/
|
|
32
|
+
export class SessionTurnTracker implements HandbackSlot {
|
|
27
33
|
toolCalls = 0;
|
|
28
34
|
/** What the child did this turn, for auto mode's return review. */
|
|
29
35
|
actions: ChildAction[] = [];
|
|
@@ -43,12 +49,15 @@ export class SessionTurnTracker {
|
|
|
43
49
|
aborted: string | undefined;
|
|
44
50
|
/** Every turn's final text joined, for task_output on a resident agent. */
|
|
45
51
|
transcript = "";
|
|
52
|
+
/** The report this turn handed back through SubagentHandback, if it did. */
|
|
53
|
+
handback: string | undefined;
|
|
46
54
|
|
|
47
55
|
/** Call when a new turn is started (a prompt sent while idle). */
|
|
48
56
|
beginTurn(): void {
|
|
49
57
|
this.turnText = "";
|
|
50
58
|
this.providerError = undefined;
|
|
51
59
|
this.aborted = undefined;
|
|
60
|
+
this.handback = undefined;
|
|
52
61
|
// Per turn, not per session: the review that reads this judges the turn just finished.
|
|
53
62
|
this.actions = [];
|
|
54
63
|
}
|
|
@@ -60,6 +69,17 @@ export class SessionTurnTracker {
|
|
|
60
69
|
this.aborted ??= reason;
|
|
61
70
|
}
|
|
62
71
|
|
|
72
|
+
recordHandback(message: string): boolean {
|
|
73
|
+
if (this.handback !== undefined) return false;
|
|
74
|
+
this.handback = message;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** What the turn returns: the handed-back report, else the final assistant text. */
|
|
79
|
+
private get result(): string {
|
|
80
|
+
return this.handback ?? this.turnText;
|
|
81
|
+
}
|
|
82
|
+
|
|
63
83
|
/** Feed one subscribed event. Returns true when a turn just settled. */
|
|
64
84
|
process(event: TrackedEvent): boolean {
|
|
65
85
|
switch (event.type) {
|
|
@@ -83,8 +103,8 @@ export class SessionTurnTracker {
|
|
|
83
103
|
return false;
|
|
84
104
|
}
|
|
85
105
|
case "agent_settled": {
|
|
86
|
-
if (this.
|
|
87
|
-
this.transcript = this.transcript ? `${this.transcript}\n\n---\n\n${this.
|
|
106
|
+
if (this.result.trim()) {
|
|
107
|
+
this.transcript = this.transcript ? `${this.transcript}\n\n---\n\n${this.result}` : this.result;
|
|
88
108
|
}
|
|
89
109
|
return true;
|
|
90
110
|
}
|
|
@@ -95,7 +115,7 @@ export class SessionTurnTracker {
|
|
|
95
115
|
|
|
96
116
|
/** The outcome of the turn that just settled. */
|
|
97
117
|
turnOutcome(): ChildOutcome {
|
|
98
|
-
return finishOutcome(this.
|
|
118
|
+
return finishOutcome(this.result, this.providerError, this.toolCalls, this.usage, this.actions, this.aborted);
|
|
99
119
|
}
|
|
100
120
|
}
|
|
101
121
|
|
|
@@ -103,16 +123,20 @@ export class SessionTurnTracker {
|
|
|
103
123
|
* Shape a run/turn's collected state into a ChildOutcome, matching the spawned
|
|
104
124
|
* child's messages. `aborted` (a reason) wins over everything: an aborted turn's
|
|
105
125
|
* partial text is reported as terminated and failed, never as the answer.
|
|
126
|
+
*
|
|
127
|
+
* The text is kept whole. Every consumer bounds it with `persistIfLarge`, which
|
|
128
|
+
* saves an oversized report to a file and points at it; a cut here left the
|
|
129
|
+
* persist step nothing over its limit, so a long report lost its end with no
|
|
130
|
+
* marker (SUBAGENTS-WORKFLOWS-REVIEW-2026-09-26 M1).
|
|
106
131
|
*/
|
|
107
132
|
export function finishOutcome(
|
|
108
|
-
|
|
133
|
+
output: string,
|
|
109
134
|
providerError: string | undefined,
|
|
110
135
|
toolCalls: number,
|
|
111
136
|
usage: UsageTotals,
|
|
112
137
|
actions: ChildAction[],
|
|
113
138
|
aborted?: string,
|
|
114
139
|
): ChildOutcome {
|
|
115
|
-
const output = rawOutput.slice(0, OUTPUT_CAP);
|
|
116
140
|
if (aborted) {
|
|
117
141
|
return {
|
|
118
142
|
output: output.trim() ? `${output}\n\n[${aborted}]` : `Subagent ${aborted}.`,
|
|
@@ -134,5 +158,5 @@ export function finishOutcome(
|
|
|
134
158
|
};
|
|
135
159
|
}
|
|
136
160
|
if (output.trim()) return { output, toolCalls, usage, actions };
|
|
137
|
-
return { output: "Subagent
|
|
161
|
+
return { output: "(Subagent completed but returned no output.)", toolCalls, usage, actions };
|
|
138
162
|
}
|