@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
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-job bounded continuity store (Hermes-inspired cron notepad).
|
|
3
|
+
*
|
|
4
|
+
* Isolated JSON files under `<cron-root>/notepads/<jobId>.json`, written
|
|
5
|
+
* atomically (0600): 16 KiB per value, 128-char keys, 64 KiB per serialized
|
|
6
|
+
* record. Mutations use an exclusive per-job lock across local processes.
|
|
7
|
+
* Contention requires retry; abandoned locks require operator inspection and
|
|
8
|
+
* are never stolen. Invalid/corrupt records are preserved for inspection.
|
|
9
|
+
*/
|
|
10
|
+
import { promises as fs } from 'node:fs';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import { writeJsonAtomic } from '../utils/atomic-write.js';
|
|
14
|
+
import { logger } from '../utils/logger.js';
|
|
15
|
+
export const MAX_VALUE_BYTES = 16 * 1024;
|
|
16
|
+
export const MAX_KEY_CHARS = 128;
|
|
17
|
+
export const MAX_JOB_TOTAL_BYTES = 64 * 1024;
|
|
18
|
+
export const MAX_LAST_OUTPUT_BYTES = 16 * 1024;
|
|
19
|
+
export class JobNotepadBusyError extends Error {
|
|
20
|
+
constructor() {
|
|
21
|
+
super('Job notepad mutation locked; retry. An abandoned lock requires operator inspection.');
|
|
22
|
+
this.name = 'JobNotepadBusyError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function isRecord(value) {
|
|
26
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
27
|
+
&& (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
28
|
+
}
|
|
29
|
+
/** Validate operator/tool input before changing jobs or writing any notes. */
|
|
30
|
+
export function validateContinuity(value) {
|
|
31
|
+
if (typeof value === 'boolean')
|
|
32
|
+
return value;
|
|
33
|
+
if (!isRecord(value) || Object.keys(value).some(key => key !== 'enabled' && key !== 'notes')
|
|
34
|
+
|| (value.enabled !== undefined && typeof value.enabled !== 'boolean')) {
|
|
35
|
+
throw new Error('Invalid continuity: expected a boolean or an object with enabled and notes');
|
|
36
|
+
}
|
|
37
|
+
if (value.notes !== undefined)
|
|
38
|
+
assertNotesWithinCaps(value.notes);
|
|
39
|
+
return {
|
|
40
|
+
...(value.enabled !== undefined ? { enabled: value.enabled } : {}),
|
|
41
|
+
...(value.notes !== undefined ? { notes: { ...value.notes } } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function isContinuityEnabled(continuity) {
|
|
45
|
+
if (!isRecord(continuity)) {
|
|
46
|
+
return continuity === true;
|
|
47
|
+
}
|
|
48
|
+
return continuity.enabled !== false;
|
|
49
|
+
}
|
|
50
|
+
/** Persist the opt-in flag only — notes live in the notepad file, not jobs.json. */
|
|
51
|
+
export function persistableContinuity(continuity) {
|
|
52
|
+
if (continuity === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
continuity = validateContinuity(continuity);
|
|
55
|
+
if (typeof continuity === 'boolean')
|
|
56
|
+
return continuity;
|
|
57
|
+
return { enabled: continuity.enabled !== false };
|
|
58
|
+
}
|
|
59
|
+
export function defaultNotepadDir() {
|
|
60
|
+
const root = process.env.CODEBUDDY_CRON_HOME
|
|
61
|
+
? path.resolve(process.env.CODEBUDDY_CRON_HOME)
|
|
62
|
+
: path.join(homedir(), '.codebuddy', 'cron');
|
|
63
|
+
return path.join(root, 'notepads');
|
|
64
|
+
}
|
|
65
|
+
export function utf8ByteLength(value) {
|
|
66
|
+
return Buffer.byteLength(value, 'utf8');
|
|
67
|
+
}
|
|
68
|
+
export function sanitizeJobId(jobId) {
|
|
69
|
+
if (typeof jobId !== 'string' || !jobId || jobId.length > 128 || !/^[A-Za-z0-9._-]+$/.test(jobId)) {
|
|
70
|
+
throw new Error('invalid job id');
|
|
71
|
+
}
|
|
72
|
+
return jobId;
|
|
73
|
+
}
|
|
74
|
+
export function truncateUtf8(value, maxBytes) {
|
|
75
|
+
if (maxBytes <= 0)
|
|
76
|
+
return '';
|
|
77
|
+
const buf = Buffer.from(value, 'utf8');
|
|
78
|
+
if (buf.length <= maxBytes)
|
|
79
|
+
return value;
|
|
80
|
+
let end = maxBytes;
|
|
81
|
+
while (end > 0 && ((buf[end] ?? 0) & 0xc0) === 0x80)
|
|
82
|
+
end -= 1;
|
|
83
|
+
return buf.subarray(0, end).toString('utf8');
|
|
84
|
+
}
|
|
85
|
+
export function notesByteTotal(notes) {
|
|
86
|
+
let total = 0;
|
|
87
|
+
for (const [key, value] of Object.entries(notes)) {
|
|
88
|
+
total += utf8ByteLength(key) + utf8ByteLength(value);
|
|
89
|
+
}
|
|
90
|
+
return total;
|
|
91
|
+
}
|
|
92
|
+
export function assertNotesWithinCaps(notes) {
|
|
93
|
+
if (!isRecord(notes))
|
|
94
|
+
throw new Error('notes must be an object of strings');
|
|
95
|
+
let total = 0;
|
|
96
|
+
for (const [key, value] of Object.entries(notes)) {
|
|
97
|
+
if (!key)
|
|
98
|
+
throw new Error('key must be non-empty');
|
|
99
|
+
if (key.length > MAX_KEY_CHARS) {
|
|
100
|
+
throw new Error(`key too long (max ${MAX_KEY_CHARS} characters)`);
|
|
101
|
+
}
|
|
102
|
+
if (typeof value !== 'string') {
|
|
103
|
+
throw new Error(`note '${key}' must be a string`);
|
|
104
|
+
}
|
|
105
|
+
const valueBytes = utf8ByteLength(value);
|
|
106
|
+
if (valueBytes > MAX_VALUE_BYTES) {
|
|
107
|
+
throw new Error(`value too large (max ${MAX_VALUE_BYTES} bytes per key)`);
|
|
108
|
+
}
|
|
109
|
+
total += utf8ByteLength(key) + valueBytes;
|
|
110
|
+
if (total > MAX_JOB_TOTAL_BYTES) {
|
|
111
|
+
throw new Error(`notepad full: would exceed ${MAX_JOB_TOTAL_BYTES} bytes total`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
assertRecordSize({ ...emptyRecord(), notes: notes });
|
|
115
|
+
}
|
|
116
|
+
function assertNotepadRecord(value) {
|
|
117
|
+
if (!isRecord(value) || Object.keys(value).some(key => !['notes', 'updatedAt', 'lastSuccessfulOutput'].includes(key))
|
|
118
|
+
|| typeof value.updatedAt !== 'string' || !Number.isFinite(Date.parse(value.updatedAt))) {
|
|
119
|
+
throw new Error('Invalid notepad record');
|
|
120
|
+
}
|
|
121
|
+
assertNotesWithinCaps(value.notes);
|
|
122
|
+
if (value.lastSuccessfulOutput !== undefined && (typeof value.lastSuccessfulOutput !== 'string'
|
|
123
|
+
|| utf8ByteLength(value.lastSuccessfulOutput) > MAX_LAST_OUTPUT_BYTES))
|
|
124
|
+
throw new Error('Invalid last successful output');
|
|
125
|
+
assertRecordSize(value);
|
|
126
|
+
}
|
|
127
|
+
function emptyRecord() {
|
|
128
|
+
return { notes: {}, updatedAt: new Date().toISOString() };
|
|
129
|
+
}
|
|
130
|
+
function serializedBytes(record) {
|
|
131
|
+
// Match writeJsonAtomic's indentation and final newline, including escaping.
|
|
132
|
+
return utf8ByteLength(`${JSON.stringify(record, null, 2)}\n`);
|
|
133
|
+
}
|
|
134
|
+
function assertRecordSize(record) {
|
|
135
|
+
if (serializedBytes(record) > MAX_JOB_TOTAL_BYTES) {
|
|
136
|
+
throw new Error(`notepad full: would exceed ${MAX_JOB_TOTAL_BYTES} serialized bytes total`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export class JobNotepadStore {
|
|
140
|
+
dir;
|
|
141
|
+
constructor(dir = defaultNotepadDir()) {
|
|
142
|
+
this.dir = dir;
|
|
143
|
+
}
|
|
144
|
+
fileFor(jobId) {
|
|
145
|
+
const safe = sanitizeJobId(jobId);
|
|
146
|
+
const root = path.resolve(this.dir);
|
|
147
|
+
const file = path.resolve(root, `${safe}.json`);
|
|
148
|
+
const prefix = root.endsWith(path.sep) ? root : root + path.sep;
|
|
149
|
+
if (file !== path.join(root, `${safe}.json`) && !file.startsWith(prefix)) {
|
|
150
|
+
throw new Error('invalid job id');
|
|
151
|
+
}
|
|
152
|
+
return file;
|
|
153
|
+
}
|
|
154
|
+
async load(jobId) {
|
|
155
|
+
let handle;
|
|
156
|
+
try {
|
|
157
|
+
handle = await fs.open(this.fileFor(jobId), 'r');
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (error.code === 'ENOENT')
|
|
161
|
+
return emptyRecord();
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const stat = await handle.stat();
|
|
166
|
+
if (!stat.isFile())
|
|
167
|
+
throw new Error('Notepad must be a regular file');
|
|
168
|
+
if (stat.size > MAX_JOB_TOTAL_BYTES)
|
|
169
|
+
throw new Error('Notepad record exceeds size limit');
|
|
170
|
+
const buffer = Buffer.alloc(MAX_JOB_TOTAL_BYTES + 1);
|
|
171
|
+
let length = 0;
|
|
172
|
+
while (length < buffer.length) {
|
|
173
|
+
const { bytesRead } = await handle.read(buffer, length, buffer.length - length, length);
|
|
174
|
+
if (bytesRead === 0)
|
|
175
|
+
break;
|
|
176
|
+
length += bytesRead;
|
|
177
|
+
}
|
|
178
|
+
if (length > MAX_JOB_TOTAL_BYTES)
|
|
179
|
+
throw new Error('Notepad record exceeds size limit');
|
|
180
|
+
const loaded = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(0, length)));
|
|
181
|
+
assertNotepadRecord(loaded);
|
|
182
|
+
return loaded;
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
await handle.close();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async setNote(jobId, key, value) {
|
|
189
|
+
assertNotesWithinCaps({ [key]: value });
|
|
190
|
+
return this.withMutationLock(jobId, async () => {
|
|
191
|
+
const current = await this.load(jobId);
|
|
192
|
+
return this.commitNotes(jobId, current, { ...current.notes, [key]: value });
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
async replaceNotes(jobId, notes) {
|
|
196
|
+
assertNotesWithinCaps(notes);
|
|
197
|
+
const snapshot = { ...notes };
|
|
198
|
+
return this.withMutationLock(jobId, async () => this.commitNotes(jobId, await this.load(jobId), snapshot));
|
|
199
|
+
}
|
|
200
|
+
async getNote(jobId, key) {
|
|
201
|
+
const current = await this.load(jobId);
|
|
202
|
+
return Object.hasOwn(current.notes, key) ? current.notes[key] : undefined;
|
|
203
|
+
}
|
|
204
|
+
async deleteNote(jobId, key) {
|
|
205
|
+
return this.withMutationLock(jobId, async () => {
|
|
206
|
+
const current = await this.load(jobId);
|
|
207
|
+
if (!Object.hasOwn(current.notes, key))
|
|
208
|
+
return false;
|
|
209
|
+
const nextNotes = { ...current.notes };
|
|
210
|
+
delete nextNotes[key];
|
|
211
|
+
await this.commitNotes(jobId, current, nextNotes);
|
|
212
|
+
return true;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
async listNotes(jobId) {
|
|
216
|
+
const current = await this.load(jobId);
|
|
217
|
+
return Object.keys(current.notes)
|
|
218
|
+
.sort()
|
|
219
|
+
.map((key) => ({ key, value: current.notes[key] ?? '' }));
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Record the last *successful* job output. Truncates to the per-value and
|
|
223
|
+
* serialized per-job caps. Storage errors are reported to the caller.
|
|
224
|
+
* Empty output leaves the previous non-empty successful output in place.
|
|
225
|
+
*/
|
|
226
|
+
async saveLastSuccessfulOutput(jobId, output) {
|
|
227
|
+
if (typeof output !== 'string' || output.length === 0)
|
|
228
|
+
return;
|
|
229
|
+
await this.withMutationLock(jobId, async () => {
|
|
230
|
+
const current = await this.load(jobId);
|
|
231
|
+
const capped = truncateUtf8(output, MAX_LAST_OUTPUT_BYTES);
|
|
232
|
+
const next = { ...current, lastSuccessfulOutput: capped, updatedAt: new Date().toISOString() };
|
|
233
|
+
let low = 0;
|
|
234
|
+
let high = utf8ByteLength(capped);
|
|
235
|
+
let best = '';
|
|
236
|
+
while (low <= high) {
|
|
237
|
+
const middle = Math.floor((low + high) / 2);
|
|
238
|
+
const candidate = truncateUtf8(capped, middle);
|
|
239
|
+
if (serializedBytes({ ...next, lastSuccessfulOutput: candidate }) <= MAX_JOB_TOTAL_BYTES) {
|
|
240
|
+
best = candidate;
|
|
241
|
+
low = middle + 1;
|
|
242
|
+
}
|
|
243
|
+
else
|
|
244
|
+
high = middle - 1;
|
|
245
|
+
}
|
|
246
|
+
if (!best)
|
|
247
|
+
throw new Error('notepad full: no room for the latest successful output');
|
|
248
|
+
await this.write(jobId, { ...next, lastSuccessfulOutput: best });
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/** Missing files are a no-op; existing files share writer exclusion. */
|
|
252
|
+
async clear(jobId) {
|
|
253
|
+
try {
|
|
254
|
+
await fs.lstat(this.fileFor(jobId));
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
if (error.code === 'ENOENT')
|
|
258
|
+
return;
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
await this.withMutationLock(jobId, async () => {
|
|
262
|
+
try {
|
|
263
|
+
await fs.unlink(this.fileFor(jobId));
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
if (error.code !== 'ENOENT')
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Prompt section for this job. Empty notepad MUST return '' so jobs that
|
|
273
|
+
* never use continuity keep a byte-identical agent prompt.
|
|
274
|
+
*/
|
|
275
|
+
async renderSection(jobId) {
|
|
276
|
+
try {
|
|
277
|
+
const rec = await this.load(jobId);
|
|
278
|
+
const keys = Object.keys(rec.notes).sort();
|
|
279
|
+
const hasNotes = keys.length > 0;
|
|
280
|
+
const last = rec.lastSuccessfulOutput;
|
|
281
|
+
const hasOutput = typeof last === 'string' && last.length > 0;
|
|
282
|
+
if (!hasNotes && !hasOutput)
|
|
283
|
+
return '';
|
|
284
|
+
const parts = [];
|
|
285
|
+
if (hasNotes) {
|
|
286
|
+
const lines = keys.map((key) => `- ${key}: ${rec.notes[key] ?? ''}`);
|
|
287
|
+
parts.push('## Job notepad (persistent across runs)\n'
|
|
288
|
+
+ 'This durable scratchpad survives between scheduled runs of this job.\n\n'
|
|
289
|
+
+ lines.join('\n'));
|
|
290
|
+
}
|
|
291
|
+
if (hasOutput) {
|
|
292
|
+
parts.push(`## Last successful output (last non-empty result)\n${last}`);
|
|
293
|
+
}
|
|
294
|
+
return `${parts.join('\n\n')}\n`;
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
logger.warn('Cron continuity could not be loaded; proceeding without stored context', { jobId, error: String(error) });
|
|
298
|
+
return '';
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async commitNotes(jobId, current, nextNotes) {
|
|
302
|
+
const next = {
|
|
303
|
+
notes: nextNotes,
|
|
304
|
+
updatedAt: new Date().toISOString(),
|
|
305
|
+
...(current.lastSuccessfulOutput !== undefined
|
|
306
|
+
? { lastSuccessfulOutput: current.lastSuccessfulOutput }
|
|
307
|
+
: {}),
|
|
308
|
+
};
|
|
309
|
+
await this.write(jobId, next);
|
|
310
|
+
return next;
|
|
311
|
+
}
|
|
312
|
+
async write(jobId, record) {
|
|
313
|
+
assertNotepadRecord(record);
|
|
314
|
+
await writeJsonAtomic(this.fileFor(jobId), record, { mode: 0o600 });
|
|
315
|
+
}
|
|
316
|
+
async withMutationLock(jobId, action) {
|
|
317
|
+
const file = this.fileFor(jobId);
|
|
318
|
+
await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
319
|
+
const lock = `${file}.lock`;
|
|
320
|
+
let handle;
|
|
321
|
+
try {
|
|
322
|
+
handle = await fs.open(lock, 'wx', 0o600);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
if (error.code === 'EEXIST')
|
|
326
|
+
throw new JobNotepadBusyError();
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }));
|
|
331
|
+
return await action();
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
await handle.close();
|
|
335
|
+
await fs.unlink(lock);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=job-notepad.js.map
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* for code generation operations. Provides a persistent audit trail
|
|
6
6
|
* that can be reviewed for compliance and debugging.
|
|
7
7
|
*/
|
|
8
|
-
export type AuditAction = 'code_validation' | 'command_validation' | 'file_write' | 'file_edit' | 'file_delete' | 'patch_apply' | 'bash_execute' | 'confirmation_requested' | 'confirmation_granted' | 'confirmation_denied' | 'checkpoint_created' | 'checkpoint_restored' | 'sandbox_execute' | 'pattern_matched' | 'tool_execution' | 'self_improvement';
|
|
8
|
+
export type AuditAction = 'code_validation' | 'command_validation' | 'file_write' | 'file_edit' | 'file_delete' | 'patch_apply' | 'bash_execute' | 'confirmation_requested' | 'confirmation_granted' | 'confirmation_denied' | 'checkpoint_created' | 'checkpoint_restored' | 'sandbox_execute' | 'pattern_matched' | 'tool_execution' | 'self_improvement' | 'device_register' | 'device_verify' | 'device_revoke';
|
|
9
9
|
export type AuditDecision = 'allow' | 'block' | 'warn' | 'confirm';
|
|
10
10
|
export interface AuditEntry {
|
|
11
11
|
/** Unique entry ID */
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** Kernel-enforced compute sandbox. Fail closed if the required Linux facilities are absent. */
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
// Bootstrap is trusted, runs before user code, and applies restrictions inherited across exec.
|
|
4
|
+
// Landlock restricts file content access; seccomp denies networking and cross-process access.
|
|
5
|
+
const BOOTSTRAP = String.raw `
|
|
6
|
+
import ctypes, errno, os, platform, resource, sys
|
|
7
|
+
libc = ctypes.CDLL(None, use_errno=True)
|
|
8
|
+
def check(value):
|
|
9
|
+
if value < 0: raise OSError(ctypes.get_errno(), 'compute confinement unavailable')
|
|
10
|
+
return value
|
|
11
|
+
arch = platform.machine()
|
|
12
|
+
if arch not in ('x86_64', 'aarch64'): raise RuntimeError('unsupported compute sandbox architecture')
|
|
13
|
+
abi = check(libc.syscall(444, 0, 0, 1))
|
|
14
|
+
if abi < 3: raise RuntimeError('compute sandbox requires Landlock ABI 3 or newer')
|
|
15
|
+
root, executable = sys.argv[1:3]
|
|
16
|
+
class Rules(ctypes.Structure):
|
|
17
|
+
_fields_ = [('access', ctypes.c_uint64)]
|
|
18
|
+
class PathRule(ctypes.Structure):
|
|
19
|
+
_pack_ = 1
|
|
20
|
+
_fields_ = [('access', ctypes.c_uint64), ('fd', ctypes.c_int32)]
|
|
21
|
+
rights = (1 << 15) - 1
|
|
22
|
+
rules = Rules(rights)
|
|
23
|
+
fd = check(libc.syscall(444, ctypes.byref(rules), ctypes.sizeof(rules), 0))
|
|
24
|
+
def allow(p, access):
|
|
25
|
+
if not os.path.exists(p): return
|
|
26
|
+
p = os.path.realpath(p)
|
|
27
|
+
if not os.path.isdir(p): access &= (1 | 2 | 4 | (1 << 14))
|
|
28
|
+
handle = os.open(p, os.O_PATH | os.O_CLOEXEC)
|
|
29
|
+
try:
|
|
30
|
+
rule = PathRule(access, handle)
|
|
31
|
+
check(libc.syscall(445, fd, 1, ctypes.byref(rule), 0))
|
|
32
|
+
finally: os.close(handle)
|
|
33
|
+
# Runtime libraries are readable, never writable or executable as programs.
|
|
34
|
+
for p in ('/usr/lib', '/usr/lib64', '/usr/local/lib', '/usr/share/nodejs', '/usr/share/zoneinfo', '/lib', '/lib64', '/etc/ld.so.cache', '/dev/null', '/dev/urandom'):
|
|
35
|
+
allow(p, 4 | 8)
|
|
36
|
+
allow(root, rights & ~(1 | (1 << 6) | (1 << 9) | (1 << 10) | (1 << 11) | (1 << 12)))
|
|
37
|
+
allow(executable, 1 | 4)
|
|
38
|
+
allow('/lib64/ld-linux-x86-64.so.2' if arch == 'x86_64' else '/lib/ld-linux-aarch64.so.1', 1 | 4)
|
|
39
|
+
# Drop inherited capabilities as well (the host may itself run as root).
|
|
40
|
+
class CapHeader(ctypes.Structure):
|
|
41
|
+
_fields_ = [('version',ctypes.c_uint32),('pid',ctypes.c_int)]
|
|
42
|
+
class CapData(ctypes.Structure):
|
|
43
|
+
_fields_ = [('effective',ctypes.c_uint32),('permitted',ctypes.c_uint32),('inheritable',ctypes.c_uint32)]
|
|
44
|
+
cap_header, cap_data = CapHeader(0x20080522, 0), (CapData * 2)()
|
|
45
|
+
check(libc.capset(ctypes.byref(cap_header), ctypes.byref(cap_data)))
|
|
46
|
+
check(libc.prctl(38, 1, 0, 0, 0)) # PR_SET_NO_NEW_PRIVS
|
|
47
|
+
check(libc.syscall(446, fd, 0))
|
|
48
|
+
os.close(fd)
|
|
49
|
+
class Filter(ctypes.Structure):
|
|
50
|
+
_fields_ = [('code',ctypes.c_ushort),('jt',ctypes.c_ubyte),('jf',ctypes.c_ubyte),('k',ctypes.c_uint32)]
|
|
51
|
+
class Program(ctypes.Structure):
|
|
52
|
+
_fields_ = [('len',ctypes.c_ushort),('filter',ctypes.POINTER(Filter))]
|
|
53
|
+
# Check audit architecture, deny x32 ABI, then block sockets, ptrace, process_vm,
|
|
54
|
+
# pidfd, io_uring, signals to other processes and namespace/mount manipulation.
|
|
55
|
+
expected = 0xc000003e if arch == 'x86_64' else 0xc00000b7
|
|
56
|
+
denied = ([41,42,43,44,45,46,47,49,50,53,288,101,310,311,424,425,426,434,438,62,200,234,57,58,165,166,272,308]
|
|
57
|
+
if arch == 'x86_64' else [198,203,202,206,207,211,212,200,201,199,242,117,270,271,424,425,426,434,438,129,130,131,40,39,97,268])
|
|
58
|
+
ops = [Filter(0x20,0,0,4), Filter(0x15,1,0,expected), Filter(0x06,0,0,0x80000000), Filter(0x20,0,0,0)]
|
|
59
|
+
if arch == 'x86_64': ops += [Filter(0x45,0,1,0x40000000), Filter(0x06,0,0,0x80000000)]
|
|
60
|
+
# Deny process creation, while allowing CLONE_THREAD for interpreter worker threads.
|
|
61
|
+
clone = 56 if arch == 'x86_64' else 220
|
|
62
|
+
ops += [Filter(0x15,0,4,clone), Filter(0x20,0,0,16), Filter(0x45,1,0,0x10000), Filter(0x06,0,0,0x00050000 | errno.EPERM), Filter(0x06,0,0,0x7fff0000)]
|
|
63
|
+
# libc retries clone() when clone3 reports ENOSYS.
|
|
64
|
+
ops += [Filter(0x15,0,1,435), Filter(0x06,0,0,0x00050000 | errno.ENOSYS)]
|
|
65
|
+
for nr in denied: ops += [Filter(0x15,0,1,nr),Filter(0x06,0,0,0x00050000 | errno.EPERM)]
|
|
66
|
+
ops += [Filter(0x06,0,0,0x7fff0000)]
|
|
67
|
+
filters = (Filter * len(ops))(*ops)
|
|
68
|
+
program = Program(len(ops), filters)
|
|
69
|
+
check(libc.prctl(22, 2, ctypes.byref(program), 0, 0)) # SECCOMP_MODE_FILTER
|
|
70
|
+
resource.setrlimit(resource.RLIMIT_CORE, (0,0))
|
|
71
|
+
resource.setrlimit(resource.RLIMIT_DATA, (536870912,536870912))
|
|
72
|
+
resource.setrlimit(resource.RLIMIT_FSIZE, (2097152,2097152))
|
|
73
|
+
resource.setrlimit(resource.RLIMIT_NOFILE, (128,128))
|
|
74
|
+
resource.setrlimit(resource.RLIMIT_CPU, (30,30))
|
|
75
|
+
os.environ['OPENSSL_CONF'] = '/dev/null'
|
|
76
|
+
for key in ('TMPDIR','TMP','TEMP'): os.environ[key] = root
|
|
77
|
+
os.environ.pop('NODE_PATH', None)
|
|
78
|
+
os.environ.pop('NODE_OPTIONS', None)
|
|
79
|
+
args = sys.argv[3:]
|
|
80
|
+
if os.path.basename(executable) == 'node': args = ['--jitless', '--max-old-space-size=96'] + args
|
|
81
|
+
os.execv(executable, [executable] + args)
|
|
82
|
+
`;
|
|
83
|
+
export function confineComputeInvocation(command, args, root) {
|
|
84
|
+
if (process.platform !== 'linux')
|
|
85
|
+
throw new Error('Compute confinement requires Linux Landlock/seccomp; refusing unrestricted execution');
|
|
86
|
+
const executable = realpathSync(command === 'python3' ? '/usr/bin/python3' : command);
|
|
87
|
+
return { command: '/usr/bin/python3', args: ['-I', '-c', BOOTSTRAP, realpathSync(root), executable, ...args] };
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=compute-confinement.js.map
|
|
@@ -25,6 +25,8 @@ export interface EncryptionConfig {
|
|
|
25
25
|
usePassword?: boolean;
|
|
26
26
|
/** Enable encryption (can be disabled for performance) */
|
|
27
27
|
enabled?: boolean;
|
|
28
|
+
/** Refuse volatile/machine-derived fallback for durable session storage. */
|
|
29
|
+
requirePersistentKey?: boolean;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Session encryption manager
|
|
@@ -38,6 +40,8 @@ export declare class SessionEncryption {
|
|
|
38
40
|
* Initialize encryption with machine-generated key
|
|
39
41
|
*/
|
|
40
42
|
initialize(): Promise<void>;
|
|
43
|
+
/** Read existing key only: decrypting must never generate a replacement key. */
|
|
44
|
+
initializeForRead(): void;
|
|
41
45
|
/**
|
|
42
46
|
* Initialize with password-based key
|
|
43
47
|
*/
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as crypto from 'crypto';
|
|
10
10
|
import fs from 'fs-extra';
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { withSessionLock } from '../persistence/session-lock.js';
|
|
11
13
|
import * as path from 'path';
|
|
12
14
|
import * as os from 'os';
|
|
13
15
|
import { logger } from '../utils/logger.js';
|
|
@@ -39,6 +41,7 @@ export class SessionEncryption {
|
|
|
39
41
|
keyPath: config.keyPath ?? DEFAULT_CONFIG.keyPath,
|
|
40
42
|
usePassword: config.usePassword ?? DEFAULT_CONFIG.usePassword,
|
|
41
43
|
enabled: config.enabled ?? DEFAULT_CONFIG.enabled,
|
|
44
|
+
requirePersistentKey: config.requirePersistentKey ?? false,
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
47
|
/**
|
|
@@ -48,6 +51,26 @@ export class SessionEncryption {
|
|
|
48
51
|
if (this.initialized || !this.config.enabled) {
|
|
49
52
|
return;
|
|
50
53
|
}
|
|
54
|
+
if (this.config.requirePersistentKey) {
|
|
55
|
+
await fs.ensureDir(path.dirname(this.config.keyPath));
|
|
56
|
+
await withSessionLock(this.config.keyPath, async () => {
|
|
57
|
+
let key;
|
|
58
|
+
try {
|
|
59
|
+
key = await fs.readFile(this.config.keyPath);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error.code !== 'ENOENT')
|
|
63
|
+
throw error;
|
|
64
|
+
key = crypto.randomBytes(KEY_LENGTH);
|
|
65
|
+
await writeFileAtomic(this.config.keyPath, key, { mode: 0o600 });
|
|
66
|
+
}
|
|
67
|
+
if (key.length !== KEY_LENGTH)
|
|
68
|
+
throw new Error('Invalid session encryption key');
|
|
69
|
+
this.key = key;
|
|
70
|
+
this.initialized = true;
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
51
74
|
try {
|
|
52
75
|
// Try to load existing key
|
|
53
76
|
if (await fs.pathExists(this.config.keyPath)) {
|
|
@@ -69,6 +92,14 @@ export class SessionEncryption {
|
|
|
69
92
|
this.initialized = true;
|
|
70
93
|
}
|
|
71
94
|
}
|
|
95
|
+
/** Read existing key only: decrypting must never generate a replacement key. */
|
|
96
|
+
initializeForRead() {
|
|
97
|
+
const key = readFileSync(this.config.keyPath);
|
|
98
|
+
if (key.length !== KEY_LENGTH)
|
|
99
|
+
throw new Error('Invalid session encryption key');
|
|
100
|
+
this.key = key;
|
|
101
|
+
this.initialized = true;
|
|
102
|
+
}
|
|
72
103
|
/**
|
|
73
104
|
* Initialize with password-based key
|
|
74
105
|
*/
|
|
@@ -22,6 +22,7 @@ import { type LocalTtsEngine } from '../voice/local-tts.js';
|
|
|
22
22
|
import { type TwoSpeedTtsRouteHint } from '../voice/two-speed-voice.js';
|
|
23
23
|
export type { TwoSpeedTtsRouteHint } from '../voice/two-speed-voice.js';
|
|
24
24
|
import type { PermissionMode } from '../security/permission-modes.js';
|
|
25
|
+
import { type ResponseDecider } from './respond-decider.js';
|
|
25
26
|
import { type VoiceDeliveryProfile, type VoiceTurnContext } from './voice-entrainment.js';
|
|
26
27
|
import { type VisualGroundingFn } from '../companion/visual-grounding.js';
|
|
27
28
|
import type { CameraShareOptions, CameraShareResult } from '../companion/camera-share.js';
|
|
@@ -89,6 +90,15 @@ export interface VoiceStepOptions {
|
|
|
89
90
|
shortFirst?: VoiceShortFirstConfig;
|
|
90
91
|
/** Tell the outer audio pipeline that the fast route accepted the short-first contract. */
|
|
91
92
|
onShortFirstReady?: (config: VoiceShortFirstConfig) => void;
|
|
93
|
+
/** Decision from respond-decider if already evaluated */
|
|
94
|
+
respondDecision?: {
|
|
95
|
+
respond: boolean;
|
|
96
|
+
reason: string;
|
|
97
|
+
};
|
|
98
|
+
/** Injected response decider for conversational continuity */
|
|
99
|
+
responseDecider?: ResponseDecider;
|
|
100
|
+
/** Explicit override for whether the robot was named or in engagement window */
|
|
101
|
+
robotNamed?: boolean;
|
|
92
102
|
}
|
|
93
103
|
export interface VoiceShortFirstConfig {
|
|
94
104
|
/** Total number of spoken answer sentences, including the first useful sentence. */
|
|
@@ -500,6 +510,13 @@ export declare function rewriteRepeatedVoiceOpener(text: string, recentOpeners?:
|
|
|
500
510
|
* can be proven without starting a model.
|
|
501
511
|
*/
|
|
502
512
|
export declare function buildSpokenPromptAugmentation(heard: string, history?: VoiceHistoryTurn[], spokenPrefix?: string, delivery?: VoiceDeliveryProfile, options?: SpokenPromptAugmentationOptions): Promise<string>;
|
|
513
|
+
export declare function getVoiceResponseDecider(): ResponseDecider;
|
|
514
|
+
export declare function setVoiceResponseDecider(decider: ResponseDecider | undefined): void;
|
|
515
|
+
/**
|
|
516
|
+
* Determine if the utterance addresses the robot by name or takes place inside
|
|
517
|
+
* an active engagement window, consulting the respond-decider.
|
|
518
|
+
*/
|
|
519
|
+
export declare function resolveVoiceRobotNamed(heard: string, replyOpts?: VoiceStepOptions): Promise<boolean>;
|
|
503
520
|
export declare function defaultReply(heard: string, history?: VoiceHistoryTurn[], replyOpts?: VoiceStepOptions): Promise<string>;
|
|
504
521
|
/**
|
|
505
522
|
* Fast first proposition for an eligible developed/deliberative turn. Eligibility and
|
|
@@ -45,6 +45,7 @@ import { resolveElevenLabsCacheVoice, resolveElevenLabsFallbackEngine, resolveTt
|
|
|
45
45
|
import { resolveKyutaiCacheVoice } from '../voice/kyutai-local-voice.js';
|
|
46
46
|
import { selectTwoSpeedTtsRoute, twoSpeedTtsEnabled, } from '../voice/two-speed-voice.js';
|
|
47
47
|
import { resolveVoiceboxConfig } from '../voice/voicebox-tts.js';
|
|
48
|
+
import { createResponseDecider } from './respond-decider.js';
|
|
48
49
|
import { applyLimitsContract, avoidOpenersGuidance, detectEmotion, emotionalContinuityGuidance, emotionGuidance, expressiveTextGuidance, immediateEmotionAcknowledgement, IMMEDIATE_EMOTION_ACKNOWLEDGEMENTS, limitsContractGuidance, openerKey, pushOpener, } from '../companion/reply-augment.js';
|
|
49
50
|
import { crisisGuidanceFor } from '../companion/crisis-safety.js';
|
|
50
51
|
import { evolveRelationshipFromUtterance } from '../companion/relationship-evolution.js';
|
|
@@ -1263,6 +1264,32 @@ async function prepareSpokenTurn(heard, history = [], spokenPrefix, delivery, re
|
|
|
1263
1264
|
...(cognitiveLease ? { cognitiveLease } : {}),
|
|
1264
1265
|
};
|
|
1265
1266
|
}
|
|
1267
|
+
let sharedVoiceResponseDecider;
|
|
1268
|
+
export function getVoiceResponseDecider() {
|
|
1269
|
+
if (!sharedVoiceResponseDecider) {
|
|
1270
|
+
sharedVoiceResponseDecider = createResponseDecider();
|
|
1271
|
+
}
|
|
1272
|
+
return sharedVoiceResponseDecider;
|
|
1273
|
+
}
|
|
1274
|
+
export function setVoiceResponseDecider(decider) {
|
|
1275
|
+
sharedVoiceResponseDecider = decider;
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Determine if the utterance addresses the robot by name or takes place inside
|
|
1279
|
+
* an active engagement window, consulting the respond-decider.
|
|
1280
|
+
*/
|
|
1281
|
+
export async function resolveVoiceRobotNamed(heard, replyOpts) {
|
|
1282
|
+
if (typeof replyOpts?.robotNamed === 'boolean') {
|
|
1283
|
+
return replyOpts.robotNamed;
|
|
1284
|
+
}
|
|
1285
|
+
if (replyOpts?.respondDecision) {
|
|
1286
|
+
const reason = replyOpts.respondDecision.reason;
|
|
1287
|
+
return reason === 'addressed' || reason === 'engaged';
|
|
1288
|
+
}
|
|
1289
|
+
const decider = replyOpts?.responseDecider ?? getVoiceResponseDecider();
|
|
1290
|
+
const decision = await decider.decide(heard);
|
|
1291
|
+
return decision.reason === 'addressed' || decision.reason === 'engaged';
|
|
1292
|
+
}
|
|
1266
1293
|
export async function defaultReply(heard, history = [], replyOpts) {
|
|
1267
1294
|
const fast = fastCompanionReply(heard);
|
|
1268
1295
|
if (fast) {
|
|
@@ -1331,21 +1358,51 @@ export async function defaultReply(heard, history = [], replyOpts) {
|
|
|
1331
1358
|
cognitiveLease = prepared.cognitiveLease;
|
|
1332
1359
|
replyOpts?.onProviderResolved?.(route);
|
|
1333
1360
|
logger.debug(`[voice] reply model: ${route.model} — ${route.reason}`);
|
|
1334
|
-
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
{
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1361
|
+
let reply = '';
|
|
1362
|
+
const { isCompanionToolsEnabled } = await import('../companion/companion-toolset.js');
|
|
1363
|
+
if (isCompanionToolsEnabled(process.env)) {
|
|
1364
|
+
const { resolveCompanionIdentity } = await import('../companion/companion-identity.js');
|
|
1365
|
+
const { runCompanionChannelTurn } = await import('../channels/companion-channel-turn.js');
|
|
1366
|
+
const robotNamed = await resolveVoiceRobotNamed(heard, replyOpts);
|
|
1367
|
+
const identity = resolveCompanionIdentity({
|
|
1368
|
+
channel: 'voice',
|
|
1369
|
+
isVoicePresence: true,
|
|
1370
|
+
robotNamed,
|
|
1371
|
+
env: process.env,
|
|
1372
|
+
});
|
|
1373
|
+
const turnResult = await runCompanionChannelTurn({
|
|
1374
|
+
apiKey: route.apiKey,
|
|
1375
|
+
baseUrl: route.baseURL,
|
|
1376
|
+
model: route.model,
|
|
1377
|
+
messages: [
|
|
1378
|
+
{ role: 'system', content: systemPrompt },
|
|
1379
|
+
...history,
|
|
1380
|
+
{ role: 'user', content: heard },
|
|
1381
|
+
],
|
|
1382
|
+
identity,
|
|
1383
|
+
surface: 'voice',
|
|
1384
|
+
env: process.env,
|
|
1385
|
+
signal: replyOpts?.signal,
|
|
1386
|
+
maxTokens: voiceMaxTokens(heard, history),
|
|
1387
|
+
});
|
|
1388
|
+
reply = turnResult.text.trim();
|
|
1389
|
+
}
|
|
1390
|
+
else {
|
|
1391
|
+
const client = new CodeBuddyClient(route.apiKey, route.model, route.baseURL);
|
|
1392
|
+
const resp = await client.chat([
|
|
1393
|
+
{ role: 'system', content: systemPrompt },
|
|
1394
|
+
...history,
|
|
1395
|
+
{ role: 'user', content: heard },
|
|
1396
|
+
], [],
|
|
1397
|
+
// Additive: thread the barge-in signal so an interrupt aborts the in-flight
|
|
1398
|
+
// LLM call. Undefined when not interruptible → the call is unchanged.
|
|
1399
|
+
{
|
|
1400
|
+
temperature: voiceTemperature(),
|
|
1401
|
+
maxTokens: voiceMaxTokens(heard, history),
|
|
1402
|
+
...(replyOpts?.signal ? { signal: replyOpts.signal } : {}),
|
|
1403
|
+
});
|
|
1404
|
+
reply = (resp?.choices?.[0]?.message?.content ?? '').trim();
|
|
1405
|
+
}
|
|
1349
1406
|
if (reply && !replyOpts?.signal?.aborted)
|
|
1350
1407
|
cognitiveLease?.commit();
|
|
1351
1408
|
else
|