@phuetz/code-buddy 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.fr.md +56 -0
- package/README.md +73 -16
- package/codebuddy-runtime.json +5 -5
- package/dist/agent/codebuddy-agent.d.ts +18 -0
- package/dist/agent/codebuddy-agent.js +45 -8
- package/dist/agent/execution/agent-executor.d.ts +0 -9
- package/dist/agent/execution/agent-executor.js +165 -12
- package/dist/agent/execution/context-pipeline.d.ts +9 -0
- package/dist/agent/execution/context-pipeline.js +64 -2
- package/dist/agent/execution/tool-loop-guard.d.ts +76 -0
- package/dist/agent/execution/tool-loop-guard.js +165 -0
- package/dist/agent/execution/tool-selection-strategy.js +19 -2
- package/dist/agent/facades/session-facade.d.ts +0 -6
- package/dist/agent/facades/session-facade.js +0 -45
- package/dist/agent/hermes-claw-migrate.d.ts +0 -21
- package/dist/agent/hermes-claw-migrate.js +9 -4
- package/dist/agent/hermes-protocol-gateways.js +8 -1
- package/dist/agent/repo-profiler.js +17 -4
- package/dist/agent/self-improvement/authored-tool-runtime.js +10 -2
- package/dist/agent/self-improvement/evolution/ast-novelty.d.ts +5 -0
- package/dist/agent/self-improvement/evolution/ast-novelty.js +58 -23
- package/dist/agent/self-improvement/evolution/code-variant-store.d.ts +3 -0
- package/dist/agent/self-improvement/evolution/evolution-engine.js +2 -0
- package/dist/agent/self-improvement/evolution/protected-paths.js +6 -0
- package/dist/agent/self-improvement/evolution/variant-fitness.d.ts +13 -2
- package/dist/agent/self-improvement/evolution/variant-fitness.js +184 -58
- package/dist/agent/self-improvement/evolution/worktree-scorer.d.ts +2 -0
- package/dist/agent/self-improvement/evolution/worktree-scorer.js +15 -0
- package/dist/agent/self-improvement/evolutionary-archive.d.ts +2 -1
- package/dist/agent/self-improvement/evolutionary-archive.js +34 -5
- package/dist/agent/self-improvement/proposal-store.d.ts +1 -0
- package/dist/agent/self-improvement/proposal-store.js +79 -17
- package/dist/agent/self-improvement/skill-apply-journal.d.ts +44 -0
- package/dist/agent/self-improvement/skill-apply-journal.js +178 -0
- package/dist/agent/self-improvement/skill-behavior-benchmark.d.ts +3 -0
- package/dist/agent/self-improvement/skill-behavior-benchmark.js +19 -0
- package/dist/agent/self-improvement/skill-behavior-gate.d.ts +34 -0
- package/dist/agent/self-improvement/skill-behavior-gate.js +96 -0
- package/dist/agent/self-improvement/skill-engine.d.ts +22 -0
- package/dist/agent/self-improvement/skill-engine.js +385 -13
- package/dist/agent/self-improvement/skill-gate.d.ts +2 -0
- package/dist/agent/self-improvement/skill-gate.js +17 -2
- package/dist/agent/self-improvement/skill-mutator.d.ts +6 -1
- package/dist/agent/self-improvement/skill-mutator.js +51 -15
- package/dist/agent/self-improvement/skill-types.d.ts +2 -1
- package/dist/agent/self-improvement/types.d.ts +11 -0
- package/dist/agent/tool-handler.d.ts +1 -1
- package/dist/agent/tool-handler.js +28 -9
- package/dist/auth/profile-manager.js +1 -1
- package/dist/channels/companion-channel-turn.d.ts +53 -4
- package/dist/channels/companion-channel-turn.js +218 -16
- package/dist/channels/slash-parity.d.ts +4 -6
- package/dist/channels/slash-parity.js +9 -44
- package/dist/cli/session-commands.d.ts +9 -0
- package/dist/cli/session-commands.js +45 -5
- package/dist/cli/session-picker.d.ts +43 -0
- package/dist/cli/session-picker.js +128 -0
- package/dist/codebuddy/a2a-call-tool-defs.d.ts +26 -0
- package/dist/codebuddy/a2a-call-tool-defs.js +12 -0
- package/dist/codebuddy/fleet-tool-defs.d.ts +1 -0
- package/dist/codebuddy/fleet-tool-defs.js +24 -0
- package/dist/codebuddy/providers/chatgpt-headers.d.ts +18 -0
- package/dist/codebuddy/providers/chatgpt-headers.js +37 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +2 -1
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +10 -23
- package/dist/codebuddy/providers/provider-openai-compat.js +1 -1
- package/dist/codebuddy/ragchat-tool-defs.d.ts +31 -0
- package/dist/codebuddy/ragchat-tool-defs.js +19 -0
- package/dist/codebuddy/resource-catalog-tool-defs.d.ts +28 -0
- package/dist/codebuddy/resource-catalog-tool-defs.js +18 -0
- package/dist/codebuddy/tool-definitions/agent-tools.js +2 -2
- package/dist/codebuddy/tool-definitions/code-exec-tools.js +5 -1
- package/dist/codebuddy/tool-definitions/cron-tools.js +6 -0
- package/dist/codebuddy/tool-definitions/self-describe-tools.js +6 -1
- package/dist/codebuddy/tools.d.ts +0 -6
- package/dist/codebuddy/tools.js +25 -3
- package/dist/commands/cli/daemon-commands.js +3 -1
- package/dist/commands/cli/evolve-command.js +21 -5
- package/dist/commands/cli/fleet-collaboration-commands.d.ts +2 -0
- package/dist/commands/cli/fleet-collaboration-commands.js +57 -0
- package/dist/commands/cli/fleet-commands.js +29 -0
- package/dist/commands/cli/fleet-mission-commands.d.ts +3 -0
- package/dist/commands/cli/fleet-mission-commands.js +72 -0
- package/dist/commands/cli/fleet-rooms-commands.d.ts +10 -0
- package/dist/commands/cli/fleet-rooms-commands.js +289 -0
- package/dist/commands/cli/tools-commands.js +2 -1
- package/dist/commands/cli/triage-command.d.ts +8 -0
- package/dist/commands/cli/triage-command.js +72 -0
- package/dist/commands/cli/utility-commands.js +25 -1
- package/dist/commands/client-dispatcher.d.ts +7 -0
- package/dist/commands/client-dispatcher.js +73 -21
- package/dist/commands/cron-cli/index.d.ts +3 -1
- package/dist/commands/cron-cli/index.js +25 -0
- package/dist/commands/device-auth.d.ts +10 -0
- package/dist/commands/device-auth.js +91 -0
- package/dist/commands/enhanced-command-handler.d.ts +9 -0
- package/dist/commands/enhanced-command-handler.js +28 -6
- package/dist/commands/handlers/agent-handlers.d.ts +1 -1
- package/dist/commands/handlers/agent-handlers.js +4 -4
- package/dist/commands/handlers/channel-handlers.js +53 -2
- package/dist/commands/handlers/export-handlers.d.ts +3 -2
- package/dist/commands/handlers/export-handlers.js +55 -28
- package/dist/commands/handlers/extra-handlers.js +5 -1
- package/dist/commands/handlers/grill-me-handler.js +26 -18
- package/dist/commands/handlers/memory-handlers.d.ts +6 -2
- package/dist/commands/handlers/memory-handlers.js +35 -15
- package/dist/commands/handlers/missing-handlers.d.ts +2 -2
- package/dist/commands/handlers/missing-handlers.js +4 -4
- package/dist/commands/handlers/resources-handler.d.ts +12 -0
- package/dist/commands/handlers/resources-handler.js +52 -0
- package/dist/commands/handlers/starter-handlers.d.ts +1 -1
- package/dist/commands/handlers/starter-handlers.js +32 -2
- package/dist/commands/handlers/switch-handler.d.ts +1 -1
- package/dist/commands/handlers/switch-handler.js +15 -0
- package/dist/commands/handlers/test-handlers.js +4 -86
- package/dist/commands/handlers/ui-handlers.js +11 -5
- package/dist/commands/login-chatgpt.d.ts +8 -0
- package/dist/commands/login-chatgpt.js +17 -0
- package/dist/commands/mcp-import.d.ts +11 -0
- package/dist/commands/mcp-import.js +67 -0
- package/dist/commands/mcp-invoke.d.ts +2 -0
- package/dist/commands/mcp-invoke.js +68 -0
- package/dist/commands/mcp.js +4 -0
- package/dist/commands/pipeline.d.ts +7 -1
- package/dist/commands/pipeline.js +104 -7
- package/dist/commands/resources.d.ts +2 -0
- package/dist/commands/resources.js +64 -0
- package/dist/commands/skills-cli/index.js +35 -12
- package/dist/commands/slash/builtin-commands.js +16 -3
- package/dist/commands/slash/index.d.ts +3 -1
- package/dist/commands/slash/index.js +2 -0
- package/dist/commands/slash/surfaces.d.ts +53 -0
- package/dist/commands/slash/surfaces.js +177 -0
- package/dist/commands/slash/types.d.ts +17 -0
- package/dist/commands/try.d.ts +1 -1
- package/dist/commands/try.js +1 -1
- package/dist/companion/companion-identity.d.ts +43 -0
- package/dist/companion/companion-identity.js +136 -0
- package/dist/companion/companion-toolset.d.ts +66 -0
- package/dist/companion/companion-toolset.js +368 -0
- package/dist/companion/companion-turn.d.ts +10 -2
- package/dist/companion/companion-turn.js +38 -1
- package/dist/config/code-exec-policy.d.ts +25 -0
- package/dist/config/code-exec-policy.js +39 -0
- package/dist/config/model-tools.d.ts +9 -0
- package/dist/config/model-tools.js +4 -0
- package/dist/context/context-manager-v2.js +2 -2
- package/dist/context/lm-resizer-compressor.js +3 -2
- package/dist/context/lm-resizer-diagnostics.d.ts +9 -0
- package/dist/context/lm-resizer-diagnostics.js +30 -0
- package/dist/context/tool-output-masking.js +3 -4
- package/dist/curator/curator.d.ts +6 -0
- package/dist/curator/curator.js +53 -3
- package/dist/daemon/cron-agent-bridge.d.ts +6 -0
- package/dist/daemon/cron-agent-bridge.js +31 -4
- package/dist/doctor/index.d.ts +11 -2
- package/dist/doctor/index.js +84 -21
- package/dist/doctor/integrations.d.ts +179 -0
- package/dist/doctor/integrations.js +227 -0
- package/dist/doctor/triage.d.ts +108 -0
- package/dist/doctor/triage.js +304 -0
- package/dist/fleet/collaboration.d.ts +44 -0
- package/dist/fleet/collaboration.js +170 -0
- package/dist/fleet/fleet-listener.d.ts +1 -0
- package/dist/fleet/fleet-listener.js +87 -46
- package/dist/fleet/fleet-registry.d.ts +2 -0
- package/dist/fleet/resource-catalog.d.ts +245 -0
- package/dist/fleet/resource-catalog.js +211 -0
- package/dist/fleet/rooms/room-access.d.ts +138 -0
- package/dist/fleet/rooms/room-access.js +196 -0
- package/dist/fleet/rooms/room-client.d.ts +158 -0
- package/dist/fleet/rooms/room-client.js +685 -0
- package/dist/fleet/rooms/room-event.d.ts +139 -0
- package/dist/fleet/rooms/room-event.js +330 -0
- package/dist/fleet/rooms/room-filter.d.ts +48 -0
- package/dist/fleet/rooms/room-filter.js +152 -0
- package/dist/fleet/rooms/room-hub.d.ts +154 -0
- package/dist/fleet/rooms/room-hub.js +363 -0
- package/dist/fleet/rooms/room-identity.d.ts +26 -0
- package/dist/fleet/rooms/room-identity.js +82 -0
- package/dist/fleet/rooms/room-lock.d.ts +34 -0
- package/dist/fleet/rooms/room-lock.js +180 -0
- package/dist/fleet/rooms/room-observations.d.ts +7 -0
- package/dist/fleet/rooms/room-observations.js +61 -0
- package/dist/fleet/rooms/room-server.d.ts +28 -0
- package/dist/fleet/rooms/room-server.js +75 -0
- package/dist/fleet/rooms/room-store.d.ts +119 -0
- package/dist/fleet/rooms/room-store.js +448 -0
- package/dist/fleet/rooms/room-ws-bridge.d.ts +27 -0
- package/dist/fleet/rooms/room-ws-bridge.js +137 -0
- package/dist/fleet/sensory-mission-bridge.d.ts +42 -0
- package/dist/fleet/sensory-mission-bridge.js +136 -0
- package/dist/git/worktree-sessions.js +1 -0
- package/dist/harness/contract.d.ts +4 -4
- package/dist/harness/fleet-supervisor.d.ts +16 -0
- package/dist/harness/fleet-supervisor.js +64 -0
- package/dist/harness/index.d.ts +4 -0
- package/dist/harness/index.js +4 -0
- package/dist/harness/mission-runner.d.ts +3 -0
- package/dist/harness/mission-runner.js +53 -0
- package/dist/harness/mission-store.d.ts +93 -0
- package/dist/harness/mission-store.js +297 -0
- package/dist/harness/tool-harness.d.ts +56 -0
- package/dist/harness/tool-harness.js +153 -0
- package/dist/hooks/use-enhanced-input.js +48 -4
- package/dist/hooks/use-input-handler.d.ts +1 -0
- package/dist/hooks/use-input-handler.js +82 -22
- package/dist/hooks/use-input-history.js +1 -0
- package/dist/identity/lisa-introspection.js +5 -2
- package/dist/identity/operational-self-model.d.ts +2 -0
- package/dist/identity/operational-self-model.js +4 -0
- package/dist/index.js +52 -6
- package/dist/mcp/client.js +6 -0
- package/dist/mcp/config.d.ts +20 -0
- package/dist/mcp/config.js +121 -8
- package/dist/mcp/import-normalize.d.ts +12 -0
- package/dist/mcp/import-normalize.js +114 -0
- package/dist/mcp/transports.d.ts +10 -5
- package/dist/mcp/transports.js +61 -99
- package/dist/mcp/types.d.ts +5 -0
- package/dist/media/comfyui-recipe-contract.d.ts +7 -7
- package/dist/memory/facts-memory.d.ts +11 -1
- package/dist/memory/facts-memory.js +47 -8
- package/dist/memory/memory-candidate-queue.d.ts +2 -2
- package/dist/memory/memory-candidate-queue.js +6 -6
- package/dist/memory/persistent-memory.d.ts +13 -8
- package/dist/memory/persistent-memory.js +77 -30
- package/dist/nodes/device-node.js +5 -9
- package/dist/observability/mobile-supervision-gateway-contract.js +20 -7
- package/dist/observability/run-event-writer.d.ts +21 -0
- package/dist/observability/run-event-writer.js +81 -0
- package/dist/observability/run-store.d.ts +5 -0
- package/dist/observability/run-store.js +18 -13
- package/dist/observability/run-trajectory.js +1 -1
- package/dist/optimization/prompt-cache.d.ts +19 -2
- package/dist/optimization/prompt-cache.js +41 -6
- package/dist/orchestration/orchestrator.d.ts +21 -0
- package/dist/orchestration/orchestrator.js +122 -15
- package/dist/persistence/session-content.d.ts +8 -0
- package/dist/persistence/session-content.js +76 -0
- package/dist/persistence/session-handoff.d.ts +64 -0
- package/dist/persistence/session-handoff.js +80 -0
- package/dist/persistence/session-history.d.ts +4 -0
- package/dist/persistence/session-history.js +38 -0
- package/dist/persistence/session-store.d.ts +9 -0
- package/dist/persistence/session-store.js +48 -20
- package/dist/personas/persona-manager.js +17 -1
- package/dist/plugins/code-explorer/CodeExplorerManager.d.ts +10 -4
- package/dist/plugins/code-explorer/CodeExplorerManager.js +69 -16
- package/dist/plugins/plugin-manager.js +10 -1
- package/dist/prompts/prompt-manager.js +11 -4
- package/dist/prompts/system-base.js +6 -2
- package/dist/protocols/a2a/codebuddy-executor.d.ts +11 -2
- package/dist/protocols/a2a/codebuddy-executor.js +9 -8
- package/dist/protocols/a2a/index.d.ts +1 -1
- package/dist/protocols/a2a/jsonrpc-v1.d.ts +41 -0
- package/dist/protocols/a2a/jsonrpc-v1.js +145 -0
- package/dist/protocols/a2a/peer-config.d.ts +20 -0
- package/dist/protocols/a2a/peer-config.js +55 -0
- package/dist/providers/codex-oauth.js +30 -6
- package/dist/providers/model-provider-compat.js +2 -1
- package/dist/sandbox/os-sandbox.d.ts +1 -1
- package/dist/scheduler/cron-scheduler.d.ts +15 -2
- package/dist/scheduler/cron-scheduler.js +35 -4
- package/dist/scheduler/job-notepad.d.ts +69 -0
- package/dist/scheduler/job-notepad.js +339 -0
- package/dist/security/audit-logger.d.ts +1 -1
- package/dist/security/compute-confinement.d.ts +4 -0
- package/dist/security/compute-confinement.js +89 -0
- package/dist/security/session-encryption.d.ts +4 -0
- package/dist/security/session-encryption.js +31 -0
- package/dist/sensory/voice-loop.d.ts +17 -0
- package/dist/sensory/voice-loop.js +72 -15
- package/dist/server/auth/device-session-context.d.ts +9 -0
- package/dist/server/auth/device-session-context.js +13 -0
- package/dist/server/auth/device-store.d.ts +89 -0
- package/dist/server/auth/device-store.js +261 -0
- package/dist/server/auth/device-token.d.ts +3 -0
- package/dist/server/auth/device-token.js +6 -0
- package/dist/server/auth/jwt.js +4 -0
- package/dist/server/index.d.ts +0 -10
- package/dist/server/index.js +28 -0
- package/dist/server/middleware/auth.js +3 -2
- package/dist/server/routes/a2a-jsonrpc.d.ts +9 -0
- package/dist/server/routes/a2a-jsonrpc.js +66 -0
- package/dist/server/routes/a2a-protocol.d.ts +12 -0
- package/dist/server/routes/a2a-protocol.js +1 -1
- package/dist/server/routes/device-auth.d.ts +4 -0
- package/dist/server/routes/device-auth.js +40 -0
- package/dist/server/types.d.ts +4 -0
- package/dist/server/websocket/handler.d.ts +13 -0
- package/dist/server/websocket/handler.js +102 -17
- package/dist/services/prompt-builder.d.ts +2 -0
- package/dist/services/prompt-builder.js +13 -5
- package/dist/services/runtime-settings-context.d.ts +73 -0
- package/dist/services/runtime-settings-context.js +69 -0
- package/dist/skills/executor.js +2 -0
- package/dist/skills/hub.js +16 -14
- package/dist/skills/local-inventory.d.ts +21 -0
- package/dist/skills/local-inventory.js +72 -0
- package/dist/skills/skill-loader.js +2 -2
- package/dist/skills/skill-usage-store.d.ts +49 -0
- package/dist/skills/skill-usage-store.js +121 -0
- package/dist/testing/ai-integration-tests.js +8 -5
- package/dist/themes/theme-manager.d.ts +12 -0
- package/dist/themes/theme-manager.js +29 -0
- package/dist/themes/theme-schema.d.ts +24 -24
- package/dist/tools/a2a-call-tool.d.ts +34 -0
- package/dist/tools/a2a-call-tool.js +75 -0
- package/dist/tools/bash/streaming-executor.d.ts +1 -0
- package/dist/tools/bash/streaming-executor.js +78 -90
- package/dist/tools/code-exec-preflight-runner.d.ts +41 -0
- package/dist/tools/code-exec-preflight-runner.js +254 -0
- package/dist/tools/code-exec-preflight.d.ts +44 -0
- package/dist/tools/code-exec-preflight.js +501 -0
- package/dist/tools/code-exec-tool.d.ts +48 -1
- package/dist/tools/code-exec-tool.js +191 -47
- package/dist/tools/comment-watcher.js +29 -12
- package/dist/tools/core-code-inspection.d.ts +56 -0
- package/dist/tools/core-code-inspection.js +86 -0
- package/dist/tools/create-skill-tool.d.ts +2 -3
- package/dist/tools/create-skill-tool.js +16 -75
- package/dist/tools/cronjob-tool.d.ts +1 -0
- package/dist/tools/cronjob-tool.js +1 -0
- package/dist/tools/env-doctor-tool.d.ts +2 -0
- package/dist/tools/env-doctor-tool.js +4 -3
- package/dist/tools/execute-code-rpc-invoker.d.ts +1 -1
- package/dist/tools/execute-code-rpc-invoker.js +35 -19
- package/dist/tools/execute-code-runner.d.ts +2 -0
- package/dist/tools/execute-code-runner.js +52 -17
- package/dist/tools/fleet-room-tool.d.ts +104 -0
- package/dist/tools/fleet-room-tool.js +124 -0
- package/dist/tools/integration-tool-hints.d.ts +4 -0
- package/dist/tools/integration-tool-hints.js +19 -0
- package/dist/tools/list-peers-tool.d.ts +3 -0
- package/dist/tools/list-peers-tool.js +11 -4
- package/dist/tools/media-generation-tool.d.ts +16 -1
- package/dist/tools/media-generation-tool.js +341 -9
- package/dist/tools/merge-conflict-tool.js +53 -25
- package/dist/tools/metadata.d.ts +1 -5
- package/dist/tools/metadata.js +21 -28
- package/dist/tools/ragchat-tool.d.ts +39 -0
- package/dist/tools/ragchat-tool.js +105 -0
- package/dist/tools/registry/fleet-tools.d.ts +9 -0
- package/dist/tools/registry/fleet-tools.js +22 -1
- package/dist/tools/registry/knowledge-tools.d.ts +2 -2
- package/dist/tools/registry/knowledge-tools.js +2 -2
- package/dist/tools/registry/lessons-tools.d.ts +7 -0
- package/dist/tools/registry/lessons-tools.js +43 -3
- package/dist/tools/registry/memory-tools.d.ts +1 -1
- package/dist/tools/registry/memory-tools.js +8 -8
- package/dist/tools/registry/research-tools.d.ts +1 -0
- package/dist/tools/registry/research-tools.js +4 -13
- package/dist/tools/registry/self-describe-tools.d.ts +1 -1
- package/dist/tools/registry/self-describe-tools.js +43 -30
- package/dist/tools/resource-catalog-tool.d.ts +38 -0
- package/dist/tools/resource-catalog-tool.js +46 -0
- package/dist/tools/skills-inspection-tool.js +30 -1
- package/dist/tools/stream-tool-output.d.ts +3 -0
- package/dist/tools/stream-tool-output.js +33 -0
- package/dist/tools/tool-call-scheduler.d.ts +10 -0
- package/dist/tools/tool-call-scheduler.js +58 -0
- package/dist/tools/tool-effect.d.ts +5 -0
- package/dist/tools/tool-effect.js +26 -0
- package/dist/tools/tool-search.d.ts +12 -11
- package/dist/tools/tool-search.js +67 -26
- package/dist/tools/types.d.ts +6 -0
- package/dist/ui/components/ChatHistory.d.ts +1 -1
- package/dist/ui/components/ChatHistory.js +2 -2
- package/dist/ui/components/ChatInput.d.ts +2 -1
- package/dist/ui/components/ChatInput.js +42 -88
- package/dist/ui/components/ChatInterface.d.ts +4 -2
- package/dist/ui/components/ChatInterface.js +27 -63
- package/dist/ui/components/CommandSuggestions.d.ts +0 -1
- package/dist/ui/components/CommandSuggestions.js +4 -6
- package/dist/ui/components/FileAutocomplete.js +1 -1
- package/dist/ui/components/KeyboardHelp.js +8 -6
- package/dist/ui/components/ModelSelection.js +6 -5
- package/dist/ui/components/StatusBar.d.ts +2 -1
- package/dist/ui/components/StatusBar.js +8 -7
- package/dist/ui/context/theme-context.js +3 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui/utils/markdown-renderer.js +4 -13
- package/dist/utils/atomic-write.d.ts +22 -0
- package/dist/utils/atomic-write.js +29 -0
- package/dist/utils/bounded-output.d.ts +16 -0
- package/dist/utils/bounded-output.js +63 -0
- package/dist/utils/device-store-file.d.ts +4 -0
- package/dist/utils/device-store-file.js +40 -0
- package/dist/utils/export-manager.d.ts +9 -0
- package/dist/utils/export-manager.js +19 -0
- package/dist/utils/first-use-hints.d.ts +25 -0
- package/dist/utils/first-use-hints.js +58 -0
- package/dist/utils/history-manager.js +1 -1
- package/dist/utils/update-notifier.js +16 -18
- package/dist/wizard/environment-detection.d.ts +5 -1
- package/dist/wizard/environment-detection.js +49 -7
- package/dist/wizard/onboarding.d.ts +5 -1
- package/dist/wizard/onboarding.js +90 -30
- package/dist/wizard/provider-onboarding.d.ts +2 -0
- package/dist/wizard/provider-onboarding.js +55 -17
- package/package.json +7 -3
|
@@ -16,6 +16,7 @@ import { spawn } from 'node:child_process';
|
|
|
16
16
|
import { randomUUID } from 'node:crypto';
|
|
17
17
|
import { BaseTool } from './base-tool.js';
|
|
18
18
|
import { logger } from '../utils/logger.js';
|
|
19
|
+
import { ToolCallScheduler } from './tool-call-scheduler.js';
|
|
19
20
|
export const CODE_EXEC_RUNTIME_CONTEXT_KEY = 'codeExecRuntime';
|
|
20
21
|
/** Attach a runtime without changing any other tool-execution context data. */
|
|
21
22
|
export function attachCodeExecRuntime(context, runtime) {
|
|
@@ -58,6 +59,22 @@ export const CODE_EXEC_LIMITS = Object.freeze({
|
|
|
58
59
|
scopeTtlMs: 2 * 60 * 60 * 1000,
|
|
59
60
|
});
|
|
60
61
|
const scopedStates = new Map();
|
|
62
|
+
const scopeSchedulers = new Map();
|
|
63
|
+
async function runScoped(scopeId, action, signal) {
|
|
64
|
+
let entry = scopeSchedulers.get(scopeId);
|
|
65
|
+
if (!entry) {
|
|
66
|
+
entry = { scheduler: new ToolCallScheduler(1), users: 0 };
|
|
67
|
+
scopeSchedulers.set(scopeId, entry);
|
|
68
|
+
}
|
|
69
|
+
entry.users++;
|
|
70
|
+
try {
|
|
71
|
+
return await entry.scheduler.schedule(false, action, signal);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
if (--entry.users === 0)
|
|
75
|
+
scopeSchedulers.delete(scopeId);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
61
78
|
/** Backward-compatible direct-use runtime. Production ToolHandler never uses it. */
|
|
62
79
|
let legacySessionId = 'default';
|
|
63
80
|
let legacyExecutor = null;
|
|
@@ -167,17 +184,19 @@ function truncate(value, limit) {
|
|
|
167
184
|
return value.slice(0, Math.max(0, limit - marker.length)) + marker;
|
|
168
185
|
}
|
|
169
186
|
function jsonSafe(value) {
|
|
170
|
-
const
|
|
187
|
+
const ancestors = [];
|
|
171
188
|
try {
|
|
172
|
-
const encoded = JSON.stringify(value, (_key, current)
|
|
189
|
+
const encoded = JSON.stringify(value, function (_key, current) {
|
|
173
190
|
if (typeof current === 'bigint')
|
|
174
191
|
return current.toString();
|
|
175
192
|
if (typeof current === 'function' || typeof current === 'symbol')
|
|
176
193
|
return undefined;
|
|
177
194
|
if (current && typeof current === 'object') {
|
|
178
|
-
|
|
195
|
+
while (ancestors.length && ancestors[ancestors.length - 1] !== this)
|
|
196
|
+
ancestors.pop();
|
|
197
|
+
if (ancestors.includes(current))
|
|
179
198
|
return '[Circular]';
|
|
180
|
-
|
|
199
|
+
ancestors.push(current);
|
|
181
200
|
}
|
|
182
201
|
return current;
|
|
183
202
|
});
|
|
@@ -195,23 +214,35 @@ function jsonSafe(value) {
|
|
|
195
214
|
return String(value);
|
|
196
215
|
}
|
|
197
216
|
}
|
|
198
|
-
function serializeToolResult(result) {
|
|
217
|
+
function serializeToolResult(result, format) {
|
|
218
|
+
if (format !== 'legacy') {
|
|
219
|
+
const payload = jsonSafe(result);
|
|
220
|
+
if (payload && typeof payload === 'object' && 'truncated' in payload && 'preview' in payload) {
|
|
221
|
+
return JSON.stringify({ success: result.success, truncated: true,
|
|
222
|
+
output: truncate(result.output ?? String(payload.preview), CODE_EXEC_LIMITS.maxToolResultChars - CODE_EXEC_LIMITS.maxErrorChars),
|
|
223
|
+
...(result.error ? { error: truncate(result.error, CODE_EXEC_LIMITS.maxErrorChars) } : {}),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return JSON.stringify(payload);
|
|
227
|
+
}
|
|
199
228
|
const payload = result.success
|
|
200
229
|
? (result.output ?? result.data ?? '')
|
|
201
230
|
: { error: truncate(result.error ?? 'Tool execution failed', CODE_EXEC_LIMITS.maxErrorChars) };
|
|
202
231
|
return JSON.stringify(jsonSafe(payload));
|
|
203
232
|
}
|
|
204
|
-
function sanitizeToolName(name) {
|
|
233
|
+
export function sanitizeToolName(name) {
|
|
205
234
|
const replaced = name.replace(/[^a-zA-Z0-9_$]/g, '_');
|
|
206
235
|
if (!replaced)
|
|
207
236
|
return '_tool';
|
|
208
237
|
return /^[a-zA-Z_$]/.test(replaced) ? replaced : `_${replaced}`;
|
|
209
238
|
}
|
|
210
|
-
function buildToolBindings(toolNames) {
|
|
239
|
+
export function buildToolBindings(toolNames) {
|
|
211
240
|
const bindings = [];
|
|
212
241
|
const seenTools = new Set();
|
|
213
242
|
const seenBindings = new Set(['call']);
|
|
214
|
-
|
|
243
|
+
// Keep discovery reachable even when direct bindings hit their budget.
|
|
244
|
+
const ordered = toolNames.includes('tool_search') ? ['tool_search', ...toolNames] : toolNames;
|
|
245
|
+
for (const toolName of ordered) {
|
|
215
246
|
if (bindings.length >= CODE_EXEC_LIMITS.maxAvailableTools)
|
|
216
247
|
break;
|
|
217
248
|
if (typeof toolName !== 'string' ||
|
|
@@ -222,9 +253,11 @@ function buildToolBindings(toolNames) {
|
|
|
222
253
|
continue;
|
|
223
254
|
}
|
|
224
255
|
seenTools.add(toolName);
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
256
|
+
const baseName = sanitizeToolName(toolName);
|
|
257
|
+
let exposedName = baseName;
|
|
258
|
+
let suffix = 2;
|
|
259
|
+
while (seenBindings.has(exposedName))
|
|
260
|
+
exposedName = `${baseName}_${suffix++}`;
|
|
228
261
|
seenBindings.add(exposedName);
|
|
229
262
|
bindings.push({ exposedName, toolName });
|
|
230
263
|
}
|
|
@@ -305,27 +338,31 @@ async function execute(message) {
|
|
|
305
338
|
const outputLimit = ${CODE_EXEC_LIMITS.maxOutputChars};
|
|
306
339
|
const storeEntries = JSON.stringify(message.storeEntries || []);
|
|
307
340
|
const toolBindings = JSON.stringify(message.toolBindings || []);
|
|
308
|
-
const allTools = JSON.stringify((message.toolBindings || []).map((entry) => entry.toolName));
|
|
341
|
+
const allTools = JSON.stringify(message.toolMetadata || (message.toolBindings || []).map((entry) => ({ name: entry.toolName, description: '' })));
|
|
309
342
|
const bootstrap =
|
|
310
343
|
'const __cbOutput = []; let __cbOutputLength = 0; let __cbTruncated = false; let __cbYielded = false;' +
|
|
311
344
|
'const __cbStore = new Map(' + storeEntries + ');' +
|
|
312
345
|
'const __cbBindings = ' + toolBindings + ';' +
|
|
313
346
|
'const __cbBridge = globalThis.__codeBuddyBridge; delete globalThis.__codeBuddyBridge;' +
|
|
347
|
+
'const __cbYield = globalThis.__codeBuddyYield; delete globalThis.__codeBuddyYield;' +
|
|
314
348
|
'function __cbFormat(value) { if (typeof value === "string") return value; try { const json = JSON.stringify(value); return json === undefined ? String(value) : json; } catch { return String(value); } }' +
|
|
315
349
|
'function __cbAppend(values, prefix = "") { if (__cbTruncated) return; const line = prefix + values.map(__cbFormat).join(" "); const separator = __cbOutput.length ? "\\n" : ""; const remaining = ' + outputLimit + ' - __cbOutputLength; if (remaining <= 0) { __cbTruncated = true; return; } const next = separator + line; if (next.length > remaining) { __cbOutput.push(next.slice(0, remaining)); __cbOutputLength += remaining; __cbTruncated = true; return; } __cbOutput.push(next); __cbOutputLength += next.length; }' +
|
|
316
350
|
'function text(content) { __cbAppend([content]); }' +
|
|
317
351
|
'function store(key, value) { if (typeof key !== "string" || key.length === 0 || key.length > 256) throw new Error("store key must be 1..256 characters"); const encoded = JSON.stringify(value); if (encoded === undefined) throw new Error("store values must be JSON-compatible"); const cloned = JSON.parse(encoded); const candidate = new Map(__cbStore); candidate.set(key, cloned); if (candidate.size > ${CODE_EXEC_LIMITS.maxStoreEntries}) throw new Error("store entry limit reached"); const total = JSON.stringify(Array.from(candidate.entries())).length; if (total > ${CODE_EXEC_LIMITS.maxStoreBytes}) throw new Error("store byte limit reached"); __cbStore.set(key, cloned); }' +
|
|
318
352
|
'function load(key) { const value = __cbStore.get(key); return value === undefined ? undefined : JSON.parse(JSON.stringify(value)); }' +
|
|
319
|
-
'function yield_control() { __cbYielded = true; }' +
|
|
353
|
+
'let __cbEmittedLength = 0; async function yield_control() { __cbYielded = true; const output = __cbOutput.join(""); const delta = output.slice(__cbEmittedLength); __cbEmittedLength = output.length; await __cbYield(delta); }' +
|
|
320
354
|
'const tools = Object.create(null);' +
|
|
321
355
|
'for (const binding of __cbBindings) { tools[binding.exposedName] = async function(args = {}) { const encoded = JSON.stringify(args); if (encoded === undefined || args === null || typeof args !== "object" || Array.isArray(args)) throw new Error("tool arguments must be a JSON object"); return JSON.parse(await __cbBridge(binding.toolName, encoded)); }; }' +
|
|
322
|
-
'tools.call = async function(name, args = {}) { if (typeof name !== "string") throw new Error("tool name must be a string"); const
|
|
356
|
+
'tools.call = async function(name, args = {}) { if (typeof name !== "string") throw new Error("tool name must be a string"); const encoded = JSON.stringify(args); if (encoded === undefined || args === null || typeof args !== "object" || Array.isArray(args)) throw new Error("tool arguments must be a JSON object"); return JSON.parse(await __cbBridge(name, encoded)); };' +
|
|
323
357
|
'Object.freeze(tools); Object.freeze(__cbBindings);' +
|
|
324
|
-
'const ALL_TOOLS = Object.freeze(' + allTools + ');' +
|
|
358
|
+
'const ALL_TOOLS = Object.freeze(' + allTools + '.map(Object.freeze)); const ALL_TOOL_NAMES = Object.freeze(ALL_TOOLS.map(t => t.name));' +
|
|
325
359
|
'const console = Object.freeze({ log: (...args) => __cbAppend(args), error: (...args) => __cbAppend(args, "[ERROR] "), warn: (...args) => __cbAppend(args, "[WARN] ") });';
|
|
326
360
|
|
|
327
361
|
const sandbox = Object.create(null);
|
|
328
362
|
sandbox.__codeBuddyBridge = bridgeCall;
|
|
363
|
+
const yieldOutput = (output) => send({ type: 'output', output });
|
|
364
|
+
Object.setPrototypeOf(yieldOutput, null);
|
|
365
|
+
sandbox.__codeBuddyYield = yieldOutput;
|
|
329
366
|
const context = vm.createContext(sandbox, {
|
|
330
367
|
name: 'codebuddy-code-exec',
|
|
331
368
|
codeGeneration: { strings: false, wasm: false },
|
|
@@ -370,12 +407,60 @@ function childExecArgs() {
|
|
|
370
407
|
args.push('-e', SANDBOX_RUNNER_SOURCE);
|
|
371
408
|
return args;
|
|
372
409
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
410
|
+
/** Upper bound on waiting for the sandbox process to report `close`. */
|
|
411
|
+
export const CHILD_CLOSE_GRACE_MS = 2_000;
|
|
412
|
+
/**
|
|
413
|
+
* Wait for the OBSERVED `close` of the sandbox process — the event that says
|
|
414
|
+
* the process ended and its stdio is released. `exit` alone is not enough:
|
|
415
|
+
* `exitCode` can be set while stdio is still open, and the sandbox runs with
|
|
416
|
+
* the caller's workspace as its working directory, so the caller must not be
|
|
417
|
+
* told the workspace is free before the process really let go of it.
|
|
418
|
+
*
|
|
419
|
+
* @returns true when `close` was observed (or there is no process to wait for),
|
|
420
|
+
* false when the grace elapsed first. The caller decides what an
|
|
421
|
+
* unterminated sandbox means; this function never claims success.
|
|
422
|
+
*/
|
|
423
|
+
export function waitForChildClose(child, graceMs = CHILD_CLOSE_GRACE_MS) {
|
|
424
|
+
// Spawn failed: no process exists, nothing holds the workspace, nothing to wait for.
|
|
425
|
+
if (child.pid === undefined && child.exitCode === null && child.signalCode === null) {
|
|
426
|
+
return Promise.resolve(true);
|
|
377
427
|
}
|
|
378
|
-
|
|
428
|
+
return new Promise((resolve) => {
|
|
429
|
+
// Named handlers so every listener and the timer are removed on both paths.
|
|
430
|
+
const onClose = () => settle(true);
|
|
431
|
+
const onTimeout = () => settle(false);
|
|
432
|
+
// Deliberately referenced: a killed child can leave nothing else pending,
|
|
433
|
+
// and an unreferenced timer would let the host exit with this promise
|
|
434
|
+
// unsettled instead of finishing the run. The wait stays bounded by graceMs.
|
|
435
|
+
const timer = setTimeout(onTimeout, graceMs);
|
|
436
|
+
const settle = (closed) => {
|
|
437
|
+
clearTimeout(timer);
|
|
438
|
+
child.removeListener('close', onClose);
|
|
439
|
+
resolve(closed);
|
|
440
|
+
};
|
|
441
|
+
child.on('close', onClose);
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* A sandbox process that did not close within the grace is a cleanup failure:
|
|
446
|
+
* it may still hold the workspace. Never report success in that case, and keep
|
|
447
|
+
* the original diagnosis when the run had already failed.
|
|
448
|
+
*/
|
|
449
|
+
export function sandboxNotTerminatedResult(result, graceMs = CHILD_CLOSE_GRACE_MS) {
|
|
450
|
+
const notice = `Sandbox process did not terminate within ${graceMs}ms; its workspace may still be locked`;
|
|
451
|
+
return {
|
|
452
|
+
...result,
|
|
453
|
+
success: false,
|
|
454
|
+
output: result.output ? `${result.output}\n${notice}` : notice,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function terminateChild(child) {
|
|
458
|
+
// No explicit disconnect(): measured on Node 24, closing the IPC channel by
|
|
459
|
+
// hand before the kill suppresses the process 'close' event entirely (only
|
|
460
|
+
// 'disconnect' and 'exit' arrive), and 'close' is the signal that the process
|
|
461
|
+
// released its stdio — and with it the working directory. The channel is
|
|
462
|
+
// closed by the kill anyway ('disconnect' still fires), and no message can be
|
|
463
|
+
// sent after this point: `settled` is already true.
|
|
379
464
|
try {
|
|
380
465
|
if (!child.killed)
|
|
381
466
|
child.kill('SIGKILL');
|
|
@@ -384,11 +469,15 @@ function terminateChild(child) {
|
|
|
384
469
|
}
|
|
385
470
|
async function runInChild(code, timeoutMs, runtime, state) {
|
|
386
471
|
const toolBindings = buildToolBindings(runtime.availableTools);
|
|
387
|
-
|
|
472
|
+
// Canonical calls use the full scoped catalogue; only JS shortcuts are capped.
|
|
473
|
+
const allowedTools = new Set(runtime.availableTools.filter(name => typeof name === 'string' && name && !['exec', 'code_exec'].includes(name)));
|
|
388
474
|
return await new Promise((resolve) => {
|
|
389
475
|
let settled = false;
|
|
390
476
|
let stderr = '';
|
|
391
|
-
|
|
477
|
+
const scheduler = new ToolCallScheduler();
|
|
478
|
+
const controller = new AbortController();
|
|
479
|
+
let toolCallCount = 0;
|
|
480
|
+
let emittedChars = 0;
|
|
392
481
|
const child = spawn(process.execPath, childExecArgs(), {
|
|
393
482
|
cwd: runtime.cwd || process.cwd(),
|
|
394
483
|
env: {
|
|
@@ -406,10 +495,16 @@ async function runInChild(code, timeoutMs, runtime, state) {
|
|
|
406
495
|
if (settled)
|
|
407
496
|
return;
|
|
408
497
|
settled = true;
|
|
498
|
+
controller.abort();
|
|
409
499
|
clearTimeout(timer);
|
|
410
500
|
runtime.abortSignal?.removeEventListener('abort', onAbort);
|
|
411
501
|
terminateChild(child);
|
|
412
|
-
|
|
502
|
+
// Report completion only once `close` was observed: callers treat the
|
|
503
|
+
// resolved promise as "the sandbox no longer touches my workspace". A
|
|
504
|
+
// process that never closes is reported as a failure, never as success.
|
|
505
|
+
void waitForChildClose(child).then((closed) => {
|
|
506
|
+
resolve(closed ? result : sandboxNotTerminatedResult(result));
|
|
507
|
+
});
|
|
413
508
|
};
|
|
414
509
|
const timer = setTimeout(() => {
|
|
415
510
|
finish({
|
|
@@ -443,8 +538,22 @@ async function runInChild(code, timeoutMs, runtime, state) {
|
|
|
443
538
|
const message = raw;
|
|
444
539
|
if (!message || typeof message !== 'object' || settled)
|
|
445
540
|
return;
|
|
541
|
+
if (message.type === 'output') {
|
|
542
|
+
if (typeof message.output === 'string' && emittedChars + message.output.length <= CODE_EXEC_LIMITS.maxOutputChars) {
|
|
543
|
+
emittedChars += message.output.length;
|
|
544
|
+
try {
|
|
545
|
+
runtime.onOutput?.(message.output);
|
|
546
|
+
}
|
|
547
|
+
catch { /* Observers do not control execution. */ }
|
|
548
|
+
}
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
446
551
|
if (message.type === 'tool_call') {
|
|
447
|
-
|
|
552
|
+
if (++toolCallCount > CODE_EXEC_LIMITS.maxToolCalls || typeof message.argsJson !== 'string' || message.argsJson.length > CODE_EXEC_LIMITS.maxCodeChars) {
|
|
553
|
+
finish({ success: false, output: 'code_exec tool-call or argument limit exceeded' });
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
void scheduler.schedule(runtime.parallelTools?.includes(message.toolName) === true, async () => {
|
|
448
557
|
if (settled || !child.connected)
|
|
449
558
|
return;
|
|
450
559
|
if (!allowedTools.has(message.toolName) ||
|
|
@@ -476,13 +585,13 @@ async function runInChild(code, timeoutMs, runtime, state) {
|
|
|
476
585
|
return;
|
|
477
586
|
}
|
|
478
587
|
try {
|
|
479
|
-
const result = await runtime.executor(message.toolName, args);
|
|
588
|
+
const result = await runtime.executor(message.toolName, args, controller.signal);
|
|
480
589
|
if (!settled && child.connected) {
|
|
481
590
|
child.send({
|
|
482
591
|
type: 'tool_result',
|
|
483
592
|
id: message.id,
|
|
484
593
|
ok: true,
|
|
485
|
-
valueJson: serializeToolResult(result),
|
|
594
|
+
valueJson: serializeToolResult(result, runtime.resultFormat),
|
|
486
595
|
});
|
|
487
596
|
}
|
|
488
597
|
}
|
|
@@ -536,6 +645,10 @@ async function runInChild(code, timeoutMs, runtime, state) {
|
|
|
536
645
|
code,
|
|
537
646
|
timeoutMs,
|
|
538
647
|
toolBindings,
|
|
648
|
+
toolMetadata: toolBindings.map(binding => ({
|
|
649
|
+
name: binding.toolName,
|
|
650
|
+
description: runtime.toolMetadata?.find(tool => tool.name === binding.toolName)?.description.slice(0, 2000) ?? '',
|
|
651
|
+
})),
|
|
539
652
|
storeEntries: Array.from(state.values.entries()),
|
|
540
653
|
}, (error) => {
|
|
541
654
|
if (error)
|
|
@@ -563,6 +676,10 @@ export class CodeExecTool extends BaseTool {
|
|
|
563
676
|
type: 'number',
|
|
564
677
|
description: `Execution timeout in milliseconds (${CODE_EXEC_LIMITS.minTimeoutMs}..${CODE_EXEC_LIMITS.maxTimeoutMs}, default ${CODE_EXEC_LIMITS.defaultTimeoutMs}).`,
|
|
565
678
|
},
|
|
679
|
+
typecheck: {
|
|
680
|
+
type: 'boolean',
|
|
681
|
+
description: 'Perform isolated TypeScript typechecking before execution. Rejects code on diagnostic errors with zero runtime side effects. Default false.',
|
|
682
|
+
},
|
|
566
683
|
};
|
|
567
684
|
}
|
|
568
685
|
validate(input) {
|
|
@@ -587,6 +704,13 @@ export class CodeExecTool extends BaseTool {
|
|
|
587
704
|
errors: [`timeout_ms must be between ${CODE_EXEC_LIMITS.minTimeoutMs} and ${CODE_EXEC_LIMITS.maxTimeoutMs}`],
|
|
588
705
|
};
|
|
589
706
|
}
|
|
707
|
+
if (args.typecheck !== undefined &&
|
|
708
|
+
typeof args.typecheck !== 'boolean') {
|
|
709
|
+
return {
|
|
710
|
+
valid: false,
|
|
711
|
+
errors: ['typecheck must be a boolean'],
|
|
712
|
+
};
|
|
713
|
+
}
|
|
590
714
|
return { valid: true };
|
|
591
715
|
}
|
|
592
716
|
async execute(input, context) {
|
|
@@ -595,9 +719,11 @@ export class CodeExecTool extends BaseTool {
|
|
|
595
719
|
return this.error(validation.errors?.join('; ') ?? 'Invalid input');
|
|
596
720
|
const code = input.code;
|
|
597
721
|
const timeoutMs = normalizeTimeout(input.timeout_ms);
|
|
722
|
+
const typecheck = input.typecheck === true;
|
|
598
723
|
const injectedRuntime = runtimeFromContext(context);
|
|
599
|
-
const runtime = injectedRuntime
|
|
724
|
+
const runtime = injectedRuntime ? { ...injectedRuntime } : {
|
|
600
725
|
scopeId: legacyScopeId(),
|
|
726
|
+
resultFormat: 'legacy',
|
|
601
727
|
sessionId: legacySessionId,
|
|
602
728
|
cwd: context?.cwd ?? process.cwd(),
|
|
603
729
|
availableTools: legacyAvailableTools,
|
|
@@ -606,28 +732,46 @@ export class CodeExecTool extends BaseTool {
|
|
|
606
732
|
error: `No ToolHandler executor is attached for ${toolName}`,
|
|
607
733
|
})),
|
|
608
734
|
};
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
scopeId: runtime.scopeId,
|
|
616
|
-
timedOut: childResult.timedOut === true,
|
|
617
|
-
elapsed,
|
|
735
|
+
runtime.abortSignal ??= context?.abortSignal;
|
|
736
|
+
let codeToRun = code;
|
|
737
|
+
if (typecheck) {
|
|
738
|
+
const { runCodeExecPreflight } = await import('./code-exec-preflight.js');
|
|
739
|
+
const preflightResult = await runCodeExecPreflight(code, runtime, context, {
|
|
740
|
+
signal: runtime.abortSignal,
|
|
618
741
|
});
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
742
|
+
if (!preflightResult.success) {
|
|
743
|
+
return this.error(preflightResult.error || 'TypeScript preflight validation failed');
|
|
744
|
+
}
|
|
745
|
+
if (preflightResult.transpiledCode) {
|
|
746
|
+
codeToRun = preflightResult.transpiledCode;
|
|
747
|
+
}
|
|
625
748
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
749
|
+
return runScoped(runtime.scopeId, async () => {
|
|
750
|
+
if (runtime.abortSignal?.aborted)
|
|
751
|
+
return this.error('Script cancelled');
|
|
752
|
+
const state = getScopedState(runtime);
|
|
753
|
+
const startedAt = Date.now();
|
|
754
|
+
const childResult = await runInChild(codeToRun, timeoutMs, runtime, state);
|
|
755
|
+
const elapsed = Date.now() - startedAt;
|
|
756
|
+
if (!childResult.success) {
|
|
757
|
+
logger.debug('code_exec failed', {
|
|
758
|
+
scopeId: runtime.scopeId,
|
|
759
|
+
timedOut: childResult.timedOut === true,
|
|
760
|
+
elapsed,
|
|
761
|
+
});
|
|
762
|
+
return this.error(truncate(childResult.output, CODE_EXEC_LIMITS.maxErrorChars));
|
|
763
|
+
}
|
|
764
|
+
// Commit state transactionally only after a successful script.
|
|
765
|
+
if (childResult.storeEntries) {
|
|
766
|
+
state.values = new Map(childResult.storeEntries);
|
|
767
|
+
state.lastAccess = Date.now();
|
|
768
|
+
}
|
|
769
|
+
const status = childResult.yielded
|
|
770
|
+
? `Script yielded control after ${elapsed}ms`
|
|
771
|
+
: `Script completed in ${elapsed}ms`;
|
|
772
|
+
const output = childResult.output ? `${status}\n\n${childResult.output}` : status;
|
|
773
|
+
return this.success(truncate(output, CODE_EXEC_LIMITS.maxOutputChars));
|
|
774
|
+
}, runtime.abortSignal).catch(error => this.error(error instanceof Error ? error.message : String(error)));
|
|
631
775
|
}
|
|
632
776
|
}
|
|
633
777
|
/** Factory used by all FormalToolRegistry assembly paths. */
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { UnifiedVfsRouter } from '../services/vfs/unified-vfs-router.js';
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import {
|
|
4
|
-
import { promisify } from "util";
|
|
3
|
+
import { execFile } from "child_process";
|
|
5
4
|
import { EventEmitter } from "events";
|
|
6
|
-
const
|
|
5
|
+
const RIPGREP_TIMEOUT_MS = 30_000;
|
|
6
|
+
/**
|
|
7
|
+
* Run ripgrep without a shell. Resolves with stdout; exit status 1 means
|
|
8
|
+
* "no match" and resolves empty. Missing binary, timeout or real errors reject
|
|
9
|
+
* so the caller can fall back to the manual scan.
|
|
10
|
+
*/
|
|
11
|
+
function runRipgrep(args, cwd) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const child = execFile("rg", args, { cwd, maxBuffer: 10 * 1024 * 1024, timeout: RIPGREP_TIMEOUT_MS, windowsHide: true }, (error, stdout) => {
|
|
14
|
+
if (!error)
|
|
15
|
+
return resolve(stdout);
|
|
16
|
+
if (error.code === 1 && !error.killed)
|
|
17
|
+
return resolve("");
|
|
18
|
+
reject(error);
|
|
19
|
+
});
|
|
20
|
+
child.stdin?.end();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
7
23
|
const DEFAULT_TRIGGERS = [
|
|
8
24
|
{
|
|
9
25
|
pattern: "AI:",
|
|
@@ -110,15 +126,16 @@ export class CommentWatcher extends EventEmitter {
|
|
|
110
126
|
// Build pattern for ripgrep
|
|
111
127
|
const patterns = this.config.triggers.map((t) => t.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
112
128
|
const pattern = patterns.join("|");
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
const rgArgs = [
|
|
130
|
+
"-n", "--no-heading", "--color=never", "--ignore-case",
|
|
131
|
+
...this.config.ignoreDirs.flatMap((d) => ["--glob", `!${d}/**`]),
|
|
132
|
+
...this.config.fileExtensions.flatMap((e) => ["--glob", `*${e}`]),
|
|
133
|
+
// Explicit path: without it ripgrep reads the inherited stdin pipe, which
|
|
134
|
+
// never closes inside the CLI, and the scan hangs forever.
|
|
135
|
+
"--", pattern, ".",
|
|
136
|
+
];
|
|
117
137
|
try {
|
|
118
|
-
const
|
|
119
|
-
cwd: this.projectRoot,
|
|
120
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
121
|
-
});
|
|
138
|
+
const stdout = await runRipgrep(rgArgs, this.projectRoot);
|
|
122
139
|
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
123
140
|
for (const line of lines) {
|
|
124
141
|
// Format: file:line:content
|
|
@@ -133,7 +150,7 @@ export class CommentWatcher extends EventEmitter {
|
|
|
133
150
|
const triggerMatch = content.match(trigger.regex);
|
|
134
151
|
if (triggerMatch) {
|
|
135
152
|
this.detectedComments.push({
|
|
136
|
-
file,
|
|
153
|
+
file: path.resolve(this.projectRoot, file),
|
|
137
154
|
line: parseInt(lineNum),
|
|
138
155
|
column: content.indexOf(triggerMatch[0]),
|
|
139
156
|
trigger: trigger.pattern,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { CoreRootResolution } from '../identity/operational-self-model.js';
|
|
2
|
+
export interface CoreCodeRequest {
|
|
3
|
+
operation: 'list' | 'read' | 'search';
|
|
4
|
+
path?: string;
|
|
5
|
+
query?: string;
|
|
6
|
+
line?: number;
|
|
7
|
+
offset?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Read only implementation files within the already-attested core, never the user's project. */
|
|
10
|
+
export declare function inspectCoreCode(core: CoreRootResolution, request: CoreCodeRequest): {
|
|
11
|
+
operation: string;
|
|
12
|
+
path: string;
|
|
13
|
+
line: number;
|
|
14
|
+
totalLines: number;
|
|
15
|
+
content: string;
|
|
16
|
+
truncated: boolean;
|
|
17
|
+
entries?: undefined;
|
|
18
|
+
offset?: undefined;
|
|
19
|
+
nextOffset?: undefined;
|
|
20
|
+
totalEntries?: undefined;
|
|
21
|
+
query?: undefined;
|
|
22
|
+
matches?: undefined;
|
|
23
|
+
} | {
|
|
24
|
+
operation: string;
|
|
25
|
+
path: string;
|
|
26
|
+
entries: {
|
|
27
|
+
name: string;
|
|
28
|
+
directory: boolean;
|
|
29
|
+
}[];
|
|
30
|
+
offset: number;
|
|
31
|
+
nextOffset: number | null;
|
|
32
|
+
totalEntries: number;
|
|
33
|
+
truncated: boolean;
|
|
34
|
+
line?: undefined;
|
|
35
|
+
totalLines?: undefined;
|
|
36
|
+
content?: undefined;
|
|
37
|
+
query?: undefined;
|
|
38
|
+
matches?: undefined;
|
|
39
|
+
} | {
|
|
40
|
+
operation: string;
|
|
41
|
+
path: string;
|
|
42
|
+
query: string;
|
|
43
|
+
matches: {
|
|
44
|
+
path: string;
|
|
45
|
+
line: number;
|
|
46
|
+
text: string;
|
|
47
|
+
}[];
|
|
48
|
+
truncated: boolean;
|
|
49
|
+
line?: undefined;
|
|
50
|
+
totalLines?: undefined;
|
|
51
|
+
content?: undefined;
|
|
52
|
+
entries?: undefined;
|
|
53
|
+
offset?: undefined;
|
|
54
|
+
nextOffset?: undefined;
|
|
55
|
+
totalEntries?: undefined;
|
|
56
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const TEXT_FILE = /\.(?:[cm]?[jt]sx?|md)$/i;
|
|
4
|
+
const MAX_FILE_BYTES = 512 * 1024;
|
|
5
|
+
/** Read only implementation files within the already-attested core, never the user's project. */
|
|
6
|
+
export function inspectCoreCode(core, request) {
|
|
7
|
+
if (core.layout === 'unknown')
|
|
8
|
+
throw new Error('Code Buddy implementation root is not attested.');
|
|
9
|
+
const root = fs.realpathSync(core.root);
|
|
10
|
+
const relative = request.path || (core.layout === 'source' ? 'src' : 'dist');
|
|
11
|
+
if (path.isAbsolute(relative) || relative.includes('\\') || relative.split('/').some(p => p === '..' || p.startsWith('.')) ||
|
|
12
|
+
!/^(src|dist|packages)(\/|$)/.test(relative)) {
|
|
13
|
+
throw new Error('Use a relative implementation path under src/, dist/ or packages/.');
|
|
14
|
+
}
|
|
15
|
+
const target = path.resolve(root, relative);
|
|
16
|
+
function confined(file) {
|
|
17
|
+
const real = fs.realpathSync(file);
|
|
18
|
+
const rel = path.relative(root, real);
|
|
19
|
+
return !path.isAbsolute(rel) && /^(src|dist|packages)(\/|$)/.test(rel.split(path.sep).join('/')) &&
|
|
20
|
+
!rel.split(path.sep).some(part => part.startsWith('.'));
|
|
21
|
+
}
|
|
22
|
+
if (!confined(target))
|
|
23
|
+
throw new Error('Implementation path escapes the core root.');
|
|
24
|
+
const stat = fs.statSync(target);
|
|
25
|
+
if (request.operation === 'read') {
|
|
26
|
+
if (!stat.isFile() || !TEXT_FILE.test(target) || stat.size > MAX_FILE_BYTES)
|
|
27
|
+
throw new Error('Expected an implementation text file of at most 512 KiB.');
|
|
28
|
+
const lines = fs.readFileSync(target, 'utf8').split('\n');
|
|
29
|
+
const start = Math.max(1, request.line ?? 1);
|
|
30
|
+
return { operation: 'read', path: relative, line: start, totalLines: lines.length,
|
|
31
|
+
content: lines.slice(start - 1, start + 119).map((text, i) => `${start + i}: ${text.slice(0, 500)}`).join('\n'),
|
|
32
|
+
truncated: start + 119 < lines.length };
|
|
33
|
+
}
|
|
34
|
+
if (!stat.isDirectory())
|
|
35
|
+
throw new Error('Expected an implementation directory.');
|
|
36
|
+
if (request.operation === 'list') {
|
|
37
|
+
const entries = fs.readdirSync(target, { withFileTypes: true }).filter(entry => !entry.name.startsWith('.') && entry.name !== 'node_modules' && !entry.isSymbolicLink() &&
|
|
38
|
+
(entry.isDirectory() || TEXT_FILE.test(entry.name)));
|
|
39
|
+
const offset = request.offset ?? 0;
|
|
40
|
+
if (!Number.isSafeInteger(offset) || offset < 0)
|
|
41
|
+
throw new Error('List offset must be a non-negative integer.');
|
|
42
|
+
const nextOffset = offset + 100 < entries.length ? offset + 100 : null;
|
|
43
|
+
return { operation: 'list', path: relative, entries: entries.slice(offset, offset + 100).map(entry => ({
|
|
44
|
+
name: entry.name, directory: entry.isDirectory(),
|
|
45
|
+
})), offset, nextOffset, totalEntries: entries.length, truncated: nextOffset !== null };
|
|
46
|
+
}
|
|
47
|
+
if (!request.query?.trim() || request.query.length > 160)
|
|
48
|
+
throw new Error('Search requires a literal query of 1–160 characters.');
|
|
49
|
+
const query = request.query.toLowerCase();
|
|
50
|
+
const matches = [];
|
|
51
|
+
const pending = [target];
|
|
52
|
+
let visited = 0;
|
|
53
|
+
let bytes = 0;
|
|
54
|
+
while (pending.length && visited < 4000 && matches.length < 30 && bytes < 8 * 1024 * 1024) {
|
|
55
|
+
const dir = pending.shift();
|
|
56
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
57
|
+
if (++visited >= 4000 || matches.length >= 30 || bytes >= 8 * 1024 * 1024)
|
|
58
|
+
break;
|
|
59
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.isSymbolicLink())
|
|
60
|
+
continue;
|
|
61
|
+
const file = path.join(dir, entry.name);
|
|
62
|
+
if (!confined(file))
|
|
63
|
+
continue;
|
|
64
|
+
if (entry.isDirectory()) {
|
|
65
|
+
pending.push(file);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!entry.isFile() || !TEXT_FILE.test(entry.name))
|
|
69
|
+
continue;
|
|
70
|
+
const size = fs.statSync(file).size;
|
|
71
|
+
if (size > MAX_FILE_BYTES)
|
|
72
|
+
continue;
|
|
73
|
+
bytes += size;
|
|
74
|
+
const lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
75
|
+
for (let i = 0; i < lines.length && matches.length < 30; i++) {
|
|
76
|
+
if (lines[i].toLowerCase().includes(query))
|
|
77
|
+
matches.push({
|
|
78
|
+
path: path.relative(root, file).split(path.sep).join('/'), line: i + 1, text: lines[i].slice(0, 500),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { operation: 'search', path: relative, query: request.query, matches,
|
|
84
|
+
truncated: pending.length > 0 || visited >= 4000 || matches.length >= 30 || bytes >= 8 * 1024 * 1024 };
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=core-code-inspection.js.map
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* commands it uses repeatedly and codify them as reusable skills.
|
|
11
11
|
*
|
|
12
12
|
* Skill file location:
|
|
13
|
-
* .codebuddy/skills
|
|
13
|
+
* .codebuddy/skills/<authored-slug>/SKILL.md
|
|
14
14
|
*
|
|
15
15
|
* The tool generates valid SKILL.md frontmatter and validates that
|
|
16
16
|
* the slug does not already exist before writing.
|
|
@@ -36,8 +36,7 @@ export interface CreateSkillInput {
|
|
|
36
36
|
overwrite?: boolean;
|
|
37
37
|
}
|
|
38
38
|
export declare class CreateSkillTool {
|
|
39
|
-
|
|
40
|
-
execute(input: CreateSkillInput): Promise<ToolResult>;
|
|
39
|
+
execute(input: CreateSkillInput, cwd?: string): Promise<ToolResult>;
|
|
41
40
|
getSchema(): {
|
|
42
41
|
name: string;
|
|
43
42
|
description: string;
|