luca 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yaml +169 -0
- package/AGENTS.md +99 -0
- package/CLAUDE.md +115 -0
- package/CNAME +1 -0
- package/README.md +257 -9
- package/RUNME.md +56 -0
- package/assistants/codingAssistant/ABOUT.md +5 -0
- package/assistants/codingAssistant/CORE.md +28 -0
- package/assistants/codingAssistant/hooks.ts +21 -0
- package/assistants/codingAssistant/tools.ts +12 -0
- package/assistants/inkbot/ABOUT.md +16 -0
- package/assistants/inkbot/CORE.md +330 -0
- package/assistants/inkbot/hooks.ts +6 -0
- package/assistants/inkbot/tools.ts +53 -0
- package/assistants/researcher/ABOUT.md +5 -0
- package/assistants/researcher/CORE.md +46 -0
- package/assistants/researcher/hooks.ts +16 -0
- package/assistants/researcher/tools.ts +237 -0
- package/bun.lock +2769 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-bootstrap.ts +118 -0
- package/commands/build-python-bridge.ts +43 -0
- package/commands/build-scaffolds.ts +176 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/inkbot.ts +874 -0
- package/commands/release.ts +80 -0
- package/commands/try-all-challenges.ts +543 -0
- package/commands/try-challenge.ts +100 -0
- package/dist/agi/container.server.d.ts +63 -0
- package/dist/agi/container.server.d.ts.map +1 -0
- package/dist/agi/endpoints/ask.d.ts +20 -0
- package/dist/agi/endpoints/ask.d.ts.map +1 -0
- package/dist/agi/endpoints/conversations/[id].d.ts +27 -0
- package/dist/agi/endpoints/conversations/[id].d.ts.map +1 -0
- package/dist/agi/endpoints/conversations.d.ts +18 -0
- package/dist/agi/endpoints/conversations.d.ts.map +1 -0
- package/dist/agi/endpoints/experts.d.ts +8 -0
- package/dist/agi/endpoints/experts.d.ts.map +1 -0
- package/dist/agi/feature.d.ts +9 -0
- package/dist/agi/feature.d.ts.map +1 -0
- package/dist/agi/features/assistant.d.ts +509 -0
- package/dist/agi/features/assistant.d.ts.map +1 -0
- package/dist/agi/features/assistants-manager.d.ts +236 -0
- package/dist/agi/features/assistants-manager.d.ts.map +1 -0
- package/dist/agi/features/autonomous-assistant.d.ts +281 -0
- package/dist/agi/features/autonomous-assistant.d.ts.map +1 -0
- package/dist/agi/features/browser-use.d.ts +479 -0
- package/dist/agi/features/browser-use.d.ts.map +1 -0
- package/dist/agi/features/claude-code.d.ts +824 -0
- package/dist/agi/features/claude-code.d.ts.map +1 -0
- package/dist/agi/features/conversation-history.d.ts +245 -0
- package/dist/agi/features/conversation-history.d.ts.map +1 -0
- package/dist/agi/features/conversation.d.ts +464 -0
- package/dist/agi/features/conversation.d.ts.map +1 -0
- package/dist/agi/features/docs-reader.d.ts +72 -0
- package/dist/agi/features/docs-reader.d.ts.map +1 -0
- package/dist/agi/features/file-tools.d.ts +110 -0
- package/dist/agi/features/file-tools.d.ts.map +1 -0
- package/dist/agi/features/luca-coder.d.ts +323 -0
- package/dist/agi/features/luca-coder.d.ts.map +1 -0
- package/dist/agi/features/openai-codex.d.ts +381 -0
- package/dist/agi/features/openai-codex.d.ts.map +1 -0
- package/dist/agi/features/openapi.d.ts +200 -0
- package/dist/agi/features/openapi.d.ts.map +1 -0
- package/dist/agi/features/skills-library.d.ts +167 -0
- package/dist/agi/features/skills-library.d.ts.map +1 -0
- package/dist/agi/index.d.ts +5 -0
- package/dist/agi/index.d.ts.map +1 -0
- package/dist/agi/lib/interceptor-chain.d.ts +44 -0
- package/dist/agi/lib/interceptor-chain.d.ts.map +1 -0
- package/dist/agi/lib/token-counter.d.ts +13 -0
- package/dist/agi/lib/token-counter.d.ts.map +1 -0
- package/dist/bootstrap/generated.d.ts +5 -0
- package/dist/bootstrap/generated.d.ts.map +1 -0
- package/dist/browser.d.ts +12 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/bus.d.ts +29 -0
- package/dist/bus.d.ts.map +1 -0
- package/dist/cli/build-info.d.ts +4 -0
- package/dist/cli/build-info.d.ts.map +1 -0
- package/dist/cli/cli.d.ts +3 -12
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/client.d.ts +60 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/clients/civitai/index.d.ts +472 -0
- package/dist/clients/civitai/index.d.ts.map +1 -0
- package/dist/clients/client-template.d.ts +30 -0
- package/dist/clients/client-template.d.ts.map +1 -0
- package/dist/clients/comfyui/index.d.ts +281 -0
- package/dist/clients/comfyui/index.d.ts.map +1 -0
- package/dist/clients/elevenlabs/index.d.ts +197 -0
- package/dist/clients/elevenlabs/index.d.ts.map +1 -0
- package/dist/clients/graph.d.ts +64 -0
- package/dist/clients/graph.d.ts.map +1 -0
- package/dist/clients/openai/index.d.ts +247 -0
- package/dist/clients/openai/index.d.ts.map +1 -0
- package/dist/clients/rest.d.ts +92 -0
- package/dist/clients/rest.d.ts.map +1 -0
- package/dist/clients/supabase/index.d.ts +176 -0
- package/dist/clients/supabase/index.d.ts.map +1 -0
- package/dist/clients/websocket.d.ts +127 -0
- package/dist/clients/websocket.d.ts.map +1 -0
- package/dist/command.d.ts +163 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/commands/bootstrap.d.ts +20 -0
- package/dist/commands/bootstrap.d.ts.map +1 -0
- package/dist/commands/chat.d.ts +37 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/code.d.ts +28 -0
- package/dist/commands/code.d.ts.map +1 -0
- package/dist/commands/console.d.ts +22 -0
- package/dist/commands/console.d.ts.map +1 -0
- package/dist/commands/describe.d.ts +50 -0
- package/dist/commands/describe.d.ts.map +1 -0
- package/dist/commands/eval.d.ts +23 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/help.d.ts +25 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/index.d.ts +18 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/introspect.d.ts +24 -0
- package/dist/commands/introspect.d.ts.map +1 -0
- package/dist/commands/mcp.d.ts +35 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/prompt.d.ts +38 -0
- package/dist/commands/prompt.d.ts.map +1 -0
- package/dist/commands/run.d.ts +24 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/sandbox-mcp.d.ts +34 -0
- package/dist/commands/sandbox-mcp.d.ts.map +1 -0
- package/dist/commands/save-api-docs.d.ts +21 -0
- package/dist/commands/save-api-docs.d.ts.map +1 -0
- package/dist/commands/scaffold.d.ts +24 -0
- package/dist/commands/scaffold.d.ts.map +1 -0
- package/dist/commands/select.d.ts +22 -0
- package/dist/commands/select.d.ts.map +1 -0
- package/dist/commands/serve.d.ts +29 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/container-describer.d.ts +144 -0
- package/dist/container-describer.d.ts.map +1 -0
- package/dist/container.d.ts +451 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/endpoint.d.ts +113 -0
- package/dist/endpoint.d.ts.map +1 -0
- package/dist/feature.d.ts +47 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/graft.d.ts +29 -0
- package/dist/graft.d.ts.map +1 -0
- package/dist/hash-object.d.ts +8 -0
- package/dist/hash-object.d.ts.map +1 -0
- package/dist/helper.d.ts +209 -0
- package/dist/helper.d.ts.map +1 -0
- package/dist/introspection/generated.node.d.ts +44623 -0
- package/dist/introspection/generated.node.d.ts.map +1 -0
- package/dist/introspection/generated.web.d.ts +1412 -0
- package/dist/introspection/generated.web.d.ts.map +1 -0
- package/dist/introspection/index.d.ts +156 -0
- package/dist/introspection/index.d.ts.map +1 -0
- package/dist/introspection/scan.d.ts +147 -0
- package/dist/introspection/scan.d.ts.map +1 -0
- package/dist/node/container.d.ts +256 -0
- package/dist/node/container.d.ts.map +1 -0
- package/dist/node/feature.d.ts +9 -0
- package/dist/node/feature.d.ts.map +1 -0
- package/dist/node/features/container-link.d.ts +213 -0
- package/dist/node/features/container-link.d.ts.map +1 -0
- package/dist/node/features/content-db.d.ts +354 -0
- package/dist/node/features/content-db.d.ts.map +1 -0
- package/dist/node/features/disk-cache.d.ts +236 -0
- package/dist/node/features/disk-cache.d.ts.map +1 -0
- package/dist/node/features/dns.d.ts +511 -0
- package/dist/node/features/dns.d.ts.map +1 -0
- package/dist/node/features/docker.d.ts +485 -0
- package/dist/node/features/docker.d.ts.map +1 -0
- package/dist/node/features/downloader.d.ts +73 -0
- package/dist/node/features/downloader.d.ts.map +1 -0
- package/dist/node/features/figlet-fonts.d.ts +4 -0
- package/dist/node/features/figlet-fonts.d.ts.map +1 -0
- package/dist/node/features/file-manager.d.ts +177 -0
- package/dist/node/features/file-manager.d.ts.map +1 -0
- package/dist/node/features/fs.d.ts +635 -0
- package/dist/node/features/fs.d.ts.map +1 -0
- package/dist/node/features/git.d.ts +329 -0
- package/dist/node/features/git.d.ts.map +1 -0
- package/dist/node/features/google-auth.d.ts +200 -0
- package/dist/node/features/google-auth.d.ts.map +1 -0
- package/dist/node/features/google-calendar.d.ts +194 -0
- package/dist/node/features/google-calendar.d.ts.map +1 -0
- package/dist/node/features/google-docs.d.ts +138 -0
- package/dist/node/features/google-docs.d.ts.map +1 -0
- package/dist/node/features/google-drive.d.ts +202 -0
- package/dist/node/features/google-drive.d.ts.map +1 -0
- package/dist/node/features/google-mail.d.ts +221 -0
- package/dist/node/features/google-mail.d.ts.map +1 -0
- package/dist/node/features/google-sheets.d.ts +157 -0
- package/dist/node/features/google-sheets.d.ts.map +1 -0
- package/dist/node/features/grep.d.ts +207 -0
- package/dist/node/features/grep.d.ts.map +1 -0
- package/dist/node/features/helpers.d.ts +236 -0
- package/dist/node/features/helpers.d.ts.map +1 -0
- package/dist/node/features/ink.d.ts +332 -0
- package/dist/node/features/ink.d.ts.map +1 -0
- package/dist/node/features/ipc-socket.d.ts +298 -0
- package/dist/node/features/ipc-socket.d.ts.map +1 -0
- package/dist/node/features/json-tree.d.ts +140 -0
- package/dist/node/features/json-tree.d.ts.map +1 -0
- package/dist/node/features/networking.d.ts +373 -0
- package/dist/node/features/networking.d.ts.map +1 -0
- package/dist/node/features/nlp.d.ts +125 -0
- package/dist/node/features/nlp.d.ts.map +1 -0
- package/dist/node/features/opener.d.ts +93 -0
- package/dist/node/features/opener.d.ts.map +1 -0
- package/dist/node/features/os.d.ts +168 -0
- package/dist/node/features/os.d.ts.map +1 -0
- package/dist/node/features/package-finder.d.ts +419 -0
- package/dist/node/features/package-finder.d.ts.map +1 -0
- package/dist/node/features/postgres.d.ts +173 -0
- package/dist/node/features/postgres.d.ts.map +1 -0
- package/dist/node/features/proc.d.ts +285 -0
- package/dist/node/features/proc.d.ts.map +1 -0
- package/dist/node/features/process-manager.d.ts +427 -0
- package/dist/node/features/process-manager.d.ts.map +1 -0
- package/dist/node/features/python.d.ts +477 -0
- package/dist/node/features/python.d.ts.map +1 -0
- package/dist/node/features/redis.d.ts +247 -0
- package/dist/node/features/redis.d.ts.map +1 -0
- package/dist/node/features/repl.d.ts +84 -0
- package/dist/node/features/repl.d.ts.map +1 -0
- package/dist/node/features/runpod.d.ts +527 -0
- package/dist/node/features/runpod.d.ts.map +1 -0
- package/dist/node/features/secure-shell.d.ts +145 -0
- package/dist/node/features/secure-shell.d.ts.map +1 -0
- package/dist/node/features/semantic-search.d.ts +207 -0
- package/dist/node/features/semantic-search.d.ts.map +1 -0
- package/dist/node/features/sqlite.d.ts +180 -0
- package/dist/node/features/sqlite.d.ts.map +1 -0
- package/dist/node/features/telegram.d.ts +173 -0
- package/dist/node/features/telegram.d.ts.map +1 -0
- package/dist/node/features/transpiler.d.ts +51 -0
- package/dist/node/features/transpiler.d.ts.map +1 -0
- package/dist/node/features/tts.d.ts +108 -0
- package/dist/node/features/tts.d.ts.map +1 -0
- package/dist/node/features/ui.d.ts +562 -0
- package/dist/node/features/ui.d.ts.map +1 -0
- package/dist/node/features/vault.d.ts +90 -0
- package/dist/node/features/vault.d.ts.map +1 -0
- package/dist/node/features/vm.d.ts +285 -0
- package/dist/node/features/vm.d.ts.map +1 -0
- package/dist/node/features/yaml-tree.d.ts +118 -0
- package/dist/node/features/yaml-tree.d.ts.map +1 -0
- package/dist/node/features/yaml.d.ts +127 -0
- package/dist/node/features/yaml.d.ts.map +1 -0
- package/dist/node.d.ts +67 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/python/generated.d.ts +2 -0
- package/dist/python/generated.d.ts.map +1 -0
- package/dist/react/index.d.ts +36 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/registry.d.ts +97 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/scaffolds/generated.d.ts +13 -0
- package/dist/scaffolds/generated.d.ts.map +1 -0
- package/dist/scaffolds/template.d.ts +11 -0
- package/dist/scaffolds/template.d.ts.map +1 -0
- package/dist/schemas/base.d.ts +254 -0
- package/dist/schemas/base.d.ts.map +1 -0
- package/dist/selector.d.ts +130 -0
- package/dist/selector.d.ts.map +1 -0
- package/dist/server.d.ts +89 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/servers/express.d.ts +104 -0
- package/dist/servers/express.d.ts.map +1 -0
- package/dist/servers/mcp.d.ts +201 -0
- package/dist/servers/mcp.d.ts.map +1 -0
- package/dist/servers/socket.d.ts +121 -0
- package/dist/servers/socket.d.ts.map +1 -0
- package/dist/state.d.ts +24 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/web/clients/socket.d.ts +37 -0
- package/dist/web/clients/socket.d.ts.map +1 -0
- package/dist/web/container.d.ts +55 -0
- package/dist/web/container.d.ts.map +1 -0
- package/dist/web/extension.d.ts +4 -0
- package/dist/web/extension.d.ts.map +1 -0
- package/dist/web/feature.d.ts +8 -0
- package/dist/web/feature.d.ts.map +1 -0
- package/dist/web/features/asset-loader.d.ts +35 -0
- package/dist/web/features/asset-loader.d.ts.map +1 -0
- package/dist/web/features/container-link.d.ts +167 -0
- package/dist/web/features/container-link.d.ts.map +1 -0
- package/dist/web/features/esbuild.d.ts +51 -0
- package/dist/web/features/esbuild.d.ts.map +1 -0
- package/dist/web/features/helpers.d.ts +140 -0
- package/dist/web/features/helpers.d.ts.map +1 -0
- package/dist/web/features/network.d.ts +69 -0
- package/dist/web/features/network.d.ts.map +1 -0
- package/dist/web/features/speech.d.ts +71 -0
- package/dist/web/features/speech.d.ts.map +1 -0
- package/dist/web/features/vault.d.ts +62 -0
- package/dist/web/features/vault.d.ts.map +1 -0
- package/dist/web/features/vm.d.ts +48 -0
- package/dist/web/features/vm.d.ts.map +1 -0
- package/dist/web/features/voice-recognition.d.ts +96 -0
- package/dist/web/features/voice-recognition.d.ts.map +1 -0
- package/dist/web/shims/isomorphic-vm.d.ts +22 -0
- package/dist/web/shims/isomorphic-vm.d.ts.map +1 -0
- package/docs/CLI.md +335 -0
- package/docs/CNAME +1 -0
- package/docs/README.md +60 -0
- package/docs/TABLE-OF-CONTENTS.md +183 -0
- package/docs/apis/clients/elevenlabs.md +308 -0
- package/docs/apis/clients/graph.md +107 -0
- package/docs/apis/clients/openai.md +429 -0
- package/docs/apis/clients/rest.md +161 -0
- package/docs/apis/clients/websocket.md +174 -0
- package/docs/apis/features/agi/assistant.md +625 -0
- package/docs/apis/features/agi/assistants-manager.md +282 -0
- package/docs/apis/features/agi/auto-assistant.md +279 -0
- package/docs/apis/features/agi/browser-use.md +802 -0
- package/docs/apis/features/agi/claude-code.md +884 -0
- package/docs/apis/features/agi/conversation-history.md +364 -0
- package/docs/apis/features/agi/conversation.md +548 -0
- package/docs/apis/features/agi/docs-reader.md +99 -0
- package/docs/apis/features/agi/file-tools.md +163 -0
- package/docs/apis/features/agi/luca-coder.md +407 -0
- package/docs/apis/features/agi/openai-codex.md +396 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +239 -0
- package/docs/apis/features/node/container-link.md +192 -0
- package/docs/apis/features/node/content-db.md +450 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +652 -0
- package/docs/apis/features/node/docker.md +706 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +60 -0
- package/docs/apis/features/node/file-manager.md +191 -0
- package/docs/apis/features/node/fs.md +1217 -0
- package/docs/apis/features/node/git.md +371 -0
- package/docs/apis/features/node/google-auth.md +193 -0
- package/docs/apis/features/node/google-calendar.md +202 -0
- package/docs/apis/features/node/google-docs.md +173 -0
- package/docs/apis/features/node/google-drive.md +246 -0
- package/docs/apis/features/node/google-mail.md +214 -0
- package/docs/apis/features/node/google-sheets.md +194 -0
- package/docs/apis/features/node/grep.md +292 -0
- package/docs/apis/features/node/helpers.md +164 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +249 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/networking.md +316 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +146 -0
- package/docs/apis/features/node/package-finder.md +392 -0
- package/docs/apis/features/node/postgres.md +234 -0
- package/docs/apis/features/node/proc.md +399 -0
- package/docs/apis/features/node/process-manager.md +305 -0
- package/docs/apis/features/node/python.md +604 -0
- package/docs/apis/features/node/redis.md +380 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +674 -0
- package/docs/apis/features/node/secure-shell.md +176 -0
- package/docs/apis/features/node/semantic-search.md +408 -0
- package/docs/apis/features/node/sqlite.md +233 -0
- package/docs/apis/features/node/telegram.md +279 -0
- package/docs/apis/features/node/transpiler.md +74 -0
- package/docs/apis/features/node/tts.md +133 -0
- package/docs/apis/features/node/ui.md +701 -0
- package/docs/apis/features/node/vault.md +59 -0
- package/docs/apis/features/node/vm.md +75 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +59 -0
- package/docs/apis/features/web/container-link.md +192 -0
- package/docs/apis/features/web/esbuild.md +54 -0
- package/docs/apis/features/web/helpers.md +164 -0
- package/docs/apis/features/web/network.md +44 -0
- package/docs/apis/features/web/speech.md +69 -0
- package/docs/apis/features/web/vault.md +59 -0
- package/docs/apis/features/web/vm.md +75 -0
- package/docs/apis/features/web/voice.md +84 -0
- package/docs/apis/servers/express.md +171 -0
- package/docs/apis/servers/mcp.md +238 -0
- package/docs/apis/servers/websocket.md +170 -0
- package/docs/bootstrap/CLAUDE.md +101 -0
- package/docs/bootstrap/SKILL.md +341 -0
- package/docs/bootstrap/templates/about-command.ts +41 -0
- package/docs/bootstrap/templates/docs-models.ts +22 -0
- package/docs/bootstrap/templates/docs-readme.md +43 -0
- package/docs/bootstrap/templates/example-feature.ts +53 -0
- package/docs/bootstrap/templates/health-endpoint.ts +15 -0
- package/docs/bootstrap/templates/luca-cli.ts +30 -0
- package/docs/bootstrap/templates/runme.md +54 -0
- package/docs/challenges/caching-proxy.md +16 -0
- package/docs/challenges/content-db-round-trip.md +14 -0
- package/docs/challenges/custom-command.md +9 -0
- package/docs/challenges/file-watcher-pipeline.md +11 -0
- package/docs/challenges/grep-audit-report.md +15 -0
- package/docs/challenges/multi-feature-dashboard.md +14 -0
- package/docs/challenges/process-orchestrator.md +17 -0
- package/docs/challenges/rest-api-server-with-client.md +12 -0
- package/docs/challenges/script-runner-with-vm.md +11 -0
- package/docs/challenges/simple-rest-api.md +15 -0
- package/docs/challenges/websocket-serve-and-client.md +11 -0
- package/docs/challenges/yaml-config-system.md +14 -0
- package/docs/command-system-overhaul.md +94 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/assistant/CORE.md +18 -0
- package/docs/examples/assistant/hooks.ts +3 -0
- package/docs/examples/assistant/tools.ts +10 -0
- package/docs/examples/assistant-hooks-reference.ts +171 -0
- package/docs/examples/assistant-with-process-manager.md +84 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/entity.md +124 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/feature-as-tool-provider.md +143 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +132 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/structured-output-with-assistants.md +144 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/websocket-ask-and-reply-example.md +128 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/assistant-factory-pattern.md +142 -0
- package/docs/in-memory-fs.md +4 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +164 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +41 -0
- package/docs/philosophy.md +86 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/check-for-undocumented-features.md +27 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/scaffolds/client.md +149 -0
- package/docs/scaffolds/command.md +120 -0
- package/docs/scaffolds/endpoint.md +171 -0
- package/docs/scaffolds/feature.md +158 -0
- package/docs/scaffolds/selector.md +91 -0
- package/docs/scaffolds/server.md +196 -0
- package/docs/selectors.md +115 -0
- package/docs/sessions/custom-command/attempt-log-2.md +195 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
- package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
- package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
- package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
- package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
- package/docs/tutorials/00-bootstrap.md +166 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +252 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +203 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +157 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/docs/tutorials/19-python-sessions.md +401 -0
- package/docs/tutorials/20-browser-esm.md +234 -0
- package/index.html +1430 -0
- package/index.ts +1 -0
- package/install.sh +84 -0
- package/luca.cli.ts +16 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +219 -58
- package/public/index.html +1430 -0
- package/public/slides-ai-native.html +902 -0
- package/public/slides-intro.html +974 -0
- package/pyproject.toml +7 -0
- package/scripts/build-web.ts +28 -0
- package/scripts/examples/ask-luca-expert.ts +42 -0
- package/scripts/examples/assistant-questions.ts +12 -0
- package/scripts/examples/excalidraw-expert.ts +75 -0
- package/scripts/examples/expert-chat.ts +0 -0
- package/scripts/examples/file-manager.ts +14 -0
- package/scripts/examples/ideas.ts +12 -0
- package/scripts/examples/interactive-chat.ts +20 -0
- package/scripts/examples/openai-tool-calls.ts +113 -0
- package/scripts/examples/opening-a-web-browser.ts +5 -0
- package/scripts/examples/telegram-bot.ts +79 -0
- package/scripts/examples/using-assistant-with-mcp.ts +555 -0
- package/scripts/examples/using-claude-code.ts +10 -0
- package/scripts/examples/using-contentdb.ts +35 -0
- package/scripts/examples/using-conversations.ts +35 -0
- package/scripts/examples/using-disk-cache.ts +10 -0
- package/scripts/examples/using-docker-shell.ts +75 -0
- package/scripts/examples/using-elevenlabs.ts +25 -0
- package/scripts/examples/using-google-calendar.ts +57 -0
- package/scripts/examples/using-google-docs.ts +74 -0
- package/scripts/examples/using-google-drive.ts +74 -0
- package/scripts/examples/using-google-sheets.ts +89 -0
- package/scripts/examples/using-nlp.ts +55 -0
- package/scripts/examples/using-ollama.ts +11 -0
- package/scripts/examples/using-postgres.ts +55 -0
- package/scripts/examples/using-runpod.ts +32 -0
- package/scripts/examples/using-tts.ts +40 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/stamp-build.sh +12 -0
- package/scripts/test-assistant-hooks.ts +13 -0
- package/scripts/test-docs-reader.ts +10 -0
- package/scripts/test-linux-binary.sh +80 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +152 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/feature.ts +13 -0
- package/src/agi/features/agent-memory.ts +694 -0
- package/src/agi/features/assistant.ts +1624 -0
- package/src/agi/features/assistants-manager.ts +418 -0
- package/src/agi/features/autonomous-assistant.ts +431 -0
- package/src/agi/features/browser-use.ts +653 -0
- package/src/agi/features/claude-code.ts +1538 -0
- package/src/agi/features/coding-tools.ts +175 -0
- package/src/agi/features/conversation-history.ts +495 -0
- package/src/agi/features/conversation.ts +1323 -0
- package/src/agi/features/docs-reader.ts +167 -0
- package/src/agi/features/file-tools.ts +293 -0
- package/src/agi/features/luca-coder.ts +639 -0
- package/src/agi/features/openai-codex.ts +651 -0
- package/src/agi/features/openapi.ts +445 -0
- package/src/agi/features/skills-library.ts +478 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/interceptor-chain.ts +89 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/bootstrap/generated.ts +9792 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +122 -0
- package/src/cli/build-info.ts +4 -0
- package/src/cli/cli.ts +355 -0
- package/src/client.ts +170 -0
- package/src/clients/civitai/index.ts +537 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +604 -0
- package/src/clients/elevenlabs/index.ts +317 -0
- package/src/clients/graph.ts +87 -0
- package/src/clients/openai/index.ts +456 -0
- package/src/clients/rest.ts +207 -0
- package/src/clients/supabase/index.ts +357 -0
- package/src/clients/voicebox/index.ts +300 -0
- package/src/clients/websocket.ts +251 -0
- package/src/command.ts +505 -0
- package/src/commands/bootstrap.ts +244 -0
- package/src/commands/chat.ts +308 -0
- package/src/commands/code.ts +371 -0
- package/src/commands/console.ts +189 -0
- package/src/commands/describe.ts +243 -0
- package/src/commands/eval.ts +121 -0
- package/src/commands/help.ts +240 -0
- package/src/commands/index.ts +19 -0
- package/src/commands/introspect.ts +218 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +982 -0
- package/src/commands/run.ts +278 -0
- package/src/commands/sandbox-mcp.ts +343 -0
- package/src/commands/save-api-docs.ts +51 -0
- package/src/commands/scaffold.ts +225 -0
- package/src/commands/select.ts +99 -0
- package/src/commands/serve.ts +208 -0
- package/src/container-describer.ts +1084 -0
- package/src/container.ts +1186 -0
- package/src/endpoint.ts +365 -0
- package/src/entity.ts +173 -0
- package/src/feature.ts +118 -0
- package/src/graft.ts +181 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +849 -0
- package/src/introspection/generated.agi.ts +40208 -0
- package/src/introspection/generated.node.ts +28686 -0
- package/src/introspection/generated.web.ts +2251 -0
- package/src/introspection/index.ts +296 -0
- package/src/introspection/scan.ts +1131 -0
- package/src/node/container.ts +409 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +559 -0
- package/src/node/features/content-db.ts +812 -0
- package/src/node/features/disk-cache.ts +388 -0
- package/src/node/features/dns.ts +669 -0
- package/src/node/features/docker.ts +921 -0
- package/src/node/features/downloader.ts +79 -0
- package/src/node/features/figlet-fonts.ts +600 -0
- package/src/node/features/file-manager.ts +535 -0
- package/src/node/features/fs.ts +1050 -0
- package/src/node/features/git.ts +592 -0
- package/src/node/features/google-auth.ts +504 -0
- package/src/node/features/google-calendar.ts +306 -0
- package/src/node/features/google-docs.ts +412 -0
- package/src/node/features/google-drive.ts +346 -0
- package/src/node/features/google-mail.ts +540 -0
- package/src/node/features/google-sheets.ts +286 -0
- package/src/node/features/grep.ts +427 -0
- package/src/node/features/helpers.ts +735 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +649 -0
- package/src/node/features/json-tree.ts +170 -0
- package/src/node/features/networking.ts +961 -0
- package/src/node/features/nlp.ts +212 -0
- package/src/node/features/opener.ts +180 -0
- package/src/node/features/os.ts +403 -0
- package/src/node/features/package-finder.ts +540 -0
- package/src/node/features/postgres.ts +289 -0
- package/src/node/features/proc.ts +503 -0
- package/src/node/features/process-manager.ts +844 -0
- package/src/node/features/python.ts +906 -0
- package/src/node/features/redis.ts +446 -0
- package/src/node/features/repl.ts +212 -0
- package/src/node/features/runpod.ts +811 -0
- package/src/node/features/secure-shell.ts +267 -0
- package/src/node/features/semantic-search.ts +935 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +343 -0
- package/src/node/features/transpiler.ts +161 -0
- package/src/node/features/tts.ts +185 -0
- package/src/node/features/ui.ts +786 -0
- package/src/node/features/vault.ts +153 -0
- package/src/node/features/vm.ts +462 -0
- package/src/node/features/yaml-tree.ts +148 -0
- package/src/node/features/yaml.ts +133 -0
- package/src/node.ts +76 -0
- package/src/python/bridge.py +220 -0
- package/src/python/generated.ts +227 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +210 -0
- package/src/scaffolds/generated.ts +1815 -0
- package/src/scaffolds/template.ts +46 -0
- package/src/schemas/base.ts +296 -0
- package/src/selector.ts +352 -0
- package/src/server.ts +229 -0
- package/src/servers/express.ts +283 -0
- package/src/servers/mcp.ts +802 -0
- package/src/servers/socket.ts +258 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +99 -0
- package/src/web/container.ts +75 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +72 -0
- package/src/web/features/container-link.ts +382 -0
- package/src/web/features/esbuild.ts +93 -0
- package/src/web/features/helpers.ts +269 -0
- package/src/web/features/network.ts +85 -0
- package/src/web/features/speech.ts +104 -0
- package/src/web/features/vault.ts +207 -0
- package/src/web/features/vm.ts +85 -0
- package/src/web/features/voice-recognition.ts +161 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/assistant-hooks.test.ts +306 -0
- package/test/assistant.test.ts +81 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +217 -0
- package/test/command.test.ts +267 -0
- package/test/container-link.test.ts +274 -0
- package/test/conversation.test.ts +220 -0
- package/test/features.test.ts +160 -0
- package/test/fork-and-research.test.ts +450 -0
- package/test/integration.test.ts +787 -0
- package/test/interceptor-chain.test.ts +61 -0
- package/test/node-container.test.ts +121 -0
- package/test/python-session.test.ts +105 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test/vm-context.test.ts +146 -0
- package/test/vm-loadmodule.test.ts +213 -0
- package/test/websocket-ask.test.ts +101 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +113 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/memory.test.ts +204 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
- package/LICENSE +0 -21
- package/dist/cli/cli.js +0 -48
- package/dist/cli/common.d.ts +0 -2
- package/dist/cli/common.js +0 -6
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -5
- package/dist/cli/run.d.ts +0 -1
- package/dist/cli/run.js +0 -38
- package/dist/core/index.d.ts +0 -4
- package/dist/core/index.js +0 -32
- package/dist/core/read.d.ts +0 -2
- package/dist/core/read.js +0 -29
- package/dist/core/request.d.ts +0 -1
- package/dist/core/request.js +0 -2
- package/dist/core/write.d.ts +0 -2
- package/dist/core/write.js +0 -21
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -5
- package/dist/utils/common.d.ts +0 -9
- package/dist/utils/common.js +0 -57
- package/dist/utils/consts.d.ts +0 -3
- package/dist/utils/consts.js +0 -11
- package/dist/utils/dict.d.ts +0 -1
- package/dist/utils/dict.js +0 -7
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.js +0 -21
- package/dist/utils/log.d.ts +0 -1
- package/dist/utils/log.js +0 -5
- package/dist/utils/types.d.ts +0 -1
- package/dist/utils/types.js +0 -2
- package/dist/utils/utils.test.d.ts +0 -1
- package/dist/utils/utils.test.js +0 -7
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
|
+
import { Feature } from '../feature.js'
|
|
4
|
+
import { State } from '../../state.js'
|
|
5
|
+
import { Bus, type EventMap } from '../../bus.js'
|
|
6
|
+
import type { ChildProcess } from './proc.js'
|
|
7
|
+
import type { Helper } from '../../helper.js'
|
|
8
|
+
|
|
9
|
+
// ─── Output Buffer ─────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
const HEAD_LINES = 20
|
|
12
|
+
const TAIL_LINES = 50
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A memory-efficient output buffer that keeps the first N lines (head)
|
|
16
|
+
* and the last M lines (tail), discarding everything in between.
|
|
17
|
+
*/
|
|
18
|
+
class OutputBuffer {
|
|
19
|
+
private _head: string[] = []
|
|
20
|
+
private _tail: string[] = []
|
|
21
|
+
private _totalLines = 0
|
|
22
|
+
private _partial = ''
|
|
23
|
+
private _headFull = false
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
private _headLimit = HEAD_LINES,
|
|
27
|
+
private _tailLimit = TAIL_LINES
|
|
28
|
+
) {}
|
|
29
|
+
|
|
30
|
+
/** Append a chunk of output (may contain partial lines) */
|
|
31
|
+
append(chunk: string): void {
|
|
32
|
+
const text = this._partial + chunk
|
|
33
|
+
const lines = text.split('\n')
|
|
34
|
+
|
|
35
|
+
// Last element is a partial line (no trailing newline) — hold it
|
|
36
|
+
this._partial = lines.pop()!
|
|
37
|
+
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
this._pushLine(line)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Flush any remaining partial line */
|
|
44
|
+
flush(): void {
|
|
45
|
+
if (this._partial) {
|
|
46
|
+
this._pushLine(this._partial)
|
|
47
|
+
this._partial = ''
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get totalLines() { return this._totalLines }
|
|
52
|
+
|
|
53
|
+
/** Get the head lines (first N lines of output) */
|
|
54
|
+
get head(): string[] { return this._head }
|
|
55
|
+
|
|
56
|
+
/** Get the tail lines (last M lines of output) */
|
|
57
|
+
get tail(): string[] { return this._tail }
|
|
58
|
+
|
|
59
|
+
/** Number of lines dropped between head and tail */
|
|
60
|
+
get droppedLines(): number {
|
|
61
|
+
return Math.max(0, this._totalLines - this._head.length - this._tail.length)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Format the buffer for display */
|
|
65
|
+
toString(): string {
|
|
66
|
+
const parts: string[] = []
|
|
67
|
+
if (this._head.length) parts.push(this._head.join('\n'))
|
|
68
|
+
if (this.droppedLines > 0) parts.push(`\n... (${this.droppedLines} lines omitted) ...\n`)
|
|
69
|
+
if (this._tail.length && this._totalLines > this._headLimit) parts.push(this._tail.join('\n'))
|
|
70
|
+
return parts.join('\n')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private _pushLine(line: string): void {
|
|
74
|
+
this._totalLines++
|
|
75
|
+
|
|
76
|
+
if (!this._headFull) {
|
|
77
|
+
this._head.push(line)
|
|
78
|
+
if (this._head.length >= this._headLimit) this._headFull = true
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this._tail.push(line)
|
|
83
|
+
if (this._tail.length > this._tailLimit) {
|
|
84
|
+
this._tail.shift()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─── Schemas ────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
const ProcessMetadataSchema = z.object({
|
|
92
|
+
id: z.string().describe('Unique process identifier'),
|
|
93
|
+
tag: z.string().optional().describe('User-defined tag for lookups'),
|
|
94
|
+
command: z.string().describe('The command that was spawned'),
|
|
95
|
+
args: z.array(z.string()).describe('Arguments passed to the command'),
|
|
96
|
+
pid: z.number().optional().describe('OS process ID'),
|
|
97
|
+
status: z.enum(['running', 'exited', 'crashed', 'killed']).describe('Current process lifecycle status'),
|
|
98
|
+
exitCode: z.number().optional().describe('Exit code after process ends'),
|
|
99
|
+
startedAt: z.number().describe('Timestamp when the process was spawned'),
|
|
100
|
+
endedAt: z.number().optional().describe('Timestamp when the process ended'),
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
export const ProcessManagerStateSchema = FeatureStateSchema.extend({
|
|
104
|
+
processes: z.record(z.string(), ProcessMetadataSchema)
|
|
105
|
+
.describe('Map of process ID to metadata'),
|
|
106
|
+
totalSpawned: z.number().default(0)
|
|
107
|
+
.describe('Total number of processes spawned since feature creation'),
|
|
108
|
+
})
|
|
109
|
+
export type ProcessManagerState = z.infer<typeof ProcessManagerStateSchema>
|
|
110
|
+
|
|
111
|
+
export const ProcessManagerOptionsSchema = FeatureOptionsSchema.extend({
|
|
112
|
+
autoCleanup: z.boolean().default(true)
|
|
113
|
+
.describe('Register process.on exit/SIGINT/SIGTERM handlers to kill all tracked processes'),
|
|
114
|
+
})
|
|
115
|
+
export type ProcessManagerOptions = z.infer<typeof ProcessManagerOptionsSchema>
|
|
116
|
+
|
|
117
|
+
export const ProcessManagerEventsSchema = FeatureEventsSchema.extend({
|
|
118
|
+
spawned: z.tuple([z.string().describe('process ID'), z.string().describe('process metadata')])
|
|
119
|
+
.describe('Emitted when a new process is spawned'),
|
|
120
|
+
exited: z.tuple([z.string().describe('process ID'), z.number().describe('exit code')])
|
|
121
|
+
.describe('Emitted when a process exits normally'),
|
|
122
|
+
crashed: z.tuple([z.string().describe('process ID'), z.number().describe('exit code'), z.string().describe('error info')])
|
|
123
|
+
.describe('Emitted when a process exits with non-zero code'),
|
|
124
|
+
killed: z.tuple([z.string().describe('process ID')])
|
|
125
|
+
.describe('Emitted when a process is killed'),
|
|
126
|
+
allStopped: z.tuple([])
|
|
127
|
+
.describe('Emitted when all tracked processes have stopped'),
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// ─── SpawnHandler ───────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
interface SpawnHandlerState {
|
|
133
|
+
id: string
|
|
134
|
+
tag?: string
|
|
135
|
+
command: string
|
|
136
|
+
args: string[]
|
|
137
|
+
pid?: number
|
|
138
|
+
status: 'running' | 'exited' | 'crashed' | 'killed'
|
|
139
|
+
exitCode?: number
|
|
140
|
+
startedAt: number
|
|
141
|
+
endedAt?: number
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface SpawnHandlerEvents extends EventMap {
|
|
145
|
+
stdout: [data: string]
|
|
146
|
+
stderr: [data: string]
|
|
147
|
+
exit: [code: number]
|
|
148
|
+
crash: [code: number]
|
|
149
|
+
killed: []
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface SpawnOptions {
|
|
153
|
+
/** User-defined tag for later lookups via getByTag() */
|
|
154
|
+
tag?: string
|
|
155
|
+
/** Working directory for the spawned process (defaults to container cwd) */
|
|
156
|
+
cwd?: string
|
|
157
|
+
/** Additional environment variables merged with process.env */
|
|
158
|
+
env?: Record<string, string>
|
|
159
|
+
/** stdin mode: 'pipe' to write to the process, 'inherit', or 'ignore' (default: 'ignore') */
|
|
160
|
+
stdin?: 'pipe' | 'inherit' | 'ignore' | null
|
|
161
|
+
/** stdout mode: 'pipe' to capture output, 'inherit', or 'ignore' (default: 'pipe') */
|
|
162
|
+
stdout?: 'pipe' | 'inherit' | 'ignore' | null
|
|
163
|
+
/** stderr mode: 'pipe' to capture errors, 'inherit', or 'ignore' (default: 'pipe') */
|
|
164
|
+
stderr?: 'pipe' | 'inherit' | 'ignore' | null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A handle to a spawned long-running process.
|
|
169
|
+
*
|
|
170
|
+
* Provides observable state, events, and methods to interact with
|
|
171
|
+
* the running process. Returned immediately from `ProcessManager.spawn()`
|
|
172
|
+
* without blocking. Maintains a memory-efficient output buffer that keeps
|
|
173
|
+
* the first 20 lines and last 50 lines of stdout/stderr.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* const handler = pm.spawn('node', ['server.js'], { tag: 'api' })
|
|
178
|
+
* handler.on('stdout', (data) => console.log(data))
|
|
179
|
+
* handler.on('crash', (code) => console.error('crashed:', code))
|
|
180
|
+
* const exitCode = await handler.await()
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export class SpawnHandler {
|
|
184
|
+
readonly state: State<SpawnHandlerState>
|
|
185
|
+
readonly events = new Bus<SpawnHandlerEvents>()
|
|
186
|
+
readonly stdout = new OutputBuffer(HEAD_LINES, TAIL_LINES)
|
|
187
|
+
readonly stderr = new OutputBuffer(HEAD_LINES, TAIL_LINES)
|
|
188
|
+
|
|
189
|
+
private _childProcess: any = null
|
|
190
|
+
private _manager: ProcessManager
|
|
191
|
+
private _exitPromise: Promise<number> | null = null
|
|
192
|
+
private _exitResolve: ((code: number) => void) | null = null
|
|
193
|
+
|
|
194
|
+
constructor(
|
|
195
|
+
id: string,
|
|
196
|
+
command: string,
|
|
197
|
+
args: string[],
|
|
198
|
+
manager: ProcessManager,
|
|
199
|
+
options: SpawnOptions = {}
|
|
200
|
+
) {
|
|
201
|
+
this._manager = manager
|
|
202
|
+
this.state = new State<SpawnHandlerState>({
|
|
203
|
+
initialState: {
|
|
204
|
+
id,
|
|
205
|
+
command,
|
|
206
|
+
args,
|
|
207
|
+
tag: options.tag,
|
|
208
|
+
status: 'running',
|
|
209
|
+
startedAt: Date.now(),
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
this._exitPromise = new Promise<number>((resolve) => {
|
|
214
|
+
this._exitResolve = resolve
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** The unique process identifier */
|
|
219
|
+
get id() { return this.state.get('id')! }
|
|
220
|
+
|
|
221
|
+
/** The user-defined tag, if any */
|
|
222
|
+
get tag() { return this.state.get('tag') }
|
|
223
|
+
|
|
224
|
+
/** The OS process ID */
|
|
225
|
+
get pid() { return this.state.get('pid') }
|
|
226
|
+
|
|
227
|
+
/** Whether the process is still running */
|
|
228
|
+
get isRunning() { return this.state.get('status') === 'running' }
|
|
229
|
+
|
|
230
|
+
/** Whether the process has finished (exited, crashed, or killed) */
|
|
231
|
+
get isDone() {
|
|
232
|
+
const s = this.state.get('status')
|
|
233
|
+
return s === 'exited' || s === 'crashed' || s === 'killed'
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Current process lifecycle status */
|
|
237
|
+
get status() { return this.state.get('status')! }
|
|
238
|
+
|
|
239
|
+
/** Exit code after process ends */
|
|
240
|
+
get exitCode() { return this.state.get('exitCode') }
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Start the process using proc.spawnAndCapture. Called internally by `ProcessManager.spawn()`.
|
|
244
|
+
*/
|
|
245
|
+
_start(spawnOptions: SpawnOptions = {}): void {
|
|
246
|
+
const command = this.state.get('command')!
|
|
247
|
+
const args = this.state.get('args')!
|
|
248
|
+
const proc = this._manager.container.feature('proc') as ChildProcess
|
|
249
|
+
|
|
250
|
+
const cwd = spawnOptions.cwd ?? this._manager.container.cwd
|
|
251
|
+
|
|
252
|
+
// Use proc.spawnAndCapture with hooks for real-time streaming
|
|
253
|
+
proc.spawnAndCapture(command, args, {
|
|
254
|
+
cwd,
|
|
255
|
+
onStart: (childProcess: any) => {
|
|
256
|
+
this._childProcess = childProcess
|
|
257
|
+
if (childProcess.pid) {
|
|
258
|
+
this.state.set('pid', childProcess.pid)
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
onOutput: (data: string) => {
|
|
262
|
+
this.stdout.append(data)
|
|
263
|
+
this.events.emit('stdout', data)
|
|
264
|
+
},
|
|
265
|
+
onError: (data: string) => {
|
|
266
|
+
this.stderr.append(data)
|
|
267
|
+
this.events.emit('stderr', data)
|
|
268
|
+
},
|
|
269
|
+
onExit: (code: number) => {
|
|
270
|
+
this.stdout.flush()
|
|
271
|
+
this.stderr.flush()
|
|
272
|
+
this._onExit(code)
|
|
273
|
+
},
|
|
274
|
+
}).catch((err: any) => {
|
|
275
|
+
// spawnAndCapture rejected — treat as crash
|
|
276
|
+
this.stdout.flush()
|
|
277
|
+
this.stderr.flush()
|
|
278
|
+
if (!this.isDone) {
|
|
279
|
+
this._onExit(err?.code ?? 1)
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Kill the process.
|
|
286
|
+
*
|
|
287
|
+
* @param signal - Signal to send (default: SIGTERM)
|
|
288
|
+
*/
|
|
289
|
+
kill(signal: NodeJS.Signals | number = 'SIGTERM'): void {
|
|
290
|
+
if (this.isDone) return
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const pid = this.state.get('pid')
|
|
294
|
+
if (pid) {
|
|
295
|
+
process.kill(pid, signal)
|
|
296
|
+
}
|
|
297
|
+
} catch (err: any) {
|
|
298
|
+
if (err.code !== 'ESRCH') throw err
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
this.stdout.flush()
|
|
302
|
+
this.stderr.flush()
|
|
303
|
+
this.state.set('status', 'killed')
|
|
304
|
+
this.state.set('endedAt', Date.now())
|
|
305
|
+
this.events.emit('killed')
|
|
306
|
+
this._manager._onHandlerDone(this, 'killed')
|
|
307
|
+
|
|
308
|
+
if (this._exitResolve) {
|
|
309
|
+
this._exitResolve(-1)
|
|
310
|
+
this._exitResolve = null
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Returns a promise that resolves with the exit code when the process finishes.
|
|
316
|
+
*/
|
|
317
|
+
async await(): Promise<number> {
|
|
318
|
+
if (this.isDone) {
|
|
319
|
+
return this.state.get('exitCode') ?? -1
|
|
320
|
+
}
|
|
321
|
+
return this._exitPromise!
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Write data to the process's stdin (requires `stdin: 'pipe'` in spawn options).
|
|
326
|
+
*
|
|
327
|
+
* @param data - String or Uint8Array to write
|
|
328
|
+
*/
|
|
329
|
+
write(data: string | Uint8Array): void {
|
|
330
|
+
const stdin = this._childProcess?.stdin
|
|
331
|
+
if (!stdin) {
|
|
332
|
+
throw new Error('stdin is not piped — pass { stdin: "pipe" } in spawn options')
|
|
333
|
+
}
|
|
334
|
+
stdin.write(data)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Peek at the buffered output. Returns head (first 20 lines), tail (last 50 lines),
|
|
339
|
+
* and metadata about how much was dropped.
|
|
340
|
+
*/
|
|
341
|
+
peek(stream: 'stdout' | 'stderr' = 'stdout'): { head: string[]; tail: string[]; totalLines: number; droppedLines: number } {
|
|
342
|
+
const buf = stream === 'stderr' ? this.stderr : this.stdout
|
|
343
|
+
return {
|
|
344
|
+
head: buf.head,
|
|
345
|
+
tail: buf.tail,
|
|
346
|
+
totalLines: buf.totalLines,
|
|
347
|
+
droppedLines: buf.droppedLines,
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Subscribe to handler events */
|
|
352
|
+
on<E extends string & keyof SpawnHandlerEvents>(event: E, listener: (...args: SpawnHandlerEvents[E]) => void) {
|
|
353
|
+
this.events.on(event, listener)
|
|
354
|
+
return this
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Subscribe to a handler event once */
|
|
358
|
+
once<E extends string & keyof SpawnHandlerEvents>(event: E, listener: (...args: SpawnHandlerEvents[E]) => void) {
|
|
359
|
+
this.events.once(event, listener)
|
|
360
|
+
return this
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Unsubscribe from a handler event */
|
|
364
|
+
off<E extends string & keyof SpawnHandlerEvents>(event: E, listener: (...args: SpawnHandlerEvents[E]) => void) {
|
|
365
|
+
this.events.off(event, listener)
|
|
366
|
+
return this
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ─── Internal ─────────────────────────────────────────────────────────────
|
|
370
|
+
|
|
371
|
+
private _onExit(code: number): void {
|
|
372
|
+
if (this.isDone) return
|
|
373
|
+
|
|
374
|
+
const isCrash = code !== 0
|
|
375
|
+
this.state.set('exitCode', code)
|
|
376
|
+
this.state.set('endedAt', Date.now())
|
|
377
|
+
|
|
378
|
+
if (isCrash) {
|
|
379
|
+
this.state.set('status', 'crashed')
|
|
380
|
+
this.events.emit('crash', code)
|
|
381
|
+
this._manager._onHandlerDone(this, 'crashed', code)
|
|
382
|
+
} else {
|
|
383
|
+
this.state.set('status', 'exited')
|
|
384
|
+
this.events.emit('exit', code)
|
|
385
|
+
this._manager._onHandlerDone(this, 'exited', code)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (this._exitResolve) {
|
|
389
|
+
this._exitResolve(code)
|
|
390
|
+
this._exitResolve = null
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ─── ProcessManager Feature ─────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Manages long-running child processes with tracking, events, and automatic cleanup.
|
|
399
|
+
*
|
|
400
|
+
* Unlike the `proc` feature whose spawn methods block until the child exits,
|
|
401
|
+
* ProcessManager returns a SpawnHandler immediately — a handle object with its own
|
|
402
|
+
* state, events, and lifecycle methods. The feature tracks all spawned processes,
|
|
403
|
+
* maintains observable state, and can automatically kill them on parent exit.
|
|
404
|
+
*
|
|
405
|
+
* Each handler maintains a memory-efficient output buffer: the first 20 lines (head)
|
|
406
|
+
* and last 50 lines (tail) of stdout/stderr are kept, everything in between is discarded.
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```typescript
|
|
410
|
+
* const pm = container.feature('processManager', { enable: true })
|
|
411
|
+
*
|
|
412
|
+
* const server = pm.spawn('node', ['server.js'], { tag: 'api', cwd: '/app' })
|
|
413
|
+
* server.on('stdout', (data) => console.log('[api]', data))
|
|
414
|
+
* server.on('crash', (code) => console.error('API crashed:', code))
|
|
415
|
+
*
|
|
416
|
+
* // Peek at buffered output
|
|
417
|
+
* const { head, tail } = server.peek()
|
|
418
|
+
*
|
|
419
|
+
* // Kill one
|
|
420
|
+
* server.kill()
|
|
421
|
+
*
|
|
422
|
+
* // Kill all tracked processes
|
|
423
|
+
* pm.killAll()
|
|
424
|
+
*
|
|
425
|
+
* // List and lookup
|
|
426
|
+
* pm.list() // SpawnHandler[]
|
|
427
|
+
* pm.getByTag('api') // SpawnHandler | undefined
|
|
428
|
+
* ```
|
|
429
|
+
*
|
|
430
|
+
* @extends Feature
|
|
431
|
+
*/
|
|
432
|
+
export class ProcessManager extends Feature {
|
|
433
|
+
static override shortcut = 'features.processManager' as const
|
|
434
|
+
static override stateSchema = ProcessManagerStateSchema
|
|
435
|
+
static override optionsSchema = ProcessManagerOptionsSchema
|
|
436
|
+
static override eventsSchema = ProcessManagerEventsSchema
|
|
437
|
+
static { Feature.register(this, 'processManager') }
|
|
438
|
+
|
|
439
|
+
/** Tools that an assistant can use to spawn and manage processes. */
|
|
440
|
+
static override tools: Record<string, { schema: z.ZodType; handler?: Function }> = {
|
|
441
|
+
spawnProcess: {
|
|
442
|
+
schema: z.object({
|
|
443
|
+
command: z.string().describe('The executable to run (e.g. "node", "bun", "python"). NOT a shell command — use runCommand for shell syntax like pipes or &&.'),
|
|
444
|
+
args: z.string().optional().describe('Arguments as a single space-separated string. WARNING: spaces are used to split args, so paths with spaces will break. For complex argument quoting, prefer runCommand instead.'),
|
|
445
|
+
tag: z.string().optional().describe('A short, descriptive label for this process (e.g. "api-server", "file-watcher"). Always set a tag — it makes the process easy to find later with getProcessOutput and killProcess.'),
|
|
446
|
+
cwd: z.string().optional().describe('Working directory for the process. Defaults to the project root.'),
|
|
447
|
+
}).describe(
|
|
448
|
+
'Start a long-running background process (server, watcher, daemon). Returns immediately with a process ID — the process keeps running. Use this for anything that runs indefinitely. After spawning, call getProcessOutput to check if it started successfully. Always set a tag.'
|
|
449
|
+
),
|
|
450
|
+
},
|
|
451
|
+
runCommand: {
|
|
452
|
+
schema: z.object({
|
|
453
|
+
command: z.string().describe('A full shell command string (executed via sh -c). Supports pipes, &&, redirects, env vars, globs — anything you can type in a terminal. Examples: "bun test", "npm install && npm run build", "cat logs/*.txt | grep ERROR"'),
|
|
454
|
+
cwd: z.string().optional().describe('Working directory for the command. Defaults to the project root.'),
|
|
455
|
+
}).describe(
|
|
456
|
+
'Run a shell command and wait for it to complete. Returns stdout, stderr, and exit code. Use this for commands that finish on their own — builds, installs, tests, one-off scripts. For anything that runs forever (servers, watchers), use spawnProcess instead.'
|
|
457
|
+
),
|
|
458
|
+
},
|
|
459
|
+
listProcesses: {
|
|
460
|
+
schema: z.object({}).describe(
|
|
461
|
+
'List all tracked background processes with their status, PID, command, uptime, and the last few lines of output. Call this to get an overview before deciding which process to inspect or kill.'
|
|
462
|
+
),
|
|
463
|
+
},
|
|
464
|
+
getProcessOutput: {
|
|
465
|
+
schema: z.object({
|
|
466
|
+
id: z.string().optional().describe('The process ID (returned by spawnProcess). Provide either id or tag, not both.'),
|
|
467
|
+
tag: z.string().optional().describe('The tag you assigned when spawning the process. Provide either id or tag, not both.'),
|
|
468
|
+
stream: z.string().optional().describe('"stdout" (default) or "stderr". Check stderr when a process crashes or behaves unexpectedly.'),
|
|
469
|
+
}).describe(
|
|
470
|
+
'Read a background process\'s buffered output — the first 20 lines (startup) and last 50 lines (recent activity). Call this after spawning to verify the process started correctly, and periodically to monitor its health.'
|
|
471
|
+
),
|
|
472
|
+
},
|
|
473
|
+
killProcess: {
|
|
474
|
+
schema: z.object({
|
|
475
|
+
id: z.string().optional().describe('The process ID to kill. Provide either id or tag, not both.'),
|
|
476
|
+
tag: z.string().optional().describe('The tag of the process to kill. Provide either id or tag, not both.'),
|
|
477
|
+
signal: z.string().optional().describe('"SIGTERM" (default) for graceful shutdown, "SIGKILL" to force-kill a stuck process. Try SIGTERM first.'),
|
|
478
|
+
}).describe(
|
|
479
|
+
'Stop a running background process. Use SIGTERM (default) for graceful shutdown. If a process doesn\'t respond, follow up with SIGKILL. Always clean up processes you spawned when they\'re no longer needed.'
|
|
480
|
+
),
|
|
481
|
+
},
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private _handlers = new Map<string, SpawnHandler>()
|
|
485
|
+
private _cleanupRegistered = false
|
|
486
|
+
private _cleanupHandlers: Array<() => void> = []
|
|
487
|
+
|
|
488
|
+
// ─── Tool Handlers ──────────────────────────────────────────────────────
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Tool handler: spawn a long-running background process.
|
|
492
|
+
*/
|
|
493
|
+
async spawnProcess(args: { command: string; args?: string; tag?: string; cwd?: string }) {
|
|
494
|
+
const cmdArgs = args.args ? args.args.split(/\s+/) : []
|
|
495
|
+
const handler = this.spawn(args.command, cmdArgs, {
|
|
496
|
+
tag: args.tag,
|
|
497
|
+
cwd: args.cwd,
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
id: handler.id,
|
|
502
|
+
pid: handler.pid,
|
|
503
|
+
tag: handler.tag,
|
|
504
|
+
command: `${args.command} ${args.args ?? ''}`.trim(),
|
|
505
|
+
status: 'running',
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Tool handler: run a command to completion and return its output.
|
|
511
|
+
*/
|
|
512
|
+
async runCommand(args: { command: string; cwd?: string }) {
|
|
513
|
+
const proc = this.container.feature('proc') as ChildProcess
|
|
514
|
+
const osFeature = this.container.feature('os')
|
|
515
|
+
const result = await proc.spawnAndCapture(osFeature.shell, [osFeature.shellFlag, args.command], {
|
|
516
|
+
cwd: args.cwd ?? this.container.cwd,
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
exitCode: result.exitCode,
|
|
521
|
+
stdout: result.stdout,
|
|
522
|
+
stderr: result.stderr,
|
|
523
|
+
success: result.exitCode === 0,
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Tool handler: list all tracked processes.
|
|
529
|
+
*/
|
|
530
|
+
async listProcesses() {
|
|
531
|
+
const handlers = this.list()
|
|
532
|
+
if (handlers.length === 0) return { processes: [], message: 'No tracked processes.' }
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
processes: handlers.map(h => {
|
|
536
|
+
const now = Date.now()
|
|
537
|
+
const startedAt = h.state.get('startedAt')!
|
|
538
|
+
const endedAt = h.state.get('endedAt')
|
|
539
|
+
const duration = (endedAt ?? now) - startedAt
|
|
540
|
+
const lastStdout = h.stdout.tail.length ? h.stdout.tail.slice(-3) : h.stdout.head.slice(-3)
|
|
541
|
+
|
|
542
|
+
return {
|
|
543
|
+
id: h.id,
|
|
544
|
+
tag: h.tag,
|
|
545
|
+
pid: h.pid,
|
|
546
|
+
command: `${h.state.get('command')} ${(h.state.get('args') ?? []).join(' ')}`.trim(),
|
|
547
|
+
status: h.status,
|
|
548
|
+
exitCode: h.exitCode,
|
|
549
|
+
uptimeMs: duration,
|
|
550
|
+
uptime: formatDuration(duration),
|
|
551
|
+
outputLines: h.stdout.totalLines,
|
|
552
|
+
errorLines: h.stderr.totalLines,
|
|
553
|
+
recentOutput: lastStdout,
|
|
554
|
+
}
|
|
555
|
+
}),
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Tool handler: peek at a process's buffered output.
|
|
561
|
+
*/
|
|
562
|
+
async getProcessOutput(args: { id?: string; tag?: string; stream?: string }) {
|
|
563
|
+
const handler = args.id ? this.get(args.id) : args.tag ? this.getByTag(args.tag) : undefined
|
|
564
|
+
if (!handler) return { error: `Process not found. Provide a valid id or tag.` }
|
|
565
|
+
|
|
566
|
+
const streamName = (args.stream === 'stderr' ? 'stderr' : 'stdout') as 'stdout' | 'stderr'
|
|
567
|
+
const peek = handler.peek(streamName)
|
|
568
|
+
|
|
569
|
+
return {
|
|
570
|
+
id: handler.id,
|
|
571
|
+
tag: handler.tag,
|
|
572
|
+
status: handler.status,
|
|
573
|
+
stream: streamName,
|
|
574
|
+
totalLines: peek.totalLines,
|
|
575
|
+
droppedLines: peek.droppedLines,
|
|
576
|
+
head: peek.head,
|
|
577
|
+
tail: peek.tail,
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Tool handler: kill a process by ID or tag.
|
|
583
|
+
*/
|
|
584
|
+
async killProcess(args: { id?: string; tag?: string; signal?: string }) {
|
|
585
|
+
const handler = args.id ? this.get(args.id) : args.tag ? this.getByTag(args.tag) : undefined
|
|
586
|
+
if (!handler) return { error: `Process not found. Provide a valid id or tag.` }
|
|
587
|
+
|
|
588
|
+
if (handler.isDone) {
|
|
589
|
+
return { id: handler.id, status: handler.status, message: 'Process already finished.' }
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const signal = (args.signal ?? 'SIGTERM') as NodeJS.Signals
|
|
593
|
+
handler.kill(signal)
|
|
594
|
+
|
|
595
|
+
return { id: handler.id, status: handler.status, signal, message: 'Process killed.' }
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* When an assistant uses processManager, inject system prompt guidance
|
|
600
|
+
* about how to manage processes safely and effectively.
|
|
601
|
+
*/
|
|
602
|
+
override setupToolsConsumer(consumer: Helper) {
|
|
603
|
+
if (typeof (consumer as any).addSystemPromptExtension === 'function') {
|
|
604
|
+
(consumer as any).addSystemPromptExtension('processManager', [
|
|
605
|
+
'## Process Management',
|
|
606
|
+
'',
|
|
607
|
+
'**Choosing the right tool:**',
|
|
608
|
+
'- `runCommand` — for anything that finishes on its own (builds, tests, installs, queries). Blocks until done.',
|
|
609
|
+
'- `spawnProcess` — for anything that runs indefinitely (servers, watchers, tails). Returns immediately.',
|
|
610
|
+
'- When in doubt: if you\'d press Ctrl-C to stop it, use `spawnProcess`. If you\'d wait for it, use `runCommand`.',
|
|
611
|
+
'',
|
|
612
|
+
'**After spawning a process:**',
|
|
613
|
+
'1. Always assign a descriptive `tag` so you can reference it later',
|
|
614
|
+
'2. Call `getProcessOutput` within a few seconds to verify it started correctly',
|
|
615
|
+
'3. Check `stderr` if the process crashes or output looks wrong',
|
|
616
|
+
'',
|
|
617
|
+
'**Monitoring:**',
|
|
618
|
+
'- Call `listProcesses` to see all running and finished processes at a glance',
|
|
619
|
+
'- Call `getProcessOutput` to read recent output — it keeps the first 20 and last 50 lines',
|
|
620
|
+
'- A process with status "crashed" exited with a non-zero code — check its stderr for the error',
|
|
621
|
+
'',
|
|
622
|
+
'**Cleanup:**',
|
|
623
|
+
'- Always `killProcess` background processes when they\'re no longer needed',
|
|
624
|
+
'- Use SIGTERM (default) first for graceful shutdown. Only use SIGKILL if SIGTERM doesn\'t work.',
|
|
625
|
+
'- If you spawned it, you\'re responsible for killing it',
|
|
626
|
+
].join('\n'))
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// ─── Core API ───────────────────────────────────────────────────────────
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Spawn a long-running process and return a handle immediately.
|
|
634
|
+
*
|
|
635
|
+
* The returned SpawnHandler provides events for stdout/stderr streaming,
|
|
636
|
+
* exit/crash notifications, and methods to kill or await the process.
|
|
637
|
+
*
|
|
638
|
+
* @param command - The command to execute (e.g. 'node', 'bun', 'python')
|
|
639
|
+
* @param args - Arguments to pass to the command
|
|
640
|
+
* @param options - Spawn configuration
|
|
641
|
+
* @param options.tag - User-defined tag for later lookups via getByTag()
|
|
642
|
+
* @param options.cwd - Working directory (defaults to container cwd)
|
|
643
|
+
* @param options.env - Additional environment variables
|
|
644
|
+
* @param options.stdin - stdin mode: 'pipe', 'inherit', 'ignore' (default: 'ignore')
|
|
645
|
+
* @param options.stdout - stdout mode: 'pipe', 'inherit', 'ignore' (default: 'pipe')
|
|
646
|
+
* @param options.stderr - stderr mode: 'pipe', 'inherit', 'ignore' (default: 'pipe')
|
|
647
|
+
* @returns SpawnHandler — a non-blocking handle to the process
|
|
648
|
+
*/
|
|
649
|
+
spawn(command: string, args: string[] = [], options: SpawnOptions = {}): SpawnHandler {
|
|
650
|
+
const id = crypto.randomUUID()
|
|
651
|
+
const handler = new SpawnHandler(id, command, args, this, options)
|
|
652
|
+
|
|
653
|
+
this._handlers.set(id, handler)
|
|
654
|
+
|
|
655
|
+
// Register cleanup on first spawn
|
|
656
|
+
if (!this._cleanupRegistered && (this.options as ProcessManagerOptions).autoCleanup !== false) {
|
|
657
|
+
this._registerCleanup()
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
handler._start(options)
|
|
661
|
+
|
|
662
|
+
// Update feature-level state
|
|
663
|
+
const totalSpawned = ((this.state.get('totalSpawned' as any) as number) ?? 0) + 1
|
|
664
|
+
this.state.set('totalSpawned' as any, totalSpawned)
|
|
665
|
+
|
|
666
|
+
const processes = { ...(this.state.get('processes' as any) as Record<string, any> ?? {}) }
|
|
667
|
+
processes[id] = {
|
|
668
|
+
id,
|
|
669
|
+
tag: options.tag,
|
|
670
|
+
command,
|
|
671
|
+
args,
|
|
672
|
+
pid: handler.pid,
|
|
673
|
+
status: 'running',
|
|
674
|
+
startedAt: Date.now(),
|
|
675
|
+
}
|
|
676
|
+
this.state.set('processes' as any, processes)
|
|
677
|
+
|
|
678
|
+
this.emit('spawned', id, processes[id])
|
|
679
|
+
|
|
680
|
+
return handler
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Get a SpawnHandler by its unique ID.
|
|
685
|
+
*
|
|
686
|
+
* @param id - The process ID returned by spawn
|
|
687
|
+
* @returns The SpawnHandler, or undefined if not found
|
|
688
|
+
*/
|
|
689
|
+
get(id: string): SpawnHandler | undefined {
|
|
690
|
+
return this._handlers.get(id)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Find a SpawnHandler by its user-defined tag.
|
|
695
|
+
*
|
|
696
|
+
* @param tag - The tag passed to spawn()
|
|
697
|
+
* @returns The first matching SpawnHandler, or undefined
|
|
698
|
+
*/
|
|
699
|
+
getByTag(tag: string): SpawnHandler | undefined {
|
|
700
|
+
for (const handler of this._handlers.values()) {
|
|
701
|
+
if (handler.tag === tag) return handler
|
|
702
|
+
}
|
|
703
|
+
return undefined
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* List all tracked SpawnHandlers (running and finished).
|
|
708
|
+
*
|
|
709
|
+
* @returns Array of all SpawnHandlers
|
|
710
|
+
*/
|
|
711
|
+
list(): SpawnHandler[] {
|
|
712
|
+
return Array.from(this._handlers.values())
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Kill all running processes.
|
|
717
|
+
*
|
|
718
|
+
* @param signal - Signal to send (default: SIGTERM)
|
|
719
|
+
*/
|
|
720
|
+
killAll(signal?: NodeJS.Signals | number): void {
|
|
721
|
+
for (const handler of this._handlers.values()) {
|
|
722
|
+
if (handler.isRunning) {
|
|
723
|
+
handler.kill(signal)
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Stop the process manager: kill all running processes and remove cleanup handlers.
|
|
730
|
+
*/
|
|
731
|
+
async stop(): Promise<void> {
|
|
732
|
+
this.killAll()
|
|
733
|
+
this._removeCleanup()
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Remove a finished handler from tracking.
|
|
738
|
+
*
|
|
739
|
+
* @param id - The process ID to remove
|
|
740
|
+
* @returns True if the handler was found and removed
|
|
741
|
+
*/
|
|
742
|
+
remove(id: string): boolean {
|
|
743
|
+
const handler = this._handlers.get(id)
|
|
744
|
+
if (!handler) return false
|
|
745
|
+
if (handler.isRunning) {
|
|
746
|
+
throw new Error(`Cannot remove running process ${id} — kill it first`)
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
this._handlers.delete(id)
|
|
750
|
+
|
|
751
|
+
const processes = { ...(this.state.get('processes') as any ?? {}) }
|
|
752
|
+
delete processes[id]
|
|
753
|
+
this.state.set('processes' as any, processes)
|
|
754
|
+
|
|
755
|
+
return true
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
override async enable(options: any = {}): Promise<this> {
|
|
759
|
+
await super.enable(options)
|
|
760
|
+
return this
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// ─── Internal ─────────────────────────────────────────────────────────────
|
|
764
|
+
|
|
765
|
+
/** Called by SpawnHandler when a process finishes. Updates feature-level state. */
|
|
766
|
+
_onHandlerDone(handler: SpawnHandler, status: 'exited' | 'crashed' | 'killed', exitCode?: number): void {
|
|
767
|
+
const id = handler.id
|
|
768
|
+
|
|
769
|
+
// Update feature-level process record
|
|
770
|
+
const processes = { ...(this.state.get('processes') as any ?? {}) }
|
|
771
|
+
if (processes[id]) {
|
|
772
|
+
processes[id] = {
|
|
773
|
+
...processes[id],
|
|
774
|
+
status,
|
|
775
|
+
exitCode: exitCode ?? (status === 'killed' ? -1 : undefined),
|
|
776
|
+
endedAt: Date.now(),
|
|
777
|
+
}
|
|
778
|
+
this.state.set('processes' as any, processes)
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Emit feature-level events
|
|
782
|
+
if (status === 'exited') {
|
|
783
|
+
this.emit('exited', id, exitCode ?? 0)
|
|
784
|
+
} else if (status === 'crashed') {
|
|
785
|
+
this.emit('crashed', id, exitCode ?? 1, { command: handler.state.get('command'), args: handler.state.get('args') })
|
|
786
|
+
} else if (status === 'killed') {
|
|
787
|
+
this.emit('killed', id)
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Check if all processes are done
|
|
791
|
+
const allDone = Array.from(this._handlers.values()).every(h => h.isDone)
|
|
792
|
+
if (allDone && this._handlers.size > 0) {
|
|
793
|
+
this.emit('allStopped')
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
private _registerCleanup(): void {
|
|
798
|
+
if (this._cleanupRegistered) return
|
|
799
|
+
this._cleanupRegistered = true
|
|
800
|
+
|
|
801
|
+
const onExit = () => { this.killAll() }
|
|
802
|
+
const onSignal = (signal: NodeJS.Signals) => {
|
|
803
|
+
this.killAll()
|
|
804
|
+
process.removeListener(signal, onSignal as any)
|
|
805
|
+
process.kill(process.pid, signal)
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const onSigInt = () => onSignal('SIGINT')
|
|
809
|
+
const onSigTerm = () => onSignal('SIGTERM')
|
|
810
|
+
|
|
811
|
+
process.on('exit', onExit)
|
|
812
|
+
process.on('SIGINT', onSigInt)
|
|
813
|
+
process.on('SIGTERM', onSigTerm)
|
|
814
|
+
|
|
815
|
+
this._cleanupHandlers = [
|
|
816
|
+
() => process.removeListener('exit', onExit),
|
|
817
|
+
() => process.removeListener('SIGINT', onSigInt),
|
|
818
|
+
() => process.removeListener('SIGTERM', onSigTerm),
|
|
819
|
+
]
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
private _removeCleanup(): void {
|
|
823
|
+
for (const remove of this._cleanupHandlers) {
|
|
824
|
+
remove()
|
|
825
|
+
}
|
|
826
|
+
this._cleanupHandlers = []
|
|
827
|
+
this._cleanupRegistered = false
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
832
|
+
|
|
833
|
+
function formatDuration(ms: number): string {
|
|
834
|
+
const seconds = Math.floor(ms / 1000)
|
|
835
|
+
if (seconds < 60) return `${seconds}s`
|
|
836
|
+
const minutes = Math.floor(seconds / 60)
|
|
837
|
+
const secs = seconds % 60
|
|
838
|
+
if (minutes < 60) return `${minutes}m ${secs}s`
|
|
839
|
+
const hours = Math.floor(minutes / 60)
|
|
840
|
+
const mins = minutes % 60
|
|
841
|
+
return `${hours}h ${mins}m`
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
export default ProcessManager
|