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
package/extensions/mcp/index.ts
CHANGED
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import os from "node:os";
|
|
19
|
-
import {
|
|
19
|
+
import { isDeepStrictEqual } from "node:util";
|
|
20
|
+
import { CONFIG_DIR_NAME, getAgentDir, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
20
21
|
import { Type } from "typebox";
|
|
21
22
|
import { DEFER_CHANNEL } from "../lib/deferred.ts";
|
|
22
23
|
import { MCP_TOOLS_CHANNEL } from "../lib/mcp-share.ts";
|
|
23
24
|
import { persistIfLarge, sessionResultsDir } from "../lib/persisted-output.ts";
|
|
25
|
+
import { emptyToolResultContent } from "../lib/empty-tool-result.ts";
|
|
24
26
|
import { ccToolRenderers } from "../lib/tui-render.ts";
|
|
25
27
|
import {
|
|
26
28
|
callTool,
|
|
@@ -34,7 +36,7 @@ import {
|
|
|
34
36
|
readResource,
|
|
35
37
|
readResourceDir,
|
|
36
38
|
} from "./client.ts";
|
|
37
|
-
import { CONTEXT_ORDER, REMINDER_CHANNEL } from "../lib/reminders.ts";
|
|
39
|
+
import { CONTEXT_ORDER, REMINDER_CHANNEL, RENDER_CHANNEL, type RenderRequest } from "../lib/reminders.ts";
|
|
38
40
|
import { join } from "node:path";
|
|
39
41
|
import {
|
|
40
42
|
MCP_STATUS_CHANNEL,
|
|
@@ -47,14 +49,14 @@ import { readDisabledMcpServers, setMcpServerDisabled } from "../lib/mcp-overrid
|
|
|
47
49
|
import { boundedDockHeight, safeThemeBold, safeThemePaint, truncateLine } from "../lib/tui-render.ts";
|
|
48
50
|
import { authenticate as runOAuthFlow, silentProvider } from "./oauth/flow.ts";
|
|
49
51
|
import { hasStoredTokens } from "./oauth/store.ts";
|
|
50
|
-
import {
|
|
52
|
+
import { mcpChangeNotices, mcpDelta, mcpFacts, type McpSnapshot, mcpStartupNotices } from "./announce.ts";
|
|
51
53
|
import { decodeMcpKey } from "./panel/keys.ts";
|
|
52
54
|
import { type McpEntry, type McpEntryStatus } from "./panel/model.ts";
|
|
53
55
|
import { renderMcpPanel, type McpPaint } from "./panel/render.ts";
|
|
54
56
|
import { applyMcpKey, initialMcpState, type McpEffect } from "./panel/state.ts";
|
|
55
57
|
import { sessionAlive } from "../lib/session-lifecycle.ts";
|
|
56
|
-
import { loadServers, type McpServer } from "./config.ts";
|
|
57
|
-
import { approveMcpServers, persistApproval, projectRootOf } from "./trust.ts";
|
|
58
|
+
import { loadServers, type McpServer, noMcpServersMessage, piMcpConfigPaths, registeredMcpServers } from "./config.ts";
|
|
59
|
+
import { approveMcpServers, type McpTrustDeps, persistApproval, projectRootOf, reconnectRefusal } from "./trust.ts";
|
|
58
60
|
import {
|
|
59
61
|
capDescription,
|
|
60
62
|
describeContent,
|
|
@@ -65,6 +67,11 @@ import {
|
|
|
65
67
|
namespacedToolName,
|
|
66
68
|
} from "./schema.ts";
|
|
67
69
|
import { registerLocalCommand } from "../lib/local-command.ts";
|
|
70
|
+
import { canShowCustomUi, notifyRpcReadOnly } from "../lib/headless-output.ts";
|
|
71
|
+
import { claudeSourcesOn } from "../lib/config-mode.ts";
|
|
72
|
+
import { claudeJsonPath } from "../lib/paths.ts";
|
|
73
|
+
import { consentDialog, startupConsentReady } from "../lib/consent-dialogs.ts";
|
|
74
|
+
import { panelUi } from "../lib/panel-focus.ts";
|
|
68
75
|
|
|
69
76
|
/** Anthropic's hard limit on a tool name; a longer one fails the whole request. */
|
|
70
77
|
const MAX_TOOL_NAME_LENGTH = 128;
|
|
@@ -78,10 +85,13 @@ function unknownServerError(name: string) {
|
|
|
78
85
|
};
|
|
79
86
|
}
|
|
80
87
|
|
|
88
|
+
/** pi's own `mcp.json` files, read because our `/mcp` replaces pi 0.99's built-in MCP (config.ts). */
|
|
89
|
+
const piMcpConfigs = (cwd: string) => piMcpConfigPaths(getAgentDir(), cwd, CONFIG_DIR_NAME);
|
|
90
|
+
|
|
81
91
|
export default function mcpExtension(pi: ExtensionAPI) {
|
|
82
92
|
const connections = new Map<string, Connection>();
|
|
83
93
|
let failures: FailedConnection[] = [];
|
|
84
|
-
const registered = new
|
|
94
|
+
const registered = new Map<string, { server: string; tool: string }>();
|
|
85
95
|
let servers: McpServer[] = [];
|
|
86
96
|
// http servers that returned a 401 with no stored tokens — authNeeded via OAuth,
|
|
87
97
|
// distinct from a missing-env authNeeded (which Authenticate cannot fix).
|
|
@@ -93,18 +103,17 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
93
103
|
const withheldNames = new Map<string, string>();
|
|
94
104
|
// Config file paths contributed by plugins, so the panel can group them.
|
|
95
105
|
let pluginConfigPaths = new Set<string>();
|
|
106
|
+
// pi 0.99's registry of servers other extensions register; absent on older pi.
|
|
107
|
+
// Registered after the session started: our servers are read once per session.
|
|
108
|
+
pi.on("mcp_servers_change", (_event, ctx) => {
|
|
109
|
+
const text = "An extension changed its MCP servers; run /reload to connect the change.";
|
|
110
|
+
if (ctx.hasUI) ctx.ui.notify(text, "info");
|
|
111
|
+
else process.stderr.write(`${text}\n`);
|
|
112
|
+
});
|
|
96
113
|
const home = os.homedir();
|
|
97
114
|
// Live tool definitions, shared with in-process subagents so they reach MCP
|
|
98
115
|
// through these same open connections instead of connecting their own.
|
|
99
116
|
const sharedTools: ToolDefinition[] = [];
|
|
100
|
-
// Message 1 carries the MCP instructions and failed-servers reminders. Once a
|
|
101
|
-
// request has gone out it is part of every later request's cached prefix, so
|
|
102
|
-
// those blocks are frozen and later changes ride one-shot notices (./announce.ts).
|
|
103
|
-
let requestSent = false;
|
|
104
|
-
let announced = emptyAnnounced();
|
|
105
|
-
pi.on("context", () => {
|
|
106
|
-
requestSent = true;
|
|
107
|
-
});
|
|
108
117
|
|
|
109
118
|
/**
|
|
110
119
|
* Where oversized results are persisted — the session dir, matching Claude
|
|
@@ -114,6 +123,8 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
114
123
|
const resultsDir = (ctx: ExtensionContext | undefined): string => sessionResultsDir(ctx);
|
|
115
124
|
|
|
116
125
|
const registerToolsFor = (connection: Connection) => {
|
|
126
|
+
// Names other extensions own, read once per batch (getAllTools builds every definition).
|
|
127
|
+
let taken: Set<string> | undefined;
|
|
117
128
|
for (const tool of connection.tools) {
|
|
118
129
|
const name = namespacedToolName(connection.server.name, tool.name);
|
|
119
130
|
// Anthropic caps a tool name at 128 characters and rejects the whole
|
|
@@ -128,8 +139,12 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
128
139
|
});
|
|
129
140
|
continue;
|
|
130
141
|
}
|
|
131
|
-
|
|
132
|
-
|
|
142
|
+
const owner = registered.get(name);
|
|
143
|
+
// An unchanged tool on reconnect keeps its exact definition and loaded
|
|
144
|
+
// state. Only a different raw server/tool pair is a name collision.
|
|
145
|
+
if (owner?.server === connection.server.name && owner.tool === tool.name) continue;
|
|
146
|
+
if (owner || (taken ??= new Set(pi.getAllTools().map((existing) => existing.name))).has(name)) {
|
|
147
|
+
// Two tools sanitise to one name (`a.b` and `a_b`, or a server
|
|
133
148
|
// reconnecting under a stale registration). Skipping silently would
|
|
134
149
|
// make the second tool unreachable with no trace; record it where
|
|
135
150
|
// the connection summary reports it.
|
|
@@ -139,7 +154,7 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
139
154
|
});
|
|
140
155
|
continue;
|
|
141
156
|
}
|
|
142
|
-
registered.
|
|
157
|
+
registered.set(name, { server: connection.server.name, tool: tool.name });
|
|
143
158
|
|
|
144
159
|
const def: ToolDefinition = {
|
|
145
160
|
name,
|
|
@@ -147,7 +162,7 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
147
162
|
...ccToolRenderers(`${connection.server.name}: ${tool.name}`),
|
|
148
163
|
description: capDescription(tool.description) ?? `MCP tool "${tool.name}" from server "${connection.server.name}".`,
|
|
149
164
|
parameters: jsonSchemaToTypeBox(tool.inputSchema),
|
|
150
|
-
async execute(toolCallId, params,
|
|
165
|
+
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
151
166
|
const live = connections.get(connection.server.name);
|
|
152
167
|
if (!live) {
|
|
153
168
|
return {
|
|
@@ -156,17 +171,27 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
156
171
|
isError: true,
|
|
157
172
|
};
|
|
158
173
|
}
|
|
174
|
+
const current = live.tools.find((candidate) => candidate.name === tool.name);
|
|
175
|
+
if (!current || !isDeepStrictEqual(current.inputSchema, tool.inputSchema)) {
|
|
176
|
+
return {
|
|
177
|
+
content: [{ type: "text", text: !current
|
|
178
|
+
? `MCP server "${connection.server.name}" no longer exposes tool "${tool.name}".`
|
|
179
|
+
: `MCP tool "${name}" changed its input schema. Run /reload to load the new definition; this call was not sent.` }],
|
|
180
|
+
details: {} as Record<string, unknown>,
|
|
181
|
+
isError: true,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
159
184
|
try {
|
|
160
|
-
const result = await callTool(live, tool.name, (params ?? {}) as Record<string, unknown
|
|
161
|
-
const { text, images } = describeContent(result.content as McpContentBlock[] | undefined, connection.server.name);
|
|
185
|
+
const result = await callTool(live, tool.name, (params ?? {}) as Record<string, unknown>, signal);
|
|
186
|
+
const { text: contentText, images } = describeContent(result.content as McpContentBlock[] | undefined, connection.server.name);
|
|
187
|
+
const structuredText = result.structuredContent !== undefined ? JSON.stringify(result.structuredContent, null, 2) : "";
|
|
188
|
+
const text = [contentText, structuredText].filter(Boolean).join("\n");
|
|
189
|
+
const content = [
|
|
190
|
+
...(text ? [{ type: "text" as const, text: persistIfLarge(text, { dir: resultsDir(ctx), id: toolCallId }) }] : []),
|
|
191
|
+
...images.map((image) => ({ type: "image" as const, data: image.data, mimeType: image.mimeType })),
|
|
192
|
+
];
|
|
162
193
|
return {
|
|
163
|
-
content:
|
|
164
|
-
{
|
|
165
|
-
type: "text",
|
|
166
|
-
text: text ? persistIfLarge(text, { dir: resultsDir(ctx), id: toolCallId }) : "(no output)",
|
|
167
|
-
},
|
|
168
|
-
...images.map((image) => ({ type: "image" as const, data: image.data, mimeType: image.mimeType })),
|
|
169
|
-
],
|
|
194
|
+
content: emptyToolResultContent(content, name) ?? content,
|
|
170
195
|
details: { server: connection.server.name, tool: tool.name } as Record<string, unknown>,
|
|
171
196
|
isError: result.isError === true,
|
|
172
197
|
};
|
|
@@ -232,15 +257,38 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
232
257
|
});
|
|
233
258
|
emitInstructions();
|
|
234
259
|
};
|
|
260
|
+
let previousTools = connection.tools;
|
|
261
|
+
connection.onToolsChanged = (error) => {
|
|
262
|
+
if (!alive() || connection.closing || connections.get(server.name) !== connection) return;
|
|
263
|
+
if (error) {
|
|
264
|
+
failures.push({ server, error: error.message });
|
|
265
|
+
pi.events.emit(REMINDER_CHANNEL, { text: `MCP server "${server.name}" could not refresh its tool list: ${error.message}` });
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const unavailable = previousTools.filter((old) => {
|
|
269
|
+
const current = connection.tools.find((tool) => tool.name === old.name);
|
|
270
|
+
return !current || !isDeepStrictEqual(current.inputSchema, old.inputSchema);
|
|
271
|
+
});
|
|
272
|
+
previousTools = connection.tools;
|
|
273
|
+
registerToolsFor(connection);
|
|
274
|
+
if (unavailable.length > 0) {
|
|
275
|
+
pi.events.emit(REMINDER_CHANNEL, {
|
|
276
|
+
text: `MCP server "${server.name}" removed or changed these tools: ${unavailable.map((tool) => namespacedToolName(server.name, tool.name)).join(", ")}. Their old definitions cannot be used. Run /reload to refresh changed schemas.`,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
};
|
|
235
280
|
registerToolsFor(connection);
|
|
236
281
|
for (const warning of connection.warnings) failures.push({ server, error: warning });
|
|
237
|
-
if (connection.resources.length > 0) registerResourceTools();
|
|
282
|
+
if (connection.resources.length > 0 || connection.resourceTemplates?.length) registerResourceTools();
|
|
238
283
|
};
|
|
239
284
|
|
|
285
|
+
/** Servers with a connect in flight (startup, a reconnect, an authenticate): not dropped while it runs. */
|
|
286
|
+
const connectingNames = new Set<string>();
|
|
240
287
|
const connectOne = async (server: McpServer): Promise<void> => {
|
|
241
288
|
failures = failures.filter((f) => f.server.name !== server.name);
|
|
242
289
|
oauthNeeded.delete(server.name);
|
|
243
|
-
const provider = server.kind === "http" && hasStoredTokens(server
|
|
290
|
+
const provider = server.kind === "http" && hasStoredTokens(server) ? silentProvider(server) : undefined;
|
|
291
|
+
connectingNames.add(server.name);
|
|
244
292
|
try {
|
|
245
293
|
const connection = await connect(server, provider);
|
|
246
294
|
if (!alive()) {
|
|
@@ -252,6 +300,8 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
252
300
|
if (!alive()) return;
|
|
253
301
|
if (server.kind === "http" && isUnauthorized(error)) oauthNeeded.add(server.name);
|
|
254
302
|
else failures.push({ server, error: (error as Error).message });
|
|
303
|
+
} finally {
|
|
304
|
+
connectingNames.delete(server.name);
|
|
255
305
|
}
|
|
256
306
|
};
|
|
257
307
|
|
|
@@ -261,73 +311,94 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
261
311
|
* which servers failed — otherwise it concludes the tools do not exist or the
|
|
262
312
|
* user has no access instead of reporting a connection failure the user can
|
|
263
313
|
* fix (review M4). Re-run on every connect, close, reconnect, authenticate,
|
|
264
|
-
* enable and disable.
|
|
265
|
-
* message 1
|
|
266
|
-
*
|
|
314
|
+
* enable and disable. The two first-prepend blocks are live state: what an
|
|
315
|
+
* epoch's message 1 says. Inside an epoch the render step below announces
|
|
316
|
+
* only what changed since the epoch told it (./announce.ts).
|
|
267
317
|
*/
|
|
318
|
+
const liveSnapshot = (): McpSnapshot => ({
|
|
319
|
+
connected: [...connections.values()].map((c) => ({ name: c.server.name, instructions: c.instructions })),
|
|
320
|
+
failed: failures.filter((f) => !connections.has(f.server.name)).map((f) => ({ name: f.server.name, error: f.error })),
|
|
321
|
+
});
|
|
268
322
|
const emitInstructions = () => {
|
|
269
323
|
const live = [...connections.values()];
|
|
270
|
-
const snapshot
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
});
|
|
297
|
-
} else {
|
|
298
|
-
// No server has instructions anymore (e.g. the last one was disabled or
|
|
299
|
-
// disconnected) — drop the stale every-turn reminder so the model stops
|
|
300
|
-
// being told to use tools that are gone.
|
|
301
|
-
pi.events.emit(REMINDER_CHANNEL, { scope: "every-turn", key: "mcp-instructions", text: "", remove: true });
|
|
302
|
-
}
|
|
303
|
-
announced = announcedFrom(snapshot);
|
|
304
|
-
return;
|
|
324
|
+
const snapshot = liveSnapshot();
|
|
325
|
+
if (snapshot.failed.length === 0) {
|
|
326
|
+
pi.events.emit(REMINDER_CHANNEL, { key: "mcp-failures", text: "", remove: true });
|
|
327
|
+
} else {
|
|
328
|
+
pi.events.emit(REMINDER_CHANNEL, {
|
|
329
|
+
key: "mcp-failures",
|
|
330
|
+
text: mcpFailuresReminder(snapshot.failed),
|
|
331
|
+
placement: "first-prepend",
|
|
332
|
+
order: CONTEXT_ORDER.mcp + 1,
|
|
333
|
+
facts: mcpFacts({ connected: [], failed: snapshot.failed }),
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
const instructions = mcpInstructionsReminder(live);
|
|
337
|
+
if (instructions) {
|
|
338
|
+
pi.events.emit(REMINDER_CHANNEL, {
|
|
339
|
+
key: "mcp-instructions",
|
|
340
|
+
text: instructions,
|
|
341
|
+
placement: "first-prepend",
|
|
342
|
+
order: CONTEXT_ORDER.mcp,
|
|
343
|
+
facts: mcpFacts({ connected: snapshot.connected, failed: [] }),
|
|
344
|
+
});
|
|
345
|
+
} else {
|
|
346
|
+
// No server has instructions anymore (e.g. the last one was disabled or
|
|
347
|
+
// disconnected) — drop the stale block so an epoch does not tell the
|
|
348
|
+
// model to use tools that are gone.
|
|
349
|
+
pi.events.emit(REMINDER_CHANNEL, { key: "mcp-instructions", text: "", remove: true });
|
|
305
350
|
}
|
|
306
|
-
|
|
307
|
-
// Message 1 is frozen (rewriting it would re-cache the whole conversation):
|
|
308
|
-
// tell the model only what changed, as one-shots where it reads next.
|
|
309
|
-
const delta = mcpDelta(announced, snapshot);
|
|
310
|
-
for (const text of mcpChangeNotices(delta)) pi.events.emit(REMINDER_CHANNEL, { text });
|
|
311
|
-
applyMcpDelta(announced, delta);
|
|
312
351
|
};
|
|
352
|
+
// Inside an epoch, what changed since message 1 (or the last notice) rides
|
|
353
|
+
// the request's tail, decided when the request goes out.
|
|
354
|
+
pi.events.on(RENDER_CHANNEL, (data) => {
|
|
355
|
+
const request = data as RenderRequest;
|
|
356
|
+
if (request.epoch) return;
|
|
357
|
+
const delta = mcpDelta(request.toldBlocks, liveSnapshot(), !connectSettled, connectingNames);
|
|
358
|
+
const notices = mcpChangeNotices(delta);
|
|
359
|
+
notices.forEach((text, i) => request.blocks.push({ text, ...(i === 0 ? { facts: delta.facts } : {}) }));
|
|
360
|
+
});
|
|
313
361
|
|
|
314
362
|
/** Config files that exist but failed to parse (review M11) — shown at startup and in /mcp. */
|
|
315
363
|
let configErrors: string[] = [];
|
|
316
364
|
|
|
365
|
+
/** How the consent check asks, tells and records a "No", for startup and for Reconnect alike. */
|
|
366
|
+
const consentDeps = (ctx: ExtensionContext): McpTrustDeps => ({
|
|
367
|
+
hasUI: ctx.hasUI,
|
|
368
|
+
select: (title, options) => consentDialog(pi.events, (signal) => ctx.ui.select(title, options, { signal })),
|
|
369
|
+
notify: (message) => {
|
|
370
|
+
if (ctx.hasUI) ctx.ui.notify(message, "warning");
|
|
371
|
+
else process.stderr.write(`${message}\n`);
|
|
372
|
+
},
|
|
373
|
+
disable: (server) => {
|
|
374
|
+
setMcpServerDisabled(server.name, true, "project", ctx.cwd, home);
|
|
375
|
+
disabledNames.add(server.name);
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
317
379
|
const connectAll = async (ctx: ExtensionContext) => {
|
|
318
380
|
const plugins = discoverPlugins(defaultDiscoverRoots(getAgentDir(), ctx.cwd));
|
|
319
381
|
pluginConfigPaths = new Set(plugins.mcpConfigs);
|
|
320
382
|
configErrors = [];
|
|
383
|
+
const configNotes: string[] = [];
|
|
321
384
|
servers = loadServers(ctx.cwd, home, process.env, [...pluginConfigPaths], {
|
|
322
385
|
pluginNames: plugins.mcpConfigPlugins,
|
|
323
386
|
onError: (path, message) => configErrors.push(`${path}: ${message}`),
|
|
387
|
+
piConfigs: piMcpConfigs(ctx.cwd),
|
|
388
|
+
onNote: (message) => configNotes.push(message),
|
|
389
|
+
registered: registeredMcpServers(pi),
|
|
324
390
|
});
|
|
325
|
-
|
|
326
|
-
|
|
391
|
+
const configWarnings = [
|
|
392
|
+
...(configErrors.length > 0 ? [`MCP config could not be parsed (ignored): ${configErrors.join("; ")}`] : []),
|
|
393
|
+
...configNotes,
|
|
394
|
+
];
|
|
395
|
+
for (const text of configWarnings) {
|
|
327
396
|
if (ctx.hasUI) ctx.ui.notify(text, "warning");
|
|
328
397
|
else process.stderr.write(`${text}\n`);
|
|
329
398
|
}
|
|
330
399
|
disabledNames = readDisabledMcpServers(ctx.cwd, home);
|
|
400
|
+
// With no server configured there is nothing to connect; a resumed epoch
|
|
401
|
+
// that told the model about one hears that it dropped at render time.
|
|
331
402
|
if (servers.length === 0) return;
|
|
332
403
|
|
|
333
404
|
// Disabled servers are listed but never connected; a server with an unset
|
|
@@ -336,19 +407,13 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
336
407
|
// server needs the user's consent first (trust.ts — a cloned repo must not
|
|
337
408
|
// run commands at startup). Everything else connects.
|
|
338
409
|
const candidates = servers.filter((server) => !disabledNames.has(server.name) && !server.missingEnv?.length);
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
disable: (server) => {
|
|
347
|
-
setMcpServerDisabled(server.name, true, "project", ctx.cwd, home);
|
|
348
|
-
disabledNames.add(server.name);
|
|
349
|
-
},
|
|
350
|
-
});
|
|
351
|
-
if (!alive()) return;
|
|
410
|
+
// Reserve the modal position before the asynchronous policy read, so the
|
|
411
|
+
// external-includes dialog cannot jump ahead of MCP's startup consent.
|
|
412
|
+
const consent = await consentDialog(pi.events, (signal) => approveMcpServers(candidates, pluginConfigPaths, ctx.cwd, home, {
|
|
413
|
+
...consentDeps(ctx),
|
|
414
|
+
select: (title, options) => ctx.ui.select(title, options, { signal }),
|
|
415
|
+
}));
|
|
416
|
+
if (!consent || !alive()) return;
|
|
352
417
|
withheldNames.clear();
|
|
353
418
|
for (const { server, reason } of consent.withheld) {
|
|
354
419
|
withheldNames.set(
|
|
@@ -364,16 +429,15 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
364
429
|
|
|
365
430
|
if (!alive()) return;
|
|
366
431
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
);
|
|
432
|
+
// Warnings from servers that did connect are shown in /mcp, not as failures.
|
|
433
|
+
const failed = failures.filter((f) => !connections.has(f.server.name));
|
|
434
|
+
if (ctx.hasUI) {
|
|
435
|
+
// Needs auth: an OAuth sign-in, or a credential variable that is not set (never attempted).
|
|
436
|
+
const needsAuth = new Set([...oauthNeeded, ...servers.filter((server) => !disabledNames.has(server.name) && server.missingEnv?.length).map((server) => server.name)]).size;
|
|
437
|
+
for (const text of mcpStartupNotices(new Set(failed.map((f) => f.server.name)).size, needsAuth)) ctx.ui.notify(text, "warning");
|
|
438
|
+
} else if (failed.length > 0) {
|
|
439
|
+
// Headless runs have no /mcp, so they keep each server's error on stderr.
|
|
440
|
+
process.stderr.write(`${failed.map((f) => `MCP server "${f.server.name}" failed: ${f.error}`).join("\n")}\n`);
|
|
377
441
|
}
|
|
378
442
|
|
|
379
443
|
emitInstructions();
|
|
@@ -384,12 +448,17 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
384
448
|
if (resourceToolsRegistered) return;
|
|
385
449
|
resourceToolsRegistered = true;
|
|
386
450
|
|
|
387
|
-
|
|
451
|
+
const registerResourceTool: ExtensionAPI["registerTool"] = (definition) => {
|
|
452
|
+
pi.registerTool(definition);
|
|
453
|
+
sharedTools.push(definition as ToolDefinition);
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
registerResourceTool({
|
|
388
457
|
name: "list_mcp_resources",
|
|
389
458
|
label: "MCP Resources",
|
|
390
459
|
...ccToolRenderers("MCP Resources"),
|
|
391
460
|
description:
|
|
392
|
-
"List resources exposed by connected MCP servers. Resources are readable documents or data the server offers, addressed by uri.",
|
|
461
|
+
"List resources and URI templates exposed by connected MCP servers. Resources are readable documents or data the server offers, addressed by uri. Substitute template variables to construct a resource uri.",
|
|
393
462
|
parameters: Type.Object({
|
|
394
463
|
server: Type.Optional(Type.String({ description: "Limit to one server by name" })),
|
|
395
464
|
}),
|
|
@@ -417,12 +486,15 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
417
486
|
`${connection.server.name} ${resource.uri}${resource.name ? ` — ${resource.name}` : ""}${resource.description ? `: ${resource.description}` : ""}`,
|
|
418
487
|
);
|
|
419
488
|
}
|
|
489
|
+
for (const template of connection.resourceTemplates ?? []) {
|
|
490
|
+
lines.push(`${connection.server.name} ${template.uriTemplate} (URI template)${template.name ? ` — ${template.name}` : ""}${template.description ? `: ${template.description}` : ""}`);
|
|
491
|
+
}
|
|
420
492
|
}
|
|
421
493
|
return {
|
|
422
494
|
content: [
|
|
423
495
|
{
|
|
424
496
|
type: "text",
|
|
425
|
-
text: lines.length ? persistIfLarge(lines.join("\n"), { dir: resultsDir(ctx), id: toolCallId }) : "No MCP resources
|
|
497
|
+
text: lines.length ? persistIfLarge(lines.join("\n"), { dir: resultsDir(ctx), id: toolCallId }) : "No resources found. MCP servers may still provide tools even if they have no resources.",
|
|
426
498
|
},
|
|
427
499
|
],
|
|
428
500
|
details: { count: lines.length },
|
|
@@ -430,7 +502,7 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
430
502
|
},
|
|
431
503
|
});
|
|
432
504
|
|
|
433
|
-
|
|
505
|
+
registerResourceTool({
|
|
434
506
|
name: "read_mcp_resource",
|
|
435
507
|
label: "Read MCP Resource",
|
|
436
508
|
...ccToolRenderers("Read MCP Resource"),
|
|
@@ -439,17 +511,17 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
439
511
|
server: Type.String({ description: "Server name that owns the resource" }),
|
|
440
512
|
uri: Type.String({ description: "Resource uri" }),
|
|
441
513
|
}),
|
|
442
|
-
async execute(toolCallId, params,
|
|
514
|
+
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
443
515
|
const connection = connections.get(params.server);
|
|
444
516
|
if (!connection) return unknownServerError(params.server);
|
|
445
517
|
try {
|
|
446
|
-
const result = await readResource(connection, params.uri);
|
|
447
|
-
const text = describeResourceContents(result.contents as McpResourceContents[] | undefined);
|
|
518
|
+
const result = await readResource(connection, params.uri, signal);
|
|
519
|
+
const text = describeResourceContents(result.contents as McpResourceContents[] | undefined) || JSON.stringify({ contents: result.contents ?? [] });
|
|
448
520
|
return {
|
|
449
521
|
content: [
|
|
450
522
|
{
|
|
451
523
|
type: "text",
|
|
452
|
-
text:
|
|
524
|
+
text: persistIfLarge(text, { dir: resultsDir(ctx), id: toolCallId }),
|
|
453
525
|
},
|
|
454
526
|
],
|
|
455
527
|
details: { server: params.server, uri: params.uri },
|
|
@@ -464,7 +536,7 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
464
536
|
},
|
|
465
537
|
});
|
|
466
538
|
|
|
467
|
-
|
|
539
|
+
registerResourceTool({
|
|
468
540
|
name: "read_mcp_resource_dir",
|
|
469
541
|
label: "List MCP Resource Directory",
|
|
470
542
|
...ccToolRenderers("List MCP Resource Directory"),
|
|
@@ -474,18 +546,18 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
474
546
|
server: Type.String({ description: "Server name that owns the directory" }),
|
|
475
547
|
uri: Type.String({ description: "The directory resource uri to list" }),
|
|
476
548
|
}),
|
|
477
|
-
async execute(toolCallId, params,
|
|
549
|
+
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
478
550
|
const connection = connections.get(params.server);
|
|
479
551
|
if (!connection) return unknownServerError(params.server);
|
|
480
552
|
try {
|
|
481
|
-
const result = await readResourceDir(connection, params.uri);
|
|
553
|
+
const result = await readResourceDir(connection, params.uri, signal);
|
|
482
554
|
const entries = (result.resources ?? result.entries ?? []) as Array<{ uri?: string; name?: string; mimeType?: string }>;
|
|
483
555
|
const lines = entries.map((e) => `${e.uri ?? "(no uri)"}${e.name ? ` — ${e.name}` : ""}${e.mimeType ? ` (${e.mimeType})` : ""}`);
|
|
484
556
|
return {
|
|
485
557
|
content: [
|
|
486
558
|
{
|
|
487
559
|
type: "text",
|
|
488
|
-
text: lines.length ? persistIfLarge(lines.join("\n"), { dir: resultsDir(ctx), id: toolCallId }) :
|
|
560
|
+
text: lines.length ? persistIfLarge(lines.join("\n"), { dir: resultsDir(ctx), id: toolCallId }) : 'Directory is empty.\n{"resources":[]}',
|
|
489
561
|
},
|
|
490
562
|
],
|
|
491
563
|
details: { server: params.server, uri: params.uri },
|
|
@@ -503,13 +575,10 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
503
575
|
for (const name of ["list_mcp_resources", "read_mcp_resource", "read_mcp_resource_dir"]) {
|
|
504
576
|
pi.events.emit(DEFER_CHANNEL, { name, keywords: ["mcp", "resource", "document", "uri", "directory"] });
|
|
505
577
|
}
|
|
578
|
+
pi.events.emit(MCP_TOOLS_CHANNEL, { tools: [...sharedTools] });
|
|
506
579
|
};
|
|
507
580
|
|
|
508
581
|
pi.on("session_start", async (_event, ctx) => {
|
|
509
|
-
// A new session (/clear, resume in a new process) has no cached prefix yet:
|
|
510
|
-
// its message-1 blocks are written fresh (same reset as tool-search).
|
|
511
|
-
requestSent = false;
|
|
512
|
-
announced = emptyAnnounced();
|
|
513
582
|
// pi awaits session_start handlers serially before the prompt opens, and
|
|
514
583
|
// remote servers take seconds to answer — awaiting here was the entire
|
|
515
584
|
// "slow startup" (4.9s → 0.24s measured, findings §15). In the interactive
|
|
@@ -524,6 +593,9 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
524
593
|
// `failures`, so this only catches setup bugs — surface them loud.
|
|
525
594
|
if (ctx.hasUI) ctx.ui.notify(`MCP startup failed: ${(error as Error).message}`, "error");
|
|
526
595
|
});
|
|
596
|
+
// The external-includes question follows the startup hooks/MCP consent,
|
|
597
|
+
// but does not wait for slow server connections.
|
|
598
|
+
startupConsentReady(pi.events);
|
|
527
599
|
connecting.finally(() => {
|
|
528
600
|
connectSettled = true;
|
|
529
601
|
// The session may be gone by now (/clear while a remote server was still
|
|
@@ -600,12 +672,18 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
600
672
|
if (pluginConfigPaths.has(server.source)) {
|
|
601
673
|
return { rank: 3, group: "Plugin MCPs", configLocation: "Plugin configuration", scope: "project" };
|
|
602
674
|
}
|
|
603
|
-
if (server.source ===
|
|
675
|
+
if (server.source === claudeJsonPath(home) || server.piOrigin === "user") {
|
|
604
676
|
return { rank: 0, group: `User MCPs (${shorten(server.source)})`, configLocation: shorten(server.source), scope: "user" };
|
|
605
677
|
}
|
|
606
678
|
if (server.source.endsWith(join(".claude", "settings.local.json"))) {
|
|
607
679
|
return { rank: 2, group: "Project MCPs (.claude/settings.local.json)", configLocation: shorten(server.source), scope: "project" };
|
|
608
680
|
}
|
|
681
|
+
if (server.piOrigin === "extension") {
|
|
682
|
+
return { rank: 3, group: "Extension MCPs", configLocation: shorten(server.source), scope: "user" };
|
|
683
|
+
}
|
|
684
|
+
if (server.piOrigin === "project") {
|
|
685
|
+
return { rank: 1, group: `Project MCPs (${join(CONFIG_DIR_NAME, "mcp.json")})`, configLocation: shorten(server.source), scope: "project" };
|
|
686
|
+
}
|
|
609
687
|
return { rank: 1, group: `Project MCPs (${shorten(server.source)})`, configLocation: shorten(server.source), scope: "project" };
|
|
610
688
|
};
|
|
611
689
|
|
|
@@ -626,7 +704,7 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
626
704
|
// honestly from whether tokens are stored — not just "failed http".
|
|
627
705
|
const authState =
|
|
628
706
|
status === "failed" && server.kind === "http"
|
|
629
|
-
? hasStoredTokens(server
|
|
707
|
+
? hasStoredTokens(server)
|
|
630
708
|
? ("authenticated" as const)
|
|
631
709
|
: ("notAuthenticated" as const)
|
|
632
710
|
: undefined;
|
|
@@ -656,7 +734,7 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
656
734
|
const MCP_PANEL_MAX_HEIGHT = 22;
|
|
657
735
|
|
|
658
736
|
const openMcpPanel = async (ctx: ExtensionContext): Promise<void> => {
|
|
659
|
-
await ctx.ui.custom<null>((tui, theme, _keybindings, done) => {
|
|
737
|
+
await panelUi(pi.events, ctx.ui).custom<null>((tui, theme, _keybindings, done) => {
|
|
660
738
|
const paint: McpPaint = { fg: safeThemePaint(theme), bold: safeThemeBold(theme) };
|
|
661
739
|
const state = initialMcpState();
|
|
662
740
|
let notices: string[] = [];
|
|
@@ -692,7 +770,24 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
692
770
|
const runReconnect = async (entry: McpEntry) => {
|
|
693
771
|
const server = servers.find((s) => s.name === entry.name);
|
|
694
772
|
if (!server || busy.has(entry.name)) return;
|
|
773
|
+
const refusal = reconnectRefusal(server);
|
|
774
|
+
if (refusal) {
|
|
775
|
+
notices = [refusal];
|
|
776
|
+
syncRepaint();
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
695
779
|
busy.add(entry.name);
|
|
780
|
+
// Reconnect spawns the command again, so a project server needs the
|
|
781
|
+
// same consent startup asked for (a stored or session approval
|
|
782
|
+
// passes without a prompt).
|
|
783
|
+
const consent = await approveMcpServers([server], pluginConfigPaths, ctx.cwd, home, consentDeps(ctx));
|
|
784
|
+
if (consent.approved.length === 0) {
|
|
785
|
+
busy.delete(entry.name);
|
|
786
|
+
if (!alive()) return;
|
|
787
|
+
notices = [`"${entry.name}" was not approved, so it was not reconnected.`];
|
|
788
|
+
syncRepaint();
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
696
791
|
notices = [`Reconnecting to "${entry.name}"…`];
|
|
697
792
|
syncRepaint();
|
|
698
793
|
const existing = connections.get(entry.name);
|
|
@@ -844,16 +939,19 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
844
939
|
};
|
|
845
940
|
|
|
846
941
|
registerLocalCommand(pi, "mcp", {
|
|
942
|
+
reportsResult: true,
|
|
847
943
|
description: "Manage MCP servers (status, reconnect, authenticate, enable/disable)",
|
|
848
944
|
handler: async (args, ctx) => {
|
|
849
945
|
if (servers.length === 0) {
|
|
850
|
-
|
|
851
|
-
|
|
946
|
+
const stdout = noMcpServersMessage(claudeSourcesOn());
|
|
947
|
+
ctx.ui.notify(stdout, "info");
|
|
948
|
+
return stdout;
|
|
852
949
|
}
|
|
853
|
-
if (ctx
|
|
950
|
+
if (canShowCustomUi(ctx)) {
|
|
854
951
|
await openMcpPanel(ctx);
|
|
855
|
-
return;
|
|
952
|
+
return "MCP dialog dismissed";
|
|
856
953
|
}
|
|
954
|
+
const readOnly = notifyRpcReadOnly(ctx, "/mcp", "reconnect, authenticate, enable or disable servers");
|
|
857
955
|
// Non-interactive fallback: a flat status listing, with tool/resource
|
|
858
956
|
// counts and any warning for connected servers.
|
|
859
957
|
const lines = servers.map((server) => {
|
|
@@ -869,6 +967,7 @@ export default function mcpExtension(pi: ExtensionAPI) {
|
|
|
869
967
|
return `${status.padEnd(12)} ${server.name}${suffix} (${server.source})`;
|
|
870
968
|
});
|
|
871
969
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
970
|
+
return [readOnly, ...lines].filter(Boolean).join("\n");
|
|
872
971
|
},
|
|
873
972
|
});
|
|
874
973
|
}
|