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,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* guard.ts — the mechanical safety floor for the mutating tools.
|
|
3
|
+
*
|
|
4
|
+
* Two deterministic checks, no model judgment and no prompt rules (keeping the
|
|
5
|
+
* "how to behave safely" decision out of the prompt is deliberate — the wall is
|
|
6
|
+
* physical, the model is simply told why and adapts):
|
|
7
|
+
*
|
|
8
|
+
* 1. `protectedPathReason` — some files must never be read or written by the
|
|
9
|
+
* agent regardless of what it's asked: secrets (`.env`), keys (`.ssh`,
|
|
10
|
+
* `*.pem`, `id_rsa`), and the git internals (`.git/`) whose corruption would
|
|
11
|
+
* wreck the repo. This mirrors the deny-lists every serious coding agent
|
|
12
|
+
* ships; user-configurable rules can layer on later.
|
|
13
|
+
*
|
|
14
|
+
* 2. `catastrophicCommandReason` — a tiny, high-confidence blocklist of shell
|
|
15
|
+
* commands that are essentially never a legitimate coding action and are
|
|
16
|
+
* irreversible (wipe the disk, fork-bomb, reformat). This is NOT a sandbox
|
|
17
|
+
* or an injection parser — a single-user local tool doesn't need a heavyweight
|
|
18
|
+
* shell analyzer. It's a seatbelt against the few commands that turn a model
|
|
19
|
+
* mistake into a destroyed machine.
|
|
20
|
+
*
|
|
21
|
+
* Both return a human reason string when they fire, or `null` to allow. Fail
|
|
22
|
+
* open by design: anything not explicitly matched is allowed.
|
|
23
|
+
*/
|
|
24
|
+
// Path segments / names that are off-limits. Matched against the POSIX-style
|
|
25
|
+
// path so it works the same on Windows and Unix.
|
|
26
|
+
const PROTECTED_PATTERNS = [
|
|
27
|
+
// Three spellings, because two of them were getting through. `.env` and `.env.local`
|
|
28
|
+
// were covered; `prod.env`, `staging.env` and `production.env` were NOT, and a
|
|
29
|
+
// per-environment file is one of the commonest places a real secret actually lives.
|
|
30
|
+
// `.envrc` (direnv) was not covered either, and it routinely holds exported keys.
|
|
31
|
+
//
|
|
32
|
+
// The suffix form is anchored to the end of the BASENAME rather than matched loosely,
|
|
33
|
+
// which is what keeps ordinary code out of the net: `src/environment.ts` and
|
|
34
|
+
// `src/env.ts` must stay readable, and a floor that blocked those would be worked
|
|
35
|
+
// around rather than obeyed.
|
|
36
|
+
{ test: /(^|\/)\.env(\.|$|\/)/i, what: "an environment/secrets file" },
|
|
37
|
+
{ test: /(^|\/)[^/]*\.env$/i, what: "an environment/secrets file" },
|
|
38
|
+
{ test: /(^|\/)\.envrc$/i, what: "an environment/secrets file" },
|
|
39
|
+
{ test: /(^|\/)\.git(\/|$)/i, what: "the git internals directory" },
|
|
40
|
+
{ test: /(^|\/)\.ssh(\/|$)/i, what: "an SSH key directory" },
|
|
41
|
+
{ test: /(^|\/)id_(rsa|ed25519|ecdsa|dsa)(\.|$)/i, what: "a private SSH key" },
|
|
42
|
+
{ test: /\.pem$/i, what: "a private key file" },
|
|
43
|
+
{ test: /(^|\/)(secrets?|credentials)(\/|\.|$)/i, what: "a secrets/credentials file" },
|
|
44
|
+
];
|
|
45
|
+
/**
|
|
46
|
+
* If `absPath` is a file the agent must never touch, return a short reason;
|
|
47
|
+
* otherwise null. `absPath` may use either slash style.
|
|
48
|
+
*/
|
|
49
|
+
export function protectedPathReason(absPath) {
|
|
50
|
+
const posix = absPath.split("\\").join("/");
|
|
51
|
+
for (const { test, what } of PROTECTED_PATTERNS) {
|
|
52
|
+
if (test.test(posix))
|
|
53
|
+
return what;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
// Another coding agent's private working data: its saved sessions, its memory of
|
|
58
|
+
// past conversations, its rules and skills. A project that has been worked on by
|
|
59
|
+
// more than one tool carries several of these side by side.
|
|
60
|
+
//
|
|
61
|
+
// This is NOT the same thing as a secret, and it is deliberately a separate list.
|
|
62
|
+
// A secret must never be read at all. This data is simply not ours: it belongs to
|
|
63
|
+
// a different tool and a different set of conversations, and helping ourselves to
|
|
64
|
+
// it means presenting someone else's history as if it were our own, or inheriting
|
|
65
|
+
// stale decisions the user never made with us. So the rule is "ask first", not
|
|
66
|
+
// "never" — the user can always say yes.
|
|
67
|
+
//
|
|
68
|
+
// Deliberately absent from this list: our own directory. Our sessions and memory
|
|
69
|
+
// live under the user's home directory rather than in the project, but a project
|
|
70
|
+
// may still hold our own notes, and reading our own work is the entire point.
|
|
71
|
+
const AGENT_PATTERNS = [
|
|
72
|
+
{ test: /(^|\/)\.claude(\/|$)/i, what: "Claude Code" },
|
|
73
|
+
{ test: /(^|\/)CLAUDE\.md$/i, what: "Claude Code" },
|
|
74
|
+
{ test: /(^|\/)\.cursor(\/|$)/i, what: "Cursor" },
|
|
75
|
+
{ test: /(^|\/)\.cursorrules$/i, what: "Cursor" },
|
|
76
|
+
{ test: /(^|\/)\.aider[^/]*$/i, what: "Aider" },
|
|
77
|
+
{ test: /(^|\/)\.aider(\/|$)/i, what: "Aider" },
|
|
78
|
+
{ test: /(^|\/)\.continue(\/|$)/i, what: "Continue" },
|
|
79
|
+
{ test: /(^|\/)\.windsurf(\/|$)/i, what: "Windsurf" },
|
|
80
|
+
{ test: /(^|\/)\.codeium(\/|$)/i, what: "Codeium" },
|
|
81
|
+
{ test: /(^|\/)AGENTS\.md$/i, what: "another coding agent" },
|
|
82
|
+
];
|
|
83
|
+
/**
|
|
84
|
+
* If `absPath` belongs to a DIFFERENT coding agent, return that tool's name;
|
|
85
|
+
* otherwise null. Callers use this to ask the user before touching it, never to
|
|
86
|
+
* refuse outright — see `requestAgentDataAccess`.
|
|
87
|
+
*/
|
|
88
|
+
export function foreignAgentReason(absPath) {
|
|
89
|
+
const posix = absPath.split("\\").join("/");
|
|
90
|
+
for (const { test, what } of AGENT_PATTERNS) {
|
|
91
|
+
if (test.test(posix))
|
|
92
|
+
return what;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/** Directory names belonging to other coding agents, for skipping during a walk
|
|
97
|
+
* or search. Files that are not directories (CLAUDE.md, .cursorrules) are matched
|
|
98
|
+
* by `foreignAgentReason` instead. */
|
|
99
|
+
export const AGENT_DIRS = [
|
|
100
|
+
".claude",
|
|
101
|
+
".cursor",
|
|
102
|
+
".aider",
|
|
103
|
+
".continue",
|
|
104
|
+
".windsurf",
|
|
105
|
+
".codeium",
|
|
106
|
+
];
|
|
107
|
+
/**
|
|
108
|
+
* Glob patterns a CONTENT SEARCH must never look inside.
|
|
109
|
+
*
|
|
110
|
+
* Search is the quiet way past a per-file gate: `read_file` refuses to open
|
|
111
|
+
* `.env`, but an unfiltered `grep -r "KEY" .` prints the matching lines anyway,
|
|
112
|
+
* and the same trick would surface another agent's saved conversations without
|
|
113
|
+
* ever opening a file. Secrets are excluded outright; another agent's data is
|
|
114
|
+
* excluded because a search is a poor place to ask a question — the model can
|
|
115
|
+
* still read those files deliberately, which is where the user gets asked.
|
|
116
|
+
*/
|
|
117
|
+
export const SEARCH_EXCLUDE_GLOBS = [
|
|
118
|
+
".env",
|
|
119
|
+
".env.*",
|
|
120
|
+
".ssh",
|
|
121
|
+
"*.pem",
|
|
122
|
+
"id_rsa",
|
|
123
|
+
"id_ed25519",
|
|
124
|
+
"id_ecdsa",
|
|
125
|
+
"id_dsa",
|
|
126
|
+
"secrets",
|
|
127
|
+
"secret",
|
|
128
|
+
"credentials",
|
|
129
|
+
...AGENT_DIRS,
|
|
130
|
+
"CLAUDE.md",
|
|
131
|
+
"AGENTS.md",
|
|
132
|
+
".cursorrules",
|
|
133
|
+
".aider*",
|
|
134
|
+
];
|
|
135
|
+
/** True if a search result from `absPath` must be withheld — a secret, or another
|
|
136
|
+
* coding agent's data. The counterpart to SEARCH_EXCLUDE_GLOBS for the built-in
|
|
137
|
+
* walker, which filters after walking rather than excluding at the source. */
|
|
138
|
+
export function excludedFromSearch(absPath) {
|
|
139
|
+
return protectedPathReason(absPath) !== null || foreignAgentReason(absPath) !== null;
|
|
140
|
+
}
|
|
141
|
+
// Paths a shell command must not PRINT the contents of.
|
|
142
|
+
const COMMAND_SENSITIVE = [
|
|
143
|
+
{ needle: /(^|[\s"'`/\\=<])\.env\b/i, what: "an environment/secrets file" },
|
|
144
|
+
{ needle: /(^|[\s"'`/\\=<])\.ssh\b/i, what: "an SSH key directory" },
|
|
145
|
+
{ needle: /\bid_(rsa|ed25519|ecdsa|dsa)\b/i, what: "a private SSH key" },
|
|
146
|
+
{ needle: /\.pem\b/i, what: "a private key file" },
|
|
147
|
+
{ needle: /(^|[\s"'`/\\=<])\.claude\b/i, what: "Claude Code's data" },
|
|
148
|
+
{ needle: /(^|[\s"'`/\\=<])\.cursor(rules)?\b/i, what: "Cursor's data" },
|
|
149
|
+
{ needle: /(^|[\s"'`/\\=<])\.aider/i, what: "Aider's data" },
|
|
150
|
+
{ needle: /(^|[\s"'`/\\=<])\.continue\b/i, what: "Continue's data" },
|
|
151
|
+
{ needle: /(^|[\s"'`/\\=<])\.windsurf\b/i, what: "Windsurf's data" },
|
|
152
|
+
{ needle: /(^|[\s"'`/\\=<])\.codeium\b/i, what: "Codeium's data" },
|
|
153
|
+
];
|
|
154
|
+
// Commands whose OUTPUT is the contents of a file. This is the thing that matters:
|
|
155
|
+
// the harm is a secret being printed into the model's context (and from there into
|
|
156
|
+
// a saved transcript, and into the next request to a provider). Copying, moving,
|
|
157
|
+
// listing, or testing for a file does none of that, so none of them belong here.
|
|
158
|
+
//
|
|
159
|
+
// Matched in COMMAND POSITION only — start of the command, or after a pipe,
|
|
160
|
+
// semicolon, `&&`, or a subshell opener. Without that anchor, ordinary words like
|
|
161
|
+
// `type` in `npm run typecheck` or a `--head` flag would trip it.
|
|
162
|
+
//
|
|
163
|
+
// PowerShell shares this list: `cat`, `type`, and `gc` are all aliases for
|
|
164
|
+
// Get-Content there, so the same names cover both shells.
|
|
165
|
+
const CONTENT_READERS = /(^|[|;&]|&&|\$\(|\bthen\b|\bdo\b)\s*(sudo\s+)?(cat|type|more|less|head|tail|strings|xxd|od|base64|gc|Get-Content|sls|Select-String|findstr|grep|rg|ack|awk|sed|nl|tac|Format-Hex)\b/i;
|
|
166
|
+
// `< file` feeds a file's contents in, which can print it just as directly.
|
|
167
|
+
const INPUT_REDIRECT = /<\s*[^\s|;&]+/;
|
|
168
|
+
/**
|
|
169
|
+
* If `command` would PRINT the contents of a secret or another agent's data,
|
|
170
|
+
* return what it is reaching for; otherwise null.
|
|
171
|
+
*
|
|
172
|
+
* Two conditions, both required: the command reads file content, AND it names one
|
|
173
|
+
* of the sensitive paths. That pairing is what keeps this useful rather than
|
|
174
|
+
* merely irritating: `Test-Path .env` (does it exist?), `ls .claude`, and
|
|
175
|
+
* `cp .env.example .env` all pass, while `cat .env` and `Get-Content .env` do not.
|
|
176
|
+
*
|
|
177
|
+
* This is deliberately not adversarial defense. A model set on evading a string
|
|
178
|
+
* check can always do so, and no amount of pattern-matching a shell fixes that.
|
|
179
|
+
* It is here to stop the accidental `cat .env` that would drop live credentials
|
|
180
|
+
* into the transcript, which is a mistake rather than an attack. The caller points
|
|
181
|
+
* the model at `read_file`, which asks the user properly.
|
|
182
|
+
*/
|
|
183
|
+
export function sensitiveCommandReason(command) {
|
|
184
|
+
const reads = CONTENT_READERS.test(command) || INPUT_REDIRECT.test(command);
|
|
185
|
+
if (!reads)
|
|
186
|
+
return null;
|
|
187
|
+
for (const { needle, what } of COMMAND_SENSITIVE) {
|
|
188
|
+
if (needle.test(command))
|
|
189
|
+
return what;
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
// Irreversible, essentially-never-legitimate commands. Patterns are intentionally
|
|
194
|
+
// narrow (high precision) so they don't get in the way of real work — the goal is
|
|
195
|
+
// to catch the catastrophic mistake, not to police the shell.
|
|
196
|
+
const CATASTROPHIC_PATTERNS = [
|
|
197
|
+
{ test: /\brm\s+(-[a-z]*\s+)*-[a-z]*[rf][a-z]*\s+(-[a-z]+\s+)*(\/|~|\$HOME)(\s|$)/i, what: "recursively deleting the filesystem root or home directory" },
|
|
198
|
+
// Real disk-format commands only. `\bformat\b` matched the word anywhere, so every
|
|
199
|
+
// benign PowerShell display cmdlet — `Format-Table`, `Format-List`, `Format-Hex` — and
|
|
200
|
+
// even `git log --format=…` was refused as "reformatting a disk", blocking ordinary
|
|
201
|
+
// work. Now it catches `mkfs`, the CMD `format <drive>:` / `format /switch`, and the
|
|
202
|
+
// PowerShell cmdlets that actually erase a volume (`Format-Volume`, `Format-Disk`).
|
|
203
|
+
{ test: /\bmkfs\b|\bformat\s+(?:[a-z]:|\/)|\bformat-(?:volume|disk)\b/i, what: "reformatting a disk" },
|
|
204
|
+
{ test: /\bdd\b[^\n]*\bof=\/dev\/(sd|nvme|disk|hd)/i, what: "overwriting a raw disk device" },
|
|
205
|
+
{ test: />\s*\/dev\/(sd|nvme|disk|hd)/i, what: "writing to a raw disk device" },
|
|
206
|
+
{ test: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/, what: "a fork bomb" },
|
|
207
|
+
{ test: /\bRemove-Item\b[^\n]*\b-Recurse\b[^\n]*(\\|\/|\$env:|~)(\s|$)/i, what: "recursively deleting a drive root or home directory" },
|
|
208
|
+
];
|
|
209
|
+
/**
|
|
210
|
+
* If `command` is an irreversible, catastrophic action, return a short reason;
|
|
211
|
+
* otherwise null.
|
|
212
|
+
*/
|
|
213
|
+
export function catastrophicCommandReason(command) {
|
|
214
|
+
for (const { test, what } of CATASTROPHIC_PATTERNS) {
|
|
215
|
+
if (test.test(command))
|
|
216
|
+
return what;
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/tools/guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* killTree.ts — spawning a child we can later kill completely, and killing it.
|
|
3
|
+
*
|
|
4
|
+
* Spawn and kill live in ONE module because they share one invariant. Killing a
|
|
5
|
+
* shell alone leaves grandchildren (node → jest, npx → the real server, a dev
|
|
6
|
+
* server) holding the output pipe open, so the job looks hung forever. Killing the
|
|
7
|
+
* whole tree needs `taskkill /T` on Windows and the negative-pid process group on
|
|
8
|
+
* POSIX — and the process group only exists if the child was spawned `detached`.
|
|
9
|
+
*
|
|
10
|
+
* That invariant used to be a sentence in a comment, and two of the three callers
|
|
11
|
+
* did not honour it: the MCP stdio transport and the language-server host both
|
|
12
|
+
* spawned attached and then skipped the tree kill entirely off Windows, so on macOS
|
|
13
|
+
* and Linux only the direct child was ever signalled and its children were orphaned.
|
|
14
|
+
* `spawnManaged` exists so that cannot happen again: every child that will later be
|
|
15
|
+
* tree-killed is created through it, and it sets the group up correctly by
|
|
16
|
+
* construction rather than by remembering.
|
|
17
|
+
*
|
|
18
|
+
* TWO KILL VARIANTS, and the difference is not cosmetic. `killTree` is asynchronous
|
|
19
|
+
* and escalates politely. It does NOT work from a `process.on("exit")` handler: Node
|
|
20
|
+
* runs no async work during exit, so neither a spawn nor a timer ever reaches the
|
|
21
|
+
* OS and the process survives — measured, not assumed. Anything disposing from an
|
|
22
|
+
* exit handler must call `killTreeSync`, which blocks and goes straight to the
|
|
23
|
+
* forceful signal because there is no time left to be polite.
|
|
24
|
+
*
|
|
25
|
+
* Why not make everything synchronous and delete the footgun? Measured on this
|
|
26
|
+
* machine, `spawnSync("taskkill /F /T")` blocks for a median of 122ms (max 140ms)
|
|
27
|
+
* per tree. Session teardown kills several at once, so an always-sync version would
|
|
28
|
+
* freeze the UI for a noticeable fraction of a second on every swap.
|
|
29
|
+
*/
|
|
30
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
31
|
+
import { readFileSync } from "node:fs";
|
|
32
|
+
const IS_WINDOWS = process.platform === "win32";
|
|
33
|
+
/**
|
|
34
|
+
* How long a process gets to shut down cleanly before it is killed outright.
|
|
35
|
+
*
|
|
36
|
+
* POSIX only, and it matters: a dev server, a language server, or an MCP server
|
|
37
|
+
* asked to stop will flush state, release a port, and close a socket if it is given
|
|
38
|
+
* a chance. Going straight to SIGKILL — which is what this module used to do — denies
|
|
39
|
+
* that, and the cost shows up later as a held port or a half-written file.
|
|
40
|
+
*/
|
|
41
|
+
const TERM_GRACE_MS = 2_000;
|
|
42
|
+
/** taskkill arguments for a whole process tree. */
|
|
43
|
+
function killArgs(pid) {
|
|
44
|
+
return ["/F", "/T", "/PID", String(pid)];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Spawn a child that can later be tree-killed.
|
|
48
|
+
*
|
|
49
|
+
* Use this for any process with a lifetime — a shell command, an MCP server, a
|
|
50
|
+
* language server. On POSIX it becomes its own process-group leader, which is the
|
|
51
|
+
* precondition `killTree` depends on; on Windows the group is handled by `taskkill
|
|
52
|
+
* /T` and nothing extra is needed. Callers keep full control of stdio, cwd and env;
|
|
53
|
+
* only the group setting is decided here, because that is the part that has to be
|
|
54
|
+
* consistent for the kill path to work.
|
|
55
|
+
*/
|
|
56
|
+
export function spawnManaged(command, args, options = {}) {
|
|
57
|
+
return spawn(command, args, {
|
|
58
|
+
...options,
|
|
59
|
+
windowsHide: options.windowsHide ?? true,
|
|
60
|
+
// POSIX: lead our own process group so the whole tree can be signalled at once.
|
|
61
|
+
// Windows has no process groups in this sense; taskkill /T walks the tree instead.
|
|
62
|
+
detached: IS_WINDOWS ? false : (options.detached ?? true),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Kill `pid` and everything beneath it. Non-blocking.
|
|
67
|
+
*
|
|
68
|
+
* POSIX escalates: SIGTERM to the group, then SIGKILL to whatever is still there
|
|
69
|
+
* after the grace period. Windows uses `taskkill /F /T`, which is already a forced
|
|
70
|
+
* whole-tree kill. Use only where an event loop is still running; see `killTreeSync`.
|
|
71
|
+
*/
|
|
72
|
+
/**
|
|
73
|
+
* Signal the process GROUP led by `pid`, falling back to the process itself.
|
|
74
|
+
*
|
|
75
|
+
* The group is the point: it reaches grandchildren. But a group only exists if the
|
|
76
|
+
* child was spawned `detached`, which is what `spawnManaged` guarantees. A pid from
|
|
77
|
+
* a plain `spawn` leads no group, so `kill(-pid)` raises ESRCH and the whole kill
|
|
78
|
+
* used to be swallowed by a `catch` and do NOTHING, with no error and no signal.
|
|
79
|
+
*
|
|
80
|
+
* That silent no-op is what hangs a POSIX process: the child survives, its handle
|
|
81
|
+
* keeps the event loop alive, and the parent never exits. MEASURED on Linux, a
|
|
82
|
+
* plainly-spawned child was still in state `S` after killTreeSync returned.
|
|
83
|
+
*
|
|
84
|
+
* Falling back to the bare pid means the caller always gets the kill they asked
|
|
85
|
+
* for. Grandchildren of a non-detached child are still missed, which is exactly why
|
|
86
|
+
* `spawnManaged` exists, but one dead process beats zero.
|
|
87
|
+
*/
|
|
88
|
+
function signalGroupThenSelf(pid, signal) {
|
|
89
|
+
try {
|
|
90
|
+
process.kill(-pid, signal);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
try {
|
|
94
|
+
process.kill(pid, signal);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Already gone, or no permission — nothing more we can do.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export function killTree(pid) {
|
|
102
|
+
if (pid === undefined)
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
if (IS_WINDOWS) {
|
|
106
|
+
spawn("taskkill", killArgs(pid), { windowsHide: true, stdio: "ignore" });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
signalGroupThenSelf(pid, "SIGTERM");
|
|
110
|
+
const escalate = setTimeout(() => {
|
|
111
|
+
signalGroupThenSelf(pid, "SIGKILL");
|
|
112
|
+
}, TERM_GRACE_MS);
|
|
113
|
+
// Never hold the process open just to deliver a follow-up kill.
|
|
114
|
+
escalate.unref?.();
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Already gone, or no permission — nothing more we can do.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* How long to wait for the OS to actually finish killing before giving up.
|
|
122
|
+
*
|
|
123
|
+
* SIGKILL cannot be caught, blocked or ignored, so this deadline is not there for
|
|
124
|
+
* a process that resists — nothing resists SIGKILL. It exists for the pathological
|
|
125
|
+
* case where a process is wedged in uninterruptible kernel I/O (state `D`), where
|
|
126
|
+
* the kill is pending until that I/O completes and waiting longer would not help.
|
|
127
|
+
* Better to return late than to hang a shutdown forever.
|
|
128
|
+
*/
|
|
129
|
+
const KILL_CONFIRM_MS = 1_500;
|
|
130
|
+
/** One shared buffer for the sync sleep; allocating per poll would churn. */
|
|
131
|
+
const SLEEP_SLOT = new Int32Array(new SharedArrayBuffer(4));
|
|
132
|
+
/**
|
|
133
|
+
* Block this thread for `ms` with no event loop involved.
|
|
134
|
+
*
|
|
135
|
+
* `Atomics.wait` on a value that never changes is the only true synchronous sleep
|
|
136
|
+
* in Node. setTimeout is useless here by definition: the callers of this module
|
|
137
|
+
* run from `process.on("exit")`, where no timer will ever fire again.
|
|
138
|
+
*/
|
|
139
|
+
function sleepSync(ms) {
|
|
140
|
+
Atomics.wait(SLEEP_SLOT, 0, 0, ms);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Kill `pid` and its descendants, blocking until it has happened.
|
|
144
|
+
*
|
|
145
|
+
* The variant that works from a `process.on("exit")` handler. No grace period: the
|
|
146
|
+
* process is on its way out and there is no event loop left to deliver a follow-up
|
|
147
|
+
* signal on, so a polite first attempt would simply be the only attempt and would
|
|
148
|
+
* leave anything that ignores SIGTERM running.
|
|
149
|
+
*
|
|
150
|
+
* "Blocking until it has happened" is the whole contract, and it used to be a lie.
|
|
151
|
+
* Sending a signal is not the same as the target being dead: `process.kill` returns
|
|
152
|
+
* as soon as the signal is QUEUED, and for a window after that the process is still
|
|
153
|
+
* scheduled, still holding its port, still listed as running. An exit handler gets
|
|
154
|
+
* no second chance to look, so a caller that returned into `process.exit` left a
|
|
155
|
+
* live process behind. MEASURED: `isProcessStopped` reported a just-SIGKILLed child
|
|
156
|
+
* as alive immediately after this function returned.
|
|
157
|
+
*
|
|
158
|
+
* So the kill is now CONFIRMED before returning, not merely sent.
|
|
159
|
+
*/
|
|
160
|
+
export function killTreeSync(pid) {
|
|
161
|
+
if (pid === undefined)
|
|
162
|
+
return;
|
|
163
|
+
try {
|
|
164
|
+
if (IS_WINDOWS) {
|
|
165
|
+
spawnSync("taskkill", killArgs(pid), { windowsHide: true, stdio: "ignore" });
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
signalGroupThenSelf(pid, "SIGKILL");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// Already gone, or no permission — nothing more we can do.
|
|
173
|
+
}
|
|
174
|
+
// Poll until the OS agrees the process is gone. Costs nothing in the common case
|
|
175
|
+
// (the first check almost always passes); it is the rare slow reap that this
|
|
176
|
+
// exists for, because that is exactly when an exit handler would leak a process.
|
|
177
|
+
const deadline = Date.now() + KILL_CONFIRM_MS;
|
|
178
|
+
while (!isProcessStopped(pid) && Date.now() < deadline) {
|
|
179
|
+
sleepSync(1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Has `pid` stopped running? Not the same as "does the pid exist".
|
|
184
|
+
*
|
|
185
|
+
* On POSIX a SIGKILLed child stays a ZOMBIE until its parent reaps it, and
|
|
186
|
+
* `kill(pid, 0)` succeeds against a zombie, so the obvious liveness check reports a
|
|
187
|
+
* process we just killed as still alive. MEASURED: right after killTreeSync, a
|
|
188
|
+
* correctly killed child sat in state `Z`. Callers that need "is it gone" therefore
|
|
189
|
+
* have to read the state, not just probe for existence.
|
|
190
|
+
*
|
|
191
|
+
* Windows has no zombie stage, so existence is the whole answer there.
|
|
192
|
+
*/
|
|
193
|
+
export function isProcessStopped(pid) {
|
|
194
|
+
if (pid === undefined)
|
|
195
|
+
return true;
|
|
196
|
+
try {
|
|
197
|
+
process.kill(pid, 0);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return true; // no such process
|
|
201
|
+
}
|
|
202
|
+
if (IS_WINDOWS)
|
|
203
|
+
return false;
|
|
204
|
+
try {
|
|
205
|
+
// /proc/<pid>/stat: the state letter follows the parenthesised command name,
|
|
206
|
+
// which can itself contain spaces or parens, so split on the LAST ") ".
|
|
207
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
208
|
+
const after = stat.slice(stat.lastIndexOf(") ") + 2);
|
|
209
|
+
return after.startsWith("Z"); // reaped-pending is stopped for every practical purpose
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return true; // /proc entry vanished between the two checks
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=killTree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"killTree.js","sourceRoot":"","sources":["../../src/tools/killTree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* listDir.ts — list the entries of one directory.
|
|
3
|
+
*
|
|
4
|
+
* The quickest way for the model to get its bearings: what's in this folder?
|
|
5
|
+
* Read-only, single level (use glob for a recursive search). Directories are
|
|
6
|
+
* marked with a trailing slash so the model can tell them from files at a glance.
|
|
7
|
+
*/
|
|
8
|
+
import { promises as fs } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { isMultiRoot, relativize, searchUnits } from "./paths.js";
|
|
11
|
+
import { DEFAULT_IGNORES } from "./walk.js";
|
|
12
|
+
import { fail } from "./results.js";
|
|
13
|
+
/** Entries shown per directory before the list is cut short (the cut is always
|
|
14
|
+
* announced). Exported so the number in the description is pinned to the real value. */
|
|
15
|
+
export const MAX_ENTRIES = 400;
|
|
16
|
+
export const listDir = {
|
|
17
|
+
name: "list_dir",
|
|
18
|
+
readOnly: true,
|
|
19
|
+
// The trailing-slash promise was the whole reading key and it was silently wrong on
|
|
20
|
+
// symlinked directories, so the marks each get spelled out now. The paragraph about
|
|
21
|
+
// seeing more than a search does is the one that earns its place: this is the only
|
|
22
|
+
// tool that reports the disk as it is, so it is where the model finds out that a
|
|
23
|
+
// file it cannot grep for, or cannot read at all, nevertheless exists.
|
|
24
|
+
description: "List the immediate contents of a directory: one level, not recursive. Use glob to " +
|
|
25
|
+
"match paths recursively, and grep to search inside files. Given no path it lists " +
|
|
26
|
+
"every session root.\n" +
|
|
27
|
+
"HOW TO READ IT: entries come directories first, then alphabetically. A trailing " +
|
|
28
|
+
"slash means a directory. `(symlink)` means the entry is a link, and it still gets " +
|
|
29
|
+
"the slash when it points at a directory; `(broken symlink)` means the target is " +
|
|
30
|
+
"gone. `(skipped by search)` marks a directory that is really there but that glob " +
|
|
31
|
+
"and grep will not descend into, which is why a file inside it can exist and still " +
|
|
32
|
+
`never appear in a search. Long listings stop after ${MAX_ENTRIES} entries and say ` +
|
|
33
|
+
"how many were left out.\n" +
|
|
34
|
+
"This reports the DISK, not the searchable tree, so it shows more than the search " +
|
|
35
|
+
"tools do: secrets, other agents' folders, and ignored build output all appear " +
|
|
36
|
+
"here. Seeing something listed is therefore not a promise you may open it — " +
|
|
37
|
+
"read_file still refuses secrets, and asks before another agent's data. Treat a " +
|
|
38
|
+
"listing as evidence of what exists, and let the read decide what you may see.",
|
|
39
|
+
parameters: {
|
|
40
|
+
type: "object",
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
properties: {
|
|
43
|
+
path: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Directory (or a root's label) to list. Defaults to every session root.",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
async execute(args, ctx) {
|
|
50
|
+
const rawPath = typeof args.path === "string" && args.path.trim() ? args.path.trim() : undefined;
|
|
51
|
+
const units = searchUnits(ctx, rawPath);
|
|
52
|
+
const multi = isMultiRoot(ctx) && !rawPath; // group with headers when sweeping roots
|
|
53
|
+
const blocks = [];
|
|
54
|
+
let total = 0;
|
|
55
|
+
for (const unit of units) {
|
|
56
|
+
const dir = unit.sub ? join(unit.root, unit.sub) : unit.root;
|
|
57
|
+
const listed = await listOne(ctx, dir, unit);
|
|
58
|
+
if (!listed) {
|
|
59
|
+
if (rawPath)
|
|
60
|
+
return fail(await whyUnreadable(dir, rawPath));
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
total += listed.count;
|
|
64
|
+
blocks.push(multi ? `${relativize(ctx, dir)}/\n${indent(listed.body)}` : listed.body);
|
|
65
|
+
}
|
|
66
|
+
if (blocks.length === 0)
|
|
67
|
+
return fail(`directory not found: ${rawPath ?? "."}`);
|
|
68
|
+
return {
|
|
69
|
+
output: blocks.join("\n\n"),
|
|
70
|
+
summary: `list ${units.length > 1 ? `${units.length} roots` : relativize(ctx, units[0].sub ? join(units[0].root, units[0].sub) : units[0].root)} (${total} item${total === 1 ? "" : "s"})`,
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* What an entry actually IS, following symlinks.
|
|
76
|
+
*
|
|
77
|
+
* `readdir` reports a symlink as a symlink and nothing else: for a junction or
|
|
78
|
+
* symlink pointing at a directory, `isDirectory()` and `isFile()` are BOTH false
|
|
79
|
+
* (measured, not assumed). So the trailing slash this tool promises never appeared
|
|
80
|
+
* on a linked directory and it read as an ordinary file — which matters most exactly
|
|
81
|
+
* where links are common, in node_modules/.bin and in a linked monorepo package.
|
|
82
|
+
* Only symlinks cost an extra stat, and a broken one is reported rather than thrown.
|
|
83
|
+
*/
|
|
84
|
+
async function classify(dir, e) {
|
|
85
|
+
if (!e.isSymbolicLink())
|
|
86
|
+
return { isDir: e.isDirectory(), note: "" };
|
|
87
|
+
try {
|
|
88
|
+
const st = await fs.stat(join(dir, e.name)); // follows the link
|
|
89
|
+
return { isDir: st.isDirectory(), note: " (symlink)" };
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { isDir: false, note: " (broken symlink)" };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** List one directory's immediate entries (dirs first), or null if unreadable. */
|
|
96
|
+
async function listOne(ctx, dir, _unit) {
|
|
97
|
+
let entries;
|
|
98
|
+
try {
|
|
99
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const resolved = await Promise.all(entries.map(async (e) => ({ name: e.name, ...(await classify(dir, e)) })));
|
|
105
|
+
resolved.sort((a, b) => {
|
|
106
|
+
const ad = a.isDir ? 0 : 1;
|
|
107
|
+
const bd = b.isDir ? 0 : 1;
|
|
108
|
+
return ad !== bd ? ad - bd : a.name.localeCompare(b.name);
|
|
109
|
+
});
|
|
110
|
+
if (resolved.length === 0)
|
|
111
|
+
return { body: "(empty directory)", count: 0 };
|
|
112
|
+
const truncated = resolved.length > MAX_ENTRIES;
|
|
113
|
+
const shown = resolved.slice(0, MAX_ENTRIES).map((e) => {
|
|
114
|
+
const noisy = e.isDir && DEFAULT_IGNORES.has(e.name);
|
|
115
|
+
return `${e.name}${e.isDir ? "/" : ""}${e.note}${noisy ? " (skipped by search)" : ""}`;
|
|
116
|
+
});
|
|
117
|
+
if (truncated)
|
|
118
|
+
shown.push(`… (${resolved.length - MAX_ENTRIES} more)`);
|
|
119
|
+
return { body: shown.join("\n"), count: resolved.length };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Say why a directory could not be listed instead of always claiming it is missing.
|
|
123
|
+
*
|
|
124
|
+
* `readdir` fails the same way for a path that does not exist, a path that is a FILE,
|
|
125
|
+
* and a directory the OS will not open. Reporting all three as "directory not found"
|
|
126
|
+
* sent the model looking for a file it had already found: pointing `list_dir` at a
|
|
127
|
+
* real file answered as though the file were not there.
|
|
128
|
+
*/
|
|
129
|
+
async function whyUnreadable(dir, shown) {
|
|
130
|
+
try {
|
|
131
|
+
const st = await fs.stat(dir);
|
|
132
|
+
if (st.isDirectory())
|
|
133
|
+
return `cannot read directory (permission denied?): ${shown}`;
|
|
134
|
+
return `not a directory, it is a file: ${shown} — use read_file to see its contents`;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return `directory not found: ${shown}`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function indent(text) {
|
|
141
|
+
return text.split("\n").map((l) => ` ${l}`).join("\n");
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=listDir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listDir.js","sourceRoot":"","sources":["../../src/tools/listDir.ts"],"names":[],"mappings":"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"}
|