one-code-extension 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -5
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +40 -28
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +26 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +58 -31
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +124 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +642 -0
- package/extensions/background/index.ts +614 -248
- package/extensions/background/loop-fire.ts +173 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +56 -0
- package/extensions/background/wakeup.ts +178 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +66 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +250 -78
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/cache-marks/index.ts +42 -0
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +436 -63
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +219 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/budget.ts +16 -2
- package/extensions/context-budget/index.ts +35 -15
- package/extensions/context-management/index.ts +36 -28
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +141 -25
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +420 -42
- package/extensions/file-tracker/replay.ts +137 -24
- package/extensions/file-tracker/restore.ts +66 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +45 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +209 -58
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/prompt-context.ts +59 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +26 -12
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/ledger/index.ts +46 -0
- package/extensions/ledger/wire.ts +154 -0
- package/extensions/lib/agent-cron.ts +146 -0
- package/extensions/lib/agent-loader.ts +18 -3
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-auth.ts +98 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +52 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +97 -0
- package/extensions/lib/covered-messages.ts +23 -0
- package/extensions/lib/deferred.ts +359 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/ledger/fold-memo.ts +49 -0
- package/extensions/lib/ledger/fold.ts +265 -0
- package/extensions/lib/ledger/layout.ts +72 -0
- package/extensions/lib/ledger/plan.ts +102 -0
- package/extensions/lib/ledger/records.ts +276 -0
- package/extensions/lib/ledger/state.ts +47 -0
- package/extensions/lib/ledger/validate.ts +141 -0
- package/extensions/lib/ledger/wire.ts +632 -0
- package/extensions/lib/local-command.ts +71 -19
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +178 -0
- package/extensions/lib/model-catalog/models-dev.json +18533 -0
- package/extensions/lib/model-catalog/openrouter.json +5050 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +357 -73
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +38 -2
- package/extensions/lib/plan-mode-channels.ts +42 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/previous-mark.ts +101 -0
- package/extensions/lib/process-state.ts +48 -0
- package/extensions/lib/process-tree.ts +132 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +370 -358
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +547 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +24 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +112 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +129 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +159 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +76 -12
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +90 -43
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +220 -121
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +164 -73
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/model-default/index.ts +86 -19
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1426 -398
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +11 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +276 -92
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/run-state.ts +26 -0
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +64 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +14 -7
- package/extensions/recap/index.ts +73 -71
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/announce.ts +30 -0
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +469 -132
- package/extensions/skill/invoke.ts +118 -75
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +146 -31
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +82 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1594 -852
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +5 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/parent-mode.ts +50 -0
- package/extensions/subagents/pending-claim.ts +18 -1
- package/extensions/subagents/runner.ts +135 -80
- package/extensions/subagents/runs.ts +44 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +165 -47
- package/extensions/system-prompt/template.ts +34 -51
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +204 -70
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/index.ts +366 -144
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +108 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +157 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +33 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +162 -44
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +21 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
- package/extensions/tool-search/announce.ts +0 -42
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plan-mode tools' model-facing texts (pure): Claude Code's EnterPlanMode
|
|
3
|
+
* and ExitPlanMode descriptions and results, with One Code's snake_case tool
|
|
4
|
+
* names. The plan stays file-based (decisions/modes.md, "Plan mode is
|
|
5
|
+
* file-based"); only the wording follows Claude Code.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DescriptionForm } from "../lib/tool-variants.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Claude Code's EnterPlanMode description, which prefers plan mode for any
|
|
12
|
+
* non-trivial implementation task. Frontier and workhorse models get it.
|
|
13
|
+
*/
|
|
14
|
+
export const ENTER_PLAN_MODE_DESCRIPTION = [
|
|
15
|
+
"Use this tool proactively when you're about to start a non-trivial implementation task. Getting user sign-off on your approach before writing code prevents wasted effort and ensures alignment. This tool transitions you into plan mode where you can explore the codebase and design an implementation approach for user approval.",
|
|
16
|
+
"",
|
|
17
|
+
"## When to Use This Tool",
|
|
18
|
+
"",
|
|
19
|
+
"**Prefer using enter_plan_mode** for implementation tasks unless they're simple. Use it when ANY of these conditions apply:",
|
|
20
|
+
"",
|
|
21
|
+
"1. **New Feature Implementation**: Adding meaningful new functionality",
|
|
22
|
+
" - Example: \"Add a logout button\" - where should it go? What should happen on click?",
|
|
23
|
+
" - Example: \"Add form validation\" - what rules? What error messages?",
|
|
24
|
+
"",
|
|
25
|
+
"2. **Multiple Valid Approaches**: The task can be solved in several different ways",
|
|
26
|
+
" - Example: \"Add caching to the API\" - could use Redis, in-memory, file-based, etc.",
|
|
27
|
+
" - Example: \"Improve performance\" - many optimization strategies possible",
|
|
28
|
+
"",
|
|
29
|
+
"3. **Code Modifications**: Changes that affect existing behavior or structure",
|
|
30
|
+
" - Example: \"Update the login flow\" - what exactly should change?",
|
|
31
|
+
" - Example: \"Refactor this component\" - what's the target architecture?",
|
|
32
|
+
"",
|
|
33
|
+
"4. **Architectural Decisions**: The task requires choosing between patterns or technologies",
|
|
34
|
+
" - Example: \"Add real-time updates\" - WebSockets vs SSE vs polling",
|
|
35
|
+
" - Example: \"Implement state management\" - Redux vs Context vs custom solution",
|
|
36
|
+
"",
|
|
37
|
+
"5. **Multi-File Changes**: The task will likely touch more than 2-3 files",
|
|
38
|
+
" - Example: \"Refactor the authentication system\"",
|
|
39
|
+
" - Example: \"Add a new API endpoint with tests\"",
|
|
40
|
+
"",
|
|
41
|
+
"6. **Unclear Requirements**: You need to explore before understanding the full scope",
|
|
42
|
+
" - Example: \"Make the app faster\" - need to profile and identify bottlenecks",
|
|
43
|
+
" - Example: \"Fix the bug in checkout\" - need to investigate root cause",
|
|
44
|
+
"",
|
|
45
|
+
"7. **User Preferences Matter**: The implementation could reasonably go multiple ways",
|
|
46
|
+
" - If you would use ask_user_question to clarify the approach, use enter_plan_mode instead",
|
|
47
|
+
" - Plan mode lets you explore first, then present options with context",
|
|
48
|
+
"",
|
|
49
|
+
"## When NOT to Use This Tool",
|
|
50
|
+
"",
|
|
51
|
+
"Only skip enter_plan_mode for simple tasks:",
|
|
52
|
+
"- Single-line or few-line fixes (typos, obvious bugs, small tweaks)",
|
|
53
|
+
"- Adding a single function with clear requirements",
|
|
54
|
+
"- Tasks where the user has given very specific, detailed instructions",
|
|
55
|
+
"- Pure research/exploration tasks (use the Agent tool instead)",
|
|
56
|
+
"",
|
|
57
|
+
"## What Happens in Plan Mode",
|
|
58
|
+
"",
|
|
59
|
+
"In plan mode, you'll:",
|
|
60
|
+
"1. Thoroughly explore the codebase using `find`, `grep`, and read",
|
|
61
|
+
"2. Understand existing patterns and architecture",
|
|
62
|
+
"3. Design an implementation approach",
|
|
63
|
+
"4. Present your plan to the user for approval",
|
|
64
|
+
"5. Use ask_user_question if you need to clarify approaches",
|
|
65
|
+
"6. Exit plan mode with exit_plan_mode when ready to implement",
|
|
66
|
+
"",
|
|
67
|
+
"## Examples",
|
|
68
|
+
"",
|
|
69
|
+
"### GOOD - Use enter_plan_mode:",
|
|
70
|
+
"User: \"Add user authentication to the app\"",
|
|
71
|
+
"- Requires architectural decisions (session vs JWT, where to store tokens, middleware structure)",
|
|
72
|
+
"",
|
|
73
|
+
"User: \"Optimize the database queries\"",
|
|
74
|
+
"- Multiple approaches possible, need to profile first, significant impact",
|
|
75
|
+
"",
|
|
76
|
+
"User: \"Implement dark mode\"",
|
|
77
|
+
"- Architectural decision on theme system, affects many components",
|
|
78
|
+
"",
|
|
79
|
+
"User: \"Add a delete button to the user profile\"",
|
|
80
|
+
"- Seems simple but involves: where to place it, confirmation dialog, API call, error handling, state updates",
|
|
81
|
+
"",
|
|
82
|
+
"User: \"Update the error handling in the API\"",
|
|
83
|
+
"- Affects multiple files, user should approve the approach",
|
|
84
|
+
"",
|
|
85
|
+
"### BAD - Don't use enter_plan_mode:",
|
|
86
|
+
"User: \"Fix the typo in the README\"",
|
|
87
|
+
"- Straightforward, no planning needed",
|
|
88
|
+
"",
|
|
89
|
+
"User: \"Add a console.log to debug this function\"",
|
|
90
|
+
"- Simple, obvious implementation",
|
|
91
|
+
"",
|
|
92
|
+
"User: \"What files handle routing?\"",
|
|
93
|
+
"- Research task, not implementation planning",
|
|
94
|
+
"",
|
|
95
|
+
"## Important Notes",
|
|
96
|
+
"",
|
|
97
|
+
"- This tool REQUIRES user approval - they must consent to entering plan mode",
|
|
98
|
+
"- If unsure whether to use it, err on the side of planning - it's better to get alignment upfront than to redo work",
|
|
99
|
+
"- Users appreciate being consulted before significant changes are made to their codebase",
|
|
100
|
+
"",
|
|
101
|
+
].join("\n");
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* One Code's own description for the cheap and tiny tiers. Weak models read a
|
|
105
|
+
* "prefer plan mode" description as standing policy and planned trivial tasks
|
|
106
|
+
* (decisions/tools.md, "Weak-model steering"), so this one says most tasks do
|
|
107
|
+
* not need it.
|
|
108
|
+
*/
|
|
109
|
+
export const ENTER_PLAN_MODE_DESCRIPTION_WEAK =
|
|
110
|
+
"Enter plan mode: read-only investigation to design an approach before changing anything. Most tasks do not need it.\n" +
|
|
111
|
+
"For a small or clearly-scoped change, act directly instead. Enter plan mode only for multi-file work whose design is genuinely unclear, or when the user asks for a plan. In plan mode only read-only tools are available, plus one writable file: the plan file whose path you are told, where you build the plan incrementally.";
|
|
112
|
+
|
|
113
|
+
/** The enter_plan_mode description for a description form: Claude Code's short form, ours as the long one. */
|
|
114
|
+
export function enterPlanModeDescription(form: DescriptionForm): string {
|
|
115
|
+
return form === "long" ? ENTER_PLAN_MODE_DESCRIPTION_WEAK : ENTER_PLAN_MODE_DESCRIPTION;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Claude Code's ExitPlanMode description. */
|
|
119
|
+
export const EXIT_PLAN_MODE_DESCRIPTION = [
|
|
120
|
+
"Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.",
|
|
121
|
+
"",
|
|
122
|
+
"## How This Tool Works",
|
|
123
|
+
"- You should have already written your plan to the plan file specified in the plan mode system message",
|
|
124
|
+
"- This tool does NOT take the plan content as a parameter - it will read the plan from the file you wrote",
|
|
125
|
+
"- This tool simply signals that you're done planning and ready for the user to review and approve",
|
|
126
|
+
"- The user will see the contents of your plan file when they review it",
|
|
127
|
+
"",
|
|
128
|
+
"## When to Use This Tool",
|
|
129
|
+
"IMPORTANT: Only use this tool when the task requires planning the implementation steps of a task that requires writing code. For research tasks where you're gathering information, searching files, reading files or in general trying to understand the codebase - do NOT use this tool.",
|
|
130
|
+
"",
|
|
131
|
+
"## Before Using This Tool",
|
|
132
|
+
"Ensure your plan is complete and unambiguous:",
|
|
133
|
+
"- If you have unresolved questions about requirements or approach, use ask_user_question first (in earlier phases)",
|
|
134
|
+
"- Once your plan is finalized, use THIS tool to request approval",
|
|
135
|
+
"",
|
|
136
|
+
"**Important:** Do NOT use ask_user_question to ask \"Is this plan okay?\" or \"Should I proceed?\" - that's exactly what THIS tool does. exit_plan_mode inherently requests user approval of your plan.",
|
|
137
|
+
"",
|
|
138
|
+
"## Examples",
|
|
139
|
+
"",
|
|
140
|
+
"1. Initial task: \"Search for and understand the implementation of vim mode in the codebase\" - Do not use the exit plan mode tool because you are not planning the implementation steps of a task.",
|
|
141
|
+
"2. Initial task: \"Help me implement yank mode for vim\" - Use the exit plan mode tool after you have finished planning the implementation steps of the task.",
|
|
142
|
+
"3. Initial task: \"Add a new feature to handle user authentication\" - If unsure about auth method (OAuth, JWT, etc.), use ask_user_question first, then use exit plan mode tool after clarifying the approach.",
|
|
143
|
+
"",
|
|
144
|
+
].join("\n");
|
|
145
|
+
|
|
146
|
+
/** Claude Code's EnterPlanMode result. */
|
|
147
|
+
const ENTERED_PLAN_MODE = [
|
|
148
|
+
"Entered plan mode. You should now focus on exploring the codebase and designing an implementation approach.",
|
|
149
|
+
"",
|
|
150
|
+
"In plan mode, you should:",
|
|
151
|
+
"1. Thoroughly explore the codebase to understand existing patterns",
|
|
152
|
+
"2. Identify similar features and architectural approaches",
|
|
153
|
+
"3. Consider multiple approaches and their trade-offs",
|
|
154
|
+
"4. Use ask_user_question if you need to clarify the approach",
|
|
155
|
+
"5. Design a concrete implementation strategy",
|
|
156
|
+
"6. When ready, use exit_plan_mode to present your plan for approval",
|
|
157
|
+
"",
|
|
158
|
+
"Remember: DO NOT write or edit any files yet. This is a read-only exploration and planning phase.",
|
|
159
|
+
].join("\n");
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* enter_plan_mode's result: Claude Code's text, then the plan file's path so
|
|
163
|
+
* the model can start writing the plan in the same turn.
|
|
164
|
+
*/
|
|
165
|
+
export function enteredPlanModeText(planFilePath: string): string {
|
|
166
|
+
return `${ENTERED_PLAN_MODE}\n\nYour plan file is ${planFilePath}, the one file you may write: build your plan there.`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** exit_plan_mode's result when the user approves: Claude Code's text, with the plan file's contents. */
|
|
170
|
+
export function approvedPlanText(planFilePath: string, plan: string): string {
|
|
171
|
+
return `User has approved your plan. You can now start coding. Start with updating your todo list if applicable\n\nYour plan has been saved to: ${planFilePath}\nYou can refer back to it if needed during implementation.\n\n## Approved Plan:\n${plan}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Claude Code's reminder after the approval: plan mode is over and the plan file stays readable. */
|
|
175
|
+
export function exitedPlanModeText(planFilePath: string): string {
|
|
176
|
+
return `## Exited Plan Mode\n\nYou have exited plan mode. You can now make edits, run tools, and take actions. The plan file is located at ${planFilePath} if you need to reference it.`;
|
|
177
|
+
}
|
|
@@ -6,18 +6,54 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Every rendered line must stay within the width given: pi-tui crashes the
|
|
8
8
|
* whole app on an overwide line. Content is wrapped (not truncated — a plan
|
|
9
|
-
* must stay readable in full)
|
|
10
|
-
* painting so ANSI escapes never confuse
|
|
9
|
+
* must stay readable in full) and then cut ANSI-aware as a guard, and chrome
|
|
10
|
+
* lines are cut plain-text *before* painting so ANSI escapes never confuse
|
|
11
|
+
* the width accounting.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
import { cutPlainText } from "../lib/tui-render.ts";
|
|
14
|
+
import { cutPlainText, hintLines, truncateLine, wrapProse } from "../lib/tui-render.ts";
|
|
15
|
+
import { keyId } from "../lib/key-input.ts";
|
|
14
16
|
|
|
15
|
-
/**
|
|
16
|
-
export
|
|
17
|
+
/** Renders Markdown to painted lines at a width (pi-tui's Markdown, which the transcript uses). */
|
|
18
|
+
export type PlanMarkdown = (text: string, width: number) => string[];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The plan's lines at `width`: Markdown rendered the way the transcript renders
|
|
22
|
+
* assistant text when a renderer is available, else plain text wrapped at word
|
|
23
|
+
* boundaries. A renderer that throws falls back to the plain wrap, so a
|
|
24
|
+
* Markdown failure never costs the user the plan.
|
|
25
|
+
*/
|
|
26
|
+
export function planLines(plan: string, width: number, markdown?: PlanMarkdown): string[] {
|
|
27
|
+
if (markdown) {
|
|
28
|
+
try {
|
|
29
|
+
return markdown(plan, width);
|
|
30
|
+
} catch {
|
|
31
|
+
// fall through to the plain wrap
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return wrapProse(plan, width);
|
|
35
|
+
}
|
|
17
36
|
|
|
18
37
|
/** What the dialog resolves to; index into the choices list, or null for cancel. */
|
|
19
38
|
export type PlanChoice = number;
|
|
20
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The approval through a plain select, for RPC: its `custom()` resolves
|
|
42
|
+
* undefined (findings §8), which read as "dismissed" and rejected every plan.
|
|
43
|
+
* The plan rides in the title; a reply that is no choice is a dismissal.
|
|
44
|
+
*/
|
|
45
|
+
export async function selectPlanChoice(
|
|
46
|
+
ui: { select(title: string, options: string[], opts?: { signal?: AbortSignal }): Promise<string | undefined> },
|
|
47
|
+
plan: string,
|
|
48
|
+
path: string,
|
|
49
|
+
choices: string[],
|
|
50
|
+
signal?: AbortSignal,
|
|
51
|
+
): Promise<PlanChoice | null> {
|
|
52
|
+
const label = await ui.select(`Plan (${path}):\n\n${plan}\n\nApprove the plan?`, choices, { signal });
|
|
53
|
+
const index = signal?.aborted || label === undefined ? -1 : choices.indexOf(label);
|
|
54
|
+
return index >= 0 ? index : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
21
57
|
export type ViewerKey =
|
|
22
58
|
| { kind: "scroll"; delta: number }
|
|
23
59
|
| { kind: "choice"; delta: number }
|
|
@@ -32,33 +68,29 @@ export type ViewerKey =
|
|
|
32
68
|
* classifier model is reachable).
|
|
33
69
|
*/
|
|
34
70
|
export function decodeViewerKey(data: string, page: number): ViewerKey | undefined {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
case "
|
|
71
|
+
const id = keyId(data);
|
|
72
|
+
switch (id) {
|
|
73
|
+
case "up":
|
|
38
74
|
return { kind: "scroll", delta: -1 };
|
|
39
|
-
case "
|
|
40
|
-
case "\x1bOB":
|
|
75
|
+
case "down":
|
|
41
76
|
return { kind: "scroll", delta: 1 };
|
|
42
|
-
case "
|
|
77
|
+
case "pageUp":
|
|
43
78
|
return { kind: "scroll", delta: -page };
|
|
44
|
-
case "
|
|
79
|
+
case "pageDown":
|
|
45
80
|
return { kind: "scroll", delta: page };
|
|
46
|
-
case "
|
|
47
|
-
case "\x1bOD":
|
|
81
|
+
case "left":
|
|
48
82
|
return { kind: "choice", delta: -1 };
|
|
49
|
-
case "
|
|
50
|
-
case "
|
|
51
|
-
case "\t":
|
|
83
|
+
case "right":
|
|
84
|
+
case "tab":
|
|
52
85
|
return { kind: "choice", delta: 1 };
|
|
53
|
-
case "
|
|
54
|
-
case "\n":
|
|
86
|
+
case "enter":
|
|
55
87
|
return { kind: "confirm" };
|
|
56
|
-
case "
|
|
57
|
-
case "
|
|
88
|
+
case "escape":
|
|
89
|
+
case "ctrl+c": // same intent as escape while the dialog is focused
|
|
58
90
|
return { kind: "cancel" };
|
|
59
91
|
default:
|
|
60
|
-
if (
|
|
61
|
-
return { kind: "pick", index: Number(
|
|
92
|
+
if (id && id.length === 1 && id >= "1" && id <= "9") {
|
|
93
|
+
return { kind: "pick", index: Number(id) - 1 };
|
|
62
94
|
}
|
|
63
95
|
return undefined;
|
|
64
96
|
}
|
|
@@ -90,7 +122,7 @@ export function initialPlanChoice(options: readonly { mode?: string; restore?: b
|
|
|
90
122
|
export type Paint = (color: string, text: string) => string;
|
|
91
123
|
|
|
92
124
|
export interface PlanViewerView {
|
|
93
|
-
/**
|
|
125
|
+
/** The plan's lines for this width (planLines output: painted Markdown or plain wrapped text). */
|
|
94
126
|
lines: string[];
|
|
95
127
|
offset: number;
|
|
96
128
|
choice: PlanChoice;
|
|
@@ -103,18 +135,20 @@ export function renderPlanViewer(view: PlanViewerView, paint: Paint, width: numb
|
|
|
103
135
|
const maxVisible = view.maxVisible ?? 12;
|
|
104
136
|
// Cut plain text before painting so escapes never enter the width math.
|
|
105
137
|
const cut = (line: string) => cutPlainText(line, width);
|
|
106
|
-
const rule = paint("dim", "─".repeat(Math.max(0,
|
|
138
|
+
const rule = paint("dim", "─".repeat(Math.max(0, width)));
|
|
107
139
|
const pickHint = view.choices.length > 1 ? `1-${view.choices.length} pick · ` : "";
|
|
108
140
|
|
|
109
141
|
const out: string[] = [];
|
|
110
142
|
out.push("");
|
|
111
143
|
out.push(paint("accent", cut("Approve this plan?")));
|
|
112
|
-
|
|
144
|
+
// Each hint sits beside what its keys move: scrolling above the plan, choosing under the choices.
|
|
145
|
+
out.push(paint("dim", cut("↑/↓ scroll the plan")));
|
|
113
146
|
out.push(rule);
|
|
114
147
|
|
|
148
|
+
// Plan lines may arrive painted (Markdown), so the guard cut is ANSI-aware.
|
|
115
149
|
const offset = clampOffset(view.offset, view.lines.length, maxVisible);
|
|
116
150
|
for (const line of view.lines.slice(offset, offset + maxVisible)) {
|
|
117
|
-
out.push(
|
|
151
|
+
out.push(truncateLine(line, width));
|
|
118
152
|
}
|
|
119
153
|
if (view.lines.length > maxVisible) {
|
|
120
154
|
const last = Math.min(view.lines.length, offset + maxVisible);
|
|
@@ -128,6 +162,37 @@ export function renderPlanViewer(view: PlanViewerView, paint: Paint, width: numb
|
|
|
128
162
|
index === view.choice ? paint("accent", cut(`❯ ${numbered}`)) : cut(` ${numbered}`),
|
|
129
163
|
);
|
|
130
164
|
}
|
|
165
|
+
// The keys wrap at their separators on a narrow terminal, so "esc cancel" is never cut.
|
|
166
|
+
out.push(...hintLines(`←/→ choose · ${pickHint}enter confirm · esc cancel`, width).map((line) => paint("dim", line)));
|
|
131
167
|
out.push("");
|
|
132
168
|
return out;
|
|
133
169
|
}
|
|
170
|
+
|
|
171
|
+
/** The question the keep-planning choice asks (Claude Code's placeholder, naming the agent). */
|
|
172
|
+
export const KEEP_PLANNING_QUESTION = "Tell the agent what to change";
|
|
173
|
+
|
|
174
|
+
/** How the approval ended: an approving choice, or a rejection carrying the user's words when they gave some. */
|
|
175
|
+
export type PlanDecision = { kind: "approve"; index: PlanChoice } | { kind: "reject"; feedback?: string };
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The approval flow, as in Claude Code: keeping planning asks what to change,
|
|
179
|
+
* and those words go back to the model, which keeps planning. A blank answer
|
|
180
|
+
* returns to the dialog (Claude Code ignores Enter on an empty "keep planning"
|
|
181
|
+
* input). Esc on the dialog or on the question rejects without words, which
|
|
182
|
+
* stops the turn. The option without a mode is the keep-planning one.
|
|
183
|
+
*/
|
|
184
|
+
export async function runPlanApproval(
|
|
185
|
+
options: readonly { mode?: string }[],
|
|
186
|
+
showDialog: () => Promise<PlanChoice | null>,
|
|
187
|
+
askFeedback: () => Promise<string | undefined>,
|
|
188
|
+
signal?: AbortSignal,
|
|
189
|
+
): Promise<PlanDecision> {
|
|
190
|
+
for (;;) {
|
|
191
|
+
const choice = await showDialog();
|
|
192
|
+
if (choice == null || signal?.aborted) return { kind: "reject" };
|
|
193
|
+
if (options[choice]?.mode) return { kind: "approve", index: choice };
|
|
194
|
+
const feedback = await askFeedback();
|
|
195
|
+
if (feedback === undefined || signal?.aborted) return { kind: "reject" };
|
|
196
|
+
if (feedback.trim()) return { kind: "reject", feedback: feedback.trim() };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -48,9 +48,9 @@ export async function fetchInstallCounts(root: string, now: Date = new Date()):
|
|
|
48
48
|
if (cached) return cached;
|
|
49
49
|
|
|
50
50
|
try {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const counts = await fetchWithTimeout(INSTALL_COUNTS_URL, FETCH_TIMEOUT_MS, async (response) =>
|
|
52
|
+
response.ok ? ((await response.json()) as CountsCache["counts"]) : undefined,
|
|
53
|
+
);
|
|
54
54
|
if (!Array.isArray(counts)) return undefined;
|
|
55
55
|
writeJsonAtomic(join(root, CACHE_FILE), { version: 1, fetchedAt: now.toISOString(), counts } satisfies CountsCache);
|
|
56
56
|
return toMap(counts);
|
|
@@ -16,14 +16,17 @@
|
|
|
16
16
|
* ~/.claude is read-only here, always.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { randomUUID } from "node:crypto";
|
|
20
20
|
import { readFileSync, rmSync } from "node:fs";
|
|
21
|
-
import { join } from "node:path";
|
|
22
|
-
import { promisify } from "node:util";
|
|
21
|
+
import { dirname, join } from "node:path";
|
|
23
22
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
24
23
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
25
24
|
import { readFavorites, toggleFavorite } from "../lib/favorites.ts";
|
|
25
|
+
import { announceArgumentHint, type CommandHint, frontmatterCommandHint } from "../lib/argument-hints.ts";
|
|
26
26
|
import { MCP_STATUS_CHANNEL, MCP_STATUS_REQUEST_CHANNEL, type McpStatusEvent } from "../lib/mcp-status.ts";
|
|
27
|
+
import { canShowCustomUi, notifyOrPrint, notifyRpcReadOnly } from "../lib/headless-output.ts";
|
|
28
|
+
import { createUserMessageSender, sessionOutlivesTurn } from "../lib/notifications.ts";
|
|
29
|
+
import { ONE_SHOT_COMMAND_FAILED_CHANNEL } from "../lib/interrupt.ts";
|
|
27
30
|
import { persistIfLarge, sessionResultsDir } from "../lib/persisted-output.ts";
|
|
28
31
|
import { setOverride } from "../lib/plugin-overrides.ts";
|
|
29
32
|
import { pathWithinBase, pluginRoot } from "../lib/plugin-root.ts";
|
|
@@ -62,13 +65,15 @@ import { decodePanelKey } from "./panel/keys.ts";
|
|
|
62
65
|
import { type DiscoverDetail, renderPanel, type PanelPaint } from "./panel/render.ts";
|
|
63
66
|
import { buildDiscoverRows, buildInstalledRows, buildMarketplaceRows, type DiscoverRow } from "./panel/rows.ts";
|
|
64
67
|
import { applyPanelKey, initialPanelState, type PanelEffect, type PanelView } from "./panel/state.ts";
|
|
65
|
-
import {
|
|
68
|
+
import { expandTemplate, runPlaceholderCommand } from "./shell-expand.ts";
|
|
69
|
+
import { watchPermissionBridge } from "../permissions/subagent-gate.ts";
|
|
70
|
+
import { followWorkCwd } from "../lib/worktree-channel.ts";
|
|
66
71
|
import { parseFrontmatterLoosely } from "../lib/frontmatter.ts";
|
|
67
72
|
import { sessionAlive } from "../lib/session-lifecycle.ts";
|
|
68
73
|
import { registerLocalCommand } from "../lib/local-command.ts";
|
|
74
|
+
import { promptCommandText } from "../skill/invoke.ts";
|
|
75
|
+
import { panelUi } from "../lib/panel-focus.ts";
|
|
69
76
|
|
|
70
|
-
const run = promisify(execFile);
|
|
71
|
-
const SHELL_TIMEOUT_MS = 30_000;
|
|
72
77
|
|
|
73
78
|
// The panel is a bounded dock like Claude Code's /plugins, NOT a full-screen
|
|
74
79
|
// takeover: it swaps the editor container and leaves the transcript visible
|
|
@@ -76,36 +81,6 @@ const SHELL_TIMEOUT_MS = 30_000;
|
|
|
76
81
|
// terminals still shrink to fit. ~6 discover rows show at this cap.
|
|
77
82
|
const PANEL_MAX_HEIGHT = 22;
|
|
78
83
|
|
|
79
|
-
async function expandTemplate(body: string, args: string, cwd: string, persist?: { dir: string; name: string }): Promise<string> {
|
|
80
|
-
const withArgs = substituteArguments(body, args);
|
|
81
|
-
const commands = [...new Set(findShellPlaceholders(withArgs))];
|
|
82
|
-
if (commands.length === 0) return withArgs;
|
|
83
|
-
|
|
84
|
-
const outputs = new Map<string, string>();
|
|
85
|
-
await Promise.all(
|
|
86
|
-
commands.map(async (command, index) => {
|
|
87
|
-
let text: string;
|
|
88
|
-
try {
|
|
89
|
-
const { stdout, stderr } = await run(command, {
|
|
90
|
-
cwd,
|
|
91
|
-
shell: true,
|
|
92
|
-
timeout: SHELL_TIMEOUT_MS,
|
|
93
|
-
maxBuffer: 2 * 1024 * 1024,
|
|
94
|
-
});
|
|
95
|
-
text = (stdout || stderr || "").trim();
|
|
96
|
-
} catch (error) {
|
|
97
|
-
const detail = error as { stdout?: string; stderr?: string; message?: string };
|
|
98
|
-
text = (detail.stdout || detail.stderr || detail.message || "command failed").trim();
|
|
99
|
-
}
|
|
100
|
-
// A `!`command's output (a `git diff` of a large change) lands in the
|
|
101
|
-
// user turn bounded only by maxBuffer — persist it past the cap so the
|
|
102
|
-
// model gets a preview plus the path, never a wall of text (review L3).
|
|
103
|
-
outputs.set(command, persist ? persistIfLarge(text, { dir: persist.dir, id: `plugin-cmd-${persist.name}-${index}` }) : text);
|
|
104
|
-
}),
|
|
105
|
-
);
|
|
106
|
-
return replaceShellPlaceholders(withArgs, outputs);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
84
|
/** An empty discovery result — the fallback when a scan throws. */
|
|
110
85
|
function emptyDiscovered(): DiscoveredPlugins {
|
|
111
86
|
return {
|
|
@@ -136,6 +111,9 @@ function safeDiscover(roots: ReturnType<typeof defaultDiscoverRoots>): Discovere
|
|
|
136
111
|
}
|
|
137
112
|
|
|
138
113
|
export default function pluginsExtension(pi: ExtensionAPI) {
|
|
114
|
+
const sendUserMessage = createUserMessageSender(pi);
|
|
115
|
+
const getPermissionBridge = watchPermissionBridge(pi);
|
|
116
|
+
const workCwd = followWorkCwd(pi.events);
|
|
139
117
|
// False once the session is replaced: a panel action still awaiting then
|
|
140
118
|
// throws on its first pi.* call, which is expected and not an error to show.
|
|
141
119
|
const alive = sessionAlive(pi);
|
|
@@ -155,7 +133,7 @@ export default function pluginsExtension(pi: ExtensionAPI) {
|
|
|
155
133
|
plugins.plugins.find((p) => p.name === command.plugin);
|
|
156
134
|
if (!plugin) continue;
|
|
157
135
|
registeredCommands.add(command.name);
|
|
158
|
-
registerPluginCommand(pi, plugin, command.name, command.path);
|
|
136
|
+
registerPluginCommand(pi, plugin, command.name, command.path, sendUserMessage, getPermissionBridge, workCwd);
|
|
159
137
|
}
|
|
160
138
|
};
|
|
161
139
|
|
|
@@ -183,7 +161,7 @@ export default function pluginsExtension(pi: ExtensionAPI) {
|
|
|
183
161
|
panelOpen = true;
|
|
184
162
|
let restartNotice = false;
|
|
185
163
|
try {
|
|
186
|
-
await ctx.ui.custom<null>((tui, theme, _keybindings, done) => {
|
|
164
|
+
await panelUi(pi.events, ctx.ui).custom<null>((tui, theme, _keybindings, done) => {
|
|
187
165
|
const paint: PanelPaint = {
|
|
188
166
|
fg: safeThemePaint(theme),
|
|
189
167
|
bold: safeThemeBold(theme),
|
|
@@ -522,10 +500,11 @@ export default function pluginsExtension(pi: ExtensionAPI) {
|
|
|
522
500
|
registerLocalCommand(pi, "plugins", {
|
|
523
501
|
description: "Browse, install, and manage Claude Code-compatible plugins and marketplaces",
|
|
524
502
|
handler: async (args, ctx) => {
|
|
525
|
-
if (ctx
|
|
503
|
+
if (canShowCustomUi(ctx)) {
|
|
526
504
|
await openPanel(ctx);
|
|
527
505
|
return;
|
|
528
506
|
}
|
|
507
|
+
notifyRpcReadOnly(ctx, "/plugins", "install or manage plugins and marketplaces");
|
|
529
508
|
// Non-interactive fallback: the old text listing.
|
|
530
509
|
if (discovered.plugins.length === 0) {
|
|
531
510
|
ctx.ui.notify("No plugins installed.", "info");
|
|
@@ -547,34 +526,73 @@ export default function pluginsExtension(pi: ExtensionAPI) {
|
|
|
547
526
|
});
|
|
548
527
|
}
|
|
549
528
|
|
|
550
|
-
function registerPluginCommand(pi: ExtensionAPI, plugin: Plugin, name: string, path: string): void {
|
|
529
|
+
function registerPluginCommand(pi: ExtensionAPI, plugin: Plugin, name: string, path: string, sendUserMessage: ReturnType<typeof createUserMessageSender>, getPermissionBridge: ReturnType<typeof watchPermissionBridge>, workCwd: (sessionCwd: string) => string): void {
|
|
551
530
|
let description = `Command from the ${plugin.name} plugin`;
|
|
552
|
-
let argumentHint:
|
|
531
|
+
let argumentHint: CommandHint | undefined;
|
|
553
532
|
try {
|
|
554
533
|
const { frontmatter } = parseFrontmatterLoosely(readFileSync(path, "utf-8")) as {
|
|
555
534
|
frontmatter?: Record<string, unknown>;
|
|
556
535
|
};
|
|
557
536
|
if (typeof frontmatter?.description === "string") description = frontmatter.description;
|
|
558
|
-
|
|
537
|
+
argumentHint = frontmatterCommandHint(frontmatter);
|
|
559
538
|
} catch {
|
|
560
539
|
// Fall back to the generic description.
|
|
561
540
|
}
|
|
562
541
|
|
|
542
|
+
if (argumentHint) announceArgumentHint(pi, name, argumentHint);
|
|
563
543
|
pi.registerCommand(name, {
|
|
564
|
-
description: argumentHint ? `${description} (${argumentHint})` : description,
|
|
544
|
+
description: argumentHint?.hint ? `${description} (${argumentHint.hint})` : description,
|
|
565
545
|
handler: async (args, ctx) => {
|
|
546
|
+
// Registered commands cannot be removed from pi. Resolve their live
|
|
547
|
+
// owner and path on invocation, after toggles, updates or a cwd change.
|
|
548
|
+
const current = safeDiscover(defaultDiscoverRoots(getAgentDir(), ctx.cwd));
|
|
549
|
+
const command = current.commands.find((entry) => entry.name === name);
|
|
550
|
+
const plugin = command && current.enabledPlugins.find((entry) =>
|
|
551
|
+
entry.name === command.plugin && dirname(command.path) === join(entry.path, "commands"),
|
|
552
|
+
);
|
|
553
|
+
if (!command || !plugin) {
|
|
554
|
+
notifyOrPrint(ctx, `/${name} is unavailable in this project. Enable or reinstall its plugin in /plugins, then retry.`, "error");
|
|
555
|
+
if (!sessionOutlivesTurn(ctx.mode)) pi.events.emit(ONE_SHOT_COMMAND_FAILED_CHANNEL, {});
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const path = command.path;
|
|
566
559
|
let body: string;
|
|
567
560
|
try {
|
|
568
561
|
const parsed = parseFrontmatterLoosely(readFileSync(path, "utf-8")) as { body: string };
|
|
569
562
|
body = parsed.body;
|
|
570
563
|
} catch (error) {
|
|
571
|
-
ctx
|
|
564
|
+
notifyOrPrint(ctx, `Could not read ${path}: ${(error as Error).message}. Restore the command file or reinstall the ${plugin.name} plugin, then retry.`, "error");
|
|
565
|
+
// A one-shot run must not exit 0 for a command that never ran (exit/index.ts).
|
|
566
|
+
if (!sessionOutlivesTurn(ctx.mode)) pi.events.emit(ONE_SHOT_COMMAND_FAILED_CHANNEL, {});
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
// A `!`command's output (a `git diff` of a large change) lands in the
|
|
570
|
+
// user turn: persist it past the cap so the model gets a preview plus
|
|
571
|
+
// the path, never a wall of text (review L3).
|
|
572
|
+
const dir = sessionResultsDir(ctx);
|
|
573
|
+
const runId = randomUUID();
|
|
574
|
+
let expanded: string;
|
|
575
|
+
try {
|
|
576
|
+
// Placeholders run where the session works: an entered worktree, not the checkout it left.
|
|
577
|
+
expanded = await expandTemplate(body, args, workCwd(ctx.cwd), {
|
|
578
|
+
// A unique id per run: the directory is shared across sessions and processes.
|
|
579
|
+
persist: (text, index) => persistIfLarge(text, { dir, id: `plugin-cmd-${name}-${runId}-${index}` }),
|
|
580
|
+
run: async (command, cwd) => {
|
|
581
|
+
const decide = getPermissionBridge();
|
|
582
|
+
if (!decide) throw new Error("The permission gate is unavailable. Load the permissions extension before running shell placeholders.");
|
|
583
|
+
const decision = await decide({ toolName: "bash", input: { command }, cwd, model: ctx.model });
|
|
584
|
+
if (decision?.block) throw new Error(`Shell placeholder blocked: ${decision.reason}`);
|
|
585
|
+
return runPlaceholderCommand(command, cwd);
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
} catch (error) {
|
|
589
|
+
notifyOrPrint(ctx, `Could not expand /${name}: ${(error as Error).message}`, "error");
|
|
590
|
+
if (!sessionOutlivesTurn(ctx.mode)) pi.events.emit(ONE_SHOT_COMMAND_FAILED_CHANNEL, {});
|
|
572
591
|
return;
|
|
573
592
|
}
|
|
574
|
-
const expanded = await expandTemplate(body, args, ctx.cwd, { dir: sessionResultsDir(ctx), name });
|
|
575
593
|
recordUsage(pluginRoot(getAgentDir()), "command", name);
|
|
576
594
|
// Deliver as a user turn, which is how Claude Code runs a command template.
|
|
577
|
-
|
|
595
|
+
await sendUserMessage(ctx, promptCommandText(name, args.trim(), expanded));
|
|
578
596
|
},
|
|
579
597
|
});
|
|
580
598
|
}
|
|
@@ -18,6 +18,7 @@ import { gitClone, gitHeadSha } from "../marketplace/git.ts";
|
|
|
18
18
|
import type { MarketplaceEntry } from "../marketplace/types.ts";
|
|
19
19
|
import { addInstalledPlugin, installedEntry, removeInstalledPlugin } from "./registry.ts";
|
|
20
20
|
import { pluginCacheDir, versionedCachePath, withinBase } from "./paths.ts";
|
|
21
|
+
import { loadInstalledPlugins, validPluginName } from "../../lib/plugins.ts";
|
|
21
22
|
|
|
22
23
|
export interface InstallResult {
|
|
23
24
|
id: string;
|
|
@@ -82,6 +83,7 @@ export async function installPlugin(
|
|
|
82
83
|
contentRoot: string | undefined,
|
|
83
84
|
): Promise<InstallResult> {
|
|
84
85
|
const id = `${entry.name}@${marketplaceName}`;
|
|
86
|
+
if (validPluginName(entry.name) !== entry.name) throw new Error(`${id}: invalid plugin name — refusing to install`);
|
|
85
87
|
const raw = entry.raw;
|
|
86
88
|
if (raw.dependencies !== undefined) {
|
|
87
89
|
throw new Error(`${id}: plugin dependencies are not supported yet — install the dependencies manually first`);
|
|
@@ -99,6 +101,9 @@ export async function installPlugin(
|
|
|
99
101
|
}
|
|
100
102
|
sourceDir = resolve(contentRoot, source);
|
|
101
103
|
if (!existsSync(sourceDir)) throw new Error(`${id}: source path "${source}" does not exist in the marketplace`);
|
|
104
|
+
if (!withinBase(realpathSync(contentRoot), realpathSync(sourceDir))) {
|
|
105
|
+
throw new Error(`${id}: source path "${source}" escapes the marketplace directory through a symlink — refusing to install`);
|
|
106
|
+
}
|
|
102
107
|
} else if (source.source === "github" || source.source === "url" || source.source === "git-subdir") {
|
|
103
108
|
if (source.sha) {
|
|
104
109
|
throw new Error(`${id}: commit-sha pinning is not supported yet — use a ref (branch/tag) instead`);
|
|
@@ -121,6 +126,10 @@ export async function installPlugin(
|
|
|
121
126
|
rmSync(temp, { recursive: true, force: true });
|
|
122
127
|
throw new Error(`${id}: "${source.source === "git-subdir" ? source.path : url}" has no content at the expected path`);
|
|
123
128
|
}
|
|
129
|
+
if (source.source === "git-subdir" && !withinBase(realpathSync(cloneDir), realpathSync(sourceDir))) {
|
|
130
|
+
rmSync(temp, { recursive: true, force: true });
|
|
131
|
+
throw new Error(`${id}: git-subdir path "${source.path}" escapes the repository through a symlink — refusing to install`);
|
|
132
|
+
}
|
|
124
133
|
} else {
|
|
125
134
|
throw new Error(`${id}: source kind "${(source as { source: string }).source}" is not supported`);
|
|
126
135
|
}
|
|
@@ -137,6 +146,13 @@ export async function installPlugin(
|
|
|
137
146
|
if (!withinBase(pluginCacheBase, installPath)) {
|
|
138
147
|
throw new Error(`${id}: computed install path "${installPath}" escapes the plugin cache directory — refusing to install`);
|
|
139
148
|
}
|
|
149
|
+
// Sanitization is lossy (a.b and a-b share a cache segment). Never
|
|
150
|
+
// replace files that another registered plugin still owns.
|
|
151
|
+
if (existsSync(installPath)) {
|
|
152
|
+
const target = realpathSync(installPath);
|
|
153
|
+
const owner = loadInstalledPlugins(root).find((plugin) => plugin.id !== id && realpathSync(plugin.path) === target);
|
|
154
|
+
if (owner) throw new Error(`${id}: cache path "${installPath}" is already owned by ${owner.id} — refusing to overwrite it`);
|
|
155
|
+
}
|
|
140
156
|
copyIntoCache(sourceDir, installPath);
|
|
141
157
|
const now = new Date().toISOString();
|
|
142
158
|
addInstalledPlugin(root, id, {
|
|
@@ -46,9 +46,15 @@ function cloneUrl(source: MarketplaceSource): string | undefined {
|
|
|
46
46
|
const FETCH_TIMEOUT_MS = 10_000;
|
|
47
47
|
|
|
48
48
|
async function fetchManifestJson(url: string): Promise<unknown> {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
return fetchWithTimeout(
|
|
50
|
+
url,
|
|
51
|
+
FETCH_TIMEOUT_MS,
|
|
52
|
+
async (response) => {
|
|
53
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
54
|
+
return (await response.json()) as unknown;
|
|
55
|
+
},
|
|
56
|
+
{ init: { headers: { "user-agent": "one-code-plugins" } } },
|
|
57
|
+
);
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
/**
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* entry never blanks the rest of the marketplace.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { validPluginName } from "../../lib/plugins.ts";
|
|
12
|
+
|
|
11
13
|
export type MarketplaceSource =
|
|
12
14
|
| { source: "github"; repo: string; ref?: string }
|
|
13
15
|
| { source: "git"; url: string; ref?: string }
|
|
@@ -81,7 +83,7 @@ export function parseMarketplaceManifest(raw: unknown): { manifest?: Marketplace
|
|
|
81
83
|
|
|
82
84
|
const plugins: MarketplaceEntry[] = [];
|
|
83
85
|
for (const [index, entry] of raw.plugins.entries()) {
|
|
84
|
-
if (!isRecord(entry) || typeof entry.name !== "string" || entry.name
|
|
86
|
+
if (!isRecord(entry) || typeof entry.name !== "string" || validPluginName(entry.name) !== entry.name) {
|
|
85
87
|
errors.push(`plugins[${index}]: missing or invalid "name" — entry skipped`);
|
|
86
88
|
continue;
|
|
87
89
|
}
|