one-code-extension 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -5
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +40 -28
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +26 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +58 -31
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +124 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +642 -0
- package/extensions/background/index.ts +614 -248
- package/extensions/background/loop-fire.ts +173 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +56 -0
- package/extensions/background/wakeup.ts +178 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +66 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +250 -78
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/cache-marks/index.ts +42 -0
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +436 -63
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +219 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/budget.ts +16 -2
- package/extensions/context-budget/index.ts +35 -15
- package/extensions/context-management/index.ts +36 -28
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +141 -25
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +420 -42
- package/extensions/file-tracker/replay.ts +137 -24
- package/extensions/file-tracker/restore.ts +66 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +45 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +209 -58
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/prompt-context.ts +59 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +26 -12
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/ledger/index.ts +46 -0
- package/extensions/ledger/wire.ts +154 -0
- package/extensions/lib/agent-cron.ts +146 -0
- package/extensions/lib/agent-loader.ts +18 -3
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-auth.ts +98 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +52 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +97 -0
- package/extensions/lib/covered-messages.ts +23 -0
- package/extensions/lib/deferred.ts +359 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/ledger/fold-memo.ts +49 -0
- package/extensions/lib/ledger/fold.ts +265 -0
- package/extensions/lib/ledger/layout.ts +72 -0
- package/extensions/lib/ledger/plan.ts +102 -0
- package/extensions/lib/ledger/records.ts +276 -0
- package/extensions/lib/ledger/state.ts +47 -0
- package/extensions/lib/ledger/validate.ts +141 -0
- package/extensions/lib/ledger/wire.ts +632 -0
- package/extensions/lib/local-command.ts +71 -19
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +178 -0
- package/extensions/lib/model-catalog/models-dev.json +18533 -0
- package/extensions/lib/model-catalog/openrouter.json +5050 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +357 -73
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +38 -2
- package/extensions/lib/plan-mode-channels.ts +42 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/previous-mark.ts +101 -0
- package/extensions/lib/process-state.ts +48 -0
- package/extensions/lib/process-tree.ts +132 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +370 -358
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +547 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +24 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +112 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +129 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +159 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +76 -12
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +90 -43
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +220 -121
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +164 -73
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/model-default/index.ts +86 -19
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1426 -398
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +11 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +276 -92
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/run-state.ts +26 -0
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +64 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +14 -7
- package/extensions/recap/index.ts +73 -71
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/announce.ts +30 -0
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +469 -132
- package/extensions/skill/invoke.ts +118 -75
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +146 -31
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +82 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1594 -852
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +5 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/parent-mode.ts +50 -0
- package/extensions/subagents/pending-claim.ts +18 -1
- package/extensions/subagents/runner.ts +135 -80
- package/extensions/subagents/runs.ts +44 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +165 -47
- package/extensions/system-prompt/template.ts +34 -51
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +204 -70
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/index.ts +366 -144
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +108 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +157 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +33 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +162 -44
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +21 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
- package/extensions/tool-search/announce.ts +0 -42
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What to tell the model when MCP state changes
|
|
2
|
+
* What to tell the model when MCP state changes inside an epoch (pure).
|
|
3
3
|
*
|
|
4
4
|
* The MCP instructions and failed-servers reminders are `first-prepend` blocks
|
|
5
|
-
* on message 1.
|
|
6
|
-
* message 1 is
|
|
7
|
-
*
|
|
8
|
-
* deferred-tools listing
|
|
9
|
-
*
|
|
5
|
+
* on message 1. A request that opens an epoch renders them from live state;
|
|
6
|
+
* inside an epoch message 1 is the record (extensions/ledger), so changes ride
|
|
7
|
+
* notices on the request's tail, decided when the request goes out from what
|
|
8
|
+
* the epoch has told (the same rule as the deferred-tools listing). What was
|
|
9
|
+
* told is a whole-text block fact per server (`mcp-instructed:<name>`,
|
|
10
|
+
* `mcp-failed:<name>`): the hash of the instructions or error last told, or of
|
|
11
|
+
* the empty text once a drop or a recovery was announced.
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
14
|
import { mcpFailuresReminder, mcpInstructionsReminder } from "./client.ts";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
instructed: Map<string, string>;
|
|
17
|
-
/** Servers the model has been told failed, with the error it saw. */
|
|
18
|
-
failed: Map<string, string>;
|
|
19
|
-
}
|
|
15
|
+
import type { BlockFact, ToldFact } from "../lib/ledger/records.ts";
|
|
16
|
+
import { toldBlockFact } from "../lib/reminders.ts";
|
|
17
|
+
import { countNoun } from "../lib/tui-render.ts";
|
|
20
18
|
|
|
21
19
|
export interface McpSnapshot {
|
|
22
20
|
connected: Array<{ name: string; instructions?: string }>;
|
|
@@ -32,47 +30,83 @@ export interface McpDelta {
|
|
|
32
30
|
recovered: string[];
|
|
33
31
|
/** Servers whose instructions the model saw that are no longer connected and not reported failed. */
|
|
34
32
|
dropped: string[];
|
|
33
|
+
/** What announcing this delta tells, for the request that carries it. */
|
|
34
|
+
facts: ToldFact[];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const INSTRUCTED = "mcp-instructed:";
|
|
38
|
+
const FAILED = "mcp-failed:";
|
|
39
|
+
/** The told hash of "nothing": a drop or a recovery was announced. */
|
|
40
|
+
const NONE = toldBlockFact("", "").hash;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Store and compare the exact bounded values the reminder puts on the wire.
|
|
44
|
+
* A changing tail beyond either cap is not a model-visible capability change
|
|
45
|
+
* and must not become a false notice.
|
|
46
|
+
*/
|
|
47
|
+
function displayedInstructions(name: string, instructions: string): string {
|
|
48
|
+
const rendered = mcpInstructionsReminder([{ server: { name }, instructions }]);
|
|
49
|
+
const prefix = "# MCP Server Instructions\n\nThe following MCP servers have provided instructions for how to use their tools and resources:\n\n" + `## ${name}\n`;
|
|
50
|
+
return rendered?.startsWith(prefix) ? rendered.slice(prefix.length) : instructions;
|
|
51
|
+
}
|
|
52
|
+
function displayedFailure(error: string): string {
|
|
53
|
+
return error.split("\n")[0].slice(0, 300);
|
|
39
54
|
}
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
const instructedFact = (name: string, instructions: string): BlockFact => toldBlockFact(INSTRUCTED + name, displayedInstructions(name, instructions));
|
|
57
|
+
const failedFact = (name: string, error: string): BlockFact => toldBlockFact(FAILED + name, displayedFailure(error));
|
|
58
|
+
|
|
59
|
+
/** What message 1's two MCP blocks rendered from `snapshot` tell. */
|
|
60
|
+
export function mcpFacts(snapshot: McpSnapshot): ToldFact[] {
|
|
61
|
+
return [
|
|
62
|
+
...snapshot.connected.flatMap((c) => (c.instructions ? [instructedFact(c.name, c.instructions)] : [])),
|
|
63
|
+
...snapshot.failed.map((f) => failedFact(f.name, f.error)),
|
|
64
|
+
];
|
|
49
65
|
}
|
|
50
66
|
|
|
51
|
-
|
|
67
|
+
/**
|
|
68
|
+
* What changed since the epoch told `told` (whole-text block hashes by key),
|
|
69
|
+
* and the facts announcing it tells. While the startup connections are still
|
|
70
|
+
* `settling`, a server not connected yet is not a drop, and neither is one in
|
|
71
|
+
* `connecting` (a reconnect in flight).
|
|
72
|
+
*/
|
|
73
|
+
export function mcpDelta(told: ReadonlyMap<string, string>, snapshot: McpSnapshot, settling = false, connecting: ReadonlySet<string> = new Set()): McpDelta {
|
|
52
74
|
const connectedNames = new Set(snapshot.connected.map((c) => c.name));
|
|
53
|
-
|
|
75
|
+
// One failure per server (the latest): two kept errors would alternate as the told fact.
|
|
76
|
+
const failed = [...new Map(snapshot.failed.map((f) => [f.name, f])).values()];
|
|
77
|
+
const failedNames = new Set(failed.map((f) => f.name));
|
|
78
|
+
const toldNames = (prefix: string) => [...told].filter(([key, hash]) => key.startsWith(prefix) && hash !== NONE).map(([key]) => key.slice(prefix.length));
|
|
79
|
+
const newInstructions = snapshot.connected
|
|
80
|
+
.filter((c) => c.instructions && told.get(INSTRUCTED + c.name) !== instructedFact(c.name, c.instructions).hash)
|
|
81
|
+
.map((c) => ({ name: c.name, instructions: displayedInstructions(c.name, c.instructions as string) }));
|
|
82
|
+
const newFailures = failed
|
|
83
|
+
.filter((f) => told.get(FAILED + f.name) !== failedFact(f.name, f.error).hash)
|
|
84
|
+
.map((f) => ({ name: f.name, error: displayedFailure(f.error) }));
|
|
85
|
+
const recovered = toldNames(FAILED).filter((name) => connectedNames.has(name));
|
|
86
|
+
const dropped = settling ? [] : toldNames(INSTRUCTED).filter((name) => !connectedNames.has(name) && !failedNames.has(name) && !connecting.has(name));
|
|
54
87
|
return {
|
|
55
|
-
newInstructions
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
88
|
+
newInstructions,
|
|
89
|
+
newFailures,
|
|
90
|
+
recovered,
|
|
91
|
+
dropped,
|
|
92
|
+
facts: [
|
|
93
|
+
...newInstructions.map((s) => toldBlockFact(INSTRUCTED + s.name, s.instructions)),
|
|
94
|
+
...newFailures.map((f) => toldBlockFact(FAILED + f.name, f.error)),
|
|
95
|
+
...recovered.map((name) => toldBlockFact(FAILED + name, "")),
|
|
96
|
+
...dropped.map((name) => toldBlockFact(INSTRUCTED + name, "")),
|
|
97
|
+
],
|
|
61
98
|
};
|
|
62
99
|
}
|
|
63
100
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
for (const name of delta.dropped) announced.instructed.delete(name);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** One-shot notice texts for a delta, in the order they should be queued. Empty when nothing changed. */
|
|
101
|
+
/**
|
|
102
|
+
* The notices for a delta, in the order they go on the tail. Empty when
|
|
103
|
+
* nothing changed.
|
|
104
|
+
*/
|
|
72
105
|
export function mcpChangeNotices(delta: McpDelta): string[] {
|
|
73
106
|
const notices: string[] = [];
|
|
107
|
+
const add = (text: string) => notices.push(text);
|
|
74
108
|
if (delta.recovered.length > 0) {
|
|
75
|
-
|
|
109
|
+
add(
|
|
76
110
|
`MCP server${delta.recovered.length === 1 ? "" : "s"} ${delta.recovered.join(", ")} ` +
|
|
77
111
|
"connected after this conversation started (an earlier notice reported a connection failure). " +
|
|
78
112
|
"The server's tools are available as deferred tools — load them with tool_search.",
|
|
@@ -80,20 +114,33 @@ export function mcpChangeNotices(delta: McpDelta): string[] {
|
|
|
80
114
|
}
|
|
81
115
|
if (delta.newInstructions.length > 0) {
|
|
82
116
|
const names = delta.newInstructions.map((s) => s.name);
|
|
83
|
-
|
|
117
|
+
add(
|
|
84
118
|
`MCP server${names.length === 1 ? "" : "s"} ${names.join(", ")} connected after this conversation started; ` +
|
|
85
119
|
"the tools are available as deferred tools (load with tool_search). Server instructions:\n\n" +
|
|
86
120
|
mcpInstructionsReminder(delta.newInstructions.map((s) => ({ server: { name: s.name }, instructions: s.instructions }))),
|
|
87
121
|
);
|
|
88
122
|
}
|
|
89
123
|
if (delta.newFailures.length > 0) {
|
|
90
|
-
|
|
124
|
+
add(mcpFailuresReminder(delta.newFailures));
|
|
91
125
|
}
|
|
92
126
|
if (delta.dropped.length > 0) {
|
|
93
|
-
|
|
127
|
+
add(
|
|
94
128
|
`MCP server${delta.dropped.length === 1 ? "" : "s"} ${delta.dropped.join(", ")} disconnected; ` +
|
|
95
129
|
'their tools answer "not connected" until the server is reconnected (the user can do that in /mcp).',
|
|
96
130
|
);
|
|
97
131
|
}
|
|
98
132
|
return notices;
|
|
99
133
|
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The user's startup notices, Claude Code's wording (`useMcpConnectivityStatus`):
|
|
137
|
+
* `2 MCP servers failed · /mcp` and `1 MCP server needs auth · /mcp`, with each
|
|
138
|
+
* server's error left to /mcp. Printing every server's raw error ("fetch
|
|
139
|
+
* failed" per remote server when offline) buried the prompt.
|
|
140
|
+
*/
|
|
141
|
+
export function mcpStartupNotices(failedServers: number, needsAuth: number): string[] {
|
|
142
|
+
const out: string[] = [];
|
|
143
|
+
if (failedServers > 0) out.push(`${countNoun(failedServers, "MCP server")} failed · /mcp`);
|
|
144
|
+
if (needsAuth > 0) out.push(`${countNoun(needsAuth, "MCP server")} ${needsAuth === 1 ? "needs" : "need"} auth · /mcp`);
|
|
145
|
+
return out;
|
|
146
|
+
}
|
package/extensions/mcp/client.ts
CHANGED
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7
7
|
import type { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
8
|
+
import type { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
8
9
|
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
9
10
|
import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
11
|
+
import { childProcessEnv } from "../lib/app-launch.mjs";
|
|
10
12
|
import type { McpServer } from "./config.ts";
|
|
13
|
+
import { collectPages } from "./discovery.ts";
|
|
14
|
+
import { McpAuthenticationRequiredError } from "./oauth/provider.ts";
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* The MCP SDK costs ~70-80ms to load (findings §15), so it is imported on the
|
|
@@ -19,6 +23,7 @@ let sdkPromise:
|
|
|
19
23
|
Client: typeof import("@modelcontextprotocol/sdk/client/index.js").Client;
|
|
20
24
|
StdioClientTransport: typeof import("@modelcontextprotocol/sdk/client/stdio.js").StdioClientTransport;
|
|
21
25
|
StreamableHTTPClientTransport: typeof import("@modelcontextprotocol/sdk/client/streamableHttp.js").StreamableHTTPClientTransport;
|
|
26
|
+
SSEClientTransport: typeof import("@modelcontextprotocol/sdk/client/sse.js").SSEClientTransport;
|
|
22
27
|
UnauthorizedError: typeof import("@modelcontextprotocol/sdk/client/auth.js").UnauthorizedError;
|
|
23
28
|
}>
|
|
24
29
|
| undefined;
|
|
@@ -27,6 +32,10 @@ let sdkPromise:
|
|
|
27
32
|
// (the SDK's UnauthorizedError does not set `.name`, so a name/message check is
|
|
28
33
|
// unreliable — a custom-message instance would be missed).
|
|
29
34
|
let unauthorizedClass: typeof import("@modelcontextprotocol/sdk/client/auth.js").UnauthorizedError | undefined;
|
|
35
|
+
let httpErrorClass: typeof import("@modelcontextprotocol/sdk/client/streamableHttp.js").StreamableHTTPError | undefined;
|
|
36
|
+
let sseErrorClass: typeof import("@modelcontextprotocol/sdk/client/sse.js").SseError | undefined;
|
|
37
|
+
|
|
38
|
+
type HttpTransport = StreamableHTTPClientTransport | SSEClientTransport;
|
|
30
39
|
|
|
31
40
|
function loadSdk() {
|
|
32
41
|
sdkPromise ??= Promise.all([
|
|
@@ -34,12 +43,16 @@ function loadSdk() {
|
|
|
34
43
|
import("@modelcontextprotocol/sdk/client/stdio.js"),
|
|
35
44
|
import("@modelcontextprotocol/sdk/client/streamableHttp.js"),
|
|
36
45
|
import("@modelcontextprotocol/sdk/client/auth.js"),
|
|
37
|
-
|
|
46
|
+
import("@modelcontextprotocol/sdk/client/sse.js"),
|
|
47
|
+
]).then(([client, stdio, http, auth, sse]) => {
|
|
38
48
|
unauthorizedClass = auth.UnauthorizedError;
|
|
49
|
+
httpErrorClass = http.StreamableHTTPError;
|
|
50
|
+
sseErrorClass = sse.SseError;
|
|
39
51
|
return {
|
|
40
52
|
Client: client.Client,
|
|
41
53
|
StdioClientTransport: stdio.StdioClientTransport,
|
|
42
54
|
StreamableHTTPClientTransport: http.StreamableHTTPClientTransport,
|
|
55
|
+
SSEClientTransport: sse.SSEClientTransport,
|
|
43
56
|
UnauthorizedError: auth.UnauthorizedError,
|
|
44
57
|
};
|
|
45
58
|
});
|
|
@@ -62,11 +75,21 @@ export interface McpResource {
|
|
|
62
75
|
mimeType?: string;
|
|
63
76
|
}
|
|
64
77
|
|
|
78
|
+
export interface McpResourceTemplate {
|
|
79
|
+
uriTemplate: string;
|
|
80
|
+
name?: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
mimeType?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
65
85
|
export interface Connection {
|
|
66
86
|
server: McpServer;
|
|
67
87
|
client: Client;
|
|
68
88
|
tools: McpTool[];
|
|
69
89
|
resources: McpResource[];
|
|
90
|
+
resourceTemplates?: McpResourceTemplate[];
|
|
91
|
+
/** Called after a tool-list refresh, or with its error while the last good list stays intact. */
|
|
92
|
+
onToolsChanged?: (error?: Error) => void;
|
|
70
93
|
/** The server's own usage instructions from its initialize result, if any. */
|
|
71
94
|
instructions?: string;
|
|
72
95
|
/** Non-fatal problems after a successful connect (e.g. listTools failed). */
|
|
@@ -126,8 +149,11 @@ function withTimeout<T>(promise: Promise<T>, ms: number, what: string): Promise<
|
|
|
126
149
|
});
|
|
127
150
|
}
|
|
128
151
|
|
|
129
|
-
/** True when the
|
|
152
|
+
/** True when the server requires authentication, including an expired stored grant. */
|
|
130
153
|
export function isUnauthorized(error: unknown): boolean {
|
|
154
|
+
if (error instanceof McpAuthenticationRequiredError) return true;
|
|
155
|
+
if (httpErrorClass && error instanceof httpErrorClass && error.code === 401) return true;
|
|
156
|
+
if (sseErrorClass && error instanceof sseErrorClass && error.code === 401) return true;
|
|
131
157
|
// The class is captured on the first loadSdk(); every caller runs after a
|
|
132
158
|
// connect (which awaits it), so it is set. The message fallback only covers
|
|
133
159
|
// the impossible pre-load case.
|
|
@@ -140,61 +166,140 @@ function buildTransport(
|
|
|
140
166
|
server: McpServer,
|
|
141
167
|
sdk: Awaited<ReturnType<typeof loadSdk>>,
|
|
142
168
|
authProvider?: OAuthClientProvider,
|
|
169
|
+
keepOAuthOpen?: () => boolean,
|
|
143
170
|
): { transport: Transport; stderrTail: () => string } {
|
|
144
171
|
const tail = createTailBuffer();
|
|
145
172
|
if (server.kind === "stdio") {
|
|
146
173
|
const transport = new sdk.StdioClientTransport({
|
|
147
174
|
command: server.command,
|
|
148
175
|
args: server.args,
|
|
149
|
-
|
|
176
|
+
// The user's own environment under the bundled app (lib/app-launch.mjs), then the server's.
|
|
177
|
+
env: { ...(childProcessEnv(process.env) as Record<string, string>), ...(server.env ?? {}) },
|
|
178
|
+
...(server.cwd ? { cwd: server.cwd } : {}),
|
|
150
179
|
stderr: "pipe",
|
|
151
180
|
});
|
|
152
181
|
// The PassThrough exists before start(); drain it from the first byte.
|
|
153
182
|
transport.stderr?.on("data", (chunk: string | Uint8Array) => tail.push(chunk));
|
|
154
183
|
return { transport, stderrTail: tail.text };
|
|
155
184
|
}
|
|
156
|
-
const
|
|
185
|
+
const lifetime = new AbortController();
|
|
186
|
+
const RemoteTransport = server.transport === "sse" ? sdk.SSEClientTransport : sdk.StreamableHTTPClientTransport;
|
|
187
|
+
const transport = new RemoteTransport(new URL(server.url), {
|
|
157
188
|
authProvider,
|
|
158
189
|
requestInit: server.headers ? { headers: server.headers } : undefined,
|
|
190
|
+
// The SDK's close signal covers MCP requests, but not the discovery and
|
|
191
|
+
// token fetches it starts during OAuth. Those must stop with the client too.
|
|
192
|
+
fetch: (input, init) => fetch(input, {
|
|
193
|
+
...init,
|
|
194
|
+
signal: init?.signal ? AbortSignal.any([lifetime.signal, init.signal]) : lifetime.signal,
|
|
195
|
+
}),
|
|
159
196
|
});
|
|
197
|
+
const closeTransport = transport.close.bind(transport);
|
|
198
|
+
transport.close = async () => {
|
|
199
|
+
if (!keepOAuthOpen?.()) lifetime.abort();
|
|
200
|
+
await closeTransport();
|
|
201
|
+
};
|
|
160
202
|
return { transport, stderrTail: tail.text };
|
|
161
203
|
}
|
|
162
204
|
|
|
163
|
-
/**
|
|
205
|
+
/** Bound the complete list, not each page, and cancel its last request on timeout. */
|
|
206
|
+
async function discoverPages<T>(
|
|
207
|
+
fetchPage: (cursor: string | undefined, signal: AbortSignal) => Promise<{ items: T[]; nextCursor?: string }>,
|
|
208
|
+
what: string,
|
|
209
|
+
): Promise<T[]> {
|
|
210
|
+
const controller = new AbortController();
|
|
211
|
+
try {
|
|
212
|
+
return await withTimeout(collectPages((cursor) => fetchPage(cursor, controller.signal), controller.signal), CONNECT_TIMEOUT_MS, what);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
controller.abort();
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** A fresh, complete tool snapshot. */
|
|
220
|
+
export function discoverTools(client: Client, serverName: string): Promise<McpTool[]> {
|
|
221
|
+
return discoverPages(async (cursor, signal) => {
|
|
222
|
+
const result = await client.listTools(cursor === undefined ? undefined : { cursor }, { signal });
|
|
223
|
+
return { items: result.tools, nextCursor: result.nextCursor };
|
|
224
|
+
}, `listing tools of "${serverName}"`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** List a connected client's tools, resources and URI templates into a Connection. */
|
|
164
228
|
async function finalizeConnection(client: Client, server: McpServer, stderrTail: () => string = () => ""): Promise<Connection> {
|
|
165
229
|
const warnings: string[] = [];
|
|
166
230
|
|
|
167
|
-
// Each list
|
|
168
|
-
//
|
|
169
|
-
const [toolsResult, resourcesResult] = await Promise.allSettled([
|
|
170
|
-
|
|
171
|
-
|
|
231
|
+
// Each complete list has one deadline; run them concurrently instead of
|
|
232
|
+
// stacking their worst-case latencies or granting each page a fresh timeout.
|
|
233
|
+
const [toolsResult, resourcesResult, templatesResult] = await Promise.allSettled([
|
|
234
|
+
discoverTools(client, server.name),
|
|
235
|
+
discoverPages(async (cursor, signal) => {
|
|
236
|
+
const result = await client.listResources(cursor === undefined ? undefined : { cursor }, { signal });
|
|
237
|
+
return { items: result.resources, nextCursor: result.nextCursor };
|
|
238
|
+
}, `listing resources of "${server.name}"`),
|
|
239
|
+
discoverPages(async (cursor, signal) => {
|
|
240
|
+
const result = await client.listResourceTemplates(cursor === undefined ? undefined : { cursor }, { signal });
|
|
241
|
+
return { items: result.resourceTemplates, nextCursor: result.nextCursor };
|
|
242
|
+
}, `listing resource templates of "${server.name}"`),
|
|
172
243
|
]);
|
|
173
244
|
|
|
174
245
|
let tools: McpTool[] = [];
|
|
175
246
|
if (toolsResult.status === "fulfilled") {
|
|
176
|
-
tools =
|
|
247
|
+
tools = toolsResult.value;
|
|
177
248
|
} else {
|
|
178
249
|
// A server that connects but fails listTools would otherwise register
|
|
179
250
|
// zero tools with no signal anywhere — surface it as a warning.
|
|
180
251
|
warnings.push(`connected, but listing tools failed: ${(toolsResult.reason as Error).message}`);
|
|
181
252
|
}
|
|
182
253
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
254
|
+
// Resources and templates are optional in MCP; "method not found" is
|
|
255
|
+
// normal. A timeout or protocol error must still surface as a warning.
|
|
256
|
+
const optionalList = <T>(result: PromiseSettledResult<T[]>, label: string): T[] => {
|
|
257
|
+
if (result.status === "fulfilled") return result.value;
|
|
258
|
+
const message = (result.reason as Error).message;
|
|
259
|
+
if (!/method not found|-32601/i.test(message)) warnings.push(`connected, but listing ${label} failed: ${message}`);
|
|
260
|
+
return [];
|
|
261
|
+
};
|
|
262
|
+
const resources = optionalList(resourcesResult, "resources");
|
|
263
|
+
const resourceTemplates = optionalList(templatesResult, "resource templates");
|
|
195
264
|
const instructions = client.getInstructions()?.trim() || undefined;
|
|
196
265
|
|
|
197
|
-
|
|
266
|
+
const connection: Connection = { server, client, tools, resources, resourceTemplates, instructions, warnings, stderrTail };
|
|
267
|
+
const { ToolListChangedNotificationSchema } = await import("@modelcontextprotocol/sdk/types.js");
|
|
268
|
+
let refreshing = false;
|
|
269
|
+
let pending = false;
|
|
270
|
+
client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
|
|
271
|
+
if (connection.closing) return;
|
|
272
|
+
pending = true;
|
|
273
|
+
if (refreshing) return;
|
|
274
|
+
refreshing = true;
|
|
275
|
+
try {
|
|
276
|
+
// A burst during an in-flight list gets one follow-up refresh, never
|
|
277
|
+
// concurrent snapshots that could overwrite a newer list with an old one.
|
|
278
|
+
while (pending && !connection.closing) {
|
|
279
|
+
pending = false;
|
|
280
|
+
let refreshed: McpTool[];
|
|
281
|
+
try {
|
|
282
|
+
refreshed = await discoverTools(client, server.name);
|
|
283
|
+
} catch (cause) {
|
|
284
|
+
if (connection.closing) return;
|
|
285
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
286
|
+
const warning = `connected, but refreshing tools failed: ${error.message}`;
|
|
287
|
+
if (!connection.warnings.includes(warning)) connection.warnings.push(warning);
|
|
288
|
+
connection.onToolsChanged?.(error);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (connection.closing) return;
|
|
292
|
+
connection.tools = refreshed;
|
|
293
|
+
connection.onToolsChanged?.();
|
|
294
|
+
}
|
|
295
|
+
} finally {
|
|
296
|
+
refreshing = false;
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
// A server can exit after initialize but before discovery finishes. Its
|
|
300
|
+
// onclose already fired, before index.ts can adopt the connection.
|
|
301
|
+
if (!client.transport) throw new Error(withStderr(`connection to "${server.name}" closed during discovery`, stderrTail()));
|
|
302
|
+
return connection;
|
|
198
303
|
}
|
|
199
304
|
|
|
200
305
|
/**
|
|
@@ -208,10 +313,11 @@ async function finalizeConnection(client: Client, server: McpServer, stderrTail:
|
|
|
208
313
|
async function openClient(
|
|
209
314
|
server: McpServer,
|
|
210
315
|
authProvider?: OAuthClientProvider,
|
|
316
|
+
keepOAuthOpen?: () => boolean,
|
|
211
317
|
): Promise<{ client: Client; transport: Transport; stderrTail: () => string }> {
|
|
212
318
|
const sdk = await loadSdk();
|
|
213
319
|
const client = new sdk.Client({ name: "one-code", version: "0.1.0" }, { capabilities: {} });
|
|
214
|
-
return { client, ...buildTransport(server, sdk, authProvider) };
|
|
320
|
+
return { client, ...buildTransport(server, sdk, authProvider, keepOAuthOpen) };
|
|
215
321
|
}
|
|
216
322
|
|
|
217
323
|
/**
|
|
@@ -254,22 +360,35 @@ export async function connect(server: McpServer, authProvider?: OAuthClientProvi
|
|
|
254
360
|
export async function beginInteractiveAuth(
|
|
255
361
|
server: McpServer,
|
|
256
362
|
authProvider: OAuthClientProvider,
|
|
257
|
-
): Promise<{ transport:
|
|
363
|
+
): Promise<{ transport: HttpTransport } | { connection: Connection }> {
|
|
258
364
|
if (server.kind !== "http") throw new Error(`OAuth is only available for http MCP servers; "${server.name}" is stdio.`);
|
|
259
|
-
|
|
260
|
-
|
|
365
|
+
// Client.connect closes the transport after the expected UnauthorizedError.
|
|
366
|
+
// Keep OAuth fetches usable until the redirect has been handed to the caller.
|
|
367
|
+
let awaitingRedirect = true;
|
|
368
|
+
const { client, transport: baseTransport, stderrTail } = await openClient(server, authProvider, () => awaitingRedirect);
|
|
369
|
+
const transport = baseTransport as HttpTransport;
|
|
261
370
|
try {
|
|
262
371
|
// Not connectOrClose: on a 401 the transport must stay open — it carries
|
|
263
372
|
// the OAuth flow state the caller finishes with finishAuth(code).
|
|
264
373
|
await withTimeout(client.connect(transport), CONNECT_TIMEOUT_MS, `connecting to "${server.name}"`);
|
|
265
374
|
return { connection: await finalizeConnection(client, server, stderrTail) };
|
|
266
375
|
} catch (error) {
|
|
267
|
-
|
|
376
|
+
// Only the SDK's redirect signal means a browser callback is coming.
|
|
377
|
+
// A raw 401 after refreshing tokens is an auth failure, not a redirect.
|
|
378
|
+
if (unauthorizedClass && error instanceof unauthorizedClass) return { transport };
|
|
379
|
+
awaitingRedirect = false;
|
|
268
380
|
await transport.close().catch(() => {});
|
|
269
381
|
throw error;
|
|
382
|
+
} finally {
|
|
383
|
+
awaitingRedirect = false;
|
|
270
384
|
}
|
|
271
385
|
}
|
|
272
386
|
|
|
387
|
+
/** Bound the token exchange too; the callback deadline only covers waiting for the browser. */
|
|
388
|
+
export async function finishInteractiveAuth(transport: HttpTransport, code: string): Promise<void> {
|
|
389
|
+
await withTimeout(transport.finishAuth(code), CONNECT_TIMEOUT_MS, "exchanging the authorization code");
|
|
390
|
+
}
|
|
391
|
+
|
|
273
392
|
const INSTRUCTIONS_CAP = 3000;
|
|
274
393
|
|
|
275
394
|
/**
|
|
@@ -322,21 +441,15 @@ export async function callTool(
|
|
|
322
441
|
connection: Connection,
|
|
323
442
|
toolName: string,
|
|
324
443
|
args: Record<string, unknown>,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
);
|
|
331
|
-
return result as { content?: unknown[]; isError?: boolean };
|
|
444
|
+
signal?: AbortSignal,
|
|
445
|
+
): Promise<{ content?: unknown[]; isError?: boolean; structuredContent?: Record<string, unknown> }> {
|
|
446
|
+
return await connection.client.callTool({ name: toolName, arguments: args }, undefined, { signal, timeout: CALL_TIMEOUT_MS }) as {
|
|
447
|
+
content?: unknown[]; isError?: boolean; structuredContent?: Record<string, unknown>;
|
|
448
|
+
};
|
|
332
449
|
}
|
|
333
450
|
|
|
334
|
-
export async function readResource(connection: Connection, uri: string): Promise<{ contents?: unknown[] }> {
|
|
335
|
-
return
|
|
336
|
-
connection.client.readResource({ uri }),
|
|
337
|
-
CALL_TIMEOUT_MS,
|
|
338
|
-
`reading resource "${uri}"`,
|
|
339
|
-
)) as { contents?: unknown[] };
|
|
451
|
+
export async function readResource(connection: Connection, uri: string, signal?: AbortSignal): Promise<{ contents?: unknown[] }> {
|
|
452
|
+
return await connection.client.readResource({ uri }, { signal, timeout: CALL_TIMEOUT_MS });
|
|
340
453
|
}
|
|
341
454
|
|
|
342
455
|
/**
|
|
@@ -345,13 +458,13 @@ export async function readResource(connection: Connection, uri: string): Promise
|
|
|
345
458
|
* The SDK only calls `safeParse` on the result schema, and the response shape is
|
|
346
459
|
* server-defined, so a passthrough schema beats pinning one the spec hasn't settled.
|
|
347
460
|
*/
|
|
348
|
-
export async function readResourceDir(connection: Connection, uri: string): Promise<Record<string, unknown>> {
|
|
461
|
+
export async function readResourceDir(connection: Connection, uri: string, signal?: AbortSignal): Promise<Record<string, unknown>> {
|
|
349
462
|
const passthrough = { safeParse: (data: unknown) => ({ success: true as const, data }) };
|
|
350
|
-
return
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
)
|
|
463
|
+
return await connection.client.request(
|
|
464
|
+
{ method: "resources/directory/read", params: { uri } },
|
|
465
|
+
passthrough as never,
|
|
466
|
+
{ signal, timeout: CALL_TIMEOUT_MS },
|
|
467
|
+
) as Record<string, unknown>;
|
|
355
468
|
}
|
|
356
469
|
|
|
357
470
|
export async function close(connection: Connection): Promise<void> {
|