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
|
@@ -1,198 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure state + rendering for the background-shells
|
|
3
|
-
* panel
|
|
4
|
-
*
|
|
2
|
+
* Pure state + rendering for the background-shells chip in the below-editor
|
|
3
|
+
* panel, Claude Code's shell pill. The FIRST down-arrow focuses it (a second ↓
|
|
4
|
+
* moves on to the agent rows), and Enter opens the Background dialog that
|
|
5
|
+
* `/tasks` opens too (tasks-dialog.ts), where shells and agents are listed
|
|
6
|
+
* together:
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
* list the Background panel: selectable command rows, x stops, Esc backs out
|
|
8
|
-
* details one shell: Status/Runtime/Command + a live bordered output box
|
|
8
|
+
* "2 shells · ↓ to manage" → focused: "[2 shells] · Enter to view tasks"
|
|
9
9
|
*
|
|
10
10
|
* Task objects arrive over TASK_REGISTER_CHANNEL (lib/shell-tasks.ts) and stay
|
|
11
|
-
* live
|
|
12
|
-
*
|
|
11
|
+
* live; status is read at render time. Key wiring lives in index.ts; this file
|
|
12
|
+
* owns layout and the focus reducer only.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { BackgroundTask } from "../background/registry.ts";
|
|
16
|
-
import { countNoun, cutPlainText as cut
|
|
16
|
+
import { countNoun, cutPlainText as cut } from "../lib/tui-render.ts";
|
|
17
17
|
import type { StripKey } from "./panel-keys.ts";
|
|
18
|
-
import type
|
|
18
|
+
import { type Paint, STRIP_LINGER_MS } from "./panel-render.ts";
|
|
19
19
|
|
|
20
20
|
export interface ShellPaint extends Paint {
|
|
21
21
|
inverse(text: string): string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export type ShellFocus =
|
|
25
|
-
| { stage: "chip" }
|
|
26
|
-
| { stage: "list"; selectedId: string }
|
|
27
|
-
| { stage: "details"; selectedId: string };
|
|
28
|
-
|
|
29
|
-
/** A finished shell stays listed this long, so its terminal status is seen. */
|
|
30
|
-
export const SHELL_LIST_LINGER_MS = 60_000;
|
|
31
|
-
|
|
32
|
-
/** Output-box height in the details view (content rows, borders excluded). */
|
|
33
|
-
export const OUTPUT_BOX_ROWS = 10;
|
|
34
|
-
|
|
35
|
-
/** Most list rows shown at once; the selection slides a window over the rest. */
|
|
36
|
-
export const MAX_SHELL_LIST_ROWS = 8;
|
|
37
|
-
|
|
38
|
-
/** Only this much of the spool is split per frame — the box shows 10 lines. */
|
|
39
|
-
const OUTPUT_TAIL_CHARS = 8192;
|
|
40
|
-
|
|
41
|
-
/** The list rows: running shells first, newest-first within each group. */
|
|
42
|
-
export function shellRows(tasks: BackgroundTask[], now: number): BackgroundTask[] {
|
|
43
|
-
return tasks
|
|
44
|
-
.filter((t) => t.status === "running" || (t.finishedAt ?? now) > now - SHELL_LIST_LINGER_MS)
|
|
45
|
-
.sort((a, b) => Number(b.status === "running") - Number(a.status === "running") || b.startedAt - a.startedAt);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
24
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
25
|
+
* The shells the chip counts: running ones, and finished ones for as long as a
|
|
26
|
+
* finished agent keeps its strip row (STRIP_LINGER_MS).
|
|
51
27
|
*/
|
|
52
|
-
export function
|
|
53
|
-
|
|
54
|
-
return ids.length ? { ...focus, selectedId: ids[0] } : { stage: "chip" };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface ShellTransition {
|
|
58
|
-
/** The next focus; undefined closes the shell section entirely. */
|
|
59
|
-
focus: ShellFocus | undefined;
|
|
60
|
-
/** stopSelected: stop the selected task; toAgents: hand ↓ on to the agent
|
|
61
|
-
* rows; passthrough: unhandled byte — drop focus and let the editor have it. */
|
|
62
|
-
effect?: "stopSelected" | "toAgents" | "passthrough";
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* The focus reducer — Claude Code's shell-manager keys. `ids` is the current
|
|
67
|
-
* row order (shellRows), `focus` is already anchored against it.
|
|
68
|
-
*/
|
|
69
|
-
export function reduceShellKey(focus: ShellFocus, key: StripKey | undefined, ids: string[]): ShellTransition {
|
|
70
|
-
const index = focus.stage === "chip" ? -1 : ids.indexOf(focus.selectedId);
|
|
71
|
-
switch (focus.stage) {
|
|
72
|
-
case "chip":
|
|
73
|
-
if (key === "down") return { focus: undefined, effect: "toAgents" };
|
|
74
|
-
if (key === "open") return { focus: ids.length ? { stage: "list", selectedId: ids[0] } : focus };
|
|
75
|
-
if (key === "up" || key === "leave") return { focus: undefined };
|
|
76
|
-
return { focus: undefined, effect: "passthrough" };
|
|
77
|
-
case "list":
|
|
78
|
-
if (key === "up") return { focus: index > 0 ? { stage: "list", selectedId: ids[index - 1] } : focus };
|
|
79
|
-
if (key === "down")
|
|
80
|
-
return { focus: index < ids.length - 1 ? { stage: "list", selectedId: ids[index + 1] } : focus };
|
|
81
|
-
if (key === "open") return { focus: { stage: "details", selectedId: focus.selectedId } };
|
|
82
|
-
if (key === "stop") return { focus, effect: "stopSelected" };
|
|
83
|
-
if (key === "leave" || key === "left") return { focus: { stage: "chip" } };
|
|
84
|
-
if (key === "pageUp" || key === "pageDown") return { focus };
|
|
85
|
-
return { focus: undefined, effect: "passthrough" };
|
|
86
|
-
case "details":
|
|
87
|
-
if (key === "left") return { focus: { stage: "list", selectedId: focus.selectedId } };
|
|
88
|
-
if (key === "leave" || key === "open" || key === "space") return { focus: undefined };
|
|
89
|
-
if (key === "stop") return { focus, effect: "stopSelected" };
|
|
90
|
-
if (key === "up" || key === "down" || key === "pageUp" || key === "pageDown") return { focus };
|
|
91
|
-
return { focus: undefined, effect: "passthrough" };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface ShellSectionInput {
|
|
96
|
-
/** shellRows()-ordered. */
|
|
97
|
-
rows: BackgroundTask[];
|
|
98
|
-
runningCount: number;
|
|
99
|
-
focus: ShellFocus | undefined;
|
|
100
|
-
width: number;
|
|
101
|
-
now: number;
|
|
28
|
+
export function shellRows(tasks: BackgroundTask[], now: number): BackgroundTask[] {
|
|
29
|
+
return tasks.filter((t) => t.status === "running" || (t.finishedAt !== undefined && t.finishedAt > now - STRIP_LINGER_MS));
|
|
102
30
|
}
|
|
103
31
|
|
|
104
32
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
33
|
+
* What a key does on the focused chip. Every key drops the chip's focus;
|
|
34
|
+
* openTasks opens the Background dialog, toAgents hands ↓ on to the agent rows,
|
|
35
|
+
* passthrough lets the editor have an unhandled byte, and undefined (↑, Esc)
|
|
36
|
+
* only leaves the chip.
|
|
109
37
|
*/
|
|
110
|
-
export
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (!
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return [`${paint.inverse(label)}${hint ? ` ${paint.fg("dim", hint)}` : ""}`];
|
|
129
|
-
}
|
|
130
|
-
if (focus.stage === "list") return renderShellList(input, focus.selectedId, paint, width);
|
|
131
|
-
return renderShellDetails(input, focus.selectedId, paint, width);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function renderShellList(input: ShellSectionInput, selectedId: string, paint: ShellPaint, width: number): string[] {
|
|
135
|
-
const out: string[] = [];
|
|
136
|
-
out.push(paint.fg("accent", paint.bold("Background")));
|
|
137
|
-
out.push(cut(countNoun(input.runningCount, "active shell"), width));
|
|
138
|
-
out.push("");
|
|
139
|
-
// A window of MAX_SHELL_LIST_ROWS slid to keep the selection visible, so a
|
|
140
|
-
// big shell fan-out cannot grow the panel past the terminal.
|
|
141
|
-
const selectedIndex = Math.max(0, input.rows.findIndex((t) => t.id === selectedId));
|
|
142
|
-
const start = Math.min(Math.max(0, selectedIndex - MAX_SHELL_LIST_ROWS + 1), Math.max(0, input.rows.length - MAX_SHELL_LIST_ROWS));
|
|
143
|
-
const shown = input.rows.slice(start, start + MAX_SHELL_LIST_ROWS);
|
|
144
|
-
if (start > 0) out.push(paint.fg("dim", ` +${start} more above`));
|
|
145
|
-
for (const task of shown) {
|
|
146
|
-
const marker = task.id === selectedId ? "❯ " : " ";
|
|
147
|
-
const status = ` (${task.status})`;
|
|
148
|
-
const command = cut(task.command ?? task.description, Math.max(1, width - marker.length - status.length));
|
|
149
|
-
out.push(`${marker}${command}${paint.fg(STATUS_COLOR[task.status] ?? "dim", status)}`);
|
|
150
|
-
}
|
|
151
|
-
const below = input.rows.length - start - shown.length;
|
|
152
|
-
if (below > 0) out.push(paint.fg("dim", ` +${below} more below`));
|
|
153
|
-
if (!input.rows.length) out.push(paint.fg("dim", " no shells"));
|
|
154
|
-
out.push("");
|
|
155
|
-
out.push(paint.fg("dim", cut("↑/↓ to select · Enter to view · x to stop · Esc to close", width)));
|
|
156
|
-
return out;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function renderShellDetails(input: ShellSectionInput, selectedId: string, paint: ShellPaint, width: number): string[] {
|
|
160
|
-
const task = input.rows.find((t) => t.id === selectedId);
|
|
161
|
-
const out: string[] = [];
|
|
162
|
-
out.push(paint.fg("accent", paint.bold("Shell details")));
|
|
163
|
-
out.push("");
|
|
164
|
-
if (!task) {
|
|
165
|
-
out.push(paint.fg("dim", "shell gone"));
|
|
166
|
-
return out;
|
|
167
|
-
}
|
|
168
|
-
const field = (label: string, value: string) => `${paint.bold(padPlainText(label, 9))} ${value}`;
|
|
169
|
-
out.push(field("Status:", paint.fg(STATUS_COLOR[task.status] ?? "dim", task.status)));
|
|
170
|
-
out.push(field("Runtime:", formatDuration(task.startedAt, task.finishedAt, input.now)));
|
|
171
|
-
out.push(field("Command:", cut(task.command ?? task.description, Math.max(1, width - 10))));
|
|
172
|
-
out.push("");
|
|
173
|
-
out.push(paint.bold("Output:"));
|
|
174
|
-
|
|
175
|
-
let output = "";
|
|
176
|
-
try {
|
|
177
|
-
output = task.output();
|
|
178
|
-
} catch {
|
|
179
|
-
output = "(output unavailable)";
|
|
180
|
-
}
|
|
181
|
-
// Bound the split to a tail slice — re-splitting a 200KB spool every ticker
|
|
182
|
-
// frame to keep 10 lines is O(buffer) work for nothing. A mid-line cut only
|
|
183
|
-
// matters when the tail holds fewer lines than the box, and those are
|
|
184
|
-
// width-cut anyway.
|
|
185
|
-
const tail = output.length > OUTPUT_TAIL_CHARS ? output.slice(-OUTPUT_TAIL_CHARS) : output;
|
|
186
|
-
const lines = tail ? tail.replace(/\r\n/g, "\n").replace(/\n$/, "").split("\n") : [];
|
|
187
|
-
const shown = lines.slice(-OUTPUT_BOX_ROWS);
|
|
188
|
-
const inner = Math.max(1, width - 4);
|
|
189
|
-
out.push(`┌${"─".repeat(Math.max(0, width - 2))}┐`);
|
|
190
|
-
for (let i = 0; i < OUTPUT_BOX_ROWS; i++) {
|
|
191
|
-
out.push(`│ ${padPlainText(cut(shown[i] ?? "", inner), inner)} │`);
|
|
192
|
-
}
|
|
193
|
-
out.push(`└${"─".repeat(Math.max(0, width - 2))}┘`);
|
|
194
|
-
out.push(paint.fg("dim", `Showing ${countNoun(shown.length, "line")}`));
|
|
195
|
-
out.push("");
|
|
196
|
-
out.push(paint.fg("dim", cut("← to go back · Esc/Enter/Space to close · x to stop", width)));
|
|
197
|
-
return out;
|
|
38
|
+
export type ShellKeyEffect = "openTasks" | "toAgents" | "passthrough" | undefined;
|
|
39
|
+
|
|
40
|
+
/** The focused chip's keys. */
|
|
41
|
+
export function reduceShellKey(key: StripKey | undefined): ShellKeyEffect {
|
|
42
|
+
if (key === "down") return "toAgents";
|
|
43
|
+
if (key === "open") return "openTasks";
|
|
44
|
+
if (key === "up" || key === "leave") return undefined;
|
|
45
|
+
return "passthrough";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The chip's one line. Callers render it only while shellRows() is non-empty. */
|
|
49
|
+
export function renderShellChip(count: number, focused: boolean, width: number, paint: ShellPaint): string {
|
|
50
|
+
width = Math.max(20, width);
|
|
51
|
+
if (!focused) return paint.fg("dim", cut(`${countNoun(count, "shell")} · ↓ to manage`, width));
|
|
52
|
+
// Cut the plain parts BEFORE painting: cutting painted text breaks escapes.
|
|
53
|
+
const label = cut(` ${countNoun(count, "shell")} `, width);
|
|
54
|
+
const hint = cut("· Enter to view tasks", Math.max(0, width - label.length - 1));
|
|
55
|
+
return `${paint.inverse(label)}${hint ? ` ${paint.fg("dim", hint)}` : ""}`;
|
|
198
56
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/tasks`: Claude Code 2.1.283's Background dialog (findings §40), pure
|
|
3
|
+
* layout and key handling. It lists the shells the chip counts (running ones,
|
|
4
|
+
* and finished ones for the chip's linger window, so Enter on the chip always
|
|
5
|
+
* finds them), the running local agents, and the finished agents the user
|
|
6
|
+
* viewed:
|
|
7
|
+
*
|
|
8
|
+
* Background
|
|
9
|
+
* 2 active shells · 1 active agent
|
|
10
|
+
* Shells (2)
|
|
11
|
+
* ❯ ⏺ sleep 25; echo done running
|
|
12
|
+
* Local agents (1)
|
|
13
|
+
* ⏺ Read notes.txt and report after delay running · Sonnet 5
|
|
14
|
+
* Completed (1)
|
|
15
|
+
* ✔ write a 300-word poem about the word in the file done · Haiku 4.5
|
|
16
|
+
* ↑/↓ to select · Enter to view · x to stop · Esc to close
|
|
17
|
+
*
|
|
18
|
+
* With nothing to list it reads `No tasks currently running`. Enter on a shell
|
|
19
|
+
* shows its details in the same dialog (renderShellDetail), and ← goes back
|
|
20
|
+
* to the list. The shells chip below the editor opens this dialog too.
|
|
21
|
+
* index.ts owns the dialog and what Enter and x do.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { BackgroundTask } from "../background/registry.ts";
|
|
25
|
+
import { countNoun, cutPlainText as cut, formatDuration, hintLines, padPlainText } from "../lib/tui-render.ts";
|
|
26
|
+
import { sanitizeDisplayText } from "../lib/terminal-text.ts";
|
|
27
|
+
|
|
28
|
+
/** One selectable row. */
|
|
29
|
+
export type TasksItem =
|
|
30
|
+
| { kind: "shell"; id: string; text: string; running: boolean; status: BackgroundTask["status"] }
|
|
31
|
+
| { kind: "agent"; taskId: string; text: string; model?: string; running: boolean };
|
|
32
|
+
|
|
33
|
+
export interface TasksDialogInput {
|
|
34
|
+
shells: Array<Extract<TasksItem, { kind: "shell" }>>;
|
|
35
|
+
/** Running agents first, then the viewed finished ones (the caller splits them). */
|
|
36
|
+
agents: Array<Extract<TasksItem, { kind: "agent" }>>;
|
|
37
|
+
selected: number;
|
|
38
|
+
width: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TasksPaint {
|
|
42
|
+
fg(color: string, text: string): string;
|
|
43
|
+
bold(text: string): string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The agents split into running and completed, each in the caller's order. */
|
|
47
|
+
function splitAgents(agents: TasksDialogInput["agents"]) {
|
|
48
|
+
return { running: agents.filter((a) => a.running), completed: agents.filter((a) => !a.running) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Every row in display order: shells, running agents, completed agents. */
|
|
52
|
+
export function tasksItems(input: Pick<TasksDialogInput, "shells" | "agents">): TasksItem[] {
|
|
53
|
+
const agents = splitAgents(input.agents);
|
|
54
|
+
return [...input.shells, ...agents.running, ...agents.completed];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The dialog's lines, every one cut to `width`. */
|
|
58
|
+
export function renderTasksDialog(input: TasksDialogInput, paint: TasksPaint): string[] {
|
|
59
|
+
const width = Math.max(20, input.width);
|
|
60
|
+
const line = (text: string) => cut(text, width);
|
|
61
|
+
const out = [paint.bold(line(" Background"))];
|
|
62
|
+
const items = tasksItems(input);
|
|
63
|
+
const current = items[input.selected];
|
|
64
|
+
// Hints wrap at their separators on a narrow terminal, so none is cut off.
|
|
65
|
+
const hint = ["↑/↓ to select", "Enter to view", ...(current?.running ? ["x to stop"] : []), "Esc to close"].join(" · ");
|
|
66
|
+
const pushHints = () => out.push(...hintLines(hint, width, " ").map((hintLine) => paint.fg("dim", hintLine)));
|
|
67
|
+
if (items.length === 0) {
|
|
68
|
+
out.push(line(" No tasks currently running"));
|
|
69
|
+
pushHints();
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
const agents = splitAgents(input.agents);
|
|
73
|
+
const runningShells = input.shells.filter((s) => s.running).length;
|
|
74
|
+
const counts = [runningShells ? countNoun(runningShells, "active shell") : "", agents.running.length ? countNoun(agents.running.length, "active agent") : ""]
|
|
75
|
+
.filter(Boolean)
|
|
76
|
+
.join(" · ");
|
|
77
|
+
if (counts) out.push(paint.fg("dim", line(` ${counts}`)));
|
|
78
|
+
const groups: Array<[title: string, rows: TasksItem[]]> = [
|
|
79
|
+
["Shells", input.shells],
|
|
80
|
+
["Local agents", agents.running],
|
|
81
|
+
["Completed", agents.completed],
|
|
82
|
+
];
|
|
83
|
+
let index = 0;
|
|
84
|
+
for (const [title, rows] of groups) {
|
|
85
|
+
if (rows.length === 0) continue;
|
|
86
|
+
out.push(paint.fg("dim", line(` ${title} (${rows.length})`)));
|
|
87
|
+
for (const row of rows) {
|
|
88
|
+
const selected = index === input.selected;
|
|
89
|
+
// A finished shell shows how it ended; only a clean finish reads "done",
|
|
90
|
+
// and only a failure gets the cross (a stop is the user's own choice).
|
|
91
|
+
const ended = row.kind === "shell" && !row.running && row.status !== "completed" ? row.status : undefined;
|
|
92
|
+
const mark = row.running ? "⏺" : ended === "failed" ? "✗" : "✔";
|
|
93
|
+
const status = row.running ? "running" : (ended ?? "done");
|
|
94
|
+
const suffix = row.kind === "agent" && row.model ? `${status} · ${row.model}` : status;
|
|
95
|
+
const text = line(` ${selected ? "❯" : " "} ${mark} ${sanitizeDisplayText(row.text)} ${suffix}`);
|
|
96
|
+
out.push(selected ? paint.fg("accent", paint.bold(text)) : text);
|
|
97
|
+
index++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
pushHints();
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type TasksKey = "up" | "down" | "view" | "stop" | "close";
|
|
105
|
+
|
|
106
|
+
/** Move the selection within `count` rows, stopping at the ends. */
|
|
107
|
+
export function moveTasksSelection(selected: number, key: "up" | "down", count: number): number {
|
|
108
|
+
if (count === 0) return 0;
|
|
109
|
+
return key === "up" ? Math.max(0, selected - 1) : Math.min(count - 1, selected + 1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** What the dialog shows: the list, or one shell's details. */
|
|
113
|
+
export type TasksView = { mode: "list" } | { mode: "shell"; id: string };
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The view to render now. A shell's details last while the shell is known: one
|
|
117
|
+
* that finishes while shown keeps its details, with its final status and no
|
|
118
|
+
* `x`, and the dialog goes back to the list once the shell is gone.
|
|
119
|
+
*/
|
|
120
|
+
export function settleTasksView(view: TasksView, shellKnown: boolean): TasksView {
|
|
121
|
+
return view.mode === "shell" && !shellKnown ? { mode: "list" } : view;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** What a key does in a shell's details: ← goes back, Esc/Enter/Space close, x stops a running shell. */
|
|
125
|
+
export function shellDetailKey(id: string | undefined, text: string | undefined, running: boolean): "back" | "close" | "stop" | undefined {
|
|
126
|
+
if (id === "left") return "back";
|
|
127
|
+
if (id === "escape" || id === "enter" || id === "space" || text === " ") return "close";
|
|
128
|
+
if (running && text?.toLowerCase() === "x") return "stop";
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Output-box height in a shell's details (content rows, borders excluded). */
|
|
133
|
+
export const OUTPUT_BOX_ROWS = 10;
|
|
134
|
+
|
|
135
|
+
/** Only this much of the spool is split per frame: the box shows 10 lines. */
|
|
136
|
+
const OUTPUT_TAIL_CHARS = 8192;
|
|
137
|
+
|
|
138
|
+
const STATUS_COLOR: Record<string, string> = { running: "accent", failed: "error", stopped: "dim" };
|
|
139
|
+
|
|
140
|
+
/** One shell's details: Status, Runtime, Command, a bordered tail of its output, and the keys it takes. */
|
|
141
|
+
export function renderShellDetail(task: BackgroundTask, width: number, now: number, paint: TasksPaint): string[] {
|
|
142
|
+
width = Math.max(20, width);
|
|
143
|
+
const out = [paint.bold(cut("Shell details", width)), ""];
|
|
144
|
+
const field = (label: string, value: string) => `${paint.bold(padPlainText(label, 9))} ${value}`;
|
|
145
|
+
out.push(field("Status:", paint.fg(STATUS_COLOR[task.status] ?? "dim", task.status)));
|
|
146
|
+
out.push(field("Runtime:", formatDuration(task.startedAt, task.finishedAt, now)));
|
|
147
|
+
out.push(field("Command:", cut(sanitizeDisplayText(task.command ?? task.description), Math.max(1, width - 10))));
|
|
148
|
+
out.push("", paint.bold("Output:"));
|
|
149
|
+
|
|
150
|
+
let output = "";
|
|
151
|
+
try {
|
|
152
|
+
output = task.output();
|
|
153
|
+
} catch {
|
|
154
|
+
output = "(output unavailable)";
|
|
155
|
+
}
|
|
156
|
+
// Bound the split to a tail slice: re-splitting a 200KB spool every frame to
|
|
157
|
+
// keep 10 lines is O(buffer) work for nothing. A mid-line cut only matters
|
|
158
|
+
// when the tail holds fewer lines than the box, and those are width-cut anyway.
|
|
159
|
+
const tail = output.length > OUTPUT_TAIL_CHARS ? output.slice(-OUTPUT_TAIL_CHARS) : output;
|
|
160
|
+
const lines = tail ? tail.replace(/\r\n/g, "\n").replace(/\n$/, "").split("\n") : [];
|
|
161
|
+
const shown = lines.slice(-OUTPUT_BOX_ROWS);
|
|
162
|
+
const inner = Math.max(1, width - 4);
|
|
163
|
+
out.push(`┌${"─".repeat(Math.max(0, width - 2))}┐`);
|
|
164
|
+
for (let i = 0; i < OUTPUT_BOX_ROWS; i++) out.push(`│ ${padPlainText(cut(shown[i] ?? "", inner), inner)} │`);
|
|
165
|
+
out.push(`└${"─".repeat(Math.max(0, width - 2))}┘`);
|
|
166
|
+
out.push(paint.fg("dim", `Showing ${countNoun(shown.length, "line")}`), "");
|
|
167
|
+
const hint = ["← to go back", "Esc/Enter/Space to close", ...(task.status === "running" ? ["x to stop"] : [])].join(" · ");
|
|
168
|
+
out.push(...hintLines(hint, width).map((hintLine) => paint.fg("dim", hintLine)));
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Viewport mirror for the agent view (pure; no pi imports).
|
|
3
|
+
*
|
|
4
|
+
* In pi's fullscreen mode the terminal's scroll input (PageUp/PageDown, the
|
|
5
|
+
* mouse wheel, Ctrl+Home/Ctrl+End, plain Home/End before pi 1.0.3) is taken
|
|
6
|
+
* by pi-tui's own viewport handler before any extension input listener runs
|
|
7
|
+
* (findings §10), and it scrolls the main transcript. The agent view is an
|
|
8
|
+
* overlay over that transcript, so the user saw nothing move in the view while
|
|
9
|
+
* main, hidden below it, scrolled away and stayed scrolled up after the view
|
|
10
|
+
* closed.
|
|
11
|
+
*
|
|
12
|
+
* The mirror turns those moves into agent-view scrolling, as Claude Code
|
|
13
|
+
* scrolls whatever transcript is on screen: each frame it reads how far the
|
|
14
|
+
* main viewport moved since it was last parked, hands that distance to the
|
|
15
|
+
* view, and parks main again. Main is parked above its bottom by up to the
|
|
16
|
+
* view's own scroll (at most half main's range), so a later move down has room
|
|
17
|
+
* to register too. `restore()` puts main back where it was when the view
|
|
18
|
+
* opened.
|
|
19
|
+
*
|
|
20
|
+
* Only the main transcript's range can register a move: when main is shorter
|
|
21
|
+
* than one screen, the page keys move nothing and the view cannot see them.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** The slice of pi-tui's fullscreen TUI the mirror drives (structural). */
|
|
25
|
+
export interface ScrollableViewport {
|
|
26
|
+
readonly viewportTop: number;
|
|
27
|
+
readonly isFollowingOutput: boolean;
|
|
28
|
+
scrollBy(lines: number): void;
|
|
29
|
+
scrollToBottom(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The TUI's fullscreen viewport, or undefined in regular (main-screen) mode. */
|
|
33
|
+
export function asScrollableViewport(tui: unknown): ScrollableViewport | undefined {
|
|
34
|
+
const candidate = tui as Partial<ScrollableViewport> | undefined;
|
|
35
|
+
if (!candidate || typeof candidate.viewportTop !== "number" || typeof candidate.isFollowingOutput !== "boolean") return undefined;
|
|
36
|
+
if (typeof candidate.scrollBy !== "function" || typeof candidate.scrollToBottom !== "function") return undefined;
|
|
37
|
+
return candidate as ScrollableViewport;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class ViewportMirror {
|
|
41
|
+
private readonly openedFollowing: boolean;
|
|
42
|
+
private readonly openedTop: number;
|
|
43
|
+
private parkedTop: number;
|
|
44
|
+
private parkedAtBottom: boolean;
|
|
45
|
+
|
|
46
|
+
constructor(private readonly viewport: ScrollableViewport) {
|
|
47
|
+
this.openedFollowing = viewport.isFollowingOutput;
|
|
48
|
+
this.openedTop = viewport.viewportTop;
|
|
49
|
+
this.parkedTop = this.openedTop;
|
|
50
|
+
this.parkedAtBottom = this.openedFollowing;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fold any main-viewport move since the last park into the view's scroll
|
|
55
|
+
* (lines from the bottom, up is positive) and park main again. Returns the
|
|
56
|
+
* new scroll, clamped to `[0, maxScroll]`.
|
|
57
|
+
*/
|
|
58
|
+
absorb(scroll: number, maxScroll: number): number {
|
|
59
|
+
const { viewport } = this;
|
|
60
|
+
// Parked at the bottom and still following: main only grew, nobody scrolled.
|
|
61
|
+
if (this.parkedAtBottom && viewport.isFollowingOutput) {
|
|
62
|
+
this.parkedTop = viewport.viewportTop;
|
|
63
|
+
return scroll;
|
|
64
|
+
}
|
|
65
|
+
const top = viewport.viewportTop;
|
|
66
|
+
if (top === this.parkedTop) return scroll;
|
|
67
|
+
const next = clamp(scroll + (this.parkedTop - top), maxScroll);
|
|
68
|
+
this.park(next);
|
|
69
|
+
return next;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Park main for a view scroll of `scroll` lines (after a retarget, say). */
|
|
73
|
+
park(scroll: number): void {
|
|
74
|
+
const { viewport } = this;
|
|
75
|
+
viewport.scrollToBottom();
|
|
76
|
+
const bottom = viewport.viewportTop;
|
|
77
|
+
const lift = Math.min(Math.max(0, scroll), Math.floor(bottom / 2));
|
|
78
|
+
if (lift > 0) viewport.scrollBy(-lift);
|
|
79
|
+
this.parkedTop = viewport.viewportTop;
|
|
80
|
+
this.parkedAtBottom = lift === 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Put main back where it was when the view opened. */
|
|
84
|
+
restore(): void {
|
|
85
|
+
const { viewport } = this;
|
|
86
|
+
viewport.scrollToBottom();
|
|
87
|
+
if (this.openedFollowing) return;
|
|
88
|
+
const delta = this.openedTop - viewport.viewportTop;
|
|
89
|
+
if (delta !== 0) viewport.scrollBy(delta);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function clamp(value: number, max: number): number {
|
|
94
|
+
return Math.max(0, Math.min(Math.max(0, max), value));
|
|
95
|
+
}
|
|
@@ -3,25 +3,31 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Claude Code's `isolation: "worktree"` gives an agent its own checkout so
|
|
5
5
|
* parallel agents editing files cannot collide, and removes it again if the agent
|
|
6
|
-
* changed nothing
|
|
6
|
+
* changed nothing: no uncommitted change and no commit past the HEAD it was
|
|
7
|
+
* created at. A worktree whose agent committed its work is kept like one with
|
|
8
|
+
* uncommitted edits, so the commits stay reachable on its branch.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import { execFile } from "node:child_process";
|
|
10
12
|
import { mkdtempSync } from "node:fs";
|
|
11
|
-
import { tmpdir } from "node:os";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
+
import { homedir, tmpdir } from "node:os";
|
|
14
|
+
import { basename, join, resolve } from "node:path";
|
|
13
15
|
import { promisify } from "node:util";
|
|
14
16
|
import { registerWorktreeIsolation, releaseWorktreeIsolation } from "../lib/worktree-isolation.ts";
|
|
17
|
+
import { findProjectRoot, HARNESS_GIT_CONFIG, HARNESS_GIT_NO_HOOKS } from "../lib/git.ts";
|
|
18
|
+
import { checkoutGitRunsProgram } from "../auto-mode/git-checkout-programs.ts";
|
|
15
19
|
|
|
16
20
|
const run = promisify(execFile);
|
|
17
21
|
|
|
18
22
|
export interface Worktree {
|
|
19
23
|
path: string;
|
|
20
24
|
branch: string;
|
|
25
|
+
/** The commit the worktree was created at; commits past it are the agent's work. */
|
|
26
|
+
baseCommit: string;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
async function git(args: string[], cwd: string): Promise<string> {
|
|
24
|
-
const { stdout } = await run("git", args, { cwd, maxBuffer: 10 * 1024 * 1024 });
|
|
30
|
+
const { stdout } = await run("git", [...HARNESS_GIT_CONFIG, ...HARNESS_GIT_NO_HOOKS, ...args], { cwd, maxBuffer: 10 * 1024 * 1024 });
|
|
25
31
|
return stdout.trim();
|
|
26
32
|
}
|
|
27
33
|
|
|
@@ -33,31 +39,57 @@ export async function isGitRepo(cwd: string): Promise<boolean> {
|
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
/** Creates a
|
|
42
|
+
/** Creates a worktree on a new throwaway branch at the current HEAD. */
|
|
37
43
|
export async function createWorktree(cwd: string, label: string): Promise<Worktree> {
|
|
44
|
+
const program = checkoutGitRunsProgram(cwd, homedir(), []);
|
|
45
|
+
if (program) {
|
|
46
|
+
throw new Error(`Cannot create an isolation worktree: ${program}. Create the worktree with an explicitly approved git worktree add command, then start a session there without automatic isolation.`);
|
|
47
|
+
}
|
|
38
48
|
const safeLabel = label.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 24) || "agent";
|
|
39
49
|
const dir = mkdtempSync(join(tmpdir(), `cc-wt-${safeLabel}-`));
|
|
40
50
|
const path = join(dir, "tree");
|
|
41
|
-
const branch = `cc-subagent/${
|
|
51
|
+
const branch = `cc-subagent/${basename(dir).slice("cc-wt-".length)}`;
|
|
42
52
|
await git(["worktree", "add", "-b", branch, path, "HEAD"], cwd);
|
|
43
53
|
// Register for the child permission gate's git-isolation guard. The shared
|
|
44
|
-
// checkout's ROOT, not cwd — the spawn may run from a subdirectory
|
|
54
|
+
// checkout's ROOT, not cwd — the spawn may run from a subdirectory — and the
|
|
55
|
+
// MAIN checkout's root when it runs from a linked worktree (an entered
|
|
56
|
+
// `enter_worktree` session), so git into either is refused.
|
|
45
57
|
let sharedRoot = cwd;
|
|
46
58
|
try {
|
|
47
|
-
|
|
59
|
+
// git prints `C:/…` on Windows; resolve gives the native form.
|
|
60
|
+
const top = resolve(await git(["rev-parse", "--show-toplevel"], cwd));
|
|
61
|
+
sharedRoot = findProjectRoot(top) ?? top;
|
|
48
62
|
} catch {
|
|
49
63
|
// Keep cwd as the best available anchor.
|
|
50
64
|
}
|
|
51
65
|
registerWorktreeIsolation(path, sharedRoot);
|
|
52
|
-
|
|
66
|
+
let baseCommit: string;
|
|
67
|
+
try {
|
|
68
|
+
baseCommit = await git(["rev-parse", "HEAD"], path);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// Without the base, cleanup could not tell the agent's commits apart;
|
|
71
|
+
// remove the fresh worktree now rather than run in one we cannot judge.
|
|
72
|
+
await git(["worktree", "remove", "--force", path], cwd).catch(() => undefined);
|
|
73
|
+
await git(["branch", "-D", branch], cwd).catch(() => undefined);
|
|
74
|
+
releaseWorktreeIsolation(path);
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
return { path, branch, baseCommit };
|
|
53
78
|
}
|
|
54
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Whether the worktree holds work: uncommitted changes, or commits reachable
|
|
82
|
+
* from its HEAD or its branch that the base commit does not contain (an agent
|
|
83
|
+
* that commits leaves a clean status). Any git failure counts as work.
|
|
84
|
+
*/
|
|
55
85
|
export async function worktreeHasChanges(worktree: Worktree): Promise<boolean> {
|
|
56
86
|
try {
|
|
57
|
-
const status = await git(["status", "--porcelain"], worktree.path);
|
|
58
|
-
|
|
87
|
+
const status = await git(["status", "--porcelain", "--untracked-files=all", "--ignore-submodules=none"], worktree.path);
|
|
88
|
+
if (status.length > 0) return true;
|
|
89
|
+
const ahead = await git(["rev-list", "--count", "HEAD", `refs/heads/${worktree.branch}`, `^${worktree.baseCommit}`], worktree.path);
|
|
90
|
+
return ahead !== "0";
|
|
59
91
|
} catch {
|
|
60
|
-
// If
|
|
92
|
+
// If git fails, assume there is something worth keeping.
|
|
61
93
|
return true;
|
|
62
94
|
}
|
|
63
95
|
}
|
|
@@ -70,13 +102,25 @@ export async function cleanupWorktree(cwd: string, worktree: Worktree): Promise<
|
|
|
70
102
|
if (await worktreeHasChanges(worktree)) return false;
|
|
71
103
|
try {
|
|
72
104
|
await git(["worktree", "remove", "--force", worktree.path], cwd);
|
|
73
|
-
await git(["branch", "-D", worktree.branch], cwd);
|
|
74
105
|
} catch {
|
|
75
106
|
// Leave it behind rather than failing the run.
|
|
76
107
|
return false;
|
|
77
108
|
}
|
|
109
|
+
try {
|
|
110
|
+
// `-d`, not `-D`: git itself refuses to drop a branch with unmerged work,
|
|
111
|
+
// a second check behind worktreeHasChanges. A refused delete leaves a
|
|
112
|
+
// branch at the base commit, which holds none of the agent's work.
|
|
113
|
+
await git(["branch", "-d", worktree.branch], cwd);
|
|
114
|
+
} catch {
|
|
115
|
+
// The worktree is gone; the branch stays for the user to inspect.
|
|
116
|
+
}
|
|
78
117
|
// Release only when the worktree is actually gone — a kept worktree can
|
|
79
118
|
// still host a resumed session (SendMessage), which must stay guarded.
|
|
80
119
|
releaseWorktreeIsolation(worktree.path);
|
|
81
120
|
return true;
|
|
82
121
|
}
|
|
122
|
+
|
|
123
|
+
/** The note a result carries for a worktree kept because it holds the agent's work. */
|
|
124
|
+
export function keptWorktreeNote(path: string, branch: string | undefined): string {
|
|
125
|
+
return `(Changes or commits left in worktree ${path}${branch ? ` on branch ${branch}` : ""} — review or merge them.)`;
|
|
126
|
+
}
|