@phuetz/code-buddy 1.1.0 → 1.3.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.md +211 -163
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +12 -12
- package/dist/agent/base-agent.d.ts +4 -0
- package/dist/agent/base-agent.js +6 -0
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +69 -14
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/facades/infrastructure-facade.d.ts +9 -2
- package/dist/agent/facades/infrastructure-facade.js +15 -6
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/self-improvement/authored-artifact-gate.d.ts +18 -0
- package/dist/agent/self-improvement/authored-artifact-gate.js +42 -0
- package/dist/agent/self-improvement/authored-tool-runtime.d.ts +27 -0
- package/dist/agent/self-improvement/authored-tool-runtime.js +57 -0
- package/dist/agent/self-improvement/authored-tool-store.d.ts +24 -0
- package/dist/agent/self-improvement/authored-tool-store.js +57 -0
- package/dist/agent/self-improvement/llm-tool-proposer.d.ts +41 -0
- package/dist/agent/self-improvement/llm-tool-proposer.js +136 -0
- package/dist/agent/self-improvement/sandbox-scorer.d.ts +17 -0
- package/dist/agent/self-improvement/sandbox-scorer.js +43 -0
- package/dist/agent/self-improvement/self-knowledge.d.ts +8 -0
- package/dist/agent/self-improvement/self-knowledge.js +24 -0
- package/dist/agent/self-improvement/skill-benchmark.d.ts +9 -0
- package/dist/agent/self-improvement/skill-benchmark.js +22 -0
- package/dist/agent/self-improvement/skill-consolidator.d.ts +71 -0
- package/dist/agent/self-improvement/skill-consolidator.js +137 -0
- package/dist/agent/self-improvement/skill-engine.d.ts +42 -0
- package/dist/agent/self-improvement/skill-engine.js +87 -0
- package/dist/agent/self-improvement/skill-gate.d.ts +19 -0
- package/dist/agent/self-improvement/skill-gate.js +62 -0
- package/dist/agent/self-improvement/skill-mutator.d.ts +74 -0
- package/dist/agent/self-improvement/skill-mutator.js +223 -0
- package/dist/agent/self-improvement/skill-proposer.d.ts +40 -0
- package/dist/agent/self-improvement/skill-proposer.js +82 -0
- package/dist/agent/self-improvement/skill-types.d.ts +41 -0
- package/dist/agent/self-improvement/skill-types.js +13 -0
- package/dist/agent/self-improvement/tool-benchmark.d.ts +10 -0
- package/dist/agent/self-improvement/tool-benchmark.js +37 -0
- package/dist/agent/self-improvement/tool-engine.d.ts +54 -0
- package/dist/agent/self-improvement/tool-engine.js +101 -0
- package/dist/agent/self-improvement/tool-gate.d.ts +20 -0
- package/dist/agent/self-improvement/tool-gate.js +78 -0
- package/dist/agent/self-improvement/tool-proposer.d.ts +31 -0
- package/dist/agent/self-improvement/tool-proposer.js +34 -0
- package/dist/agent/self-improvement/tool-skill-mutator.d.ts +40 -0
- package/dist/agent/self-improvement/tool-skill-mutator.js +79 -0
- package/dist/agent/self-improvement/tool-types.d.ts +48 -0
- package/dist/agent/self-improvement/tool-types.js +9 -0
- package/dist/agent/self-improvement/types.d.ts +3 -1
- package/dist/agent/session-end-flush.d.ts +6 -1
- package/dist/agent/session-end-flush.js +20 -4
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +133 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +7 -1
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +8 -1
- package/dist/codebuddy/tools.js +67 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/improve-command.js +123 -0
- package/dist/commands/cli/native-engine-commands.js +102 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +2 -2
- package/dist/commands/goal-cli.d.ts +30 -0
- package/dist/commands/goal-cli.js +210 -27
- package/dist/commands/handlers/goal-handler.d.ts +9 -2
- package/dist/commands/handlers/goal-handler.js +36 -9
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/handlers/missing-handlers.d.ts +1 -1
- package/dist/commands/handlers/missing-handlers.js +26 -3
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/skills-cli/index.js +123 -0
- package/dist/commands/slash/builtin-commands.js +1 -1
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/companion/percepts.js +11 -1
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +756 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/bootstrap-loader.js +6 -23
- package/dist/context/context-manager-v2.js +1 -0
- package/dist/context/import-directive-parser.d.ts +4 -0
- package/dist/context/import-directive-parser.js +51 -6
- package/dist/context/instruction-excludes.d.ts +30 -1
- package/dist/context/instruction-excludes.js +71 -1
- package/dist/context/jit-context.d.ts +8 -10
- package/dist/context/jit-context.js +28 -106
- package/dist/context/project-context.d.ts +90 -0
- package/dist/context/project-context.js +295 -0
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +3 -3
- package/dist/daemon/autonomous-loop.d.ts +31 -1
- package/dist/daemon/autonomous-loop.js +91 -4
- package/dist/daemon/colab-goal.js +9 -1
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.js +14 -2
- package/dist/fleet/peer-session-bridge.js +50 -12
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +2 -0
- package/dist/goals/goal-judge.js +35 -6
- package/dist/goals/goal-loop.d.ts +14 -0
- package/dist/goals/goal-loop.js +47 -2
- package/dist/goals/goal-manager.d.ts +6 -0
- package/dist/goals/goal-manager.js +67 -14
- package/dist/goals/goal-state.d.ts +13 -0
- package/dist/goals/goal-state.js +83 -13
- package/dist/goals/goal-store.d.ts +3 -0
- package/dist/goals/goal-store.js +31 -15
- package/dist/harness/contract.d.ts +28 -28
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/identity/identity-manager.js +3 -2
- package/dist/index.js +481 -26
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-resources.js +2 -3
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/sensory/dreaming.d.ts +45 -0
- package/dist/sensory/dreaming.js +114 -0
- package/dist/sensory/heartbeat-scheduler.d.ts +38 -0
- package/dist/sensory/heartbeat-scheduler.js +72 -0
- package/dist/sensory/reactions.d.ts +24 -0
- package/dist/sensory/reactions.js +31 -0
- package/dist/sensory/screen-reaction.d.ts +23 -0
- package/dist/sensory/screen-reaction.js +59 -0
- package/dist/sensory/sensory-bridge.d.ts +23 -0
- package/dist/sensory/sensory-bridge.js +85 -0
- package/dist/sensory/sensory-memory.d.ts +20 -0
- package/dist/sensory/sensory-memory.js +39 -0
- package/dist/sensory/speech-reaction.d.ts +21 -0
- package/dist/sensory/speech-reaction.js +83 -0
- package/dist/sensory/vision-reaction.d.ts +31 -0
- package/dist/sensory/vision-reaction.js +74 -0
- package/dist/server/index.js +106 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.js +5 -0
- package/dist/services/prompt-builder.d.ts +11 -0
- package/dist/services/prompt-builder.js +124 -17
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/skills/parser.js +3 -0
- package/dist/skills/skill-importer.d.ts +58 -0
- package/dist/skills/skill-importer.js +261 -0
- package/dist/skills/skill-sources.d.ts +20 -0
- package/dist/skills/skill-sources.js +102 -0
- package/dist/skills/types.d.ts +6 -0
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/register-tool-handler.d.ts +25 -0
- package/dist/tools/register-tool-handler.js +100 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/registry.d.ts +6 -0
- package/dist/tools/registry.js +8 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/init-project.d.ts +7 -0
- package/dist/utils/init-project.js +37 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +19 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- package/dist/utils/kokoro-tts.js +0 -67
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screen reaction — on a `screen/change` event from the nervous-system daemon,
|
|
3
|
+
* record a companion percept and (optionally) run an injected analyzer (OCR /
|
|
4
|
+
* vision describe). DEBOUNCED, opt-in (`CODEBUDDY_SENSORY_SCREEN=true`),
|
|
5
|
+
* never-throws. The heavy OCR/describe is pluggable so we don't duplicate Code
|
|
6
|
+
* Buddy's existing OCR/vision; inject an analyzer to wire it.
|
|
7
|
+
*
|
|
8
|
+
* @module sensory/screen-reaction
|
|
9
|
+
*/
|
|
10
|
+
import { getGlobalEventBus } from '../events/event-bus.js';
|
|
11
|
+
import { logger } from '../utils/logger.js';
|
|
12
|
+
import { perceptionOf } from './reactions.js';
|
|
13
|
+
export function wireScreenReaction(options = {}) {
|
|
14
|
+
const bus = getGlobalEventBus();
|
|
15
|
+
const debounceMs = options.debounceMs ?? Number(process.env.CODEBUDDY_SCREEN_DEBOUNCE_MS ?? 5000);
|
|
16
|
+
const now = options.now ?? (() => Date.now());
|
|
17
|
+
let lastAt = Number.NEGATIVE_INFINITY;
|
|
18
|
+
let inFlight = false;
|
|
19
|
+
const id = bus.on('sensory:perception', (evt) => {
|
|
20
|
+
const p = perceptionOf(evt);
|
|
21
|
+
if (p.modality !== 'screen' || p.kind !== 'change')
|
|
22
|
+
return;
|
|
23
|
+
const t = now();
|
|
24
|
+
if (t - lastAt < debounceMs) {
|
|
25
|
+
logger.info('[screen] change (debounced)');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (inFlight)
|
|
29
|
+
return; // a prior analyze() is still running — don't stampede
|
|
30
|
+
lastAt = t;
|
|
31
|
+
inFlight = true;
|
|
32
|
+
void (async () => {
|
|
33
|
+
try {
|
|
34
|
+
const analysis = options.analyzer ? await options.analyzer.analyze() : {};
|
|
35
|
+
const { recordCompanionPercept } = await import('../companion/percepts.js');
|
|
36
|
+
const score = p.payload?.score;
|
|
37
|
+
await recordCompanionPercept({
|
|
38
|
+
modality: 'screen',
|
|
39
|
+
source: 'sensory_screen_reaction',
|
|
40
|
+
summary: `Screen changed${analysis.description ? ` → ${analysis.description}` : ''}`,
|
|
41
|
+
confidence: 0.85,
|
|
42
|
+
payload: { score, description: analysis.description },
|
|
43
|
+
tags: ['screen', 'change'],
|
|
44
|
+
}, options.cwd ? { cwd: options.cwd } : {});
|
|
45
|
+
logger.info(`[screen] change recorded${analysis.description ? ` → ${analysis.description}` : ''}`);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
logger.warn(`[screen] reaction failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
inFlight = false;
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
});
|
|
55
|
+
return () => {
|
|
56
|
+
bus.off(id);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=screen-reaction.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sensory bridge — the ingress for the Rust `buddy-sense` nervous-system daemon.
|
|
3
|
+
*
|
|
4
|
+
* A loopback-only WebSocket server that receives `SensoryEvent` JSON frames from
|
|
5
|
+
* the daemon and re-emits them onto Code Buddy's internal event bus as
|
|
6
|
+
* `sensory:perception` events. Reactions (reactions.ts) subscribe to the bus.
|
|
7
|
+
*
|
|
8
|
+
* Opt-in via `CODEBUDDY_SENSORY=true` (wired in `buddy server`). Loopback-only +
|
|
9
|
+
* optional shared token. Never throws on a malformed frame.
|
|
10
|
+
*
|
|
11
|
+
* @module sensory/sensory-bridge
|
|
12
|
+
*/
|
|
13
|
+
export interface SensoryBridgeOptions {
|
|
14
|
+
host?: string;
|
|
15
|
+
port?: number;
|
|
16
|
+
/** If set, frames must carry a matching `token` field. */
|
|
17
|
+
token?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SensoryBridgeHandle {
|
|
20
|
+
port: number;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export declare function startSensoryBridge(options?: SensoryBridgeOptions): SensoryBridgeHandle;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sensory bridge — the ingress for the Rust `buddy-sense` nervous-system daemon.
|
|
3
|
+
*
|
|
4
|
+
* A loopback-only WebSocket server that receives `SensoryEvent` JSON frames from
|
|
5
|
+
* the daemon and re-emits them onto Code Buddy's internal event bus as
|
|
6
|
+
* `sensory:perception` events. Reactions (reactions.ts) subscribe to the bus.
|
|
7
|
+
*
|
|
8
|
+
* Opt-in via `CODEBUDDY_SENSORY=true` (wired in `buddy server`). Loopback-only +
|
|
9
|
+
* optional shared token. Never throws on a malformed frame.
|
|
10
|
+
*
|
|
11
|
+
* @module sensory/sensory-bridge
|
|
12
|
+
*/
|
|
13
|
+
import { WebSocketServer } from 'ws';
|
|
14
|
+
import { getGlobalEventBus } from '../events/event-bus.js';
|
|
15
|
+
import { logger } from '../utils/logger.js';
|
|
16
|
+
/** The trust boundary: only known modalities pass; kinds are sanitized to a safe
|
|
17
|
+
* shape (no control chars / newlines, ≤64 chars) so a crafted local frame can't
|
|
18
|
+
* inject content downstream (e.g. into CODEBUDDY_MEMORY.md via dreaming). */
|
|
19
|
+
const KNOWN_MODALITIES = new Set(['audio', 'vision', 'screen', 'vital', 'ui']);
|
|
20
|
+
function sanitizeKind(kind) {
|
|
21
|
+
return kind.replace(/[^a-zA-Z0-9_:.-]/g, '').slice(0, 64);
|
|
22
|
+
}
|
|
23
|
+
export function startSensoryBridge(options = {}) {
|
|
24
|
+
const host = options.host ?? '127.0.0.1';
|
|
25
|
+
const port = options.port ?? Number(process.env.CODEBUDDY_SENSORY_PORT ?? 8129);
|
|
26
|
+
const token = options.token ?? process.env.CODEBUDDY_SENSORY_TOKEN;
|
|
27
|
+
const bus = getGlobalEventBus();
|
|
28
|
+
const wss = new WebSocketServer({ host, port });
|
|
29
|
+
wss.on('connection', (ws, req) => {
|
|
30
|
+
// Reject cross-origin (CSWSH): the Rust daemon sends NO Origin header; a
|
|
31
|
+
// browser page / extension always does. This stops a local web origin from
|
|
32
|
+
// injecting frames (and, with the camera reaction on, triggering the webcam).
|
|
33
|
+
if (req.headers.origin) {
|
|
34
|
+
ws.close();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Loopback-only: exact match, tolerating IPv4-mapped IPv6 (::ffff:127.0.0.1).
|
|
38
|
+
const remote = (req.socket.remoteAddress ?? '').replace(/^::ffff:/, '');
|
|
39
|
+
if (remote !== '127.0.0.1' && remote !== '::1') {
|
|
40
|
+
ws.close();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
logger.info('[sensory] daemon connected');
|
|
44
|
+
ws.on('message', (data) => {
|
|
45
|
+
let frame;
|
|
46
|
+
try {
|
|
47
|
+
frame = JSON.parse(String(data));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return; // ignore malformed
|
|
51
|
+
}
|
|
52
|
+
if (token && frame.token !== token)
|
|
53
|
+
return;
|
|
54
|
+
if (!frame.modality || !frame.kind)
|
|
55
|
+
return;
|
|
56
|
+
// Validate the string fields at the trust boundary (not just the numbers):
|
|
57
|
+
// unknown modality is dropped, kind is sanitized — blocks memory injection.
|
|
58
|
+
if (!KNOWN_MODALITIES.has(frame.modality))
|
|
59
|
+
return;
|
|
60
|
+
const kind = sanitizeKind(frame.kind);
|
|
61
|
+
if (!kind)
|
|
62
|
+
return;
|
|
63
|
+
// Validate/clamp numeric fields so a malformed frame can't poison downstream.
|
|
64
|
+
const salience = Number.isFinite(frame.salience) ? Math.max(0, Math.min(255, Math.round(frame.salience))) : 0;
|
|
65
|
+
const tsMs = Number.isFinite(frame.ts_ms) && frame.ts_ms >= 0 ? frame.ts_ms : undefined;
|
|
66
|
+
bus.emit('sensory:perception', {
|
|
67
|
+
source: 'buddy-sense',
|
|
68
|
+
metadata: {
|
|
69
|
+
modality: frame.modality,
|
|
70
|
+
kind,
|
|
71
|
+
salience,
|
|
72
|
+
tsMs,
|
|
73
|
+
payload: frame.payload,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
wss.on('error', (err) => logger.warn(`[sensory] bridge error: ${err instanceof Error ? err.message : String(err)}`));
|
|
79
|
+
logger.info(`[sensory] bridge listening on ws://${host}:${port}`);
|
|
80
|
+
return {
|
|
81
|
+
port,
|
|
82
|
+
close: () => new Promise((resolve) => wss.close(() => resolve())),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=sensory-bridge.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Short-term sensory memory — a bounded rolling buffer of recent perceptions
|
|
3
|
+
* (the nervous system's working memory). Reactions push every perception here;
|
|
4
|
+
* the dreaming consolidator drains + summarizes it into long-term memory every N
|
|
5
|
+
* heartbeats (like OpenClaw's dreaming: short-term recall → consolidated store).
|
|
6
|
+
*
|
|
7
|
+
* @module sensory/sensory-memory
|
|
8
|
+
*/
|
|
9
|
+
import type { Perception } from './reactions.js';
|
|
10
|
+
export declare class SensoryMemory {
|
|
11
|
+
private readonly cap;
|
|
12
|
+
private buf;
|
|
13
|
+
constructor(cap?: number);
|
|
14
|
+
push(p: Perception): void;
|
|
15
|
+
snapshot(): Perception[];
|
|
16
|
+
/** Take everything and clear (consolidation consumes the window). */
|
|
17
|
+
drain(): Perception[];
|
|
18
|
+
size(): number;
|
|
19
|
+
}
|
|
20
|
+
export declare function getSensoryMemory(): SensoryMemory;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Short-term sensory memory — a bounded rolling buffer of recent perceptions
|
|
3
|
+
* (the nervous system's working memory). Reactions push every perception here;
|
|
4
|
+
* the dreaming consolidator drains + summarizes it into long-term memory every N
|
|
5
|
+
* heartbeats (like OpenClaw's dreaming: short-term recall → consolidated store).
|
|
6
|
+
*
|
|
7
|
+
* @module sensory/sensory-memory
|
|
8
|
+
*/
|
|
9
|
+
export class SensoryMemory {
|
|
10
|
+
cap;
|
|
11
|
+
buf = [];
|
|
12
|
+
constructor(cap = 1000) {
|
|
13
|
+
this.cap = cap;
|
|
14
|
+
}
|
|
15
|
+
push(p) {
|
|
16
|
+
this.buf.push(p);
|
|
17
|
+
if (this.buf.length > this.cap)
|
|
18
|
+
this.buf.shift();
|
|
19
|
+
}
|
|
20
|
+
snapshot() {
|
|
21
|
+
return [...this.buf];
|
|
22
|
+
}
|
|
23
|
+
/** Take everything and clear (consolidation consumes the window). */
|
|
24
|
+
drain() {
|
|
25
|
+
const out = this.buf;
|
|
26
|
+
this.buf = [];
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
size() {
|
|
30
|
+
return this.buf.length;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
let singleton;
|
|
34
|
+
export function getSensoryMemory() {
|
|
35
|
+
if (!singleton)
|
|
36
|
+
singleton = new SensoryMemory();
|
|
37
|
+
return singleton;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=sensory-memory.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speech reaction — closes the perception→cognition loop. On a `speech_end` event
|
|
3
|
+
* that carries the source WAV (the daemon tags it), transcribe the utterance (STT)
|
|
4
|
+
* and record what was heard as a `hearing` percept; an optional `onHeard` hook can
|
|
5
|
+
* drive further action (a turn, a command). DEBOUNCED (one transcription per
|
|
6
|
+
* utterance — the energy VAD over-segments), opt-in (`CODEBUDDY_SENSORY_SPEECH=true`),
|
|
7
|
+
* injectable transcriber, never-throws.
|
|
8
|
+
*
|
|
9
|
+
* @module sensory/speech-reaction
|
|
10
|
+
*/
|
|
11
|
+
export type Transcriber = (wav: string) => Promise<string>;
|
|
12
|
+
export interface SpeechReactionOptions {
|
|
13
|
+
/** Injectable STT (tests / custom). Default: faster-whisper via python ($0). */
|
|
14
|
+
transcriber?: Transcriber;
|
|
15
|
+
debounceMs?: number;
|
|
16
|
+
cwd?: string;
|
|
17
|
+
now?: () => number;
|
|
18
|
+
/** Action hook for the transcript (e.g. trigger an agent turn). */
|
|
19
|
+
onHeard?: (text: string) => void | Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare function wireSpeechReaction(options?: SpeechReactionOptions): () => void;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speech reaction — closes the perception→cognition loop. On a `speech_end` event
|
|
3
|
+
* that carries the source WAV (the daemon tags it), transcribe the utterance (STT)
|
|
4
|
+
* and record what was heard as a `hearing` percept; an optional `onHeard` hook can
|
|
5
|
+
* drive further action (a turn, a command). DEBOUNCED (one transcription per
|
|
6
|
+
* utterance — the energy VAD over-segments), opt-in (`CODEBUDDY_SENSORY_SPEECH=true`),
|
|
7
|
+
* injectable transcriber, never-throws.
|
|
8
|
+
*
|
|
9
|
+
* @module sensory/speech-reaction
|
|
10
|
+
*/
|
|
11
|
+
import { getGlobalEventBus } from '../events/event-bus.js';
|
|
12
|
+
import { logger } from '../utils/logger.js';
|
|
13
|
+
import { perceptionOf } from './reactions.js';
|
|
14
|
+
/** Default transcriber: local faster-whisper (base), best-effort, $0. */
|
|
15
|
+
async function defaultTranscribe(wav) {
|
|
16
|
+
const { spawn } = await import('child_process');
|
|
17
|
+
const model = process.env.CODEBUDDY_SPEECH_MODEL ?? 'base';
|
|
18
|
+
const py = [
|
|
19
|
+
'import sys',
|
|
20
|
+
'from faster_whisper import WhisperModel',
|
|
21
|
+
`m = WhisperModel(${JSON.stringify(model)}, device='cpu', compute_type='int8')`,
|
|
22
|
+
'segs, _ = m.transcribe(sys.argv[1])',
|
|
23
|
+
"print(' '.join(s.text for s in segs).strip())",
|
|
24
|
+
].join('\n');
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const proc = spawn('python3', ['-c', py, wav], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
27
|
+
let out = '';
|
|
28
|
+
proc.stdout.on('data', (d) => (out += String(d)));
|
|
29
|
+
proc.on('close', () => resolve(out.trim()));
|
|
30
|
+
proc.on('error', () => resolve(''));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function wireSpeechReaction(options = {}) {
|
|
34
|
+
const bus = getGlobalEventBus();
|
|
35
|
+
const debounceMs = options.debounceMs ?? Number(process.env.CODEBUDDY_SPEECH_DEBOUNCE_MS ?? 4000);
|
|
36
|
+
const now = options.now ?? (() => Date.now());
|
|
37
|
+
const transcribe = options.transcriber ?? defaultTranscribe;
|
|
38
|
+
let lastAt = Number.NEGATIVE_INFINITY;
|
|
39
|
+
let inFlight = false;
|
|
40
|
+
const id = bus.on('sensory:perception', (evt) => {
|
|
41
|
+
const p = perceptionOf(evt);
|
|
42
|
+
if (p.modality !== 'audio' || p.kind !== 'speech_end')
|
|
43
|
+
return;
|
|
44
|
+
const wav = p.payload?.wav;
|
|
45
|
+
if (!wav)
|
|
46
|
+
return; // no audio to transcribe (e.g. a live-mic path not yet wired)
|
|
47
|
+
const t = now();
|
|
48
|
+
if (t - lastAt < debounceMs)
|
|
49
|
+
return; // one transcription per utterance
|
|
50
|
+
if (inFlight)
|
|
51
|
+
return; // a prior STT (faster-whisper, seconds) is still running
|
|
52
|
+
lastAt = t;
|
|
53
|
+
inFlight = true;
|
|
54
|
+
void (async () => {
|
|
55
|
+
try {
|
|
56
|
+
const text = await transcribe(wav);
|
|
57
|
+
if (!text)
|
|
58
|
+
return;
|
|
59
|
+
const { recordCompanionPercept } = await import('../companion/percepts.js');
|
|
60
|
+
await recordCompanionPercept({
|
|
61
|
+
modality: 'hearing',
|
|
62
|
+
source: 'sensory_speech_reaction',
|
|
63
|
+
summary: `Heard: ${text}`,
|
|
64
|
+
confidence: 0.8,
|
|
65
|
+
payload: { text, wav },
|
|
66
|
+
tags: ['speech', 'stt'],
|
|
67
|
+
}, options.cwd ? { cwd: options.cwd } : {});
|
|
68
|
+
logger.info(`[speech] heard → ${text}`);
|
|
69
|
+
await options.onHeard?.(text);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
logger.warn(`[speech] reaction failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
inFlight = false;
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
});
|
|
79
|
+
return () => {
|
|
80
|
+
bus.off(id);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=speech-reaction.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vision reaction — on a `vision/motion` event from the nervous-system daemon,
|
|
3
|
+
* run `camera_analyze` (capture a frame + describe it with a local gemma vision
|
|
4
|
+
* model) and record a companion percept. DEBOUNCED (gemma is heavy: 3–10s warm),
|
|
5
|
+
* opt-in (`CODEBUDDY_SENSORY_CAMERA=true`), best-effort, never-throws.
|
|
6
|
+
*
|
|
7
|
+
* @module sensory/vision-reaction
|
|
8
|
+
*/
|
|
9
|
+
export interface VisionAnalysis {
|
|
10
|
+
success: boolean;
|
|
11
|
+
description?: string;
|
|
12
|
+
imagePath?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface VisionAnalyzer {
|
|
15
|
+
analyze(prompt: string): Promise<VisionAnalysis>;
|
|
16
|
+
}
|
|
17
|
+
export interface VisionReactionOptions {
|
|
18
|
+
/** Injectable analyzer (tests / custom). Defaults to camera_analyze (local gemma). */
|
|
19
|
+
analyzer?: VisionAnalyzer;
|
|
20
|
+
debounceMs?: number;
|
|
21
|
+
cwd?: string;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
}
|
|
24
|
+
/** Security invariant (pure + testable): the camera reaction may only be wired
|
|
25
|
+
* when explicitly enabled AND a shared token is set — a crafted local frame can
|
|
26
|
+
* trigger the webcam, so an unauthenticated bridge must not be able to. */
|
|
27
|
+
export declare function shouldWireVisionReaction(env: {
|
|
28
|
+
camera?: string;
|
|
29
|
+
token?: string;
|
|
30
|
+
}): boolean;
|
|
31
|
+
export declare function wireVisionReaction(options?: VisionReactionOptions): () => void;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vision reaction — on a `vision/motion` event from the nervous-system daemon,
|
|
3
|
+
* run `camera_analyze` (capture a frame + describe it with a local gemma vision
|
|
4
|
+
* model) and record a companion percept. DEBOUNCED (gemma is heavy: 3–10s warm),
|
|
5
|
+
* opt-in (`CODEBUDDY_SENSORY_CAMERA=true`), best-effort, never-throws.
|
|
6
|
+
*
|
|
7
|
+
* @module sensory/vision-reaction
|
|
8
|
+
*/
|
|
9
|
+
import { getGlobalEventBus } from '../events/event-bus.js';
|
|
10
|
+
import { logger } from '../utils/logger.js';
|
|
11
|
+
import { perceptionOf } from './reactions.js';
|
|
12
|
+
/** Default analyzer: Code Buddy's camera_analyze tool (ffmpeg capture + local gemma). */
|
|
13
|
+
async function defaultAnalyze(prompt) {
|
|
14
|
+
const { CameraAnalyzeTool } = await import('../tools/registry/vision-tools.js');
|
|
15
|
+
const tool = new CameraAnalyzeTool({});
|
|
16
|
+
const result = await tool.execute({ prompt });
|
|
17
|
+
const data = result.data;
|
|
18
|
+
return { success: result.success, description: data?.description ?? result.output, imagePath: data?.imagePath };
|
|
19
|
+
}
|
|
20
|
+
/** Security invariant (pure + testable): the camera reaction may only be wired
|
|
21
|
+
* when explicitly enabled AND a shared token is set — a crafted local frame can
|
|
22
|
+
* trigger the webcam, so an unauthenticated bridge must not be able to. */
|
|
23
|
+
export function shouldWireVisionReaction(env) {
|
|
24
|
+
return env.camera === 'true' && Boolean(env.token);
|
|
25
|
+
}
|
|
26
|
+
export function wireVisionReaction(options = {}) {
|
|
27
|
+
const bus = getGlobalEventBus();
|
|
28
|
+
const debounceMs = options.debounceMs ?? Number(process.env.CODEBUDDY_VISION_DEBOUNCE_MS ?? 8000);
|
|
29
|
+
const now = options.now ?? (() => Date.now());
|
|
30
|
+
const analyzer = options.analyzer ?? { analyze: defaultAnalyze };
|
|
31
|
+
let lastAt = Number.NEGATIVE_INFINITY;
|
|
32
|
+
let inFlight = false;
|
|
33
|
+
const id = bus.on('sensory:perception', (evt) => {
|
|
34
|
+
const p = perceptionOf(evt);
|
|
35
|
+
if (p.modality !== 'vision' || p.kind !== 'motion')
|
|
36
|
+
return;
|
|
37
|
+
const t = now();
|
|
38
|
+
if (t - lastAt < debounceMs) {
|
|
39
|
+
logger.info('[vision] motion (debounced — analysis throttled)');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (inFlight)
|
|
43
|
+
return; // a prior analyze() (ffmpeg+gemma, 3–10s) is still running
|
|
44
|
+
lastAt = t;
|
|
45
|
+
inFlight = true;
|
|
46
|
+
void (async () => {
|
|
47
|
+
try {
|
|
48
|
+
const res = await analyzer.analyze('Motion detected — describe the scene in one sentence.');
|
|
49
|
+
if (!res.success)
|
|
50
|
+
return;
|
|
51
|
+
const { recordCompanionPercept } = await import('../companion/percepts.js');
|
|
52
|
+
await recordCompanionPercept({
|
|
53
|
+
modality: 'vision',
|
|
54
|
+
source: 'sensory_motion_reaction',
|
|
55
|
+
summary: `Motion → ${res.description ?? '(no description)'}`,
|
|
56
|
+
confidence: 0.9,
|
|
57
|
+
payload: { description: res.description, imagePath: res.imagePath },
|
|
58
|
+
tags: ['motion', 'camera', 'vision'],
|
|
59
|
+
}, options.cwd ? { cwd: options.cwd } : {});
|
|
60
|
+
logger.info(`[vision] motion analyzed → ${res.description ?? '(no description)'}`);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
logger.warn(`[vision] reaction failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
inFlight = false;
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
69
|
+
});
|
|
70
|
+
return () => {
|
|
71
|
+
bus.off(id);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=vision-reaction.js.map
|
package/dist/server/index.js
CHANGED
|
@@ -14,6 +14,8 @@ import { createRequire } from 'module';
|
|
|
14
14
|
import express from 'express';
|
|
15
15
|
import cors from 'cors';
|
|
16
16
|
import { createServer } from 'http';
|
|
17
|
+
import { createServer as createHttpsServer, Server as HttpsServer } from 'https';
|
|
18
|
+
import { resolveServerTlsOptions } from './tls-config.js';
|
|
17
19
|
const _require = createRequire(import.meta.url);
|
|
18
20
|
let SERVER_VERSION = '0.0.0';
|
|
19
21
|
try {
|
|
@@ -77,7 +79,10 @@ export function getServerBaseUrl(server, config) {
|
|
|
77
79
|
const port = typeof address === 'object' && address ? address.port : config.port;
|
|
78
80
|
const publicHost = config.host === '0.0.0.0' || config.host === '::' ? '127.0.0.1' : config.host;
|
|
79
81
|
const host = publicHost.includes(':') && !publicHost.startsWith('[') ? `[${publicHost}]` : publicHost;
|
|
80
|
-
|
|
82
|
+
// Report https:// when the server was constructed with TLS so the logged URL
|
|
83
|
+
// isn't a lie. Default (plain http.Server) stays http://.
|
|
84
|
+
const scheme = server instanceof HttpsServer ? 'https' : 'http';
|
|
85
|
+
return `${scheme}://${host}:${port}`;
|
|
81
86
|
}
|
|
82
87
|
// Default configuration
|
|
83
88
|
const DEFAULT_CONFIG = {
|
|
@@ -839,6 +844,10 @@ function createApp(config) {
|
|
|
839
844
|
app.use(errorHandler);
|
|
840
845
|
return app;
|
|
841
846
|
}
|
|
847
|
+
/** Guards the sensory layer against double-wiring on a second in-process start. */
|
|
848
|
+
let sensoryWired = false;
|
|
849
|
+
/** Teardown fns for the sensory layer (bridge close, listener unsubscribes, scheduler stop). */
|
|
850
|
+
let sensoryTeardown = [];
|
|
842
851
|
/**
|
|
843
852
|
* Start the server
|
|
844
853
|
*/
|
|
@@ -878,7 +887,17 @@ export async function startServer(userConfig = {}) {
|
|
|
878
887
|
/* prometheus exporter optional */
|
|
879
888
|
}
|
|
880
889
|
const app = createApp(config);
|
|
881
|
-
|
|
890
|
+
// Optional TLS: serve the API (including /api/mobile) over HTTPS when
|
|
891
|
+
// CODEBUDDY_HTTPS / CODEBUDDY_MOBILE_TLS is set so the mobile-supervision
|
|
892
|
+
// endpoint can be exposed off-device securely. Default (no env) is plain HTTP,
|
|
893
|
+
// byte-for-byte unchanged. resolveServerTlsOptions() throws (never silently
|
|
894
|
+
// downgrades) if TLS is requested but cannot be satisfied. The https.Server
|
|
895
|
+
// surface (listen/address/on('upgrade')/close) matches http.Server at runtime,
|
|
896
|
+
// so we keep the `HttpServer` type to avoid widening downstream signatures.
|
|
897
|
+
const tlsOptions = resolveServerTlsOptions();
|
|
898
|
+
const server = tlsOptions
|
|
899
|
+
? createHttpsServer(tlsOptions, app)
|
|
900
|
+
: createServer(app);
|
|
882
901
|
const startChannelBridge = async (baseUrl) => {
|
|
883
902
|
try {
|
|
884
903
|
const { getChannelManager } = await import('../channels/index.js');
|
|
@@ -1003,6 +1022,80 @@ export async function startServer(userConfig = {}) {
|
|
|
1003
1022
|
logger.info(`Metrics Dashboard: ${baseUrl}/api/metrics/dashboard`);
|
|
1004
1023
|
logger.info(`Docs: ${baseUrl}/api/docs`);
|
|
1005
1024
|
logger.info(`WebSocket: ${config.websocketEnabled ? 'Enabled (/ws)' : 'Disabled'}`);
|
|
1025
|
+
// Sensory nervous-system bridge (opt-in): ingress for the Rust buddy-sense
|
|
1026
|
+
// daemon → internal event bus → reactions. Loopback-only.
|
|
1027
|
+
if (process.env.CODEBUDDY_SENSORY === 'true' && !sensoryWired) {
|
|
1028
|
+
sensoryWired = true; // wire once per process (a 2nd start would double listeners + re-bind the port)
|
|
1029
|
+
try {
|
|
1030
|
+
const { startSensoryBridge } = await import('../sensory/sensory-bridge.js');
|
|
1031
|
+
const { wireSensoryReactions } = await import('../sensory/reactions.js');
|
|
1032
|
+
const { getHeartbeatScheduler } = await import('../sensory/heartbeat-scheduler.js');
|
|
1033
|
+
const sensoryBridgeHandle = startSensoryBridge();
|
|
1034
|
+
const unwireReactions = wireSensoryReactions();
|
|
1035
|
+
sensoryTeardown.push(() => sensoryBridgeHandle.close(), unwireReactions);
|
|
1036
|
+
// Vision reaction (opt-in) — vision/motion → camera_analyze (local gemma).
|
|
1037
|
+
// Requires a shared token: a frame can trigger the webcam, so refuse to
|
|
1038
|
+
// wire it on an unauthenticated bridge.
|
|
1039
|
+
const sensoryToken = process.env.CODEBUDDY_SENSORY_TOKEN;
|
|
1040
|
+
{
|
|
1041
|
+
const { shouldWireVisionReaction, wireVisionReaction } = await import('../sensory/vision-reaction.js');
|
|
1042
|
+
if (shouldWireVisionReaction({ camera: process.env.CODEBUDDY_SENSORY_CAMERA, token: sensoryToken })) {
|
|
1043
|
+
sensoryTeardown.push(wireVisionReaction());
|
|
1044
|
+
logger.info('Sensory vision reaction: Enabled (vision/motion → camera_analyze)');
|
|
1045
|
+
}
|
|
1046
|
+
else if (process.env.CODEBUDDY_SENSORY_CAMERA === 'true') {
|
|
1047
|
+
logger.warn('Sensory vision reaction NOT enabled: set CODEBUDDY_SENSORY_TOKEN to allow camera triggering.');
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
// Screen reaction (opt-in) — also token-gated (an injected analyzer could capture the desktop).
|
|
1051
|
+
if (process.env.CODEBUDDY_SENSORY_SCREEN === 'true') {
|
|
1052
|
+
if (sensoryToken) {
|
|
1053
|
+
const { wireScreenReaction } = await import('../sensory/screen-reaction.js');
|
|
1054
|
+
sensoryTeardown.push(wireScreenReaction());
|
|
1055
|
+
logger.info('Sensory screen reaction: Enabled (screen/change → percept)');
|
|
1056
|
+
}
|
|
1057
|
+
else {
|
|
1058
|
+
logger.warn('Sensory screen reaction NOT enabled: set CODEBUDDY_SENSORY_TOKEN.');
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
// Speech reaction (opt-in) — speech_end → STT → 'hearing' percept (+ onHeard hook).
|
|
1062
|
+
if (process.env.CODEBUDDY_SENSORY_SPEECH === 'true') {
|
|
1063
|
+
const { wireSpeechReaction } = await import('../sensory/speech-reaction.js');
|
|
1064
|
+
sensoryTeardown.push(wireSpeechReaction());
|
|
1065
|
+
logger.info('Sensory speech reaction: Enabled (speech_end → STT → percept)');
|
|
1066
|
+
}
|
|
1067
|
+
// Privacy: camera/screen descriptions land in percepts.jsonl — warn if not encrypted at rest.
|
|
1068
|
+
if ((process.env.CODEBUDDY_SENSORY_CAMERA === 'true' || process.env.CODEBUDDY_SENSORY_SCREEN === 'true') &&
|
|
1069
|
+
!process.env.CODEBUDDY_COMPANION_ENCRYPTION_KEY &&
|
|
1070
|
+
!process.env.CODEBUDDY_MEMORY_KEY) {
|
|
1071
|
+
logger.warn('Sensory camera/screen percepts are written UNENCRYPTED — set CODEBUDDY_COMPANION_ENCRYPTION_KEY to encrypt scene/screen descriptions at rest.');
|
|
1072
|
+
}
|
|
1073
|
+
// Heartbeat pacemaker — heartbeats trigger periodic processing (every N beats).
|
|
1074
|
+
const heart = getHeartbeatScheduler();
|
|
1075
|
+
const everyBeats = Math.max(1, Number(process.env.CODEBUDDY_HEARTBEAT_EVERY ?? 10));
|
|
1076
|
+
heart.register({
|
|
1077
|
+
name: 'pacemaker-tick',
|
|
1078
|
+
everyBeats,
|
|
1079
|
+
handler: (ctx) => logger.info(`[heartbeat] pacemaker tick — beat ${ctx.beat} (load ${ctx.load1 ?? '?'})`),
|
|
1080
|
+
});
|
|
1081
|
+
// Dreaming — consolidate short-term sensory memory every N beats.
|
|
1082
|
+
const dreamEvery = Math.max(1, Number(process.env.CODEBUDDY_DREAM_EVERY ?? 30));
|
|
1083
|
+
heart.register({
|
|
1084
|
+
name: 'dreaming',
|
|
1085
|
+
everyBeats: dreamEvery,
|
|
1086
|
+
handler: async () => {
|
|
1087
|
+
const { runDreamingPass } = await import('../sensory/dreaming.js');
|
|
1088
|
+
await runDreamingPass();
|
|
1089
|
+
},
|
|
1090
|
+
});
|
|
1091
|
+
heart.start();
|
|
1092
|
+
sensoryTeardown.push(() => heart.stop());
|
|
1093
|
+
logger.info(`Sensory bridge: Enabled (buddy-sense → event bus; heartbeat treatments every ${everyBeats} beats)`);
|
|
1094
|
+
}
|
|
1095
|
+
catch (err) {
|
|
1096
|
+
logger.warn(`Sensory bridge failed to start: ${err instanceof Error ? err.message : String(err)}`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1006
1099
|
logger.info(`Auth: ${config.authEnabled ? 'Enabled' : 'Disabled'}`);
|
|
1007
1100
|
if (!isLoopbackHost(config.host)) {
|
|
1008
1101
|
logger.warn(`Server is bound to ${config.host} (non-loopback) and is reachable from the network. ` +
|
|
@@ -1057,6 +1150,17 @@ export async function startServer(userConfig = {}) {
|
|
|
1057
1150
|
* Stop the server gracefully
|
|
1058
1151
|
*/
|
|
1059
1152
|
export async function stopServer(server) {
|
|
1153
|
+
// Tear down the sensory layer (WS bridge, bus listeners, heartbeat scheduler) so
|
|
1154
|
+
// an in-process restart doesn't leak listeners or EADDRINUSE the bridge port.
|
|
1155
|
+
for (const teardown of sensoryTeardown.splice(0)) {
|
|
1156
|
+
try {
|
|
1157
|
+
await teardown();
|
|
1158
|
+
}
|
|
1159
|
+
catch {
|
|
1160
|
+
/* never throw on shutdown */
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
sensoryWired = false;
|
|
1060
1164
|
return new Promise((resolve, reject) => {
|
|
1061
1165
|
// Phase (d).9 — cancel the heartbeat timer so it doesn't keep
|
|
1062
1166
|
// emitting against a half-shut server. Idempotent.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
2
3
|
export declare const mobileRouter: import("express-serve-static-core").Router;
|
|
3
4
|
export declare let activePairingCode: string;
|
|
4
5
|
export declare let activePairingCodeExpiresAt: number;
|
|
@@ -10,11 +11,11 @@ export declare const activeTokens: Map<string, {
|
|
|
10
11
|
* A follow-up prompt review draft. The mobile device (or a draft-only call) can
|
|
11
12
|
* *propose* a prompt, but it never executes: it lands here as
|
|
12
13
|
* `needs_local_operator` and only a local operator (loopback) can `approve` or
|
|
13
|
-
* `cancel` it.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* `cancel` it. When `CODEBUDDY_MOBILE_ALLOW_DISPATCH=true`, an approved draft
|
|
15
|
+
* can optionally be dispatched to the agent for execution. When the env var is
|
|
16
|
+
* OFF (default), approval remains a review-gate marker only.
|
|
16
17
|
*/
|
|
17
|
-
export type FollowupDraftStatus = 'needs_local_operator' | 'approved' | 'cancelled';
|
|
18
|
+
export type FollowupDraftStatus = 'needs_local_operator' | 'approved' | 'cancelled' | 'dispatched' | 'completed' | 'failed';
|
|
18
19
|
export interface FollowupDraft {
|
|
19
20
|
id: string;
|
|
20
21
|
prompt: string;
|
|
@@ -24,9 +25,16 @@ export interface FollowupDraft {
|
|
|
24
25
|
approvedBy?: string;
|
|
25
26
|
approvedAt?: number;
|
|
26
27
|
cancelledAt?: number;
|
|
28
|
+
dispatchedAt?: number;
|
|
29
|
+
completedAt?: number;
|
|
30
|
+
failedAt?: number;
|
|
31
|
+
failureReason?: string;
|
|
27
32
|
[key: string]: unknown;
|
|
28
33
|
}
|
|
29
34
|
export declare const followupDrafts: FollowupDraft[];
|
|
35
|
+
export declare const mobileEventBus: EventEmitter<[never]>;
|
|
36
|
+
/** Check whether dispatch is enabled via environment variable. */
|
|
37
|
+
export declare function isDispatchEnabled(): boolean;
|
|
30
38
|
/**
|
|
31
39
|
* Decide whether a request originates from the loopback interface. We read the
|
|
32
40
|
* raw socket address (`req.socket.remoteAddress`) on purpose: `req.ip` and the
|