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,633 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { promises as fs, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { StringDecoder } from "node:string_decoder";
|
|
6
|
+
import { catastrophicCommandReason, sensitiveCommandReason } from "./guard.js";
|
|
7
|
+
import { forbiddenCommandReason, forbiddenCommandPatternReason } from "../governor/forbidden.js";
|
|
8
|
+
import { requestForbiddenLift } from "./approval.js";
|
|
9
|
+
import { posixShell, shellMismatchNote } from "./posixShell.js";
|
|
10
|
+
import { killTree, spawnManaged } from "./killTree.js";
|
|
11
|
+
import { captureAfterCommand, looksReadOnly, snapshotBeforeCommand } from "./shellCheckpoint.js";
|
|
12
|
+
import { canonicalRoot, relativize } from "./paths.js";
|
|
13
|
+
import { outputDetail, withOutcome } from "./detail.js";
|
|
14
|
+
import { powershellLintReason, powershellParseError, powershellReservedAssignmentReason } from "./shellLint.js";
|
|
15
|
+
import { findRunningDuplicate, findRecentUserClose, guessNotifyPolicy } from "./backgroundShells.js";
|
|
16
|
+
import { fail, failQuietly } from "./results.js";
|
|
17
|
+
const IS_WINDOWS = process.platform === "win32";
|
|
18
|
+
const DEFAULT_TIMEOUT_MS = 120_000; // 2 minutes
|
|
19
|
+
const MAX_TIMEOUT_MS = 600_000; // 10 minutes
|
|
20
|
+
/**
|
|
21
|
+
* How much command output reaches the model, split across the two ends.
|
|
22
|
+
*
|
|
23
|
+
* Keeping the FIRST 30,000 characters and dropping the rest throws away the part
|
|
24
|
+
* that matters: a build or test run puts its banner and progress at the start and
|
|
25
|
+
* its diagnosis — the failing assertion, the stack, "3 tests failed" — at the very
|
|
26
|
+
* end. A verbose `tsc` or webpack run overflows the budget on progress noise alone,
|
|
27
|
+
* so the model would receive a wall of chatter, a truncation notice, and nothing
|
|
28
|
+
* about why the command failed.
|
|
29
|
+
*
|
|
30
|
+
* Both ends are kept instead, weighted toward the tail. The same total budget.
|
|
31
|
+
*/
|
|
32
|
+
const HEAD_CHARS = 8_000;
|
|
33
|
+
const TAIL_CHARS = 22_000;
|
|
34
|
+
/** How long to wait after `exit` for `close` before settling anyway. See the listener
|
|
35
|
+
* in runShell: a surviving grandchild can hold the output pipe open forever. */
|
|
36
|
+
const CLOSE_GRACE_MS = 2_000;
|
|
37
|
+
/**
|
|
38
|
+
* Human label for the shell, kept in sync with the system prompt.
|
|
39
|
+
*
|
|
40
|
+
* Reports what will ACTUALLY run, resolved on this machine, rather than a fixed
|
|
41
|
+
* string. It used to say "the POSIX shell (sh)" everywhere off Windows, which became
|
|
42
|
+
* a false claim the moment bash was preferred — and a prompt that asserts something
|
|
43
|
+
* untrue is worse than one that says nothing, because the model believes it and
|
|
44
|
+
* writes to the wrong dialect.
|
|
45
|
+
*/
|
|
46
|
+
export function commandShellLabel() {
|
|
47
|
+
if (IS_WINDOWS)
|
|
48
|
+
return "Windows PowerShell";
|
|
49
|
+
return posixShell().isBash ? "bash" : "a strict POSIX shell (sh)";
|
|
50
|
+
}
|
|
51
|
+
export const runCommand = {
|
|
52
|
+
name: "run_command",
|
|
53
|
+
readOnly: false,
|
|
54
|
+
// Two claims in the previous version of this description were false, and both were
|
|
55
|
+
// the kind a model obeys without being able to check. It promised that a backgrounded
|
|
56
|
+
// command would report when it finished, which is true only for notify:'on_finish'
|
|
57
|
+
// and flatly contradicted by 'on_failure' (the default for anything server-shaped).
|
|
58
|
+
// And it warned that a never-terminating command would "hang the turn", which the
|
|
59
|
+
// soft timeout has made untrue, while scaring the model away from the very feature
|
|
60
|
+
// built for it. What replaced them is what actually happens.
|
|
61
|
+
description: `Run a shell command in the project and return its combined output and exit ` +
|
|
62
|
+
`code. The shell is ${commandShellLabel()}. Every turn starts at the project root; ` +
|
|
63
|
+
`the working directory persists between calls WITHIN a turn (so 'cd' carries over ` +
|
|
64
|
+
`mid-turn) but resets to the root next turn. ` +
|
|
65
|
+
`A command still running after 2 minutes (or 'timeout' ms, up to 10 minutes) is ` +
|
|
66
|
+
`MOVED TO THE BACKGROUND, not killed: you get a shell id and the session carries on. ` +
|
|
67
|
+
`Read its output any time with the shells tool. WHAT YOU HEAR AFTERWARDS IS SET BY ` +
|
|
68
|
+
`'notify', so choose it deliberately: only 'on_finish' reports that the command ` +
|
|
69
|
+
`ended, and it is not the default for everything. ` +
|
|
70
|
+
`Pass 'run_in_background: true' to background it from the start, and do that for a ` +
|
|
71
|
+
`dev server, a long build or test you do not need to wait on, and anything ` +
|
|
72
|
+
`interactive or never-terminating. Waiting on one of those inline will not hang the ` +
|
|
73
|
+
`turn, but it burns the whole timeout before backgrounding itself, which is time ` +
|
|
74
|
+
`spent for nothing. ` +
|
|
75
|
+
`Write commands for ${commandShellLabel()} (see the shell section of the system prompt)` +
|
|
76
|
+
`${IS_WINDOWS ? "; or pass shell:'cmd' to run in cmd.exe instead (for && / || chaining or cmd-only tools)" : ""}. ` +
|
|
77
|
+
`Prefer Mindweave's read/edit/search tools over shelling out.`,
|
|
78
|
+
parameters: {
|
|
79
|
+
type: "object",
|
|
80
|
+
additionalProperties: false,
|
|
81
|
+
required: ["command"],
|
|
82
|
+
properties: {
|
|
83
|
+
command: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: "The shell command to run.",
|
|
86
|
+
},
|
|
87
|
+
shell: {
|
|
88
|
+
type: "string",
|
|
89
|
+
enum: ["powershell", "cmd"],
|
|
90
|
+
description: "Windows only (ignored elsewhere): which shell to run in. Default 'powershell'. Choose " +
|
|
91
|
+
"'cmd' for commands written in cmd.exe syntax — && / || chaining, or a tool that misbehaves " +
|
|
92
|
+
"under PowerShell — Mindweave runs them as a batch script. Note: in cmd a for-loop uses %%i, and " +
|
|
93
|
+
"cwd may not carry over when the command itself is a .cmd tool (chain in one call instead).",
|
|
94
|
+
},
|
|
95
|
+
timeout: {
|
|
96
|
+
type: "integer",
|
|
97
|
+
minimum: 1000,
|
|
98
|
+
maximum: MAX_TIMEOUT_MS,
|
|
99
|
+
description: `How long to wait inline before moving it to the background, in ms (default 120000, max ${MAX_TIMEOUT_MS}).`,
|
|
100
|
+
},
|
|
101
|
+
run_in_background: {
|
|
102
|
+
type: "boolean",
|
|
103
|
+
description: "Start it in the background immediately and return a shell id (don't wait).",
|
|
104
|
+
},
|
|
105
|
+
notify: {
|
|
106
|
+
type: "string",
|
|
107
|
+
enum: ["on_finish", "on_failure", "never"],
|
|
108
|
+
description: "What you want to be told about a backgrounded command. 'on_finish' (default for tasks): " +
|
|
109
|
+
"you're told when it ends, whatever the result — use it for builds, tests, installs, " +
|
|
110
|
+
"anything whose RESULT is the point. 'on_failure': you're told when it has come up, and if " +
|
|
111
|
+
"it never does, but NOT when it stops — use it for dev servers and apps, because the user " +
|
|
112
|
+
"closing their own app is not something to act on. 'never': you're told nothing at all. " +
|
|
113
|
+
"Say which; guessing from the command name gets it wrong for anything unusual.",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
async execute(args, ctx) {
|
|
118
|
+
const command = typeof args.command === "string" ? args.command.trim() : "";
|
|
119
|
+
if (!command)
|
|
120
|
+
return failQuietly("`command` is required.");
|
|
121
|
+
// A shell can change files in ways the checkpoint net never sees (a formatter, a
|
|
122
|
+
// codegen step, a `git checkout`). Flag the turn so /undo says what it did NOT
|
|
123
|
+
// cover instead of implying the whole turn was rolled back.
|
|
124
|
+
ctx.checkpoints?.noteShell();
|
|
125
|
+
const blocked = catastrophicCommandReason(command);
|
|
126
|
+
if (blocked) {
|
|
127
|
+
return fail(`Refusing to run this command: it looks like ${blocked}.`);
|
|
128
|
+
}
|
|
129
|
+
// A shell would sidestep every per-file gate the read/write tools enforce.
|
|
130
|
+
const sensitive = sensitiveCommandReason(command);
|
|
131
|
+
if (sensitive) {
|
|
132
|
+
return fail(`Refusing to run this command: it would print the contents of ${sensitive} into ` +
|
|
133
|
+
`the conversation. Checking whether the file exists, listing it, or copying it ` +
|
|
134
|
+
`is fine — reading it out is not. If you genuinely need what's inside, use ` +
|
|
135
|
+
`read_file, which asks the user first.`);
|
|
136
|
+
}
|
|
137
|
+
const forbidden = forbiddenCommandReason(ctx.governance?.forbidden, command);
|
|
138
|
+
if (forbidden) {
|
|
139
|
+
const lift = await requestForbiddenLift(ctx, forbidden, "this command", `it references '${forbidden}', which the user has forbidden touching.`);
|
|
140
|
+
if (lift)
|
|
141
|
+
return lift; // refused or deferred; an allow lifts it and falls through
|
|
142
|
+
}
|
|
143
|
+
// Forbidden COMMAND patterns: a command the user said never to run (e.g. `tauri
|
|
144
|
+
// dev`). Deterministic — the model cannot bypass it; only the user can lift it
|
|
145
|
+
// (same approval channel as forbidden paths, session-only).
|
|
146
|
+
const forbiddenCmd = forbiddenCommandPatternReason(ctx.governance?.forbidden, command);
|
|
147
|
+
if (forbiddenCmd) {
|
|
148
|
+
const lift = await requestForbiddenLift(ctx, forbiddenCmd, "this command", `the user has forbidden running '${forbiddenCmd}'.`, "forbidden command");
|
|
149
|
+
if (lift)
|
|
150
|
+
return lift;
|
|
151
|
+
}
|
|
152
|
+
let timeout = DEFAULT_TIMEOUT_MS;
|
|
153
|
+
if (typeof args.timeout === "number" && Number.isFinite(args.timeout)) {
|
|
154
|
+
timeout = Math.min(MAX_TIMEOUT_MS, Math.max(1000, Math.floor(args.timeout)));
|
|
155
|
+
}
|
|
156
|
+
const shell = args.shell === "cmd" ? "cmd" : "powershell";
|
|
157
|
+
// Already-running guard: don't start a second copy of something already running in
|
|
158
|
+
// the background. Two dev servers collide on a port and the model then burns a long
|
|
159
|
+
// loop fighting the conflict it created; two of anything else is nearly always a
|
|
160
|
+
// mistake too.
|
|
161
|
+
//
|
|
162
|
+
// This deliberately does NOT ask whether the command looks like a server. It used to,
|
|
163
|
+
// which meant the guard only covered the names in one regex: `cargo run`,
|
|
164
|
+
// `docker compose up`, `flask run` and a plain path to a binary were all unprotected.
|
|
165
|
+
// "Is this exact command already running" needs no such guess and covers everything.
|
|
166
|
+
if (ctx.backgroundShells) {
|
|
167
|
+
const dup = findRunningDuplicate(ctx.backgroundShells.running(), command);
|
|
168
|
+
if (dup) {
|
|
169
|
+
return {
|
|
170
|
+
output: `\`${clip(command)}\` is already running in the background as shell #${dup.id}, so I'm not ` +
|
|
171
|
+
`starting a second copy — a second one would collide with it. It's already up; if you want a ` +
|
|
172
|
+
`fresh start, call kill_shell(${dup.id}) first, then relaunch.`,
|
|
173
|
+
isError: true,
|
|
174
|
+
summary: `already running as shell #${dup.id}`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// Reopen guard: don't relaunch an app the USER just closed. Without this, an agent
|
|
178
|
+
// that reopens a closed window turns into a loop — the user shuts their app, it comes
|
|
179
|
+
// straight back, they shut it again, and so on. The match is scoped to apps that came
|
|
180
|
+
// up and were closed WITHOUT a kill_shell (`findRecentUserClose`), so the agent's own
|
|
181
|
+
// kill-then-relaunch is untouched and a server that failed to start can still be
|
|
182
|
+
// retried. The cooldown lets a restart the user asks for later through.
|
|
183
|
+
const closed = findRecentUserClose(ctx.backgroundShells.list(), command, Date.now());
|
|
184
|
+
if (closed) {
|
|
185
|
+
const secs = closed.finishedAt ? Math.round((Date.now() - closed.finishedAt) / 1000) : 0;
|
|
186
|
+
return {
|
|
187
|
+
output: `The user closed \`${clip(command)}\` themselves ${secs}s ago (shell #${closed.id}, after it had come up), ` +
|
|
188
|
+
`so I'm not reopening it. Reopening an app the user just shut turns into a loop where they keep closing ` +
|
|
189
|
+
`it and it keeps coming back. Leave it down. If it genuinely needs to be running, ask the user first and ` +
|
|
190
|
+
`let them decide — don't relaunch it on your own.`,
|
|
191
|
+
isError: true,
|
|
192
|
+
summary: `user closed #${closed.id} ${secs}s ago — not reopening`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Pre-execution gate: don't waste a run on a guaranteed PowerShell parse error
|
|
197
|
+
// (`&&`/`||`) or an assignment to a read-only automatic variable (`$pid = …`). Catch
|
|
198
|
+
// it here and hand back the fix so the model corrects in one step instead of running,
|
|
199
|
+
// failing, and re-reading the error (the wall that spawned a long flailing loop).
|
|
200
|
+
if (IS_WINDOWS && shell === "powershell") {
|
|
201
|
+
const parseError = powershellParseError(command);
|
|
202
|
+
if (parseError)
|
|
203
|
+
return fail(parseError);
|
|
204
|
+
const reserved = powershellReservedAssignmentReason(command);
|
|
205
|
+
if (reserved)
|
|
206
|
+
return fail(reserved);
|
|
207
|
+
}
|
|
208
|
+
const declared = args.notify === "on_finish" || args.notify === "on_failure" || args.notify === "never"
|
|
209
|
+
? args.notify
|
|
210
|
+
: undefined;
|
|
211
|
+
// Bring shell-caused changes into /undo. Snapshot the read ledger first, run, then
|
|
212
|
+
// check in whatever moved — this is the one mutation path that had no checkpoint at
|
|
213
|
+
// all, which mattered precisely because improvising with a script is a capability we
|
|
214
|
+
// rely on. Skipped for obviously read-only commands, and for background ones, whose
|
|
215
|
+
// writes land long after this call has returned. See shellCheckpoint.ts for the
|
|
216
|
+
// bounds and for what is honestly NOT covered.
|
|
217
|
+
const background = args.run_in_background === true;
|
|
218
|
+
const watch = !background && !looksReadOnly(command);
|
|
219
|
+
const before = watch ? await snapshotBeforeCommand(ctx) : undefined;
|
|
220
|
+
const result = await runShell(command, ctx, timeout, background, shell, declared);
|
|
221
|
+
if (before && before.size > 0) {
|
|
222
|
+
// Never let bookkeeping fail a command that already ran and succeeded.
|
|
223
|
+
await captureAfterCommand(ctx, before).catch(() => undefined);
|
|
224
|
+
}
|
|
225
|
+
return result;
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
async function runShell(command, ctx, timeoutMs, background, shell, declaredNotify) {
|
|
229
|
+
// A unique temp file the wrapped command writes its final cwd into.
|
|
230
|
+
const cwdFile = join(tmpdir(), `mindweave-cwd-${randomBytes(6).toString("hex")}.txt`);
|
|
231
|
+
// Where we stood before the command ran — applyCwd may move ctx.cwd, and the model
|
|
232
|
+
// needs telling when it does (see cwdChangeNote). CANONICALISED, because that
|
|
233
|
+
// comparison is a string equality and the two sides otherwise come from different
|
|
234
|
+
// places: this one from the session, the other from whatever form the shell prints.
|
|
235
|
+
// On Windows those differ for any user whose name is over eight characters, since
|
|
236
|
+
// one side carries the 8.3 short form, and the session then reports a move on every
|
|
237
|
+
// command that never left the directory.
|
|
238
|
+
const cwdBefore = await canonicalRoot(ctx.cwd);
|
|
239
|
+
const { bin, args, wrapped, tempFile } = buildInvocation(command, cwdFile, shell);
|
|
240
|
+
const child = spawnManaged(bin, [...args, wrapped], {
|
|
241
|
+
cwd: ctx.cwd,
|
|
242
|
+
env: {
|
|
243
|
+
...process.env,
|
|
244
|
+
GIT_EDITOR: "true", // never drop into an interactive editor and hang
|
|
245
|
+
GIT_PAGER: "cat",
|
|
246
|
+
PAGER: "cat",
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
const mgr = ctx.backgroundShells;
|
|
250
|
+
// Explicit background: hand off immediately, don't wait for it.
|
|
251
|
+
//
|
|
252
|
+
// The abort listener below is only wired for the FOREGROUND path, so this branch
|
|
253
|
+
// has to check the signal itself. Without it an interrupted turn still adopts the
|
|
254
|
+
// process, and because backgrounding deliberately outlives the turn, Esc would
|
|
255
|
+
// leave a dev server running that the user believed they had cancelled.
|
|
256
|
+
if (background && mgr) {
|
|
257
|
+
if (ctx.abortSignal?.aborted) {
|
|
258
|
+
killTree(child.pid);
|
|
259
|
+
void fs.rm(cwdFile, { force: true }).catch(() => { });
|
|
260
|
+
if (tempFile)
|
|
261
|
+
void fs.rm(tempFile, { force: true }).catch(() => { });
|
|
262
|
+
return {
|
|
263
|
+
output: "Command interrupted before it started.",
|
|
264
|
+
isError: true,
|
|
265
|
+
summary: `interrupted \`${clip(command)}\``,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
// Declared policy wins; the name guess is only the default when nothing was said.
|
|
269
|
+
const notify = declaredNotify ?? guessNotifyPolicy(command);
|
|
270
|
+
const info = mgr.adopt(child, { command, cwd: ctx.cwd, cwdFile, tempFile, notify });
|
|
271
|
+
return backgroundedResult(info.id, command, `Started in the background as shell #${info.id}`, notify);
|
|
272
|
+
}
|
|
273
|
+
return new Promise((resolve) => {
|
|
274
|
+
let head = "";
|
|
275
|
+
let tail = "";
|
|
276
|
+
let dropped = 0;
|
|
277
|
+
let timedOut = false;
|
|
278
|
+
let settled = false;
|
|
279
|
+
// Decode ACROSS chunks. A chunk boundary can fall inside a multi-byte UTF-8
|
|
280
|
+
// sequence, and decoding each Buffer on its own turns that character into a
|
|
281
|
+
// replacement glyph — which shows up in any non-English output and in the box
|
|
282
|
+
// drawing most test runners use. The decoder holds the partial bytes back until
|
|
283
|
+
// the rest arrives.
|
|
284
|
+
const decoder = new StringDecoder("utf8");
|
|
285
|
+
const collect = (chunk) => {
|
|
286
|
+
let rest = decoder.write(chunk);
|
|
287
|
+
if (!rest)
|
|
288
|
+
return;
|
|
289
|
+
if (head.length < HEAD_CHARS) {
|
|
290
|
+
const room = HEAD_CHARS - head.length;
|
|
291
|
+
head += rest.slice(0, room);
|
|
292
|
+
rest = rest.slice(room);
|
|
293
|
+
}
|
|
294
|
+
if (!rest)
|
|
295
|
+
return;
|
|
296
|
+
tail += rest;
|
|
297
|
+
if (tail.length > TAIL_CHARS) {
|
|
298
|
+
dropped += tail.length - TAIL_CHARS;
|
|
299
|
+
tail = tail.slice(tail.length - TAIL_CHARS);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
/** Everything kept, with the gap named where it happened. */
|
|
303
|
+
const collected = () => composeOutput(head, tail, dropped);
|
|
304
|
+
child.stdout?.on("data", collect);
|
|
305
|
+
child.stderr?.on("data", collect);
|
|
306
|
+
const timer = setTimeout(() => {
|
|
307
|
+
if (settled)
|
|
308
|
+
return;
|
|
309
|
+
// Soft timeout: move the LIVE process to the background (preferred), so a long
|
|
310
|
+
// test/build keeps running instead of being lost. With no manager (bare tests)
|
|
311
|
+
// fall back to the old behavior — kill the tree.
|
|
312
|
+
if (mgr) {
|
|
313
|
+
settled = true;
|
|
314
|
+
detachAbort();
|
|
315
|
+
child.stdout?.off("data", collect);
|
|
316
|
+
child.stderr?.off("data", collect);
|
|
317
|
+
// Auto-backgrounded after running too long inline. Somebody was waiting on this,
|
|
318
|
+
// so its completion is the point unless the caller said otherwise.
|
|
319
|
+
const notify = declaredNotify ?? "on_finish";
|
|
320
|
+
const info = mgr.adopt(child, { command, cwd: ctx.cwd, initial: collected(), cwdFile, tempFile, notify });
|
|
321
|
+
resolve(backgroundedResult(info.id, command, `Still running after ${Math.round(timeoutMs / 1000)}s — moved to the background as shell #${info.id}`, notify));
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
timedOut = true;
|
|
325
|
+
killTree(child.pid);
|
|
326
|
+
}
|
|
327
|
+
}, timeoutMs);
|
|
328
|
+
// Esc / interrupt: if the turn is aborted while this command is still running,
|
|
329
|
+
// kill the whole process tree and settle immediately. Without this a hung command
|
|
330
|
+
// (an installer waiting on a GUI, an interactive prompt) freezes the agent — the
|
|
331
|
+
// engine only re-checks the abort signal BETWEEN steps, never mid-tool-call, so it
|
|
332
|
+
// would stay blocked on this promise forever.
|
|
333
|
+
const signal = ctx.abortSignal;
|
|
334
|
+
const detachAbort = () => signal?.removeEventListener("abort", onAbort);
|
|
335
|
+
function onAbort() {
|
|
336
|
+
if (settled)
|
|
337
|
+
return;
|
|
338
|
+
settled = true;
|
|
339
|
+
clearTimeout(timer);
|
|
340
|
+
child.stdout?.off("data", collect);
|
|
341
|
+
child.stderr?.off("data", collect);
|
|
342
|
+
killTree(child.pid);
|
|
343
|
+
void fs.rm(cwdFile, { force: true }).catch(() => { });
|
|
344
|
+
if (tempFile)
|
|
345
|
+
void fs.rm(tempFile, { force: true }).catch(() => { });
|
|
346
|
+
const body = collected().trim();
|
|
347
|
+
resolve({
|
|
348
|
+
output: body ? `${body}\n\n[interrupted]` : "Command interrupted before it finished.",
|
|
349
|
+
isError: true,
|
|
350
|
+
summary: `interrupted \`${clip(command)}\``,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
if (signal?.aborted)
|
|
354
|
+
return void onAbort();
|
|
355
|
+
signal?.addEventListener("abort", onAbort);
|
|
356
|
+
const finish = async (exitCode, signal) => {
|
|
357
|
+
if (settled)
|
|
358
|
+
return;
|
|
359
|
+
settled = true;
|
|
360
|
+
clearTimeout(timer);
|
|
361
|
+
detachAbort();
|
|
362
|
+
await applyCwd(cwdFile, ctx);
|
|
363
|
+
if (tempFile)
|
|
364
|
+
await fs.rm(tempFile, { force: true }).catch(() => { });
|
|
365
|
+
resolve(format(command, ctx, collected(), dropped > 0, timedOut, exitCode, signal, timeoutMs, shell, cwdBefore, child.pid));
|
|
366
|
+
};
|
|
367
|
+
child.on("error", (error) => {
|
|
368
|
+
if (settled)
|
|
369
|
+
return;
|
|
370
|
+
settled = true;
|
|
371
|
+
clearTimeout(timer);
|
|
372
|
+
detachAbort();
|
|
373
|
+
void fs.rm(cwdFile, { force: true }).catch(() => { });
|
|
374
|
+
if (tempFile)
|
|
375
|
+
void fs.rm(tempFile, { force: true }).catch(() => { });
|
|
376
|
+
resolve(fail(`could not start the command: ${error.message}`));
|
|
377
|
+
});
|
|
378
|
+
child.on("close", (code, signal) => void finish(code, signal));
|
|
379
|
+
// `close` waits for every stdio stream to close, which a surviving grandchild (a
|
|
380
|
+
// daemon the command started, inheriting stdout) holds open indefinitely. Without a
|
|
381
|
+
// fallback the call sits here until the timeout and only then gets backgrounded, so a
|
|
382
|
+
// command that merely starts something looks like it took two minutes. `exit` fires
|
|
383
|
+
// when the process itself goes; the short grace lets `close` win normally, so no
|
|
384
|
+
// ordinary command pays for this.
|
|
385
|
+
child.on("exit", (code, signal) => {
|
|
386
|
+
const grace = setTimeout(() => void finish(code, signal), CLOSE_GRACE_MS);
|
|
387
|
+
grace.unref?.();
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* The result returned when a command is (or becomes) a background shell.
|
|
393
|
+
*
|
|
394
|
+
* The text has to match what will ACTUALLY happen, and that differs by what was
|
|
395
|
+
* started. A finite task notifies on completion, so telling the model to promise a
|
|
396
|
+
* report is correct. A server or app does NOT: its stop is suppressed on purpose,
|
|
397
|
+
* because a user closing their own app is not something to act on. Telling the model
|
|
398
|
+
* it would be notified either way made it promise a report that never came, which is
|
|
399
|
+
* the prompt asserting a capability that does not exist.
|
|
400
|
+
*/
|
|
401
|
+
function backgroundedResult(id, command, lead, notify) {
|
|
402
|
+
const tail = notify === "never"
|
|
403
|
+
? `Nothing further will be reported about it. Say in ONE short line that it's running, then STOP ` +
|
|
404
|
+
`(end your turn). Use shells({id: ${id}}) to inspect it and kill_shell(${id}) to stop it.`
|
|
405
|
+
: notify === "on_failure"
|
|
406
|
+
? `You WILL be told once it has come up, so you can report that. You will NOT be told when it ` +
|
|
407
|
+
`stops, because the user closing their own app is not an event to act on — so never restart ` +
|
|
408
|
+
`it on your own. Say in ONE short line that it's starting, then STOP (end your turn). Use ` +
|
|
409
|
+
`shells({id: ${id}}) to inspect it and kill_shell(${id}) to stop it.`
|
|
410
|
+
: `You will be notified AUTOMATICALLY the moment it finishes, so do NOT poll it. Say in ONE ` +
|
|
411
|
+
`short line that it started and that you'll report back when it's done, then STOP (end your ` +
|
|
412
|
+
`turn). Only call shells({id: ${id}}) if you have a specific reason to inspect partial ` +
|
|
413
|
+
`output; use kill_shell(${id}) to stop it.`;
|
|
414
|
+
return {
|
|
415
|
+
output: `${lead}. ${tail}`,
|
|
416
|
+
// UI-only, never sent to the model (`tail` above already told IT the real
|
|
417
|
+
// notification policy). A backgrounded command has produced NO output yet, so
|
|
418
|
+
// there is nothing to put on the output rail — which is what this used to do,
|
|
419
|
+
// rendering Mindweave's own note as if the command had printed it. It is a
|
|
420
|
+
// one-line verdict on the call, which is what the ⎿ branch is for, and it now
|
|
421
|
+
// reads the same way a write's "whole file · 19 lines" does.
|
|
422
|
+
summary: `Backgrounded as shell #${id}`,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/** Build the shell invocation: which binary, its flags, and the wrapped command. */
|
|
426
|
+
function buildInvocation(command, cwdFile, shell) {
|
|
427
|
+
if (IS_WINDOWS && shell === "cmd") {
|
|
428
|
+
// cmd.exe can't run a multi-line /c string (it executes only the first line), so
|
|
429
|
+
// we materialize a tiny .bat: the command, then capture its exit code and final
|
|
430
|
+
// cwd. Run line-by-line, so `cd` persists and `&&`/`||` chains work natively.
|
|
431
|
+
// ComSpec is the reliable path to cmd.exe (a bare name may not resolve).
|
|
432
|
+
const batFile = join(tmpdir(), `mindweave-run-${randomBytes(6).toString("hex")}.bat`);
|
|
433
|
+
const script = `@echo off\r\n` +
|
|
434
|
+
`${command}\r\n` +
|
|
435
|
+
`set __ec=%ERRORLEVEL%\r\n` +
|
|
436
|
+
`cd > "${cwdFile}"\r\n` +
|
|
437
|
+
`exit /b %__ec%`;
|
|
438
|
+
writeFileSync(batFile, script, "utf8");
|
|
439
|
+
return {
|
|
440
|
+
bin: process.env.ComSpec || "cmd.exe",
|
|
441
|
+
args: ["/d", "/s", "/c"],
|
|
442
|
+
wrapped: batFile,
|
|
443
|
+
tempFile: batFile,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
if (IS_WINDOWS) {
|
|
447
|
+
// After the command, record the final location and preserve its exit code.
|
|
448
|
+
//
|
|
449
|
+
// `$?` has to be captured on the very FIRST line after the command, because every
|
|
450
|
+
// statement sets it — including an assignment, which always succeeds. Read it
|
|
451
|
+
// second and you are reading whether `$__ec = …` worked, which is always true.
|
|
452
|
+
//
|
|
453
|
+
// Both signals are needed because they cover different halves of PowerShell.
|
|
454
|
+
// `$LASTEXITCODE` is set ONLY by native executables, so a failing cmdlet
|
|
455
|
+
// (`Get-Content` on a missing file, a failed `Remove-Item` — most of what a model
|
|
456
|
+
// writes) leaves it null, which used to be coerced to 0 and reported to the model
|
|
457
|
+
// as SUCCESS. `$?` catches those. It is not enough on its own either: it says
|
|
458
|
+
// whether something failed, never with which code, and `exit 3` from a real
|
|
459
|
+
// program has to survive as 3. So: take the native code when there is one, and
|
|
460
|
+
// otherwise promote a cmdlet failure to 1.
|
|
461
|
+
const wrapped = `${command}\n` +
|
|
462
|
+
`$__ok = $?\n` +
|
|
463
|
+
`$__ec = $LASTEXITCODE; if ($null -eq $__ec) { $__ec = 0 }\n` +
|
|
464
|
+
`if (-not $__ok -and $__ec -eq 0) { $__ec = 1 }\n` +
|
|
465
|
+
`$PWD.Path | Out-File -FilePath ${psQuote(cwdFile)} -Encoding utf8\n` +
|
|
466
|
+
`exit $__ec`;
|
|
467
|
+
return {
|
|
468
|
+
bin: "powershell.exe",
|
|
469
|
+
// -ExecutionPolicy Bypass (process-scoped only) so the model can actually run
|
|
470
|
+
// npm/npx/tsc/jest — their Windows shims are .ps1 scripts, which a Restricted
|
|
471
|
+
// execution policy blocks ("npm.ps1 cannot be loaded because running scripts is
|
|
472
|
+
// disabled"). Without this the verify/test/build story silently can't run.
|
|
473
|
+
args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command"],
|
|
474
|
+
wrapped,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
// Prefer bash. `/bin/sh` is `dash` on Debian/Ubuntu, where ordinary bash syntax a
|
|
478
|
+
// model writes — `[[ ]]`, `source`, arrays — is a hard syntax error. See posixShell.
|
|
479
|
+
//
|
|
480
|
+
// `pwd -P` reports the PHYSICAL path, with symlinks resolved. That is deliberate and
|
|
481
|
+
// it is why `canonicalRoot` exists: macOS puts /tmp and the whole of os.tmpdir()
|
|
482
|
+
// behind symlinks (/tmp → /private/tmp), so a logical path and a physical one for the
|
|
483
|
+
// same directory differ constantly. Recording the physical form on both sides is what
|
|
484
|
+
// keeps them comparable; recording one of each is what silently moved a session off
|
|
485
|
+
// its own root.
|
|
486
|
+
const wrapped = `${command}\n` +
|
|
487
|
+
`__ec=$?\n` +
|
|
488
|
+
`pwd -P > ${shQuote(cwdFile)} 2>/dev/null\n` +
|
|
489
|
+
`exit $__ec`;
|
|
490
|
+
return { bin: posixShell().bin, args: ["-c"], wrapped };
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Join the kept head and tail of a long output, naming the gap (pure).
|
|
494
|
+
*
|
|
495
|
+
* The marker sits WHERE the loss happened rather than at the end, so the model can
|
|
496
|
+
* see that the two halves are not contiguous. A trailing "output truncated" note
|
|
497
|
+
* after a head-only excerpt reads as "there was a bit more", which is exactly the
|
|
498
|
+
* wrong impression when the missing part is the answer.
|
|
499
|
+
*/
|
|
500
|
+
export function composeOutput(head, tail, dropped) {
|
|
501
|
+
if (dropped <= 0)
|
|
502
|
+
return head + tail;
|
|
503
|
+
return `${head}\n… [${dropped.toLocaleString("en-US")} characters omitted from the middle] …\n${tail}`;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* The line appended to a command's output when it moved the shell (pure).
|
|
507
|
+
*
|
|
508
|
+
* `cd` persisting across calls within a turn is a real convenience, but it is also
|
|
509
|
+
* invisible: the model composes the next command's relative paths from the project
|
|
510
|
+
* root, because nothing ever told it the floor moved. That produces a doubled path
|
|
511
|
+
* (`code-blue/backend/code-blue/backend/manage.py`), which fails, and looks to the
|
|
512
|
+
* model like the file is missing rather than like it is standing somewhere else.
|
|
513
|
+
*
|
|
514
|
+
* So we say it, once, exactly when it happens. `shown` is the new location relative
|
|
515
|
+
* to the project root — the same form paths are addressed in everywhere else.
|
|
516
|
+
* Returns null when the command did not move, which is nearly every command, so
|
|
517
|
+
* ordinary output is untouched.
|
|
518
|
+
*/
|
|
519
|
+
export function cwdChangeNote(before, after, shown) {
|
|
520
|
+
if (before === after)
|
|
521
|
+
return null;
|
|
522
|
+
const where = shown === "." ? "the project root" : shown;
|
|
523
|
+
return (`[Working directory is now ${where}. It stays there for the rest of this turn, so ` +
|
|
524
|
+
`relative paths in your next command resolve from ${where}, not from the project root.]`);
|
|
525
|
+
}
|
|
526
|
+
/** Read the cwd the command ended in and adopt it (if it still exists). */
|
|
527
|
+
async function applyCwd(cwdFile, ctx) {
|
|
528
|
+
try {
|
|
529
|
+
const text = (await fs.readFile(cwdFile, "utf8")).trim();
|
|
530
|
+
if (text) {
|
|
531
|
+
// Confirm it's a real directory before adopting — a half-written file or a
|
|
532
|
+
// deleted dir must not strand the session somewhere invalid.
|
|
533
|
+
const stat = await fs.stat(text);
|
|
534
|
+
// Canonicalise on the way in, so the session only ever holds ONE form of a
|
|
535
|
+
// path. The shell prints whatever form it was handed; adopting that verbatim is
|
|
536
|
+
// how a session ends up describing one directory two ways.
|
|
537
|
+
if (stat.isDirectory())
|
|
538
|
+
ctx.cwd = await canonicalRoot(text);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
542
|
+
// No file / unreadable → command didn't change dir (or failed early); keep cwd.
|
|
543
|
+
}
|
|
544
|
+
finally {
|
|
545
|
+
await fs.rm(cwdFile, { force: true }).catch(() => { });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function format(command, ctx, output, truncated, timedOut, exitCode, signal, timeoutMs, shell, cwdBefore,
|
|
549
|
+
/** The killed process, named only when something WAS killed (see withOutcome). */
|
|
550
|
+
pid) {
|
|
551
|
+
const body = output.trim();
|
|
552
|
+
const parts = [];
|
|
553
|
+
if (timedOut) {
|
|
554
|
+
parts.push(`Command timed out after ${Math.round(timeoutMs / 1000)}s and was killed. ` +
|
|
555
|
+
`If it was a long-running or watching process, run a form that terminates.`);
|
|
556
|
+
}
|
|
557
|
+
else if (signal) {
|
|
558
|
+
// A process ended by a signal reports no exit code at all. Treating that as "not
|
|
559
|
+
// non-zero" made a killed command read as a success, and the summary said
|
|
560
|
+
// "exit null" — so a command someone stopped looked like a command that worked.
|
|
561
|
+
parts.push(`Command was terminated by ${signal} before it finished.`);
|
|
562
|
+
}
|
|
563
|
+
else if (exitCode !== 0 && exitCode !== null) {
|
|
564
|
+
parts.push(`Command exited with code ${exitCode}.`);
|
|
565
|
+
}
|
|
566
|
+
if (body) {
|
|
567
|
+
parts.push(body);
|
|
568
|
+
// The gap is already marked inline, at the point it happened; this only names
|
|
569
|
+
// the shape of what arrived so the model doesn't read the two halves as one run.
|
|
570
|
+
if (truncated)
|
|
571
|
+
parts.push("(long output: the start and the end are shown, the middle was dropped)");
|
|
572
|
+
}
|
|
573
|
+
else if (!timedOut) {
|
|
574
|
+
parts.push("(no output)");
|
|
575
|
+
}
|
|
576
|
+
// In PowerShell, nudge the model when it wrote a bash-ism that breaks there
|
|
577
|
+
// (advisory only — never blocks; the linter is conservative). Not for cmd, which
|
|
578
|
+
// supports && / || natively.
|
|
579
|
+
if (IS_WINDOWS && shell === "powershell") {
|
|
580
|
+
const lint = powershellLintReason(command);
|
|
581
|
+
if (lint)
|
|
582
|
+
parts.push(lint);
|
|
583
|
+
}
|
|
584
|
+
// The POSIX mirror of the same idea. Silent on any machine that has bash (nearly
|
|
585
|
+
// all of them), because there the mismatch cannot arise — it only speaks up on a
|
|
586
|
+
// minimal box where the command genuinely could not have parsed, and then it names
|
|
587
|
+
// the construct rather than leaving a bare `dash: syntax error` to be decoded.
|
|
588
|
+
if (!IS_WINDOWS) {
|
|
589
|
+
const mismatch = shellMismatchNote(command);
|
|
590
|
+
if (mismatch)
|
|
591
|
+
parts.push(mismatch);
|
|
592
|
+
}
|
|
593
|
+
const shown = relativize(ctx, ctx.cwd);
|
|
594
|
+
// Did this command move the shell? If so the model must hear it here, in the tool
|
|
595
|
+
// OUTPUT — the summary line below is display-only and never reaches the model.
|
|
596
|
+
const moved = cwdChangeNote(cwdBefore, ctx.cwd, shown);
|
|
597
|
+
if (moved)
|
|
598
|
+
parts.push(moved);
|
|
599
|
+
const status = timedOut ? "timed out" : signal ? `killed (${signal})` : exitCode === 0 ? "ok" : `exit ${exitCode}`;
|
|
600
|
+
return {
|
|
601
|
+
output: parts.join("\n"),
|
|
602
|
+
isError: timedOut || signal !== null || (exitCode !== 0 && exitCode !== null),
|
|
603
|
+
summary: `ran \`${clip(command)}\` in ${shown} (${status})`,
|
|
604
|
+
// The outcome is appended to what is SHOWN, not just to what the model reads. A
|
|
605
|
+
// command that printed output previously ended its row with the last line of that
|
|
606
|
+
// output and nothing else, so a build that failed and a build that passed looked
|
|
607
|
+
// identical unless you recognised the text — the exit code was known here and
|
|
608
|
+
// simply never displayed.
|
|
609
|
+
// The command leads its own block on its own row. Inline in the header it was
|
|
610
|
+
// clipped to 48 characters, which for a real command line lost the half that said
|
|
611
|
+
// what it actually did (`Run(mkdir -p ..\astra-backup; Move-Item .\astra.htm…)`).
|
|
612
|
+
detail: withOutcome(shellBody(command, body), timedOut, exitCode, signal, timeoutMs, pid),
|
|
613
|
+
detailKind: "shell",
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
/** The `$ command` header row above a command's captured output. */
|
|
617
|
+
function shellBody(command, body) {
|
|
618
|
+
const out = outputDetail(body);
|
|
619
|
+
return out ? `$ ${command}\n${out}` : `$ ${command}`;
|
|
620
|
+
}
|
|
621
|
+
/** Single-quote a string for a POSIX shell. */
|
|
622
|
+
function shQuote(s) {
|
|
623
|
+
return `'${s.split("'").join(`'\\''`)}'`;
|
|
624
|
+
}
|
|
625
|
+
/** Single-quote a string for PowerShell (double any embedded single quotes). */
|
|
626
|
+
function psQuote(s) {
|
|
627
|
+
return `'${s.split("'").join("''")}'`;
|
|
628
|
+
}
|
|
629
|
+
function clip(s, max = 60) {
|
|
630
|
+
const flat = s.replace(/\s+/g, " ").trim();
|
|
631
|
+
return flat.length <= max ? flat : flat.slice(0, max - 1) + "…";
|
|
632
|
+
}
|
|
633
|
+
//# sourceMappingURL=runCommand.js.map
|