mindweave 0.0.1 → 2.1.2
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/LICENSE +190 -0
- package/README.md +171 -14
- package/dist/alternator/chassis/cache.js +47 -0
- package/dist/alternator/chassis/cache.js.map +1 -0
- package/dist/alternator/chassis/domRefs.js +53 -0
- package/dist/alternator/chassis/domRefs.js.map +1 -0
- package/dist/alternator/chassis/extractWorker.js +65 -0
- package/dist/alternator/chassis/extractWorker.js.map +1 -0
- package/dist/alternator/chassis/graph.js +221 -0
- package/dist/alternator/chassis/graph.js.map +1 -0
- package/dist/alternator/chassis/index.js +463 -0
- package/dist/alternator/chassis/index.js.map +1 -0
- package/dist/alternator/chassis/isolation.js +336 -0
- package/dist/alternator/chassis/isolation.js.map +1 -0
- package/dist/alternator/chassis/langCases.js +69 -0
- package/dist/alternator/chassis/langCases.js.map +1 -0
- package/dist/alternator/chassis/lsp.js +469 -0
- package/dist/alternator/chassis/lsp.js.map +1 -0
- package/dist/alternator/chassis/markup.js +262 -0
- package/dist/alternator/chassis/markup.js.map +1 -0
- package/dist/alternator/chassis/provision.js +198 -0
- package/dist/alternator/chassis/provision.js.map +1 -0
- package/dist/alternator/chassis/rank.js +72 -0
- package/dist/alternator/chassis/rank.js.map +1 -0
- package/dist/alternator/chassis/servers.js +215 -0
- package/dist/alternator/chassis/servers.js.map +1 -0
- package/dist/alternator/chassis/treesitter.js +608 -0
- package/dist/alternator/chassis/treesitter.js.map +1 -0
- package/dist/alternator/chassis/types.js +24 -0
- package/dist/alternator/chassis/types.js.map +1 -0
- package/dist/alternator/lane.js +67 -0
- package/dist/alternator/lane.js.map +1 -0
- package/dist/cli/App.js +2586 -0
- package/dist/cli/App.js.map +1 -0
- package/dist/cli/altScreen.js +67 -0
- package/dist/cli/altScreen.js.map +1 -0
- package/dist/cli/approvalChannel.js +72 -0
- package/dist/cli/approvalChannel.js.map +1 -0
- package/dist/cli/attachments.js +188 -0
- package/dist/cli/attachments.js.map +1 -0
- package/dist/cli/bootstrap.js +279 -0
- package/dist/cli/bootstrap.js.map +1 -0
- package/dist/cli/chatAnchor.js +94 -0
- package/dist/cli/chatAnchor.js.map +1 -0
- package/dist/cli/commandArgs.js +79 -0
- package/dist/cli/commandArgs.js.map +1 -0
- package/dist/cli/commandRoute.js +78 -0
- package/dist/cli/commandRoute.js.map +1 -0
- package/dist/cli/compaction.js +69 -0
- package/dist/cli/compaction.js.map +1 -0
- package/dist/cli/components/ApprovalBox.js +100 -0
- package/dist/cli/components/ApprovalBox.js.map +1 -0
- package/dist/cli/components/BlockView.js +128 -0
- package/dist/cli/components/BlockView.js.map +1 -0
- package/dist/cli/components/FirstRunFrame.js +41 -0
- package/dist/cli/components/FirstRunFrame.js.map +1 -0
- package/dist/cli/components/KeyManager.js +197 -0
- package/dist/cli/components/KeyManager.js.map +1 -0
- package/dist/cli/components/KeySetup.js +125 -0
- package/dist/cli/components/KeySetup.js.map +1 -0
- package/dist/cli/components/MenuFrame.js +24 -0
- package/dist/cli/components/MenuFrame.js.map +1 -0
- package/dist/cli/components/Picker.js +82 -0
- package/dist/cli/components/Picker.js.map +1 -0
- package/dist/cli/components/PromptInput.js +509 -0
- package/dist/cli/components/PromptInput.js.map +1 -0
- package/dist/cli/components/SubagentView.js +86 -0
- package/dist/cli/components/SubagentView.js.map +1 -0
- package/dist/cli/components/ToolGroup.js +60 -0
- package/dist/cli/components/ToolGroup.js.map +1 -0
- package/dist/cli/components/ToolLine.js +123 -0
- package/dist/cli/components/ToolLine.js.map +1 -0
- package/dist/cli/components/TrustGate.js +37 -0
- package/dist/cli/components/TrustGate.js.map +1 -0
- package/dist/cli/costView.js +40 -0
- package/dist/cli/costView.js.map +1 -0
- package/dist/cli/frameRate.js +30 -0
- package/dist/cli/frameRate.js.map +1 -0
- package/dist/cli/framebuffer/paint.js +165 -0
- package/dist/cli/framebuffer/paint.js.map +1 -0
- package/dist/cli/framebuffer/parse.js +266 -0
- package/dist/cli/framebuffer/parse.js.map +1 -0
- package/dist/cli/framebuffer/screen.js +196 -0
- package/dist/cli/framebuffer/screen.js.map +1 -0
- package/dist/cli/framebuffer/writer.js +139 -0
- package/dist/cli/framebuffer/writer.js.map +1 -0
- package/dist/cli/groupReveal.js +51 -0
- package/dist/cli/groupReveal.js.map +1 -0
- package/dist/cli/help.js +55 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/inputView.js +105 -0
- package/dist/cli/inputView.js.map +1 -0
- package/dist/cli/keyManager.js +65 -0
- package/dist/cli/keyManager.js.map +1 -0
- package/dist/cli/keySetup.js +35 -0
- package/dist/cli/keySetup.js.map +1 -0
- package/dist/cli/keyStore.js +73 -0
- package/dist/cli/keyStore.js.map +1 -0
- package/dist/cli/markdown.js +460 -0
- package/dist/cli/markdown.js.map +1 -0
- package/dist/cli/messageQueue.js +96 -0
- package/dist/cli/messageQueue.js.map +1 -0
- package/dist/cli/modes.js +90 -0
- package/dist/cli/modes.js.map +1 -0
- package/dist/cli/mouse.js +78 -0
- package/dist/cli/mouse.js.map +1 -0
- package/dist/cli/pasteAssembler.js +44 -0
- package/dist/cli/pasteAssembler.js.map +1 -0
- package/dist/cli/pathComplete.js +39 -0
- package/dist/cli/pathComplete.js.map +1 -0
- package/dist/cli/perfLog.js +90 -0
- package/dist/cli/perfLog.js.map +1 -0
- package/dist/cli/reveal.js +32 -0
- package/dist/cli/reveal.js.map +1 -0
- package/dist/cli/revealPace.js +59 -0
- package/dist/cli/revealPace.js.map +1 -0
- package/dist/cli/sessionCarry.js +19 -0
- package/dist/cli/sessionCarry.js.map +1 -0
- package/dist/cli/smoothScroll.js +74 -0
- package/dist/cli/smoothScroll.js.map +1 -0
- package/dist/cli/startupArgs.js +96 -0
- package/dist/cli/startupArgs.js.map +1 -0
- package/dist/cli/terminalRestore.js +34 -0
- package/dist/cli/terminalRestore.js.map +1 -0
- package/dist/cli/toolDisplay.js +219 -0
- package/dist/cli/toolDisplay.js.map +1 -0
- package/dist/cli/toolItems.js +65 -0
- package/dist/cli/toolItems.js.map +1 -0
- package/dist/cli/transcript.js +351 -0
- package/dist/cli/transcript.js.map +1 -0
- package/dist/cli/trust.js +79 -0
- package/dist/cli/trust.js.map +1 -0
- package/dist/cli/updateCheck.js +107 -0
- package/dist/cli/updateCheck.js.map +1 -0
- package/dist/cli/version.js +39 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/cli/viewport.js +179 -0
- package/dist/cli/viewport.js.map +1 -0
- package/dist/cli/virtualWindow.js +103 -0
- package/dist/cli/virtualWindow.js.map +1 -0
- package/dist/cli/workingVerb.js +37 -0
- package/dist/cli/workingVerb.js.map +1 -0
- package/dist/cli/wrap.js +145 -0
- package/dist/cli/wrap.js.map +1 -0
- package/dist/drivers/anthropic/cachePoints.js +77 -0
- package/dist/drivers/anthropic/cachePoints.js.map +1 -0
- package/dist/drivers/anthropic/client.js +432 -0
- package/dist/drivers/anthropic/client.js.map +1 -0
- package/dist/drivers/anthropic/index.js +10 -0
- package/dist/drivers/anthropic/index.js.map +1 -0
- package/dist/drivers/anthropic/manifest.js +221 -0
- package/dist/drivers/anthropic/manifest.js.map +1 -0
- package/dist/drivers/cerebras/client.js +69 -0
- package/dist/drivers/cerebras/client.js.map +1 -0
- package/dist/drivers/cerebras/index.js +5 -0
- package/dist/drivers/cerebras/index.js.map +1 -0
- package/dist/drivers/cerebras/manifest.js +140 -0
- package/dist/drivers/cerebras/manifest.js.map +1 -0
- package/dist/drivers/clientId.js +41 -0
- package/dist/drivers/clientId.js.map +1 -0
- package/dist/drivers/contextOverflow.js +77 -0
- package/dist/drivers/contextOverflow.js.map +1 -0
- package/dist/drivers/deepseek/client.js +168 -0
- package/dist/drivers/deepseek/client.js.map +1 -0
- package/dist/drivers/deepseek/index.js +16 -0
- package/dist/drivers/deepseek/index.js.map +1 -0
- package/dist/drivers/deepseek/inlineTools.js +46 -0
- package/dist/drivers/deepseek/inlineTools.js.map +1 -0
- package/dist/drivers/deepseek/manifest.js +166 -0
- package/dist/drivers/deepseek/manifest.js.map +1 -0
- package/dist/drivers/gemini/client.js +38 -0
- package/dist/drivers/gemini/client.js.map +1 -0
- package/dist/drivers/gemini/index.js +5 -0
- package/dist/drivers/gemini/index.js.map +1 -0
- package/dist/drivers/gemini/manifest.js +150 -0
- package/dist/drivers/gemini/manifest.js.map +1 -0
- package/dist/drivers/glm/client.js +90 -0
- package/dist/drivers/glm/client.js.map +1 -0
- package/dist/drivers/glm/index.js +9 -0
- package/dist/drivers/glm/index.js.map +1 -0
- package/dist/drivers/glm/manifest.js +183 -0
- package/dist/drivers/glm/manifest.js.map +1 -0
- package/dist/drivers/groq/client.js +80 -0
- package/dist/drivers/groq/client.js.map +1 -0
- package/dist/drivers/groq/index.js +5 -0
- package/dist/drivers/groq/index.js.map +1 -0
- package/dist/drivers/groq/manifest.js +160 -0
- package/dist/drivers/groq/manifest.js.map +1 -0
- package/dist/drivers/kimi/client.js +82 -0
- package/dist/drivers/kimi/client.js.map +1 -0
- package/dist/drivers/kimi/index.js +9 -0
- package/dist/drivers/kimi/index.js.map +1 -0
- package/dist/drivers/kimi/manifest.js +168 -0
- package/dist/drivers/kimi/manifest.js.map +1 -0
- package/dist/drivers/meta/client.js +30 -0
- package/dist/drivers/meta/client.js.map +1 -0
- package/dist/drivers/meta/index.js +5 -0
- package/dist/drivers/meta/index.js.map +1 -0
- package/dist/drivers/meta/manifest.js +101 -0
- package/dist/drivers/meta/manifest.js.map +1 -0
- package/dist/drivers/minimax/client.js +36 -0
- package/dist/drivers/minimax/client.js.map +1 -0
- package/dist/drivers/minimax/index.js +5 -0
- package/dist/drivers/minimax/index.js.map +1 -0
- package/dist/drivers/minimax/manifest.js +120 -0
- package/dist/drivers/minimax/manifest.js.map +1 -0
- package/dist/drivers/mistral/client.js +54 -0
- package/dist/drivers/mistral/client.js.map +1 -0
- package/dist/drivers/mistral/index.js +5 -0
- package/dist/drivers/mistral/index.js.map +1 -0
- package/dist/drivers/mistral/manifest.js +118 -0
- package/dist/drivers/mistral/manifest.js.map +1 -0
- package/dist/drivers/openai/client.js +290 -0
- package/dist/drivers/openai/client.js.map +1 -0
- package/dist/drivers/openai/index.js +9 -0
- package/dist/drivers/openai/index.js.map +1 -0
- package/dist/drivers/openai/manifest.js +133 -0
- package/dist/drivers/openai/manifest.js.map +1 -0
- package/dist/drivers/openaiCompat/wire.js +493 -0
- package/dist/drivers/openaiCompat/wire.js.map +1 -0
- package/dist/drivers/partialTurn.js +23 -0
- package/dist/drivers/partialTurn.js.map +1 -0
- package/dist/drivers/providerError.js +181 -0
- package/dist/drivers/providerError.js.map +1 -0
- package/dist/drivers/qwen/client.js +93 -0
- package/dist/drivers/qwen/client.js.map +1 -0
- package/dist/drivers/qwen/index.js +9 -0
- package/dist/drivers/qwen/index.js.map +1 -0
- package/dist/drivers/qwen/manifest.js +146 -0
- package/dist/drivers/qwen/manifest.js.map +1 -0
- package/dist/drivers/registry.js +177 -0
- package/dist/drivers/registry.js.map +1 -0
- package/dist/drivers/retryPolicy.js +129 -0
- package/dist/drivers/retryPolicy.js.map +1 -0
- package/dist/drivers/searchBlocks.js +61 -0
- package/dist/drivers/searchBlocks.js.map +1 -0
- package/dist/drivers/types.js +2 -0
- package/dist/drivers/types.js.map +1 -0
- package/dist/drivers/xai/client.js +63 -0
- package/dist/drivers/xai/client.js.map +1 -0
- package/dist/drivers/xai/index.js +5 -0
- package/dist/drivers/xai/index.js.map +1 -0
- package/dist/drivers/xai/manifest.js +137 -0
- package/dist/drivers/xai/manifest.js.map +1 -0
- package/dist/dynamo/_probe.tmp.js +20 -0
- package/dist/dynamo/_probe.tmp.js.map +1 -0
- package/dist/dynamo/cacheBreak.js +88 -0
- package/dist/dynamo/cacheBreak.js.map +1 -0
- package/dist/dynamo/contextWindow.js +243 -0
- package/dist/dynamo/contextWindow.js.map +1 -0
- package/dist/dynamo/engine.js +2011 -0
- package/dist/dynamo/engine.js.map +1 -0
- package/dist/dynamo/guard.js +132 -0
- package/dist/dynamo/guard.js.map +1 -0
- package/dist/dynamo/liveMeter.js +58 -0
- package/dist/dynamo/liveMeter.js.map +1 -0
- package/dist/dynamo/model.js +172 -0
- package/dist/dynamo/model.js.map +1 -0
- package/dist/dynamo/planArtifact.js +125 -0
- package/dist/dynamo/planArtifact.js.map +1 -0
- package/dist/dynamo/pricing.js +140 -0
- package/dist/dynamo/pricing.js.map +1 -0
- package/dist/dynamo/prompt.js +264 -0
- package/dist/dynamo/prompt.js.map +1 -0
- package/dist/dynamo/spend.js +45 -0
- package/dist/dynamo/spend.js.map +1 -0
- package/dist/dynamo/verify.js +483 -0
- package/dist/dynamo/verify.js.map +1 -0
- package/dist/governor/forbidden.js +181 -0
- package/dist/governor/forbidden.js.map +1 -0
- package/dist/governor/freshness.js +83 -0
- package/dist/governor/freshness.js.map +1 -0
- package/dist/governor/frontmatter.js +38 -0
- package/dist/governor/frontmatter.js.map +1 -0
- package/dist/governor/glob.js +75 -0
- package/dist/governor/glob.js.map +1 -0
- package/dist/governor/index.js +87 -0
- package/dist/governor/index.js.map +1 -0
- package/dist/governor/rules.js +80 -0
- package/dist/governor/rules.js.map +1 -0
- package/dist/governor/scope.js +91 -0
- package/dist/governor/scope.js.map +1 -0
- package/dist/governor/skills.js +176 -0
- package/dist/governor/skills.js.map +1 -0
- package/dist/governor/types.js +16 -0
- package/dist/governor/types.js.map +1 -0
- package/dist/governor/write.js +182 -0
- package/dist/governor/write.js.map +1 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/catalog.js +232 -0
- package/dist/mcp/catalog.js.map +1 -0
- package/dist/mcp/config.js +116 -0
- package/dist/mcp/config.js.map +1 -0
- package/dist/mcp/configWrite.js +254 -0
- package/dist/mcp/configWrite.js.map +1 -0
- package/dist/mcp/connection.js +504 -0
- package/dist/mcp/connection.js.map +1 -0
- package/dist/mcp/deferred.js +104 -0
- package/dist/mcp/deferred.js.map +1 -0
- package/dist/mcp/discover.js +128 -0
- package/dist/mcp/discover.js.map +1 -0
- package/dist/mcp/manager.js +679 -0
- package/dist/mcp/manager.js.map +1 -0
- package/dist/mcp/paramHeaders.js +163 -0
- package/dist/mcp/paramHeaders.js.map +1 -0
- package/dist/mcp/prompts.js +136 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/protocol.js +171 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/resources.js +119 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/resultStore.js +180 -0
- package/dist/mcp/resultStore.js.map +1 -0
- package/dist/mcp/subscriptions.js +74 -0
- package/dist/mcp/subscriptions.js.map +1 -0
- package/dist/mcp/transport/headerValue.js +40 -0
- package/dist/mcp/transport/headerValue.js.map +1 -0
- package/dist/mcp/transport/http.js +321 -0
- package/dist/mcp/transport/http.js.map +1 -0
- package/dist/mcp/transport/stdio.js +234 -0
- package/dist/mcp/transport/stdio.js.map +1 -0
- package/dist/mcp/transport/types.js +27 -0
- package/dist/mcp/transport/types.js.map +1 -0
- package/dist/mcp/trust.js +98 -0
- package/dist/mcp/trust.js.map +1 -0
- package/dist/mcp/trustStore.js +55 -0
- package/dist/mcp/trustStore.js.map +1 -0
- package/dist/memory/autoMemory.js +340 -0
- package/dist/memory/autoMemory.js.map +1 -0
- package/dist/memory/compaction.js +513 -0
- package/dist/memory/compaction.js.map +1 -0
- package/dist/memory/images.js +173 -0
- package/dist/memory/images.js.map +1 -0
- package/dist/memory/presence.js +59 -0
- package/dist/memory/presence.js.map +1 -0
- package/dist/memory/projectNotes.js +247 -0
- package/dist/memory/projectNotes.js.map +1 -0
- package/dist/memory/restore.js +72 -0
- package/dist/memory/restore.js.map +1 -0
- package/dist/memory/session.js +472 -0
- package/dist/memory/session.js.map +1 -0
- package/dist/memory/sessionMemory.js +142 -0
- package/dist/memory/sessionMemory.js.map +1 -0
- package/dist/memory/sessionMemoryCompact.js +136 -0
- package/dist/memory/sessionMemoryCompact.js.map +1 -0
- package/dist/memory/store.js +217 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/types.js +14 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/memory/workingSet.js +383 -0
- package/dist/memory/workingSet.js.map +1 -0
- package/dist/project/context.js +425 -0
- package/dist/project/context.js.map +1 -0
- package/dist/tools/approval.js +189 -0
- package/dist/tools/approval.js.map +1 -0
- package/dist/tools/askUser.js +103 -0
- package/dist/tools/askUser.js.map +1 -0
- package/dist/tools/atomicWrite.js +151 -0
- package/dist/tools/atomicWrite.js.map +1 -0
- package/dist/tools/backgroundShells.js +468 -0
- package/dist/tools/backgroundShells.js.map +1 -0
- package/dist/tools/captureSweep.js +77 -0
- package/dist/tools/captureSweep.js.map +1 -0
- package/dist/tools/chassisMux.js +92 -0
- package/dist/tools/chassisMux.js.map +1 -0
- package/dist/tools/checkpoints.js +328 -0
- package/dist/tools/checkpoints.js.map +1 -0
- package/dist/tools/codeIntel.js +267 -0
- package/dist/tools/codeIntel.js.map +1 -0
- package/dist/tools/deferredNative.js +121 -0
- package/dist/tools/deferredNative.js.map +1 -0
- package/dist/tools/detail.js +135 -0
- package/dist/tools/detail.js.map +1 -0
- package/dist/tools/diagnostics.js +174 -0
- package/dist/tools/diagnostics.js.map +1 -0
- package/dist/tools/edit.js +167 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/editCore.js +283 -0
- package/dist/tools/editCore.js.map +1 -0
- package/dist/tools/editFile.js +183 -0
- package/dist/tools/editFile.js.map +1 -0
- package/dist/tools/editRipple.js +126 -0
- package/dist/tools/editRipple.js.map +1 -0
- package/dist/tools/editTarget.js +131 -0
- package/dist/tools/editTarget.js.map +1 -0
- package/dist/tools/editWindow.js +49 -0
- package/dist/tools/editWindow.js.map +1 -0
- package/dist/tools/eol.js +56 -0
- package/dist/tools/eol.js.map +1 -0
- package/dist/tools/exitPlan.js +175 -0
- package/dist/tools/exitPlan.js.map +1 -0
- package/dist/tools/focus.js +42 -0
- package/dist/tools/focus.js.map +1 -0
- package/dist/tools/gitignore.js +148 -0
- package/dist/tools/gitignore.js.map +1 -0
- package/dist/tools/glob.js +134 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/governorTools.js +258 -0
- package/dist/tools/governorTools.js.map +1 -0
- package/dist/tools/grep.js +450 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/guard.js +220 -0
- package/dist/tools/guard.js.map +1 -0
- package/dist/tools/killTree.js +215 -0
- package/dist/tools/killTree.js.map +1 -0
- package/dist/tools/listDir.js +143 -0
- package/dist/tools/listDir.js.map +1 -0
- package/dist/tools/mcpAdd.js +176 -0
- package/dist/tools/mcpAdd.js.map +1 -0
- package/dist/tools/mcpResources.js +116 -0
- package/dist/tools/mcpResources.js.map +1 -0
- package/dist/tools/mcpSearch.js +123 -0
- package/dist/tools/mcpSearch.js.map +1 -0
- package/dist/tools/multiEdit.js +168 -0
- package/dist/tools/multiEdit.js.map +1 -0
- package/dist/tools/paths.js +225 -0
- package/dist/tools/paths.js.map +1 -0
- package/dist/tools/posixShell.js +123 -0
- package/dist/tools/posixShell.js.map +1 -0
- package/dist/tools/readFile.js +371 -0
- package/dist/tools/readFile.js.map +1 -0
- package/dist/tools/readSymbol.js +145 -0
- package/dist/tools/readSymbol.js.map +1 -0
- package/dist/tools/registry.js +120 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/replaceSymbol.js +141 -0
- package/dist/tools/replaceSymbol.js.map +1 -0
- package/dist/tools/results.js +15 -0
- package/dist/tools/results.js.map +1 -0
- package/dist/tools/ripgrep.js +145 -0
- package/dist/tools/ripgrep.js.map +1 -0
- package/dist/tools/runCommand.js +633 -0
- package/dist/tools/runCommand.js.map +1 -0
- package/dist/tools/saveMemory.js +102 -0
- package/dist/tools/saveMemory.js.map +1 -0
- package/dist/tools/screenshot.js +200 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/screenshotWin.js +227 -0
- package/dist/tools/screenshotWin.js.map +1 -0
- package/dist/tools/search.js +121 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/sessionTools.js +187 -0
- package/dist/tools/sessionTools.js.map +1 -0
- package/dist/tools/shellCheckpoint.js +123 -0
- package/dist/tools/shellCheckpoint.js.map +1 -0
- package/dist/tools/shellLint.js +102 -0
- package/dist/tools/shellLint.js.map +1 -0
- package/dist/tools/shellTools.js +203 -0
- package/dist/tools/shellTools.js.map +1 -0
- package/dist/tools/spanCore.js +78 -0
- package/dist/tools/spanCore.js.map +1 -0
- package/dist/tools/subagent.js +224 -0
- package/dist/tools/subagent.js.map +1 -0
- package/dist/tools/subagentReport.js +114 -0
- package/dist/tools/subagentReport.js.map +1 -0
- package/dist/tools/tempSweep.js +118 -0
- package/dist/tools/tempSweep.js.map +1 -0
- package/dist/tools/todo.js +117 -0
- package/dist/tools/todo.js.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/untrusted.js +46 -0
- package/dist/tools/untrusted.js.map +1 -0
- package/dist/tools/useSkill.js +62 -0
- package/dist/tools/useSkill.js.map +1 -0
- package/dist/tools/walk.js +152 -0
- package/dist/tools/walk.js.map +1 -0
- package/dist/tools/web.js +61 -0
- package/dist/tools/web.js.map +1 -0
- package/dist/tools/webFetch.js +444 -0
- package/dist/tools/webFetch.js.map +1 -0
- package/dist/tools/webSearch.js +130 -0
- package/dist/tools/webSearch.js.map +1 -0
- package/dist/tools/workspace.js +212 -0
- package/dist/tools/workspace.js.map +1 -0
- package/dist/tools/workspaceDiscover.js +216 -0
- package/dist/tools/workspaceDiscover.js.map +1 -0
- package/dist/tools/writeFile.js +162 -0
- package/dist/tools/writeFile.js.map +1 -0
- package/package.json +74 -28
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
import { outputDetail } from "../tools/detail.js";
|
|
2
|
+
import { formatBytes } from "../tools/webFetch.js";
|
|
3
|
+
import { formatElapsed } from "../tools/webSearch.js";
|
|
4
|
+
import { estimateCatalogTokens, frameUntrusted, mcpToolName, normalizeServerName, parseContentBlocks, parseMcpToolName, sortCatalog, toolSchemas as schemasFor, } from "./catalog.js";
|
|
5
|
+
import { binaryPointer, isOversized, oversizedPointer, spill, sweepOldResults } from "./resultStore.js";
|
|
6
|
+
import { renderPromptMessages } from "./prompts.js";
|
|
7
|
+
import { sortResources } from "./resources.js";
|
|
8
|
+
import { McpConnection } from "./connection.js";
|
|
9
|
+
import { searchCatalog, shouldDefer } from "./deferred.js";
|
|
10
|
+
import { compareTrust, acceptTools, changedToolsQuestion, fingerprintCatalog } from "./trust.js";
|
|
11
|
+
import { loadTrust, saveTrust } from "./trustStore.js";
|
|
12
|
+
/**
|
|
13
|
+
* Wrap one MCP tool def as a runnable Tool. Shared by the live lookup and the per-turn
|
|
14
|
+
* snapshot so both dispatch through exactly the same path.
|
|
15
|
+
*/
|
|
16
|
+
function buildTool(name, def, connection, spillRoot) {
|
|
17
|
+
return {
|
|
18
|
+
name,
|
|
19
|
+
description: def.description,
|
|
20
|
+
parameters: def.inputSchema,
|
|
21
|
+
readOnly: def.readOnly,
|
|
22
|
+
async execute(args) {
|
|
23
|
+
try {
|
|
24
|
+
// Timed at the call site: a remote server's round trip is the one cost in a
|
|
25
|
+
// tool row that nothing else on screen reveals, and it is what tells a slow
|
|
26
|
+
// server apart from a slow query.
|
|
27
|
+
const startedAt = Date.now();
|
|
28
|
+
const result = await connection.callTool(def.name, args);
|
|
29
|
+
const elapsedMs = Date.now() - startedAt;
|
|
30
|
+
const { blocks, isError } = parseContentBlocks(result);
|
|
31
|
+
const text = await renderBlocks(blocks, def, spillRoot());
|
|
32
|
+
// Framed as data, not instruction: this text is written by a third party and
|
|
33
|
+
// lands where the model trusts built-in tool output.
|
|
34
|
+
return {
|
|
35
|
+
output: frameUntrusted(def.server, text),
|
|
36
|
+
summary: `${def.server} · ${def.name}`,
|
|
37
|
+
detail: mcpDetail(def.name, args, text, isError, elapsedMs),
|
|
38
|
+
...(isError ? { isError: true } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
// A tool failure is a tool RESULT, not an exception: the model should see it,
|
|
43
|
+
// reason about it, and try something else — the same contract as a failed shell
|
|
44
|
+
// command. This is also where a server that died mid-turn surfaces, which is why
|
|
45
|
+
// the snapshot can keep offering it without lying.
|
|
46
|
+
return {
|
|
47
|
+
output: `Error: MCP tool '${name}' failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
48
|
+
isError: true,
|
|
49
|
+
summary: `${name} failed`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** The UI-only detail block under an MCP call row — never sent to the model
|
|
56
|
+
* (`frameUntrusted`'s `output` is what the model sees). A short args preview
|
|
57
|
+
* plus how much came back, capped the same way any other tool's output is. */
|
|
58
|
+
function mcpDetail(toolName, args, text, isError, elapsedMs) {
|
|
59
|
+
const argsPreview = Object.keys(args).length > 0 ? JSON.stringify(args).slice(0, 120) : "";
|
|
60
|
+
const lines = [];
|
|
61
|
+
// "Connected" is implied by having a result at all; the LATENCY is the part worth
|
|
62
|
+
// stating, and it is omitted rather than invented when nothing measured it.
|
|
63
|
+
if (elapsedMs !== undefined)
|
|
64
|
+
lines.push(`Status: Connected [Latency: ${formatElapsed(elapsedMs)}]`);
|
|
65
|
+
lines.push(`Executed: ${toolName}${argsPreview ? `(${argsPreview})` : "()"}`);
|
|
66
|
+
lines.push(isError
|
|
67
|
+
? "Received an error result"
|
|
68
|
+
: `Received ${formatBytes(Buffer.byteLength(text, "utf8"))} (OK) → ${text.length.toLocaleString("en-US")} chars`);
|
|
69
|
+
return outputDetail(lines.join("\n"));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Turn a result's blocks into the text the model receives, spilling what does not belong
|
|
73
|
+
* in a prompt.
|
|
74
|
+
*
|
|
75
|
+
* Two things go to disk. BYTES always: an image or a blob is base64 the model cannot
|
|
76
|
+
* look at, and inlining it is money spent on noise. OVERSIZED TEXT past the cap: the
|
|
77
|
+
* model gets the head plus the path, which beats a bare truncation because nothing is
|
|
78
|
+
* actually lost — it can read or grep the file for the part it needs.
|
|
79
|
+
*
|
|
80
|
+
* Failing to write is not fatal. The pointer falls back to naming the content, which is
|
|
81
|
+
* what the old code did for everything, so the worst case is the previous behaviour.
|
|
82
|
+
*/
|
|
83
|
+
async function renderBlocks(blocks, def, root) {
|
|
84
|
+
const parts = [];
|
|
85
|
+
for (const block of blocks) {
|
|
86
|
+
if (block.kind === "text") {
|
|
87
|
+
parts.push(block.text);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (block.kind === "link") {
|
|
91
|
+
// A link is already a pointer; it costs nothing and read_mcp_resource can follow it.
|
|
92
|
+
parts.push(`[resource: ${block.uri}${block.label ? ` — ${block.label}` : ""}]`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const bytes = Buffer.from(block.base64, "base64");
|
|
96
|
+
const path = await spill(root, def.server, def.name, block.mime, bytes);
|
|
97
|
+
parts.push(path ? binaryPointer(block.mime, bytes.byteLength, path) : `[${block.mime} content, ${bytes.byteLength} bytes, could not be saved]`);
|
|
98
|
+
}
|
|
99
|
+
const text = parts.join("\n") || "(no output)";
|
|
100
|
+
if (!isOversized(text))
|
|
101
|
+
return text;
|
|
102
|
+
const path = await spill(root, def.server, def.name, "text/plain", text);
|
|
103
|
+
// With nowhere to put it we still must not hand over the whole thing — an unbounded
|
|
104
|
+
// result is the failure this exists to prevent.
|
|
105
|
+
return path ? oversizedPointer(text, path) : oversizedPointer(text, "(could not be saved to disk)");
|
|
106
|
+
}
|
|
107
|
+
/** The two answers to a changed-description prompt. */
|
|
108
|
+
const ALLOW_CHANGED = "Allow the changed tools";
|
|
109
|
+
const BLOCK_CHANGED = "Block them this session";
|
|
110
|
+
/** How many servers to bring up at once. Enough to start fast, bounded so a config
|
|
111
|
+
* with twenty servers does not spawn twenty processes in the same instant. */
|
|
112
|
+
const CONNECT_BATCH = 6;
|
|
113
|
+
let cleanupRegistered = false;
|
|
114
|
+
const live = new Set();
|
|
115
|
+
export class McpManager {
|
|
116
|
+
connections = new Map();
|
|
117
|
+
started = false;
|
|
118
|
+
onChange = null;
|
|
119
|
+
/** Deferred tools the model has searched for and may now call. Session-lived. */
|
|
120
|
+
activated = new Set();
|
|
121
|
+
/** Fingerprints of tools the user has accepted, loaded from the project's state dir. */
|
|
122
|
+
trust = {};
|
|
123
|
+
/** Tools blocked because their description moved and the user declined. */
|
|
124
|
+
quarantined = new Set();
|
|
125
|
+
/** Tool names the project's governor forbids outright (`forbid_mcp_tool`). */
|
|
126
|
+
forbidden = new Set();
|
|
127
|
+
/**
|
|
128
|
+
* One-shot messages the user needs to see. Drained by the CLI, never re-sent.
|
|
129
|
+
*
|
|
130
|
+
* Blocking a tool with no visible explanation is barely better than not blocking it:
|
|
131
|
+
* the user sees a shorter tool list, assumes the server is broken, and has nothing to
|
|
132
|
+
* act on. Mirrors `BackgroundShells.takeUiNotifications`, which solved the same
|
|
133
|
+
* problem for finished shells.
|
|
134
|
+
*/
|
|
135
|
+
notices = [];
|
|
136
|
+
/**
|
|
137
|
+
* The project this pool belongs to: where the trust record is read and written, and
|
|
138
|
+
* where oversized results are spilled. Set as early as the session knows it, so a
|
|
139
|
+
* dispatch that beats the (async, queued) trust check still spills to the right place.
|
|
140
|
+
*/
|
|
141
|
+
trustCwd = "";
|
|
142
|
+
/**
|
|
143
|
+
* Serializes every piece of trust work.
|
|
144
|
+
*
|
|
145
|
+
* The initial verification and a notification-driven recheck can otherwise interleave:
|
|
146
|
+
* both read the record, both write it, and the second write loses whatever the first
|
|
147
|
+
* decided. A chain is enough — this is rare, cheap work, and correctness matters more
|
|
148
|
+
* than concurrency.
|
|
149
|
+
*/
|
|
150
|
+
trustQueue = Promise.resolve();
|
|
151
|
+
/** Fingerprints as of the last check, so an unchanged catalog costs nothing. */
|
|
152
|
+
trustSignature = "";
|
|
153
|
+
/**
|
|
154
|
+
* Told whenever any server's state or catalog moves, so the CLI can repaint.
|
|
155
|
+
*
|
|
156
|
+
* Servers connect in the background and can die or revive at any moment, so without
|
|
157
|
+
* this the UI would only ever show whatever was true when the last key was pressed.
|
|
158
|
+
* Mirrors `BackgroundShells.setOnChange`, which solved the same problem.
|
|
159
|
+
*/
|
|
160
|
+
setOnChange(handler) {
|
|
161
|
+
this.onChange = handler;
|
|
162
|
+
// Our own handler stays attached even when the UI detaches: it carries the trust
|
|
163
|
+
// recheck, which is a safety gate and not a rendering concern.
|
|
164
|
+
for (const connection of this.connections.values())
|
|
165
|
+
connection.setOnChange(() => this.onConnectionChange());
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Tell the pool which project it serves, before anything connects.
|
|
169
|
+
*
|
|
170
|
+
* Also the moment to clear out stale spilled results: they are scratch, this runs once
|
|
171
|
+
* per session, and sweeping here keeps the mechanism free of any background timer.
|
|
172
|
+
*/
|
|
173
|
+
setProjectRoot(cwd) {
|
|
174
|
+
this.trustCwd = cwd;
|
|
175
|
+
void sweepOldResults(cwd).catch(() => { });
|
|
176
|
+
}
|
|
177
|
+
/** Where spilled results go. Falls back to the process cwd in bare contexts (tests),
|
|
178
|
+
* which is the same directory the rest of the tool layer defaults to. */
|
|
179
|
+
spillRoot() {
|
|
180
|
+
return this.trustCwd || process.cwd();
|
|
181
|
+
}
|
|
182
|
+
/** Bring up every configured server. Never rejects: failures become states. */
|
|
183
|
+
async start(configs, log) {
|
|
184
|
+
if (this.started)
|
|
185
|
+
return;
|
|
186
|
+
this.started = true;
|
|
187
|
+
if (configs.length === 0)
|
|
188
|
+
return;
|
|
189
|
+
registerCleanup();
|
|
190
|
+
live.add(this);
|
|
191
|
+
for (const config of configs) {
|
|
192
|
+
const connection = new McpConnection(config);
|
|
193
|
+
connection.setOnChange(() => this.onConnectionChange());
|
|
194
|
+
this.connections.set(config.name, connection);
|
|
195
|
+
}
|
|
196
|
+
const pending = [...this.connections.values()].filter((c) => !c.config.disabled);
|
|
197
|
+
for (let i = 0; i < pending.length; i += CONNECT_BATCH) {
|
|
198
|
+
await Promise.all(pending.slice(i, i + CONNECT_BATCH).map(async (connection) => {
|
|
199
|
+
// `connect` already converts failure into state; this catch is for the
|
|
200
|
+
// genuinely unexpected, so one bad server cannot reject the whole batch.
|
|
201
|
+
await connection.connect().catch(() => { });
|
|
202
|
+
const status = connection.status();
|
|
203
|
+
log?.(status.state === "connected"
|
|
204
|
+
? `mcp ${status.name}: ${status.toolCount} tool${status.toolCount === 1 ? "" : "s"} (${status.version}${status.legacy ? ", legacy" : ""})`
|
|
205
|
+
: `mcp ${status.name}: ${status.state}${status.error ? ` — ${status.error}` : ""}`);
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Check the connected catalog against the fingerprints this project accepted before,
|
|
211
|
+
* and quarantine anything whose description or schema moved until the user says
|
|
212
|
+
* otherwise.
|
|
213
|
+
*
|
|
214
|
+
* Called after `start()`, with the project root and the approval channel, so the pool
|
|
215
|
+
* itself stays free of both filesystem and UI concerns. Fail-open on the mechanics
|
|
216
|
+
* (an unreadable record means everything reads as fresh) but fail-CLOSED on the
|
|
217
|
+
* decision: with no approval channel to ask through, changed tools stay blocked.
|
|
218
|
+
*/
|
|
219
|
+
async verifyTrust(cwd, ask) {
|
|
220
|
+
this.trustQueue = this.trustQueue.then(() => this.doVerifyTrust(cwd, ask)).catch(() => { });
|
|
221
|
+
return this.trustQueue;
|
|
222
|
+
}
|
|
223
|
+
async doVerifyTrust(cwd, ask) {
|
|
224
|
+
const defs = this.catalog();
|
|
225
|
+
// Remember the root even with nothing to check: a server can connect later, and the
|
|
226
|
+
// recheck below is inert until it knows where the record lives.
|
|
227
|
+
this.trustCwd = cwd;
|
|
228
|
+
if (defs.length === 0)
|
|
229
|
+
return;
|
|
230
|
+
this.trust = await loadTrust(cwd);
|
|
231
|
+
this.trustSignature = JSON.stringify(fingerprintCatalog(defs));
|
|
232
|
+
const verdict = compareTrust(defs, this.trust);
|
|
233
|
+
// First sight is trusted by construction — there is nothing to compare against.
|
|
234
|
+
// Recording it is what makes the NEXT change detectable.
|
|
235
|
+
if (verdict.fresh.length > 0) {
|
|
236
|
+
this.trust = acceptTools(this.trust, defs, verdict.fresh);
|
|
237
|
+
}
|
|
238
|
+
if (verdict.changed.length > 0) {
|
|
239
|
+
const allow = ask ? await ask(changedToolsQuestion(verdict.changed), [ALLOW_CHANGED, BLOCK_CHANGED]) : BLOCK_CHANGED;
|
|
240
|
+
if (allow === ALLOW_CHANGED) {
|
|
241
|
+
this.trust = acceptTools(this.trust, defs, verdict.changed);
|
|
242
|
+
for (const name of verdict.changed)
|
|
243
|
+
this.quarantined.delete(name);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Blocked for the session. The stored fingerprint is left ALONE so the same
|
|
247
|
+
// question is asked again next session rather than the change being forgotten.
|
|
248
|
+
for (const name of verdict.changed)
|
|
249
|
+
this.quarantined.add(name);
|
|
250
|
+
// Say so. Without this the user just sees fewer tools — which reads as a broken
|
|
251
|
+
// server, not a security decision they can act on. This is also the safety net
|
|
252
|
+
// for the case where there was no approval channel to ask through at all.
|
|
253
|
+
this.notices.push(`Blocked ${verdict.changed.length} MCP tool${verdict.changed.length === 1 ? "" : "s"} whose description ` +
|
|
254
|
+
`changed since you last used ${verdict.changed.length === 1 ? "it" : "them"}: ${verdict.changed.join(", ")}. ` +
|
|
255
|
+
`Run /mcp to review.`);
|
|
256
|
+
this.onChange?.();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (verdict.fresh.length > 0 || verdict.changed.length > 0)
|
|
260
|
+
await saveTrust(cwd, this.trust);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Re-verify after the catalog moves UNDERNEATH us, mid-session.
|
|
264
|
+
*
|
|
265
|
+
* This closes the hole the startup check left wide open. `trust.ts` names the rug pull
|
|
266
|
+
* as its whole reason to exist — "a clean server ships an update that poisons a
|
|
267
|
+
* description, and clients reload it silently" — and that is exactly what we did: a
|
|
268
|
+
* `notifications/tools/list_changed` refreshed the catalog and nothing compared the new
|
|
269
|
+
* descriptions against anything. A server therefore only had to connect clean and then
|
|
270
|
+
* announce a change to walk straight past a check built to stop it.
|
|
271
|
+
*
|
|
272
|
+
* It QUARANTINES rather than asking. The trigger arrives whenever the server feels like
|
|
273
|
+
* it, quite possibly mid-turn, and interrupting a running turn with a security question
|
|
274
|
+
* is both jarring and racy. Blocking is the safe direction, the notice says what
|
|
275
|
+
* happened, and `/mcp` already knows how to hand the question back.
|
|
276
|
+
*/
|
|
277
|
+
async recheckTrust() {
|
|
278
|
+
// Before the startup check has run there is no baseline to compare against, and
|
|
279
|
+
// treating everything as fresh here would record fingerprints we never verified.
|
|
280
|
+
if (!this.trustCwd)
|
|
281
|
+
return;
|
|
282
|
+
const defs = this.catalog();
|
|
283
|
+
if (defs.length === 0)
|
|
284
|
+
return;
|
|
285
|
+
const signature = JSON.stringify(fingerprintCatalog(defs));
|
|
286
|
+
if (signature === this.trustSignature)
|
|
287
|
+
return; // nothing moved; the common case
|
|
288
|
+
this.trustSignature = signature;
|
|
289
|
+
const verdict = compareTrust(defs, this.trust);
|
|
290
|
+
if (verdict.fresh.length === 0 && verdict.changed.length === 0)
|
|
291
|
+
return;
|
|
292
|
+
// A server that GAINS a tool is trusted on first sight, exactly as at startup —
|
|
293
|
+
// otherwise every ordinary server upgrade would look like an attack.
|
|
294
|
+
if (verdict.fresh.length > 0)
|
|
295
|
+
this.trust = acceptTools(this.trust, defs, verdict.fresh);
|
|
296
|
+
if (verdict.changed.length > 0) {
|
|
297
|
+
for (const name of verdict.changed)
|
|
298
|
+
this.quarantined.add(name);
|
|
299
|
+
this.notices.push(`An MCP server changed ${verdict.changed.length} tool description${verdict.changed.length === 1 ? "" : "s"} ` +
|
|
300
|
+
`while this session was running: ${verdict.changed.join(", ")}. ` +
|
|
301
|
+
`Blocked for now — run /mcp to review.`);
|
|
302
|
+
}
|
|
303
|
+
await saveTrust(this.trustCwd, this.trust);
|
|
304
|
+
this.onChange?.();
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* A server's state or catalog moved. Repaint, and check what moved.
|
|
308
|
+
*
|
|
309
|
+
* One handler for both so a catalog refresh can never reach the model without passing
|
|
310
|
+
* the trust gate — the previous wiring repainted and nothing else.
|
|
311
|
+
*/
|
|
312
|
+
onConnectionChange() {
|
|
313
|
+
// Catalogs are reloaded here (connect, and again on `list_changed`), so this is the
|
|
314
|
+
// one point every drop passes through, whichever load produced it.
|
|
315
|
+
for (const connection of this.connections.values())
|
|
316
|
+
this.notices.push(...connection.takeToolWarnings());
|
|
317
|
+
this.trustQueue = this.trustQueue.then(() => this.recheckTrust()).catch(() => { });
|
|
318
|
+
this.onChange?.();
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Re-offer the currently quarantined tools, so a block is recoverable in-session.
|
|
322
|
+
*
|
|
323
|
+
* Needed because the automatic check runs while the pool connects in the background,
|
|
324
|
+
* possibly before the CLI has an approval channel to ask through. That path fails
|
|
325
|
+
* closed and silent by design; this is how the user gets the question back.
|
|
326
|
+
*/
|
|
327
|
+
async reviewQuarantine(ask) {
|
|
328
|
+
const blocked = this.quarantinedNames();
|
|
329
|
+
if (blocked.length === 0)
|
|
330
|
+
return false;
|
|
331
|
+
const answer = await ask(changedToolsQuestion(blocked), [ALLOW_CHANGED, BLOCK_CHANGED]);
|
|
332
|
+
if (answer !== ALLOW_CHANGED)
|
|
333
|
+
return false;
|
|
334
|
+
this.trust = acceptTools(this.trust, this.catalog(), blocked);
|
|
335
|
+
for (const name of blocked)
|
|
336
|
+
this.quarantined.delete(name);
|
|
337
|
+
if (this.trustCwd)
|
|
338
|
+
await saveTrust(this.trustCwd, this.trust);
|
|
339
|
+
this.onChange?.();
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
/** Drain user-facing notices. One-shot: each is reported exactly once. */
|
|
343
|
+
takeNotices() {
|
|
344
|
+
const out = this.notices;
|
|
345
|
+
this.notices = [];
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
/** How many of a server's tools are currently blocked, for `/mcp`. */
|
|
349
|
+
blockedCountFor(server) {
|
|
350
|
+
const prefix = mcpToolName(server, "");
|
|
351
|
+
return this.quarantinedNames().filter((n) => n.startsWith(prefix)).length;
|
|
352
|
+
}
|
|
353
|
+
/** Apply the project's forbidden-MCP-tool list. Replaces the previous set. */
|
|
354
|
+
setForbidden(names) {
|
|
355
|
+
this.forbidden = new Set(names);
|
|
356
|
+
// A tool that becomes forbidden must not linger in the activated set, or a deferred
|
|
357
|
+
// catalog would keep advertising it.
|
|
358
|
+
for (const name of names)
|
|
359
|
+
this.activated.delete(name);
|
|
360
|
+
}
|
|
361
|
+
/** Tools currently blocked because their description changed. */
|
|
362
|
+
quarantinedNames() {
|
|
363
|
+
return [...this.quarantined].sort();
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Bring one server up NOW, without restarting the session.
|
|
367
|
+
*
|
|
368
|
+
* Adding a server and then being told to restart would make the add path useless —
|
|
369
|
+
* the whole point is that you say what you want and it is there. Replaces any server
|
|
370
|
+
* of the same name, shutting the old one down first so we never leak a child process
|
|
371
|
+
* on a re-add.
|
|
372
|
+
*/
|
|
373
|
+
async addServer(config) {
|
|
374
|
+
registerCleanup();
|
|
375
|
+
live.add(this);
|
|
376
|
+
this.started = true;
|
|
377
|
+
const previous = this.connections.get(config.name);
|
|
378
|
+
if (previous)
|
|
379
|
+
await previous.close().catch(() => { });
|
|
380
|
+
const connection = new McpConnection(config);
|
|
381
|
+
connection.setOnChange(() => this.onConnectionChange());
|
|
382
|
+
this.connections.set(config.name, connection);
|
|
383
|
+
if (!config.disabled)
|
|
384
|
+
await connection.connect().catch(() => { });
|
|
385
|
+
// A re-added server is a different server as far as trust goes; drop any stale
|
|
386
|
+
// activation so a deferred catalog does not advertise tools it no longer has.
|
|
387
|
+
for (const name of this.activatedNames()) {
|
|
388
|
+
if (name.startsWith(mcpToolName(config.name, "")))
|
|
389
|
+
this.activated.delete(name);
|
|
390
|
+
}
|
|
391
|
+
// Fingerprint what it brought. Without this a server added mid-session was never
|
|
392
|
+
// recorded at all, so its very first descriptions became the baseline only on the
|
|
393
|
+
// NEXT session — and anything it changed in between went unnoticed.
|
|
394
|
+
this.onConnectionChange();
|
|
395
|
+
return connection.status();
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Every prompt every connected server offers, as slash commands.
|
|
399
|
+
*
|
|
400
|
+
* Synchronous because the CLI reads it while rendering the completion menu — a
|
|
401
|
+
* keystroke cannot wait on a round trip, which is why prompts are fetched at connect.
|
|
402
|
+
*/
|
|
403
|
+
promptCatalog() {
|
|
404
|
+
return [...this.connections.values()]
|
|
405
|
+
.filter((c) => c.isConnected())
|
|
406
|
+
.flatMap((c) => [...c.prompts()])
|
|
407
|
+
.sort((a, b) => a.server.localeCompare(b.server) || a.name.localeCompare(b.name));
|
|
408
|
+
}
|
|
409
|
+
/** Look up one prompt by its server and name. */
|
|
410
|
+
findPrompt(server, name) {
|
|
411
|
+
return this.promptCatalog().find((p) => p.server === server && p.name === name);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Render a prompt into the text of a user turn.
|
|
415
|
+
*
|
|
416
|
+
* Errors are returned rather than thrown: the caller is a slash command, and the user
|
|
417
|
+
* needs to be told what went wrong in the chat, not shown a stack.
|
|
418
|
+
*/
|
|
419
|
+
async renderPrompt(server, name, args) {
|
|
420
|
+
const connection = this.connectionFor(server);
|
|
421
|
+
if (!connection)
|
|
422
|
+
return { text: "", error: `MCP server '${server}' is not connected.` };
|
|
423
|
+
try {
|
|
424
|
+
const rendered = renderPromptMessages(await connection.getPrompt(name, args));
|
|
425
|
+
if (!rendered)
|
|
426
|
+
return { text: "", error: `'${server}:${name}' returned nothing to run.` };
|
|
427
|
+
return { text: rendered };
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
return { text: "", error: error instanceof Error ? error.message : String(error) };
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* List resources across every connected server, or one of them.
|
|
435
|
+
*
|
|
436
|
+
* Fetches on demand rather than at connect: most projects never touch resources, and
|
|
437
|
+
* two extra round trips per server would be paid by every session to serve the few
|
|
438
|
+
* that do. The per-connection TTL keeps a model that lists twice in one turn from
|
|
439
|
+
* paying twice.
|
|
440
|
+
*/
|
|
441
|
+
async listResources(server) {
|
|
442
|
+
const connections = [...this.connections.values()].filter((c) => c.isConnected() && c.offersResources() && (!server || matchesServer(c.config.name, server)));
|
|
443
|
+
await Promise.all(connections.map((c) => c.loadResources().catch(() => { })));
|
|
444
|
+
return {
|
|
445
|
+
resources: sortResources(connections.flatMap((c) => [...c.resources()])),
|
|
446
|
+
templates: connections.flatMap((c) => [...c.resourceTemplates()]),
|
|
447
|
+
servers: connections.length,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Read one resource, spilling it to disk if it is large or binary.
|
|
452
|
+
*
|
|
453
|
+
* Same treatment as a tool result, for the same reason: a resource is content chosen
|
|
454
|
+
* by a third party, and "read the schema" should not be able to swallow a turn.
|
|
455
|
+
*/
|
|
456
|
+
async readResource(server, uri) {
|
|
457
|
+
const connection = this.connectionFor(server);
|
|
458
|
+
if (!connection) {
|
|
459
|
+
return { text: `No connected MCP server named '${server}'.`, isError: true };
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
const blocks = await connection.readResource(uri);
|
|
463
|
+
if (blocks.length === 0)
|
|
464
|
+
return { text: `'${uri}' returned no content.`, isError: false };
|
|
465
|
+
const body = await renderBlocks(blocks, { server, name: resourceLabel(uri) }, this.spillRoot());
|
|
466
|
+
return { text: frameUntrusted(server, body), isError: false };
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
return { text: `Could not read '${uri}': ${error instanceof Error ? error.message : String(error)}`, isError: true };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/** Which servers advertise resources at all, for a useful empty-state message. */
|
|
473
|
+
resourceServers() {
|
|
474
|
+
return [...this.connections.values()]
|
|
475
|
+
.filter((c) => c.isConnected() && c.offersResources())
|
|
476
|
+
.map((c) => c.config.name)
|
|
477
|
+
.sort();
|
|
478
|
+
}
|
|
479
|
+
/** Resolve a server by its config name, tolerating the normalized form the model sees. */
|
|
480
|
+
connectionFor(server) {
|
|
481
|
+
return [...this.connections.values()].find((c) => c.isConnected() && matchesServer(c.config.name, server));
|
|
482
|
+
}
|
|
483
|
+
/** Every server's state, for `/mcp` and for diagnostics. */
|
|
484
|
+
statuses() {
|
|
485
|
+
return [...this.connections.values()].map((c) => c.status()).sort((a, b) => a.name.localeCompare(b.name));
|
|
486
|
+
}
|
|
487
|
+
/** Force a reconnect of one server by name. Returns its state afterwards. */
|
|
488
|
+
async reconnect(name) {
|
|
489
|
+
const connection = this.connections.get(name);
|
|
490
|
+
if (!connection)
|
|
491
|
+
return null;
|
|
492
|
+
await connection.reconnect().catch(() => { });
|
|
493
|
+
this.onChange?.();
|
|
494
|
+
return connection.status();
|
|
495
|
+
}
|
|
496
|
+
/** True when there is anything at all to show in `/mcp`. */
|
|
497
|
+
hasServers() {
|
|
498
|
+
return this.connections.size > 0;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* A frozen view of the catalog for ONE turn.
|
|
502
|
+
*
|
|
503
|
+
* The engine advertises a tool list to the model and then dispatches whatever it
|
|
504
|
+
* calls, and those were two separate reads of live state. In between, a server can
|
|
505
|
+
* die or send `notifications/tools/list_changed` — so a tool the model was OFFERED at
|
|
506
|
+
* the top of the turn could be gone by the time it called it, and the model would be
|
|
507
|
+
* told "no such tool" for something we had just told it about. That is a confusing
|
|
508
|
+
* failure to debug and an easy one to blame on the model.
|
|
509
|
+
*
|
|
510
|
+
* Taking one snapshot and using it for both halves makes the turn internally
|
|
511
|
+
* consistent: what was offered stays callable. If the underlying server really has
|
|
512
|
+
* gone, the call fails at dispatch with a real error the model can act on, which is
|
|
513
|
+
* an honest outcome rather than a contradiction.
|
|
514
|
+
*
|
|
515
|
+
* It also pins the exact bytes sent as `tools` for the turn, which is what keeps the
|
|
516
|
+
* cached prompt prefix intact across the turn's steps.
|
|
517
|
+
*/
|
|
518
|
+
snapshot(readOnlyOnly = false) {
|
|
519
|
+
const defs = this.allowedCatalog().filter((d) => !readOnlyOnly || d.readOnly);
|
|
520
|
+
// Bind each def to the connection that owned it AT SNAPSHOT TIME, so dispatch does
|
|
521
|
+
// not go looking through a pool that may have changed underneath us.
|
|
522
|
+
const owners = new Map();
|
|
523
|
+
for (const connection of this.connections.values()) {
|
|
524
|
+
for (const def of connection.tools())
|
|
525
|
+
owners.set(mcpToolName(def.server, def.name), connection);
|
|
526
|
+
}
|
|
527
|
+
const deferred = shouldDefer(defs.length);
|
|
528
|
+
// Under the threshold everything is exposed; over it, only what has been activated.
|
|
529
|
+
const exposed = () => (deferred ? defs.filter((d) => this.activated.has(mcpToolName(d.server, d.name))) : defs);
|
|
530
|
+
return {
|
|
531
|
+
deferred,
|
|
532
|
+
catalog: defs,
|
|
533
|
+
toolCount: defs.length,
|
|
534
|
+
exposedSchemas: () => schemasFor(exposed(), readOnlyOnly),
|
|
535
|
+
tokens: () => estimateCatalogTokens(exposed()),
|
|
536
|
+
asTool: (name) => {
|
|
537
|
+
const def = defs.find((d) => mcpToolName(d.server, d.name) === name);
|
|
538
|
+
const connection = owners.get(name);
|
|
539
|
+
if (!def || !connection)
|
|
540
|
+
return undefined;
|
|
541
|
+
return buildTool(name, def, connection, () => this.spillRoot());
|
|
542
|
+
},
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Search the catalog and ACTIVATE what matches, so those tools are advertised from
|
|
547
|
+
* the next step onward.
|
|
548
|
+
*
|
|
549
|
+
* Activation is sticky for the session on purpose. A tool the model reached for once
|
|
550
|
+
* it will likely reach for again, and re-hiding it would mean another search round
|
|
551
|
+
* trip and another change to the advertised tool list — which is the thing that costs
|
|
552
|
+
* a prompt-cache prefix. Paying once and keeping it is cheaper than paying repeatedly.
|
|
553
|
+
*/
|
|
554
|
+
/**
|
|
555
|
+
* Find deferred tools matching a query. Deliberately does NOT advertise them.
|
|
556
|
+
*
|
|
557
|
+
* It used to, and that was the expensive half of deferral: adding a found tool to the
|
|
558
|
+
* `tools` array changes the bytes the provider hashes, so every request after a search
|
|
559
|
+
* paid to rewrite the whole cached prefix — tools, system AND messages. The saving was
|
|
560
|
+
* a few hundred tokens of schema; the bill was the entire prefix. The caller now hands
|
|
561
|
+
* the model the full schema in the search RESULT instead (see renderResults), which is
|
|
562
|
+
* an appended message the cache does not care about, and `asTool` dispatches against
|
|
563
|
+
* the allowed catalog rather than against what was advertised.
|
|
564
|
+
*/
|
|
565
|
+
searchCatalogTools(query) {
|
|
566
|
+
return searchCatalog(query, this.allowedCatalog());
|
|
567
|
+
}
|
|
568
|
+
/** Tool names currently advertised out of a deferred catalog (for diagnostics). */
|
|
569
|
+
activatedNames() {
|
|
570
|
+
return [...this.activated].sort();
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Roughly what the live catalog costs in prompt tokens.
|
|
574
|
+
*
|
|
575
|
+
* The compaction bars measure the transcript only, so without this correction a large
|
|
576
|
+
* MCP catalog makes every threshold fire that much too late.
|
|
577
|
+
*/
|
|
578
|
+
estimatedTokens() {
|
|
579
|
+
// What is SENT, not what exists: a deferred catalog costs only its activated part,
|
|
580
|
+
// and counting the hidden remainder would make the compaction bars fire early for
|
|
581
|
+
// tokens the model never receives.
|
|
582
|
+
return this.snapshot().tokens();
|
|
583
|
+
}
|
|
584
|
+
/** Every tool across every connected server, in a stable order. */
|
|
585
|
+
catalog() {
|
|
586
|
+
return sortCatalog([...this.connections.values()].filter((c) => c.isConnected()).flatMap((c) => [...c.tools()]));
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* The catalog minus anything the user has blocked. ONE chokepoint for both governance
|
|
590
|
+
* gates, so a blocked tool cannot be advertised, searched, activated or dispatched —
|
|
591
|
+
* there is no path that consults the raw catalog instead.
|
|
592
|
+
*/
|
|
593
|
+
allowedCatalog() {
|
|
594
|
+
return this.catalog().filter((d) => {
|
|
595
|
+
const name = mcpToolName(d.server, d.name);
|
|
596
|
+
return !this.quarantined.has(name) && !this.forbidden.has(name);
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
/** How many tools are currently offered (for status lines and budgeting). */
|
|
600
|
+
toolCount() {
|
|
601
|
+
return this.allowedCatalog().length;
|
|
602
|
+
}
|
|
603
|
+
/** Schemas for every MCP tool. `readOnlyOnly` drops mutating tools, mirroring the
|
|
604
|
+
* built-in registry's behaviour in plan mode and read-only sub-agents. */
|
|
605
|
+
toolSchemas(readOnlyOnly = false) {
|
|
606
|
+
return schemasFor(this.allowedCatalog(), readOnlyOnly);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Resolve an advertised MCP name to a runnable Tool, or undefined if we do not serve
|
|
610
|
+
* it. Returning undefined (rather than a tool that errors) is what lets the engine's
|
|
611
|
+
* lookup fall through to its normal "no such tool" path.
|
|
612
|
+
*/
|
|
613
|
+
asTool(name) {
|
|
614
|
+
const parsed = parseMcpToolName(name);
|
|
615
|
+
if (!parsed)
|
|
616
|
+
return undefined;
|
|
617
|
+
// Resolve against the ALLOWED catalog, not the raw connections. Reading the
|
|
618
|
+
// connections directly was a hole: a forbidden or quarantined tool stayed
|
|
619
|
+
// dispatchable through this path even though it was gone from every list. A gate
|
|
620
|
+
// that closes three of four doors is not a gate.
|
|
621
|
+
const def = this.allowedCatalog().find((d) => mcpToolName(d.server, d.name) === name);
|
|
622
|
+
if (!def)
|
|
623
|
+
return undefined;
|
|
624
|
+
// Config names are normalized on the way INTO a tool name, so a server called
|
|
625
|
+
// "acme.tools" is looked up by its normalized form here.
|
|
626
|
+
const connection = [...this.connections.values()].find((c) => c.isConnected() && mcpToolName(c.config.name, "x") === mcpToolName(parsed.server, "x"));
|
|
627
|
+
if (!connection)
|
|
628
|
+
return undefined;
|
|
629
|
+
return buildTool(name, def, connection, () => this.spillRoot());
|
|
630
|
+
}
|
|
631
|
+
async dispose() {
|
|
632
|
+
const closing = [...this.connections.values()].map((c) => c.close().catch(() => { }));
|
|
633
|
+
this.connections.clear();
|
|
634
|
+
live.delete(this);
|
|
635
|
+
await Promise.all(closing);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Teardown for a process-exit handler, where nothing can be awaited.
|
|
639
|
+
*
|
|
640
|
+
* `close(true)` reaps a shelled server with a blocking kill; the synchronous
|
|
641
|
+
* part of each close runs before this returns, which is all the exit handler
|
|
642
|
+
* gets. The returned promises are deliberately dropped.
|
|
643
|
+
*/
|
|
644
|
+
disposeSync() {
|
|
645
|
+
for (const c of this.connections.values())
|
|
646
|
+
void c.close(true).catch(() => { });
|
|
647
|
+
this.connections.clear();
|
|
648
|
+
live.delete(this);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Does this config name refer to the server the model named?
|
|
653
|
+
*
|
|
654
|
+
* The model only ever sees NORMALIZED names (`acme_tools` for a server configured as
|
|
655
|
+
* `acme.tools`), because that is what survives being embedded in a tool name. So a
|
|
656
|
+
* resource or prompt call naming its server has to be matched through the same
|
|
657
|
+
* normalization, or every server with a dot in its name becomes unreachable.
|
|
658
|
+
*/
|
|
659
|
+
function matchesServer(configName, requested) {
|
|
660
|
+
return configName === requested || normalizeServerName(configName) === normalizeServerName(requested);
|
|
661
|
+
}
|
|
662
|
+
/** A short, filename-ish label for a resource URI, used when spilling it to disk. */
|
|
663
|
+
function resourceLabel(uri) {
|
|
664
|
+
const tail = uri.split(/[/?#]/).filter(Boolean).pop() ?? "resource";
|
|
665
|
+
return tail.slice(0, 32);
|
|
666
|
+
}
|
|
667
|
+
/** Kill any servers still running when the process exits. Child processes do not die
|
|
668
|
+
* with their parent on every platform, and an orphaned server holds a port or a lock. */
|
|
669
|
+
function registerCleanup() {
|
|
670
|
+
if (cleanupRegistered)
|
|
671
|
+
return;
|
|
672
|
+
cleanupRegistered = true;
|
|
673
|
+
process.once("exit", () => {
|
|
674
|
+
// Synchronous kill: an async one never reaches the OS from here.
|
|
675
|
+
for (const manager of [...live])
|
|
676
|
+
manager.disposeSync();
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
//# sourceMappingURL=manager.js.map
|