@supen-ai/cli 0.1.6
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 +235 -0
- package/daemon/dist/acp-client.d.ts +42 -0
- package/daemon/dist/acp-client.d.ts.map +1 -0
- package/daemon/dist/acp-client.js +149 -0
- package/daemon/dist/acp-client.js.map +1 -0
- package/daemon/dist/acp-types.d.ts +98 -0
- package/daemon/dist/acp-types.d.ts.map +1 -0
- package/daemon/dist/acp-types.js +2 -0
- package/daemon/dist/acp-types.js.map +1 -0
- package/daemon/dist/agent-sdk/app-server-approvals.d.ts +24 -0
- package/daemon/dist/agent-sdk/app-server-approvals.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/app-server-approvals.js +99 -0
- package/daemon/dist/agent-sdk/app-server-approvals.js.map +1 -0
- package/daemon/dist/agent-sdk/app-server-stream.d.ts +8 -0
- package/daemon/dist/agent-sdk/app-server-stream.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/app-server-stream.js +328 -0
- package/daemon/dist/agent-sdk/app-server-stream.js.map +1 -0
- package/daemon/dist/agent-sdk/driver-output-ui.d.ts +9 -0
- package/daemon/dist/agent-sdk/driver-output-ui.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/driver-output-ui.js +290 -0
- package/daemon/dist/agent-sdk/driver-output-ui.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.d.ts +21 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.js +488 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.d.ts +5 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.js +7 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.d.ts +20 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.js +264 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.d.ts +29 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.js +24 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.d.ts +25 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.js +58 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.d.ts +41 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.js +77 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.d.ts +5 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.js +7 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts +12 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js +484 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.d.ts +28 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.js +219 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.d.ts +9 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.js +82 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.d.ts +9 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.js +86 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/driver.d.ts +60 -0
- package/daemon/dist/agent-sdk/drivers/driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/driver.js +2 -0
- package/daemon/dist/agent-sdk/drivers/driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.d.ts +17 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.js +255 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/registry.d.ts +21 -0
- package/daemon/dist/agent-sdk/drivers/registry.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/registry.js +167 -0
- package/daemon/dist/agent-sdk/drivers/registry.js.map +1 -0
- package/daemon/dist/agent-sdk/index.d.ts +24 -0
- package/daemon/dist/agent-sdk/index.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/index.js +19 -0
- package/daemon/dist/agent-sdk/index.js.map +1 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.d.ts +17 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.js +2 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.js.map +1 -0
- package/daemon/dist/agent-sdk/memory/filesystem.d.ts +7 -0
- package/daemon/dist/agent-sdk/memory/filesystem.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/memory/filesystem.js +133 -0
- package/daemon/dist/agent-sdk/memory/filesystem.js.map +1 -0
- package/daemon/dist/agent-sdk/memory/subsystem.d.ts +54 -0
- package/daemon/dist/agent-sdk/memory/subsystem.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/memory/subsystem.js +106 -0
- package/daemon/dist/agent-sdk/memory/subsystem.js.map +1 -0
- package/daemon/dist/agent-sdk/session-events.d.ts +59 -0
- package/daemon/dist/agent-sdk/session-events.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/session-events.js +104 -0
- package/daemon/dist/agent-sdk/session-events.js.map +1 -0
- package/daemon/dist/agent-sdk/session-manager.d.ts +28 -0
- package/daemon/dist/agent-sdk/session-manager.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/session-manager.js +54 -0
- package/daemon/dist/agent-sdk/session-manager.js.map +1 -0
- package/daemon/dist/agent-sdk/types.d.ts +110 -0
- package/daemon/dist/agent-sdk/types.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/types.js +2 -0
- package/daemon/dist/agent-sdk/types.js.map +1 -0
- package/daemon/dist/automation-event-listener.d.ts +27 -0
- package/daemon/dist/automation-event-listener.d.ts.map +1 -0
- package/daemon/dist/automation-event-listener.js +342 -0
- package/daemon/dist/automation-event-listener.js.map +1 -0
- package/daemon/dist/automation-runner.d.ts +17 -0
- package/daemon/dist/automation-runner.d.ts.map +1 -0
- package/daemon/dist/automation-runner.js +592 -0
- package/daemon/dist/automation-runner.js.map +1 -0
- package/daemon/dist/autonomy/feedback-memory.d.ts +8 -0
- package/daemon/dist/autonomy/feedback-memory.d.ts.map +1 -0
- package/daemon/dist/autonomy/feedback-memory.js +23 -0
- package/daemon/dist/autonomy/feedback-memory.js.map +1 -0
- package/daemon/dist/autonomy/intent-router.d.ts +10 -0
- package/daemon/dist/autonomy/intent-router.d.ts.map +1 -0
- package/daemon/dist/autonomy/intent-router.js +72 -0
- package/daemon/dist/autonomy/intent-router.js.map +1 -0
- package/daemon/dist/autonomy/memory-rules.d.ts +35 -0
- package/daemon/dist/autonomy/memory-rules.d.ts.map +1 -0
- package/daemon/dist/autonomy/memory-rules.js +117 -0
- package/daemon/dist/autonomy/memory-rules.js.map +1 -0
- package/daemon/dist/autonomy/proof-packet.d.ts +17 -0
- package/daemon/dist/autonomy/proof-packet.d.ts.map +1 -0
- package/daemon/dist/autonomy/proof-packet.js +28 -0
- package/daemon/dist/autonomy/proof-packet.js.map +1 -0
- package/daemon/dist/autonomy/session-autonomy.d.ts +18 -0
- package/daemon/dist/autonomy/session-autonomy.d.ts.map +1 -0
- package/daemon/dist/autonomy/session-autonomy.js +71 -0
- package/daemon/dist/autonomy/session-autonomy.js.map +1 -0
- package/daemon/dist/autonomy/source-inventory.d.ts +3 -0
- package/daemon/dist/autonomy/source-inventory.d.ts.map +1 -0
- package/daemon/dist/autonomy/source-inventory.js +84 -0
- package/daemon/dist/autonomy/source-inventory.js.map +1 -0
- package/daemon/dist/autonomy/types.d.ts +20 -0
- package/daemon/dist/autonomy/types.d.ts.map +1 -0
- package/daemon/dist/autonomy/types.js +2 -0
- package/daemon/dist/autonomy/types.js.map +1 -0
- package/daemon/dist/bin/mcp-os.d.ts +3 -0
- package/daemon/dist/bin/mcp-os.d.ts.map +1 -0
- package/daemon/dist/bin/mcp-os.js +63 -0
- package/daemon/dist/bin/mcp-os.js.map +1 -0
- package/daemon/dist/bin/mcp-scheduler.d.ts +3 -0
- package/daemon/dist/bin/mcp-scheduler.d.ts.map +1 -0
- package/daemon/dist/bin/mcp-scheduler.js +90 -0
- package/daemon/dist/bin/mcp-scheduler.js.map +1 -0
- package/daemon/dist/bin/supen-sys.d.ts +3 -0
- package/daemon/dist/bin/supen-sys.d.ts.map +1 -0
- package/daemon/dist/bin/supen-sys.js +38 -0
- package/daemon/dist/bin/supen-sys.js.map +1 -0
- package/daemon/dist/bootstrap/hub-bootstrap.d.ts +2 -0
- package/daemon/dist/bootstrap/hub-bootstrap.d.ts.map +1 -0
- package/daemon/dist/bootstrap/hub-bootstrap.js +409 -0
- package/daemon/dist/bootstrap/hub-bootstrap.js.map +1 -0
- package/daemon/dist/bootstrap/skill-bootstrap.d.ts +2 -0
- package/daemon/dist/bootstrap/skill-bootstrap.d.ts.map +1 -0
- package/daemon/dist/bootstrap/skill-bootstrap.js +92 -0
- package/daemon/dist/bootstrap/skill-bootstrap.js.map +1 -0
- package/daemon/dist/channels/acp.d.ts +23 -0
- package/daemon/dist/channels/acp.d.ts.map +1 -0
- package/daemon/dist/channels/acp.js +916 -0
- package/daemon/dist/channels/acp.js.map +1 -0
- package/daemon/dist/channels/base.d.ts +38 -0
- package/daemon/dist/channels/base.d.ts.map +1 -0
- package/daemon/dist/channels/base.js +56 -0
- package/daemon/dist/channels/base.js.map +1 -0
- package/daemon/dist/channels/http-routes.d.ts +44 -0
- package/daemon/dist/channels/http-routes.d.ts.map +1 -0
- package/daemon/dist/channels/http-routes.js +688 -0
- package/daemon/dist/channels/http-routes.js.map +1 -0
- package/daemon/dist/channels/http.d.ts +16 -0
- package/daemon/dist/channels/http.d.ts.map +1 -0
- package/daemon/dist/channels/http.js +84 -0
- package/daemon/dist/channels/http.js.map +1 -0
- package/daemon/dist/channels/index.d.ts +3 -0
- package/daemon/dist/channels/index.d.ts.map +1 -0
- package/daemon/dist/channels/index.js +7 -0
- package/daemon/dist/channels/index.js.map +1 -0
- package/daemon/dist/channels/registry.d.ts +18 -0
- package/daemon/dist/channels/registry.d.ts.map +1 -0
- package/daemon/dist/channels/registry.js +11 -0
- package/daemon/dist/channels/registry.js.map +1 -0
- package/daemon/dist/commands/builtin.d.ts +7 -0
- package/daemon/dist/commands/builtin.d.ts.map +1 -0
- package/daemon/dist/commands/builtin.js +111 -0
- package/daemon/dist/commands/builtin.js.map +1 -0
- package/daemon/dist/commands/catalog.d.ts +47 -0
- package/daemon/dist/commands/catalog.d.ts.map +1 -0
- package/daemon/dist/commands/catalog.js +487 -0
- package/daemon/dist/commands/catalog.js.map +1 -0
- package/daemon/dist/core/app.d.ts +18 -0
- package/daemon/dist/core/app.d.ts.map +1 -0
- package/daemon/dist/core/app.js +49 -0
- package/daemon/dist/core/app.js.map +1 -0
- package/daemon/dist/core/automation-timing.d.ts +10 -0
- package/daemon/dist/core/automation-timing.d.ts.map +1 -0
- package/daemon/dist/core/automation-timing.js +211 -0
- package/daemon/dist/core/automation-timing.js.map +1 -0
- package/daemon/dist/core/codex-subscription.d.ts +8 -0
- package/daemon/dist/core/codex-subscription.d.ts.map +1 -0
- package/daemon/dist/core/codex-subscription.js +150 -0
- package/daemon/dist/core/codex-subscription.js.map +1 -0
- package/daemon/dist/core/command-hub.d.ts +22 -0
- package/daemon/dist/core/command-hub.d.ts.map +1 -0
- package/daemon/dist/core/command-hub.js +59 -0
- package/daemon/dist/core/command-hub.js.map +1 -0
- package/daemon/dist/core/config.d.ts +146 -0
- package/daemon/dist/core/config.d.ts.map +1 -0
- package/daemon/dist/core/config.js +663 -0
- package/daemon/dist/core/config.js.map +1 -0
- package/daemon/dist/core/control-commands.d.ts +17 -0
- package/daemon/dist/core/control-commands.d.ts.map +1 -0
- package/daemon/dist/core/control-commands.js +35 -0
- package/daemon/dist/core/control-commands.js.map +1 -0
- package/daemon/dist/core/control-log.d.ts +17 -0
- package/daemon/dist/core/control-log.d.ts.map +1 -0
- package/daemon/dist/core/control-log.js +67 -0
- package/daemon/dist/core/control-log.js.map +1 -0
- package/daemon/dist/core/cortex.d.ts +53 -0
- package/daemon/dist/core/cortex.d.ts.map +1 -0
- package/daemon/dist/core/cortex.js +1690 -0
- package/daemon/dist/core/cortex.js.map +1 -0
- package/daemon/dist/core/daemon-lock.d.ts +16 -0
- package/daemon/dist/core/daemon-lock.d.ts.map +1 -0
- package/daemon/dist/core/daemon-lock.js +285 -0
- package/daemon/dist/core/daemon-lock.js.map +1 -0
- package/daemon/dist/core/dispatcher.d.ts +42 -0
- package/daemon/dist/core/dispatcher.d.ts.map +1 -0
- package/daemon/dist/core/dispatcher.js +173 -0
- package/daemon/dist/core/dispatcher.js.map +1 -0
- package/daemon/dist/core/enrollment.d.ts +41 -0
- package/daemon/dist/core/enrollment.d.ts.map +1 -0
- package/daemon/dist/core/enrollment.js +195 -0
- package/daemon/dist/core/enrollment.js.map +1 -0
- package/daemon/dist/core/env.d.ts +109 -0
- package/daemon/dist/core/env.d.ts.map +1 -0
- package/daemon/dist/core/env.js +329 -0
- package/daemon/dist/core/env.js.map +1 -0
- package/daemon/dist/core/gateway-config.d.ts +16 -0
- package/daemon/dist/core/gateway-config.d.ts.map +1 -0
- package/daemon/dist/core/gateway-config.js +103 -0
- package/daemon/dist/core/gateway-config.js.map +1 -0
- package/daemon/dist/core/gateway-protocol.d.ts +80 -0
- package/daemon/dist/core/gateway-protocol.d.ts.map +1 -0
- package/daemon/dist/core/gateway-protocol.js +2 -0
- package/daemon/dist/core/gateway-protocol.js.map +1 -0
- package/daemon/dist/core/gateway-routing-config.d.ts +21 -0
- package/daemon/dist/core/gateway-routing-config.d.ts.map +1 -0
- package/daemon/dist/core/gateway-routing-config.js +56 -0
- package/daemon/dist/core/gateway-routing-config.js.map +1 -0
- package/daemon/dist/core/gateway.d.ts +124 -0
- package/daemon/dist/core/gateway.d.ts.map +1 -0
- package/daemon/dist/core/gateway.js +887 -0
- package/daemon/dist/core/gateway.js.map +1 -0
- package/daemon/dist/core/hub-snapshot.d.ts +42 -0
- package/daemon/dist/core/hub-snapshot.d.ts.map +1 -0
- package/daemon/dist/core/hub-snapshot.js +125 -0
- package/daemon/dist/core/hub-snapshot.js.map +1 -0
- package/daemon/dist/core/interrupts.d.ts +7 -0
- package/daemon/dist/core/interrupts.d.ts.map +1 -0
- package/daemon/dist/core/interrupts.js +28 -0
- package/daemon/dist/core/interrupts.js.map +1 -0
- package/daemon/dist/core/logger.d.ts +3 -0
- package/daemon/dist/core/logger.d.ts.map +1 -0
- package/daemon/dist/core/logger.js +91 -0
- package/daemon/dist/core/logger.js.map +1 -0
- package/daemon/dist/core/loop-guard.d.ts +27 -0
- package/daemon/dist/core/loop-guard.d.ts.map +1 -0
- package/daemon/dist/core/loop-guard.js +47 -0
- package/daemon/dist/core/loop-guard.js.map +1 -0
- package/daemon/dist/core/observable-logging.d.ts +43 -0
- package/daemon/dist/core/observable-logging.d.ts.map +1 -0
- package/daemon/dist/core/observable-logging.js +77 -0
- package/daemon/dist/core/observable-logging.js.map +1 -0
- package/daemon/dist/core/pairing.d.ts +51 -0
- package/daemon/dist/core/pairing.d.ts.map +1 -0
- package/daemon/dist/core/pairing.js +207 -0
- package/daemon/dist/core/pairing.js.map +1 -0
- package/daemon/dist/core/progress.d.ts +32 -0
- package/daemon/dist/core/progress.d.ts.map +1 -0
- package/daemon/dist/core/progress.js +145 -0
- package/daemon/dist/core/progress.js.map +1 -0
- package/daemon/dist/core/protocol-adapter.d.ts +14 -0
- package/daemon/dist/core/protocol-adapter.d.ts.map +1 -0
- package/daemon/dist/core/protocol-adapter.js +472 -0
- package/daemon/dist/core/protocol-adapter.js.map +1 -0
- package/daemon/dist/core/sdk-wrapper.d.ts +36 -0
- package/daemon/dist/core/sdk-wrapper.d.ts.map +1 -0
- package/daemon/dist/core/sdk-wrapper.js +533 -0
- package/daemon/dist/core/sdk-wrapper.js.map +1 -0
- package/daemon/dist/core/security.d.ts +10 -0
- package/daemon/dist/core/security.d.ts.map +1 -0
- package/daemon/dist/core/security.js +95 -0
- package/daemon/dist/core/security.js.map +1 -0
- package/daemon/dist/core/space-env.d.ts +15 -0
- package/daemon/dist/core/space-env.d.ts.map +1 -0
- package/daemon/dist/core/space-env.js +182 -0
- package/daemon/dist/core/space-env.js.map +1 -0
- package/daemon/dist/core/status-inspector.d.ts +31 -0
- package/daemon/dist/core/status-inspector.d.ts.map +1 -0
- package/daemon/dist/core/status-inspector.js +35 -0
- package/daemon/dist/core/status-inspector.js.map +1 -0
- package/daemon/dist/core/storage-paths.d.ts +30 -0
- package/daemon/dist/core/storage-paths.d.ts.map +1 -0
- package/daemon/dist/core/storage-paths.js +84 -0
- package/daemon/dist/core/storage-paths.js.map +1 -0
- package/daemon/dist/core/store.d.ts +256 -0
- package/daemon/dist/core/store.d.ts.map +1 -0
- package/daemon/dist/core/store.js +2956 -0
- package/daemon/dist/core/store.js.map +1 -0
- package/daemon/dist/core/streaming.d.ts +24 -0
- package/daemon/dist/core/streaming.d.ts.map +1 -0
- package/daemon/dist/core/streaming.js +57 -0
- package/daemon/dist/core/streaming.js.map +1 -0
- package/daemon/dist/core/task-artifacts.d.ts +17 -0
- package/daemon/dist/core/task-artifacts.d.ts.map +1 -0
- package/daemon/dist/core/task-artifacts.js +104 -0
- package/daemon/dist/core/task-artifacts.js.map +1 -0
- package/daemon/dist/core/thread-event-log.d.ts +54 -0
- package/daemon/dist/core/thread-event-log.d.ts.map +1 -0
- package/daemon/dist/core/thread-event-log.js +218 -0
- package/daemon/dist/core/thread-event-log.js.map +1 -0
- package/daemon/dist/core/thread-runtime-state.d.ts +53 -0
- package/daemon/dist/core/thread-runtime-state.d.ts.map +1 -0
- package/daemon/dist/core/thread-runtime-state.js +271 -0
- package/daemon/dist/core/thread-runtime-state.js.map +1 -0
- package/daemon/dist/core/types.d.ts +552 -0
- package/daemon/dist/core/types.d.ts.map +1 -0
- package/daemon/dist/core/types.js +2 -0
- package/daemon/dist/core/types.js.map +1 -0
- package/daemon/dist/core/utils.d.ts +5 -0
- package/daemon/dist/core/utils.d.ts.map +1 -0
- package/daemon/dist/core/utils.js +35 -0
- package/daemon/dist/core/utils.js.map +1 -0
- package/daemon/dist/http/command-catalog.d.ts +3 -0
- package/daemon/dist/http/command-catalog.d.ts.map +1 -0
- package/daemon/dist/http/command-catalog.js +2 -0
- package/daemon/dist/http/command-catalog.js.map +1 -0
- package/daemon/dist/http/context.d.ts +18 -0
- package/daemon/dist/http/context.d.ts.map +1 -0
- package/daemon/dist/http/context.js +121 -0
- package/daemon/dist/http/context.js.map +1 -0
- package/daemon/dist/http/office-preview.d.ts +10 -0
- package/daemon/dist/http/office-preview.d.ts.map +1 -0
- package/daemon/dist/http/office-preview.js +234 -0
- package/daemon/dist/http/office-preview.js.map +1 -0
- package/daemon/dist/http/response.d.ts +5 -0
- package/daemon/dist/http/response.d.ts.map +1 -0
- package/daemon/dist/http/response.js +37 -0
- package/daemon/dist/http/response.js.map +1 -0
- package/daemon/dist/http/router.d.ts +4 -0
- package/daemon/dist/http/router.d.ts.map +1 -0
- package/daemon/dist/http/router.js +57 -0
- package/daemon/dist/http/router.js.map +1 -0
- package/daemon/dist/http/routes/agents.d.ts +4 -0
- package/daemon/dist/http/routes/agents.d.ts.map +1 -0
- package/daemon/dist/http/routes/agents.js +747 -0
- package/daemon/dist/http/routes/agents.js.map +1 -0
- package/daemon/dist/http/routes/automations.d.ts +6 -0
- package/daemon/dist/http/routes/automations.d.ts.map +1 -0
- package/daemon/dist/http/routes/automations.js +530 -0
- package/daemon/dist/http/routes/automations.js.map +1 -0
- package/daemon/dist/http/routes/autonomy.d.ts +4 -0
- package/daemon/dist/http/routes/autonomy.d.ts.map +1 -0
- package/daemon/dist/http/routes/autonomy.js +78 -0
- package/daemon/dist/http/routes/autonomy.js.map +1 -0
- package/daemon/dist/http/routes/chat-input.d.ts +18 -0
- package/daemon/dist/http/routes/chat-input.d.ts.map +1 -0
- package/daemon/dist/http/routes/chat-input.js +122 -0
- package/daemon/dist/http/routes/chat-input.js.map +1 -0
- package/daemon/dist/http/routes/plugins.d.ts +5 -0
- package/daemon/dist/http/routes/plugins.d.ts.map +1 -0
- package/daemon/dist/http/routes/plugins.js +221 -0
- package/daemon/dist/http/routes/plugins.js.map +1 -0
- package/daemon/dist/http/routes/rpc.d.ts +28 -0
- package/daemon/dist/http/routes/rpc.d.ts.map +1 -0
- package/daemon/dist/http/routes/rpc.js +790 -0
- package/daemon/dist/http/routes/rpc.js.map +1 -0
- package/daemon/dist/http/routes/sessions.d.ts +11 -0
- package/daemon/dist/http/routes/sessions.d.ts.map +1 -0
- package/daemon/dist/http/routes/sessions.js +963 -0
- package/daemon/dist/http/routes/sessions.js.map +1 -0
- package/daemon/dist/http/routes/skills.d.ts +5 -0
- package/daemon/dist/http/routes/skills.d.ts.map +1 -0
- package/daemon/dist/http/routes/skills.js +420 -0
- package/daemon/dist/http/routes/skills.js.map +1 -0
- package/daemon/dist/http/routes/system.d.ts +64 -0
- package/daemon/dist/http/routes/system.d.ts.map +1 -0
- package/daemon/dist/http/routes/system.js +2676 -0
- package/daemon/dist/http/routes/system.js.map +1 -0
- package/daemon/dist/http/stream.d.ts +11 -0
- package/daemon/dist/http/stream.d.ts.map +1 -0
- package/daemon/dist/http/stream.js +100 -0
- package/daemon/dist/http/stream.js.map +1 -0
- package/daemon/dist/http/thread-stream.d.ts +10 -0
- package/daemon/dist/http/thread-stream.d.ts.map +1 -0
- package/daemon/dist/http/thread-stream.js +50 -0
- package/daemon/dist/http/thread-stream.js.map +1 -0
- package/daemon/dist/http/thread-title.d.ts +12 -0
- package/daemon/dist/http/thread-title.d.ts.map +1 -0
- package/daemon/dist/http/thread-title.js +122 -0
- package/daemon/dist/http/thread-title.js.map +1 -0
- package/daemon/dist/http/utils.d.ts +2 -0
- package/daemon/dist/http/utils.d.ts.map +1 -0
- package/daemon/dist/http/utils.js +18 -0
- package/daemon/dist/http/utils.js.map +1 -0
- package/daemon/dist/http/websocket.d.ts +5 -0
- package/daemon/dist/http/websocket.d.ts.map +1 -0
- package/daemon/dist/http/websocket.js +100 -0
- package/daemon/dist/http/websocket.js.map +1 -0
- package/daemon/dist/index.d.ts +35 -0
- package/daemon/dist/index.d.ts.map +1 -0
- package/daemon/dist/index.js +1582 -0
- package/daemon/dist/index.js.map +1 -0
- package/daemon/dist/mcp/aggregate-config.d.ts +16 -0
- package/daemon/dist/mcp/aggregate-config.d.ts.map +1 -0
- package/daemon/dist/mcp/aggregate-config.js +97 -0
- package/daemon/dist/mcp/aggregate-config.js.map +1 -0
- package/daemon/dist/mcp/client.d.ts +94 -0
- package/daemon/dist/mcp/client.d.ts.map +1 -0
- package/daemon/dist/mcp/client.js +207 -0
- package/daemon/dist/mcp/client.js.map +1 -0
- package/daemon/dist/mcp/default-servers.d.ts +35 -0
- package/daemon/dist/mcp/default-servers.d.ts.map +1 -0
- package/daemon/dist/mcp/default-servers.js +209 -0
- package/daemon/dist/mcp/default-servers.js.map +1 -0
- package/daemon/dist/mcp/gateway-client.d.ts +58 -0
- package/daemon/dist/mcp/gateway-client.d.ts.map +1 -0
- package/daemon/dist/mcp/gateway-client.js +181 -0
- package/daemon/dist/mcp/gateway-client.js.map +1 -0
- package/daemon/dist/mcp/index.d.ts +26 -0
- package/daemon/dist/mcp/index.d.ts.map +1 -0
- package/daemon/dist/mcp/index.js +50 -0
- package/daemon/dist/mcp/index.js.map +1 -0
- package/daemon/dist/mcp/settings.d.ts +3 -0
- package/daemon/dist/mcp/settings.d.ts.map +1 -0
- package/daemon/dist/mcp/settings.js +60 -0
- package/daemon/dist/mcp/settings.js.map +1 -0
- package/daemon/dist/mcp/tools.d.ts +21 -0
- package/daemon/dist/mcp/tools.d.ts.map +1 -0
- package/daemon/dist/mcp/tools.js +136 -0
- package/daemon/dist/mcp/tools.js.map +1 -0
- package/daemon/dist/plugins/catalog.d.ts +10 -0
- package/daemon/dist/plugins/catalog.d.ts.map +1 -0
- package/daemon/dist/plugins/catalog.js +304 -0
- package/daemon/dist/plugins/catalog.js.map +1 -0
- package/daemon/dist/plugins/hub.d.ts +42 -0
- package/daemon/dist/plugins/hub.d.ts.map +1 -0
- package/daemon/dist/plugins/hub.js +812 -0
- package/daemon/dist/plugins/hub.js.map +1 -0
- package/daemon/dist/plugins/types.d.ts +144 -0
- package/daemon/dist/plugins/types.d.ts.map +1 -0
- package/daemon/dist/plugins/types.js +2 -0
- package/daemon/dist/plugins/types.js.map +1 -0
- package/daemon/dist/router.d.ts +13 -0
- package/daemon/dist/router.d.ts.map +1 -0
- package/daemon/dist/router.js +43 -0
- package/daemon/dist/router.js.map +1 -0
- package/daemon/dist/skills/adapter.d.ts +4 -0
- package/daemon/dist/skills/adapter.d.ts.map +1 -0
- package/daemon/dist/skills/adapter.js +141 -0
- package/daemon/dist/skills/adapter.js.map +1 -0
- package/daemon/dist/skills/allowlist.d.ts +20 -0
- package/daemon/dist/skills/allowlist.d.ts.map +1 -0
- package/daemon/dist/skills/allowlist.js +52 -0
- package/daemon/dist/skills/allowlist.js.map +1 -0
- package/daemon/dist/skills/catalog.d.ts +26 -0
- package/daemon/dist/skills/catalog.d.ts.map +1 -0
- package/daemon/dist/skills/catalog.js +274 -0
- package/daemon/dist/skills/catalog.js.map +1 -0
- package/daemon/dist/skills/claude_code.d.ts +25 -0
- package/daemon/dist/skills/claude_code.d.ts.map +1 -0
- package/daemon/dist/skills/claude_code.js +49 -0
- package/daemon/dist/skills/claude_code.js.map +1 -0
- package/daemon/dist/skills/commands.d.ts +3 -0
- package/daemon/dist/skills/commands.d.ts.map +1 -0
- package/daemon/dist/skills/commands.js +689 -0
- package/daemon/dist/skills/commands.js.map +1 -0
- package/daemon/dist/skills/enabled.d.ts +7 -0
- package/daemon/dist/skills/enabled.d.ts.map +1 -0
- package/daemon/dist/skills/enabled.js +37 -0
- package/daemon/dist/skills/enabled.js.map +1 -0
- package/daemon/dist/skills/hub.d.ts +35 -0
- package/daemon/dist/skills/hub.d.ts.map +1 -0
- package/daemon/dist/skills/hub.js +574 -0
- package/daemon/dist/skills/hub.js.map +1 -0
- package/daemon/dist/skills/loader.d.ts +24 -0
- package/daemon/dist/skills/loader.d.ts.map +1 -0
- package/daemon/dist/skills/loader.js +693 -0
- package/daemon/dist/skills/loader.js.map +1 -0
- package/daemon/dist/skills/mcp-config.d.ts +8 -0
- package/daemon/dist/skills/mcp-config.d.ts.map +1 -0
- package/daemon/dist/skills/mcp-config.js +110 -0
- package/daemon/dist/skills/mcp-config.js.map +1 -0
- package/daemon/dist/skills/parser.d.ts +3 -0
- package/daemon/dist/skills/parser.d.ts.map +1 -0
- package/daemon/dist/skills/parser.js +279 -0
- package/daemon/dist/skills/parser.js.map +1 -0
- package/daemon/dist/skills/registry.d.ts +2 -0
- package/daemon/dist/skills/registry.d.ts.map +1 -0
- package/daemon/dist/skills/registry.js +2 -0
- package/daemon/dist/skills/registry.js.map +1 -0
- package/daemon/dist/skills/runtime-contract.d.ts +3 -0
- package/daemon/dist/skills/runtime-contract.d.ts.map +1 -0
- package/daemon/dist/skills/runtime-contract.js +92 -0
- package/daemon/dist/skills/runtime-contract.js.map +1 -0
- package/daemon/dist/skills/runtime.d.ts +28 -0
- package/daemon/dist/skills/runtime.d.ts.map +1 -0
- package/daemon/dist/skills/runtime.js +286 -0
- package/daemon/dist/skills/runtime.js.map +1 -0
- package/daemon/dist/skills/types.d.ts +228 -0
- package/daemon/dist/skills/types.d.ts.map +1 -0
- package/daemon/dist/skills/types.js +2 -0
- package/daemon/dist/skills/types.js.map +1 -0
- package/daemon/dist/start.d.ts +6 -0
- package/daemon/dist/start.d.ts.map +1 -0
- package/daemon/dist/start.js +19 -0
- package/daemon/dist/start.js.map +1 -0
- package/daemon/dist/sub-agent.d.ts +46 -0
- package/daemon/dist/sub-agent.d.ts.map +1 -0
- package/daemon/dist/sub-agent.js +120 -0
- package/daemon/dist/sub-agent.js.map +1 -0
- package/daemon/dist/sync/supabase-sync.d.ts +16 -0
- package/daemon/dist/sync/supabase-sync.d.ts.map +1 -0
- package/daemon/dist/sync/supabase-sync.js +240 -0
- package/daemon/dist/sync/supabase-sync.js.map +1 -0
- package/daemon/dist/task-executor.d.ts +6 -0
- package/daemon/dist/task-executor.d.ts.map +1 -0
- package/daemon/dist/task-executor.js +31 -0
- package/daemon/dist/task-executor.js.map +1 -0
- package/daemon/dist/tools/automations.d.ts +57 -0
- package/daemon/dist/tools/automations.d.ts.map +1 -0
- package/daemon/dist/tools/automations.js +94 -0
- package/daemon/dist/tools/automations.js.map +1 -0
- package/daemon/dist/tools/built-ins.d.ts +112 -0
- package/daemon/dist/tools/built-ins.d.ts.map +1 -0
- package/daemon/dist/tools/built-ins.js +251 -0
- package/daemon/dist/tools/built-ins.js.map +1 -0
- package/daemon/dist/tools/index.d.ts +287 -0
- package/daemon/dist/tools/index.d.ts.map +1 -0
- package/daemon/dist/tools/index.js +86 -0
- package/daemon/dist/tools/index.js.map +1 -0
- package/daemon/dist/tools/shell.d.ts +15 -0
- package/daemon/dist/tools/shell.d.ts.map +1 -0
- package/daemon/dist/tools/shell.js +46 -0
- package/daemon/dist/tools/shell.js.map +1 -0
- package/daemon/dist/tools/skill-tools.d.ts +23 -0
- package/daemon/dist/tools/skill-tools.d.ts.map +1 -0
- package/daemon/dist/tools/skill-tools.js +64 -0
- package/daemon/dist/tools/skill-tools.js.map +1 -0
- package/daemon/dist/tools/system.d.ts +36 -0
- package/daemon/dist/tools/system.d.ts.map +1 -0
- package/daemon/dist/tools/system.js +54 -0
- package/daemon/dist/tools/system.js.map +1 -0
- package/daemon/dist/tools/types.d.ts +11 -0
- package/daemon/dist/tools/types.d.ts.map +1 -0
- package/daemon/dist/tools/types.js +2 -0
- package/daemon/dist/tools/types.js.map +1 -0
- package/daemon/scripts/browser-smoke.mjs +125 -0
- package/daemon/scripts/supen-daemon.js +15 -0
- package/dist/agent.d.ts +11 -0
- package/dist/agent.js +159 -0
- package/dist/agent.js.map +1 -0
- package/dist/auth/login.d.ts +20 -0
- package/dist/auth/login.js +151 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/logout.d.ts +5 -0
- package/dist/auth/logout.js +19 -0
- package/dist/auth/logout.js.map +1 -0
- package/dist/auth/store.d.ts +37 -0
- package/dist/auth/store.js +80 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/auth/whoami.d.ts +5 -0
- package/dist/auth/whoami.js +24 -0
- package/dist/auth/whoami.js.map +1 -0
- package/dist/backend.d.ts +8 -0
- package/dist/backend.js +148 -0
- package/dist/backend.js.map +1 -0
- package/dist/bootstrap.d.ts +13 -0
- package/dist/bootstrap.js +230 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/chat.d.ts +13 -0
- package/dist/chat.js +255 -0
- package/dist/chat.js.map +1 -0
- package/dist/commands.d.ts +48 -0
- package/dist/commands.js +273 -0
- package/dist/commands.js.map +1 -0
- package/dist/computer.d.ts +2 -0
- package/dist/computer.js +510 -0
- package/dist/computer.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +149 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon-manage.d.ts +29 -0
- package/dist/daemon-manage.js +255 -0
- package/dist/daemon-manage.js.map +1 -0
- package/dist/daemon.d.ts +15 -0
- package/dist/daemon.js +205 -0
- package/dist/daemon.js.map +1 -0
- package/dist/doctor.d.ts +13 -0
- package/dist/doctor.js +323 -0
- package/dist/doctor.js.map +1 -0
- package/dist/enroll.d.ts +7 -0
- package/dist/enroll.js +154 -0
- package/dist/enroll.js.map +1 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +363 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +141 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge.d.ts +3 -0
- package/dist/knowledge.js +1581 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.js +137 -0
- package/dist/mcp.js.map +1 -0
- package/dist/model.d.ts +8 -0
- package/dist/model.js +192 -0
- package/dist/model.js.map +1 -0
- package/dist/pairing.d.ts +21 -0
- package/dist/pairing.js +376 -0
- package/dist/pairing.js.map +1 -0
- package/dist/repl-events.d.ts +60 -0
- package/dist/repl-events.js +89 -0
- package/dist/repl-events.js.map +1 -0
- package/dist/repl-renderer.d.ts +37 -0
- package/dist/repl-renderer.js +140 -0
- package/dist/repl-renderer.js.map +1 -0
- package/dist/repl.d.ts +52 -0
- package/dist/repl.js +624 -0
- package/dist/repl.js.map +1 -0
- package/dist/service.d.ts +8 -0
- package/dist/service.js +238 -0
- package/dist/service.js.map +1 -0
- package/dist/skills.d.ts +14 -0
- package/dist/skills.js +423 -0
- package/dist/skills.js.map +1 -0
- package/dist/sse.d.ts +15 -0
- package/dist/sse.js +166 -0
- package/dist/sse.js.map +1 -0
- package/dist/thread.d.ts +9 -0
- package/dist/thread.js +152 -0
- package/dist/thread.js.map +1 -0
- package/dist/transport/computer-api.d.ts +1 -0
- package/dist/transport/computer-api.js +20 -0
- package/dist/transport/computer-api.js.map +1 -0
- package/dist/transport/gateway.d.ts +23 -0
- package/dist/transport/gateway.js +161 -0
- package/dist/transport/gateway.js.map +1 -0
- package/dist/transport/index.d.ts +27 -0
- package/dist/transport/index.js +77 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/local.d.ts +20 -0
- package/dist/transport/local.js +138 -0
- package/dist/transport/local.js.map +1 -0
- package/dist/transport/types.d.ts +42 -0
- package/dist/transport/types.js +11 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/ui/app.d.ts +7 -0
- package/dist/ui/app.js +192 -0
- package/dist/ui/app.js.map +1 -0
- package/dist/ui/history-item.d.ts +9 -0
- package/dist/ui/history-item.js +35 -0
- package/dist/ui/history-item.js.map +1 -0
- package/dist/ui/input-bar.d.ts +17 -0
- package/dist/ui/input-bar.js +67 -0
- package/dist/ui/input-bar.js.map +1 -0
- package/dist/ui/streaming-view.d.ts +6 -0
- package/dist/ui/streaming-view.js +6 -0
- package/dist/ui/streaming-view.js.map +1 -0
- package/dist/ui/thread-input-history.d.ts +18 -0
- package/dist/ui/thread-input-history.js +67 -0
- package/dist/ui/thread-input-history.js.map +1 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.js +80 -0
- package/dist/utils.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,1582 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { CODEX_TRUSTED_DATA_HOSTS, ASSISTANT_NAME, SUPEN_HOME, POLL_INTERVAL, TRIGGER_PATTERN, DAEMON_HOSTNAME, SKILLS_CONFIG, MIND_ADMIN_USERS, configureSupenDaemon, initializeDataDirs, } from './core/config.js';
|
|
4
|
+
import './channels/index.js';
|
|
5
|
+
import { SkillHub } from './skills/hub.js';
|
|
6
|
+
import { getChannelFactory, getRegisteredChannelNames, } from './channels/registry.js';
|
|
7
|
+
import { ensureSession, getAllChats, getAllRegisteredProjects, getAllSessions, getMessagesSince, getNewMessages, getRouterState, getSessionForAgent, initStore, cleanupStaleSessions, cleanupStaleAutomationExecutions, touchAutomationRun, updateSessionStatus, setRegisteredProject, setRouterState, storeChatMetadata, storeMessage, storeSessionUiEvent, resolveFromChatJid, } from './core/store.js';
|
|
8
|
+
import { logger } from './core/logger.js';
|
|
9
|
+
import { readEnrollmentState, verifyEnrollmentToken, } from './core/enrollment.js';
|
|
10
|
+
import { routeOutbound, routeOutboundFile, routeSetTyping, routeEditMessage, } from './router.js';
|
|
11
|
+
import { completeAutomationRun, shouldSuppressAutomationReply, } from './automation-runner.js';
|
|
12
|
+
import { startAutomationEventListener, stopAutomationEventListener, } from './automation-event-listener.js';
|
|
13
|
+
import { AgentCortex } from './core/cortex.js';
|
|
14
|
+
import { Gateway, setGatewayInstance, getGatewayInstance } from './core/gateway.js';
|
|
15
|
+
import { createGatewayRoutingConfigSync } from './core/gateway-routing-config.js';
|
|
16
|
+
import { broadcastToChat } from './http/stream.js';
|
|
17
|
+
import { broadcastToThread } from './http/thread-stream.js';
|
|
18
|
+
import { getEnabledChannelsFromConfig } from './core/env.js';
|
|
19
|
+
import { acquireDaemonLock } from './core/daemon-lock.js';
|
|
20
|
+
import { getMcpManager } from './mcp/index.js';
|
|
21
|
+
import { executePortableBuiltinCommand } from './commands/builtin.js';
|
|
22
|
+
import { listCommands, resolveCommandInput } from './commands/catalog.js';
|
|
23
|
+
import { findLatestInterruptIndex, trimMessagesAfterInterrupt, } from './core/interrupts.js';
|
|
24
|
+
import { isSupabaseConfigured, pullFromSupabase, scheduleSupabasePush, startPeriodicSupabasePush, } from './sync/supabase-sync.js';
|
|
25
|
+
import { appendStreamChunk, createStreamState, } from './core/streaming.js';
|
|
26
|
+
const DEFAULT_AUTOMATION_TASK_TIMEOUT_MS = 12 * 60 * 1000;
|
|
27
|
+
function automationTaskTimeoutMs() {
|
|
28
|
+
const raw = process.env.SUPEN_AUTOMATION_TASK_TIMEOUT_MS;
|
|
29
|
+
if (!raw)
|
|
30
|
+
return DEFAULT_AUTOMATION_TASK_TIMEOUT_MS;
|
|
31
|
+
const parsed = Number(raw);
|
|
32
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
33
|
+
return DEFAULT_AUTOMATION_TASK_TIMEOUT_MS;
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
import { appendThreadEvent } from './core/thread-event-log.js';
|
|
37
|
+
import { parseProgressEvent, formatProgressText, progressKeyFromEvent } from './core/progress.js';
|
|
38
|
+
import { buildAssistantOutputExcerpt, buildObservableToolDetail, buildProgressDetail, buildTaskContextExcerpt, canonicalAutomationLogFields, classifyObservableLogEvent, excerptText, } from './core/observable-logging.js';
|
|
39
|
+
// Global state
|
|
40
|
+
let registeredProjects = {};
|
|
41
|
+
const sessions = {}; // folder -> sessionId
|
|
42
|
+
const lastAgentTimestamp = {}; // chatJid -> iso
|
|
43
|
+
const channels = [];
|
|
44
|
+
const activeDaemons = new Map();
|
|
45
|
+
export function captureCodexStreamPayload(input) {
|
|
46
|
+
let sequence;
|
|
47
|
+
try {
|
|
48
|
+
const event = appendThreadEvent({
|
|
49
|
+
threadId: input.threadId,
|
|
50
|
+
runtimeThreadId: input.runtimeThreadId || input.threadId,
|
|
51
|
+
source: 'codex-app-server',
|
|
52
|
+
eventType: typeof input.payload.type === 'string'
|
|
53
|
+
? input.payload.type
|
|
54
|
+
: typeof input.payload.method === 'string'
|
|
55
|
+
? input.payload.method
|
|
56
|
+
: 'codex-event',
|
|
57
|
+
rawPayload: input.payload,
|
|
58
|
+
});
|
|
59
|
+
sequence = event.sequence;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
logger.warn({
|
|
63
|
+
err: error,
|
|
64
|
+
agentId: input.agentId,
|
|
65
|
+
threadId: input.threadId,
|
|
66
|
+
runtimeThreadId: input.runtimeThreadId || input.threadId,
|
|
67
|
+
}, 'Failed to capture Codex stream payload');
|
|
68
|
+
}
|
|
69
|
+
broadcastToThread(input.threadId, input.payload, sequence === undefined ? {} : { id: sequence });
|
|
70
|
+
}
|
|
71
|
+
function appServerChunk(method, params = {}) {
|
|
72
|
+
return {
|
|
73
|
+
type: 'data-supen-event',
|
|
74
|
+
data: {
|
|
75
|
+
raw: {
|
|
76
|
+
method,
|
|
77
|
+
params,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function readTraceIdFromMetadata(metadata) {
|
|
83
|
+
const raw = typeof metadata?.trace_id === 'string' ? metadata.trace_id.trim() : '';
|
|
84
|
+
return raw || undefined;
|
|
85
|
+
}
|
|
86
|
+
function createDaemonTraceId() {
|
|
87
|
+
return `dtr_${Date.now()}_${Math.random().toString(16).slice(2, 10)}`;
|
|
88
|
+
}
|
|
89
|
+
function emitGatewayTrace(event) {
|
|
90
|
+
const traceId = event.trace_id?.trim();
|
|
91
|
+
if (!traceId)
|
|
92
|
+
return;
|
|
93
|
+
const gw = getGatewayInstance();
|
|
94
|
+
if (!gw || !gw.isConnected())
|
|
95
|
+
return;
|
|
96
|
+
gw.emitTraceEvent({
|
|
97
|
+
trace_id: traceId,
|
|
98
|
+
kind: event.kind,
|
|
99
|
+
message: event.message,
|
|
100
|
+
...(event.level ? { level: event.level } : {}),
|
|
101
|
+
...(event.channel ? { channel: event.channel } : {}),
|
|
102
|
+
...(event.jid ? { jid: event.jid } : {}),
|
|
103
|
+
...(event.agent_id ? { agent_id: event.agent_id } : {}),
|
|
104
|
+
...(event.details ? { details: event.details } : {}),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function isAdminActor(actor) {
|
|
108
|
+
if (!actor)
|
|
109
|
+
return false;
|
|
110
|
+
return MIND_ADMIN_USERS.includes(actor);
|
|
111
|
+
}
|
|
112
|
+
/** Check if a registered JID still points to a live channel. */
|
|
113
|
+
async function isChannelAlive(jid) {
|
|
114
|
+
for (const ch of channels) {
|
|
115
|
+
if (ch.ownsJid(jid) && ch.channelExists) {
|
|
116
|
+
return ch.channelExists(jid);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
let messageLoopRunning = false;
|
|
122
|
+
// Simple mutex per channel to prevent overlapping agent runs
|
|
123
|
+
const activeAgentLocks = new Map();
|
|
124
|
+
// Pending-run flags: if a second message arrives while the lock is held,
|
|
125
|
+
// set the flag so the run drains immediately after the current one finishes.
|
|
126
|
+
const pendingRuns = new Set();
|
|
127
|
+
function requestSessionStop(agentId, sessionId, actor = 'unknown') {
|
|
128
|
+
const session = getSessionForAgent(agentId, sessionId);
|
|
129
|
+
const chatJid = session?.source_ref?.trim() || `web:${agentId}:${sessionId}`;
|
|
130
|
+
const daemon = activeDaemons.get(chatJid);
|
|
131
|
+
if (!daemon) {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
code: 'session_not_running',
|
|
135
|
+
message: `Task "${sessionId}" is not currently running.`,
|
|
136
|
+
chatJid,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
logger.info({ agentId, sessionId, chatJid, actor }, 'Stop requested for active session');
|
|
140
|
+
daemon.interrupt();
|
|
141
|
+
return {
|
|
142
|
+
ok: true,
|
|
143
|
+
code: 'stop_requested',
|
|
144
|
+
message: `Stop requested for session "${sessionId}".`,
|
|
145
|
+
chatJid,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Schedule a processMessages run for a chatJid.
|
|
150
|
+
* - If no run is in progress, starts one immediately.
|
|
151
|
+
* - If one is already running, marks a pending flag so a drain run kicks off
|
|
152
|
+
* as soon as the current one finishes — no message is silently dropped.
|
|
153
|
+
*/
|
|
154
|
+
function scheduleRun(chatJid) {
|
|
155
|
+
if (!activeAgentLocks.has(chatJid)) {
|
|
156
|
+
const agentPromise = runAndDrain(chatJid);
|
|
157
|
+
activeAgentLocks.set(chatJid, agentPromise);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
const sinceTimestamp = lastAgentTimestamp[chatJid] || '';
|
|
161
|
+
const pending = getMessagesSince(chatJid, sinceTimestamp, ASSISTANT_NAME);
|
|
162
|
+
if (pending.length === 0)
|
|
163
|
+
return;
|
|
164
|
+
const latestInterruptIndex = findLatestInterruptIndex(pending);
|
|
165
|
+
if (latestInterruptIndex >= 0) {
|
|
166
|
+
const interruptMsg = pending[latestInterruptIndex];
|
|
167
|
+
lastAgentTimestamp[chatJid] = interruptMsg.timestamp;
|
|
168
|
+
setRouterState(chatJid, interruptMsg.timestamp);
|
|
169
|
+
logger.info({
|
|
170
|
+
chatJid,
|
|
171
|
+
interruptTimestamp: interruptMsg.timestamp,
|
|
172
|
+
hasActiveDaemon: activeDaemons.has(chatJid),
|
|
173
|
+
}, 'Interrupt control message received while run is active');
|
|
174
|
+
activeDaemons.get(chatJid)?.interrupt();
|
|
175
|
+
}
|
|
176
|
+
const hasMessagesAfterInterrupt = latestInterruptIndex >= 0
|
|
177
|
+
? latestInterruptIndex < pending.length - 1
|
|
178
|
+
: pending.length > 0;
|
|
179
|
+
if (hasMessagesAfterInterrupt) {
|
|
180
|
+
// Mark that we need another run after the current one finishes
|
|
181
|
+
pendingRuns.add(chatJid);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Run processMessages, then drain any pending message that arrived mid-run. */
|
|
186
|
+
async function runAndDrain(chatJid) {
|
|
187
|
+
try {
|
|
188
|
+
await processMessages(chatJid);
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
activeAgentLocks.delete(chatJid);
|
|
192
|
+
if (pendingRuns.has(chatJid)) {
|
|
193
|
+
pendingRuns.delete(chatJid);
|
|
194
|
+
scheduleRun(chatJid);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
export function resolveSessionChannel(chatJid, existingChannel) {
|
|
199
|
+
if (chatJid.startsWith('dc:'))
|
|
200
|
+
return 'discord';
|
|
201
|
+
if (chatJid.startsWith('web:'))
|
|
202
|
+
return 'http';
|
|
203
|
+
if (chatJid.startsWith('feishu:') || chatJid.startsWith('fs:'))
|
|
204
|
+
return 'feishu';
|
|
205
|
+
if (chatJid.startsWith('dingtalk:'))
|
|
206
|
+
return 'dingtalk';
|
|
207
|
+
if (chatJid.startsWith('gateway:')) {
|
|
208
|
+
return existingChannel && existingChannel !== 'unknown'
|
|
209
|
+
? existingChannel
|
|
210
|
+
: 'gateway';
|
|
211
|
+
}
|
|
212
|
+
return existingChannel || chatJid.split(':')[0] || 'unknown';
|
|
213
|
+
}
|
|
214
|
+
export function resolveStoredSessionChannel(chatJid) {
|
|
215
|
+
const resolved = resolveFromChatJid(chatJid);
|
|
216
|
+
if (!resolved)
|
|
217
|
+
return undefined;
|
|
218
|
+
return getSessionForAgent(resolved.agentId, resolved.sessionId)?.channel;
|
|
219
|
+
}
|
|
220
|
+
export function resolveRoutingChannel(chatJid) {
|
|
221
|
+
return resolveSessionChannel(chatJid, resolveStoredSessionChannel(chatJid));
|
|
222
|
+
}
|
|
223
|
+
async function processMessages(chatJid) {
|
|
224
|
+
let group = registeredProjects[chatJid];
|
|
225
|
+
if (!group) {
|
|
226
|
+
logger.info({ chatJid }, 'Creating temporary context for unregistered chat');
|
|
227
|
+
group = {
|
|
228
|
+
name: 'unknown',
|
|
229
|
+
folder: 'unknown',
|
|
230
|
+
trigger: `@${ASSISTANT_NAME}`,
|
|
231
|
+
added_at: new Date().toISOString(),
|
|
232
|
+
requiresTrigger: true,
|
|
233
|
+
isMain: false,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
// Resolve or create a session for this chat so directories exist.
|
|
237
|
+
// IMPORTANT: always resolve agentId + sessionId from the JID — never use
|
|
238
|
+
// chatJid itself as session_id, or the full JID gets stored and re-prefixed
|
|
239
|
+
// on every subsequent call, making the key grow indefinitely.
|
|
240
|
+
let agentId = group.agent_id || group.folder;
|
|
241
|
+
let sessionId;
|
|
242
|
+
const resolved = resolveFromChatJid(chatJid);
|
|
243
|
+
if (resolved) {
|
|
244
|
+
agentId = resolved.agentId;
|
|
245
|
+
sessionId = resolved.sessionId;
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// Fallback for unrecognised JID formats: split on ':' and use last segment
|
|
249
|
+
const parts = chatJid.split(':');
|
|
250
|
+
sessionId = parts.length >= 2 ? parts.slice(1).join(':') : chatJid;
|
|
251
|
+
if (agentId === 'unknown' && parts.length >= 2) {
|
|
252
|
+
agentId = parts[1];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const existingSession = getSessionForAgent(agentId, sessionId);
|
|
256
|
+
const channel = existingSession
|
|
257
|
+
? resolveSessionChannel(chatJid, existingSession.channel)
|
|
258
|
+
: resolveRoutingChannel(chatJid);
|
|
259
|
+
const session = ensureSession({
|
|
260
|
+
agent_id: agentId,
|
|
261
|
+
session_id: sessionId,
|
|
262
|
+
channel,
|
|
263
|
+
agent_name: group.name,
|
|
264
|
+
source_ref: chatJid,
|
|
265
|
+
});
|
|
266
|
+
const sinceTimestamp = lastAgentTimestamp[chatJid] || '';
|
|
267
|
+
const messages = getMessagesSince(chatJid, sinceTimestamp, ASSISTANT_NAME);
|
|
268
|
+
logger.info({ chatJid, sinceTimestamp, retrievedCount: messages.length }, 'processMessages: retrieved messages');
|
|
269
|
+
if (messages.length === 0)
|
|
270
|
+
return true;
|
|
271
|
+
// Update last timestamp BEFORE handling control messages to avoid loops on failure
|
|
272
|
+
const newest = messages[messages.length - 1].timestamp;
|
|
273
|
+
lastAgentTimestamp[chatJid] = newest;
|
|
274
|
+
setRouterState(chatJid, newest);
|
|
275
|
+
// Transform messages to the format expected by daemon.run
|
|
276
|
+
let runnableMessages = trimMessagesAfterInterrupt(messages);
|
|
277
|
+
if (runnableMessages.length === 0) {
|
|
278
|
+
logger.info({ chatJid, messageCount: messages.length }, 'processMessages: consumed interrupt control message without dispatching to agent');
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
// Extract latest message for processing
|
|
282
|
+
const latestMsg = runnableMessages[runnableMessages.length - 1];
|
|
283
|
+
const latestText = latestMsg?.content?.trim() || '';
|
|
284
|
+
const routingChannel = resolveRoutingChannel(chatJid);
|
|
285
|
+
const activeTraceId = readTraceIdFromMetadata(latestMsg?.metadata);
|
|
286
|
+
const isAutomationEventMessage = latestMsg?.metadata?.origin === 'automation_event';
|
|
287
|
+
if (isAutomationEventMessage) {
|
|
288
|
+
runnableMessages = [latestMsg];
|
|
289
|
+
}
|
|
290
|
+
// Transform messages to the format expected by daemon.run
|
|
291
|
+
// is_from_me=true means the message is from the assistant (role: assistant)
|
|
292
|
+
// is_from_me=false means the message is from the user (role: user)
|
|
293
|
+
let aiMessages = runnableMessages.map((m) => ({
|
|
294
|
+
role: m.is_from_me ? 'assistant' : 'user',
|
|
295
|
+
content: m.content,
|
|
296
|
+
...(Array.isArray(m.attachments) && m.attachments.length > 0 ? { attachments: m.attachments } : {}),
|
|
297
|
+
}));
|
|
298
|
+
logger.info({ chatJid, messageCount: messages.length }, 'processMessages: starting');
|
|
299
|
+
emitGatewayTrace({
|
|
300
|
+
trace_id: activeTraceId,
|
|
301
|
+
kind: 'daemon.run.starting',
|
|
302
|
+
message: 'Started processing queued message batch',
|
|
303
|
+
jid: chatJid,
|
|
304
|
+
agent_id: agentId,
|
|
305
|
+
details: {
|
|
306
|
+
message_count: runnableMessages.length,
|
|
307
|
+
latest_message_id: latestMsg?.id || null,
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
try {
|
|
311
|
+
// Show typing indicator while we process
|
|
312
|
+
routeSetTyping(channels, chatJid, true);
|
|
313
|
+
if (latestText.startsWith('/enroll')) {
|
|
314
|
+
const parts = latestText.split(/\s+/);
|
|
315
|
+
const sub = parts[1] || 'status';
|
|
316
|
+
if (sub === 'status') {
|
|
317
|
+
const e = readEnrollmentState(DAEMON_HOSTNAME || undefined);
|
|
318
|
+
await sendFn(chatJid, `🔐 Enrollment status\n- daemon: ${e.daemon_id}\n- fingerprint: ${e.daemon_fingerprint}\n- trust_state: ${e.trust_state}\n- token_expires_at: ${e.token_expires_at || 'none'}\n- failed_attempts: ${e.failed_attempts}${e.frozen_until ? `\n- frozen_until: ${e.frozen_until}` : ''}`);
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
if (sub === 'verify') {
|
|
322
|
+
const token = parts[2];
|
|
323
|
+
if (!token) {
|
|
324
|
+
await sendFn(chatJid, 'Usage: /enroll verify <token>');
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
const e = readEnrollmentState(DAEMON_HOSTNAME || undefined);
|
|
328
|
+
const result = verifyEnrollmentToken({
|
|
329
|
+
token,
|
|
330
|
+
daemonFingerprint: e.daemon_fingerprint,
|
|
331
|
+
daemonId: DAEMON_HOSTNAME || undefined,
|
|
332
|
+
});
|
|
333
|
+
if (result.ok) {
|
|
334
|
+
await sendFn(chatJid, `✅ Enrollment verified. trust_state=${result.state.trust_state}`);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
await sendFn(chatJid, `❌ Enrollment verification failed: ${result.code} (state=${result.state.trust_state})`);
|
|
338
|
+
}
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const resolvedSlashCommand = latestText.startsWith('/') && !isAutomationEventMessage
|
|
343
|
+
? await resolveCommandInput(latestText, { channel: routingChannel, agentId })
|
|
344
|
+
: null;
|
|
345
|
+
if (resolvedSlashCommand?.kind === 'unsupported') {
|
|
346
|
+
await sendFn(chatJid, resolvedSlashCommand.reason);
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
if (resolvedSlashCommand?.kind === 'unknown') {
|
|
350
|
+
await sendFn(chatJid, `Unknown command: ${resolvedSlashCommand.rawInput}\nType /help for available commands.`);
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
if (resolvedSlashCommand?.kind === 'builtin') {
|
|
354
|
+
const portableBuiltinReply = await executePortableBuiltinCommand(resolvedSlashCommand.text, {
|
|
355
|
+
channel: routingChannel,
|
|
356
|
+
agentId,
|
|
357
|
+
sessionId,
|
|
358
|
+
actor: latestMsg?.sender || chatJid,
|
|
359
|
+
isAdmin: isAdminActor(latestMsg?.sender || chatJid),
|
|
360
|
+
});
|
|
361
|
+
if (portableBuiltinReply) {
|
|
362
|
+
await sendFn(chatJid, portableBuiltinReply);
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
await sendFn(chatJid, `${resolvedSlashCommand.command} is only supported in the CLI or web app.`);
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
const runtimePrompt = resolvedSlashCommand?.kind === 'chat'
|
|
369
|
+
? resolvedSlashCommand.text
|
|
370
|
+
: latestMsg?.content || '';
|
|
371
|
+
if (resolvedSlashCommand?.kind === 'chat' && chatJid.startsWith('web:')) {
|
|
372
|
+
aiMessages = aiMessages.map((message, index) => index === aiMessages.length - 1
|
|
373
|
+
? { ...message, content: runtimePrompt }
|
|
374
|
+
: message);
|
|
375
|
+
}
|
|
376
|
+
// Check if we have a valid workspace for this group
|
|
377
|
+
// (group is guaranteed to be set now because of our fallback logic at the top)
|
|
378
|
+
const workspace = session.task_workspace_folder || getFactoryPath(group);
|
|
379
|
+
const hasWorkspace = fs.existsSync(workspace);
|
|
380
|
+
logger.info({ chatJid, workspace, hasWorkspace }, 'processMessages: workspace check');
|
|
381
|
+
const automationMessage = [...runnableMessages]
|
|
382
|
+
.reverse()
|
|
383
|
+
.find((msg) => msg.automation_id ||
|
|
384
|
+
msg.schedule_id ||
|
|
385
|
+
msg.automation_run_id ||
|
|
386
|
+
msg.schedule_run_id) || null;
|
|
387
|
+
const isAutomationMessage = Boolean(automationMessage);
|
|
388
|
+
let completeAutomationIfNeeded = async () => Promise.resolve();
|
|
389
|
+
try {
|
|
390
|
+
let statusMessageId = null;
|
|
391
|
+
let lastProgressSentAt = 0;
|
|
392
|
+
let lastProgressKey = '';
|
|
393
|
+
let lastProgressEvent = null;
|
|
394
|
+
const runStartedAt = Date.now();
|
|
395
|
+
let lastRunningProgressLogAt = 0;
|
|
396
|
+
let lastAutomationProgressLogAt = 0;
|
|
397
|
+
let lastAutomationProgressKey = '';
|
|
398
|
+
let heartbeatInFlight = false;
|
|
399
|
+
let heartbeatTimer = null;
|
|
400
|
+
let automationFinalized = false;
|
|
401
|
+
const summarizeAutomationProgressTarget = (value) => {
|
|
402
|
+
return excerptText(value, 280);
|
|
403
|
+
};
|
|
404
|
+
const logAutomationProgress = (eventData) => {
|
|
405
|
+
if (!isAutomationMessage)
|
|
406
|
+
return;
|
|
407
|
+
if (automationMessage?.automation_run_id) {
|
|
408
|
+
touchAutomationRun(automationMessage.automation_run_id);
|
|
409
|
+
}
|
|
410
|
+
const progressInfo = parseProgressEvent(eventData);
|
|
411
|
+
const phase = typeof eventData.phase === 'string' ? eventData.phase : 'unknown';
|
|
412
|
+
const action = typeof eventData.action === 'string' ? eventData.action : 'unknown';
|
|
413
|
+
const target = summarizeAutomationProgressTarget(eventData.target);
|
|
414
|
+
const elapsedMs = typeof eventData.elapsed_ms === 'number' && Number.isFinite(eventData.elapsed_ms)
|
|
415
|
+
? eventData.elapsed_ms
|
|
416
|
+
: Date.now() - runStartedAt;
|
|
417
|
+
const key = `${phase}|${action}|${target || ''}`;
|
|
418
|
+
const now = Date.now();
|
|
419
|
+
if (now - lastAutomationProgressLogAt < 2000 && key === lastAutomationProgressKey) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
lastAutomationProgressLogAt = now;
|
|
423
|
+
lastAutomationProgressKey = key;
|
|
424
|
+
logger.info({
|
|
425
|
+
...canonicalAutomationLogFields({
|
|
426
|
+
chat_jid: chatJid,
|
|
427
|
+
task_id: automationMessage?.task_id,
|
|
428
|
+
automation_id: automationMessage?.automation_id,
|
|
429
|
+
schedule_id: automationMessage?.schedule_id,
|
|
430
|
+
automation_run_id: automationMessage?.automation_run_id,
|
|
431
|
+
schedule_run_id: automationMessage?.schedule_run_id,
|
|
432
|
+
agent_id: agentId,
|
|
433
|
+
}),
|
|
434
|
+
elapsed_ms: elapsedMs,
|
|
435
|
+
phase,
|
|
436
|
+
detail: buildProgressDetail({
|
|
437
|
+
phase,
|
|
438
|
+
action,
|
|
439
|
+
target: progressInfo?.target ?? target,
|
|
440
|
+
}),
|
|
441
|
+
...buildObservableToolDetail({
|
|
442
|
+
tool: progressInfo?.tool,
|
|
443
|
+
target: progressInfo?.target ?? target,
|
|
444
|
+
args: progressInfo?.args,
|
|
445
|
+
}),
|
|
446
|
+
output_excerpt: action === 'speaking'
|
|
447
|
+
? buildAssistantOutputExcerpt(eventData.target)
|
|
448
|
+
: undefined,
|
|
449
|
+
status: eventData.status,
|
|
450
|
+
}, 'Automation progress');
|
|
451
|
+
};
|
|
452
|
+
const emitProgress = async (info) => {
|
|
453
|
+
if (chatJid.startsWith('web:')) {
|
|
454
|
+
broadcastToChat(chatJid, {
|
|
455
|
+
type: 'progress',
|
|
456
|
+
category: info.category,
|
|
457
|
+
skill: info.skill,
|
|
458
|
+
tool: info.tool,
|
|
459
|
+
args: info.args,
|
|
460
|
+
target: info.target,
|
|
461
|
+
elapsed_s: info.elapsed_s,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
// Non-web channels do not receive progressive UI string updates
|
|
465
|
+
// The framework strictly transmits structured data.
|
|
466
|
+
};
|
|
467
|
+
const emitProgressHeartbeat = async () => {
|
|
468
|
+
if (heartbeatInFlight)
|
|
469
|
+
return;
|
|
470
|
+
heartbeatInFlight = true;
|
|
471
|
+
try {
|
|
472
|
+
const elapsed = Date.now() - runStartedAt;
|
|
473
|
+
const heartbeatNow = Date.now();
|
|
474
|
+
if (isAutomationMessage &&
|
|
475
|
+
heartbeatNow - lastRunningProgressLogAt >= 30_000) {
|
|
476
|
+
if (automationMessage?.automation_run_id) {
|
|
477
|
+
touchAutomationRun(automationMessage.automation_run_id);
|
|
478
|
+
}
|
|
479
|
+
lastRunningProgressLogAt = heartbeatNow;
|
|
480
|
+
const heartbeatTarget = typeof lastProgressEvent?.target === 'string'
|
|
481
|
+
? lastProgressEvent.target
|
|
482
|
+
: undefined;
|
|
483
|
+
const heartbeatProgress = parseProgressEvent({
|
|
484
|
+
phase: typeof lastProgressEvent?.phase === 'string'
|
|
485
|
+
? lastProgressEvent.phase
|
|
486
|
+
: 'assistant',
|
|
487
|
+
action: typeof lastProgressEvent?.action === 'string'
|
|
488
|
+
? lastProgressEvent?.action
|
|
489
|
+
: 'thinking',
|
|
490
|
+
target: heartbeatTarget,
|
|
491
|
+
elapsed_ms: elapsed,
|
|
492
|
+
});
|
|
493
|
+
const heartbeatDetails = heartbeatProgress
|
|
494
|
+
? {
|
|
495
|
+
phase: heartbeatProgress.category,
|
|
496
|
+
skill: heartbeatProgress.skill,
|
|
497
|
+
tool: heartbeatProgress.tool,
|
|
498
|
+
target: heartbeatProgress.target,
|
|
499
|
+
progress: formatProgressText(heartbeatProgress),
|
|
500
|
+
}
|
|
501
|
+
: undefined;
|
|
502
|
+
if (heartbeatDetails) {
|
|
503
|
+
logger.info({
|
|
504
|
+
...canonicalAutomationLogFields({
|
|
505
|
+
chat_jid: chatJid,
|
|
506
|
+
task_id: automationMessage?.task_id,
|
|
507
|
+
automation_id: automationMessage?.automation_id,
|
|
508
|
+
schedule_id: automationMessage?.schedule_id,
|
|
509
|
+
automation_run_id: automationMessage?.automation_run_id,
|
|
510
|
+
schedule_run_id: automationMessage?.schedule_run_id,
|
|
511
|
+
agent_id: agentId,
|
|
512
|
+
}),
|
|
513
|
+
elapsed_s: Math.floor(elapsed / 1000),
|
|
514
|
+
phase: heartbeatDetails.phase,
|
|
515
|
+
detail: heartbeatDetails.target ||
|
|
516
|
+
heartbeatDetails.progress,
|
|
517
|
+
...buildObservableToolDetail({
|
|
518
|
+
tool: heartbeatDetails.tool,
|
|
519
|
+
target: heartbeatDetails.target,
|
|
520
|
+
}),
|
|
521
|
+
}, 'Automation heartbeat');
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
logger.info({
|
|
525
|
+
...canonicalAutomationLogFields({
|
|
526
|
+
chat_jid: chatJid,
|
|
527
|
+
task_id: automationMessage?.task_id,
|
|
528
|
+
automation_id: automationMessage?.automation_id,
|
|
529
|
+
schedule_id: automationMessage?.schedule_id,
|
|
530
|
+
automation_run_id: automationMessage?.automation_run_id,
|
|
531
|
+
schedule_run_id: automationMessage?.schedule_run_id,
|
|
532
|
+
agent_id: agentId,
|
|
533
|
+
}),
|
|
534
|
+
elapsed_s: Math.floor(elapsed / 1000),
|
|
535
|
+
detail: 'still running',
|
|
536
|
+
}, 'Automation heartbeat');
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
const heartbeatBase = lastProgressEvent
|
|
540
|
+
? {
|
|
541
|
+
...lastProgressEvent,
|
|
542
|
+
elapsed_ms: elapsed,
|
|
543
|
+
}
|
|
544
|
+
: {
|
|
545
|
+
phase: 'assistant',
|
|
546
|
+
action: 'thinking',
|
|
547
|
+
elapsed_ms: elapsed,
|
|
548
|
+
};
|
|
549
|
+
let heartbeatInfo = parseProgressEvent(heartbeatBase);
|
|
550
|
+
if (!heartbeatInfo) {
|
|
551
|
+
heartbeatInfo = parseProgressEvent({
|
|
552
|
+
phase: 'assistant',
|
|
553
|
+
action: 'thinking',
|
|
554
|
+
elapsed_ms: elapsed,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
if (!heartbeatInfo)
|
|
558
|
+
return;
|
|
559
|
+
await emitProgress(heartbeatInfo);
|
|
560
|
+
}
|
|
561
|
+
catch (err) {
|
|
562
|
+
logger.debug({ err, chatJid }, 'Progress heartbeat failed');
|
|
563
|
+
}
|
|
564
|
+
finally {
|
|
565
|
+
heartbeatInFlight = false;
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
const taskId = latestMsg?.task_id || `run-${Date.now()}`;
|
|
569
|
+
const turnId = automationMessage?.automation_run_id ||
|
|
570
|
+
automationMessage?.schedule_run_id ||
|
|
571
|
+
latestMsg?.id ||
|
|
572
|
+
taskId;
|
|
573
|
+
const streamState = createStreamState(`${chatJid}:${turnId}`);
|
|
574
|
+
const shouldMirrorTaskThread = chatJid.startsWith('task:') && Boolean(taskId);
|
|
575
|
+
const mirrorTaskThreadChunk = (payload) => {
|
|
576
|
+
if (!shouldMirrorTaskThread)
|
|
577
|
+
return;
|
|
578
|
+
captureCodexStreamPayload({
|
|
579
|
+
agentId,
|
|
580
|
+
threadId: taskId,
|
|
581
|
+
runtimeThreadId: session.session_id,
|
|
582
|
+
payload,
|
|
583
|
+
});
|
|
584
|
+
};
|
|
585
|
+
const gwChannelReplyClient = getGatewayInstance();
|
|
586
|
+
const useGatewayChannelReplyLifecycle = Boolean(gwChannelReplyClient) &&
|
|
587
|
+
chatJid.startsWith('gateway:') &&
|
|
588
|
+
!chatJid.startsWith('web:');
|
|
589
|
+
let channelReplyHandle = null;
|
|
590
|
+
let channelReplyPreviewText = '';
|
|
591
|
+
let channelReplyLastFlushAt = 0;
|
|
592
|
+
let channelReplyFlushInFlight = false;
|
|
593
|
+
const CHANNEL_REPLY_STREAM_FLUSH_MS = 800;
|
|
594
|
+
const persistOutboundLocally = (text) => {
|
|
595
|
+
const ts = new Date().toISOString();
|
|
596
|
+
lastAgentTimestamp[chatJid] = ts;
|
|
597
|
+
setRouterState(chatJid, ts);
|
|
598
|
+
if (!text.trim())
|
|
599
|
+
return;
|
|
600
|
+
if (chatJid.startsWith('task:'))
|
|
601
|
+
return;
|
|
602
|
+
storeMessage({
|
|
603
|
+
id: streamState.streamId,
|
|
604
|
+
chat_jid: chatJid,
|
|
605
|
+
sender: ASSISTANT_NAME,
|
|
606
|
+
sender_name: ASSISTANT_NAME,
|
|
607
|
+
content: text,
|
|
608
|
+
timestamp: ts,
|
|
609
|
+
is_from_me: true,
|
|
610
|
+
is_bot_message: true,
|
|
611
|
+
...(taskId ? { task_id: taskId } : {}),
|
|
612
|
+
...(activeTraceId ? { metadata: { trace_id: activeTraceId } } : {}),
|
|
613
|
+
});
|
|
614
|
+
};
|
|
615
|
+
const ensureChannelReplyHandle = async (initialContent) => {
|
|
616
|
+
if (!useGatewayChannelReplyLifecycle || !gwChannelReplyClient)
|
|
617
|
+
return null;
|
|
618
|
+
if (channelReplyHandle)
|
|
619
|
+
return channelReplyHandle;
|
|
620
|
+
try {
|
|
621
|
+
channelReplyHandle = await gwChannelReplyClient.startChannelReplyForChat(chatJid, initialContent);
|
|
622
|
+
emitGatewayTrace({
|
|
623
|
+
trace_id: activeTraceId,
|
|
624
|
+
kind: 'daemon.reply.lifecycle.started',
|
|
625
|
+
message: 'Started gateway channel reply lifecycle',
|
|
626
|
+
jid: chatJid,
|
|
627
|
+
agent_id: agentId,
|
|
628
|
+
details: { reply_handle: channelReplyHandle },
|
|
629
|
+
});
|
|
630
|
+
return channelReplyHandle;
|
|
631
|
+
}
|
|
632
|
+
catch (err) {
|
|
633
|
+
logger.warn({ err, chatJid }, 'Failed to start gateway channel reply lifecycle; falling back to channel_reply');
|
|
634
|
+
emitGatewayTrace({
|
|
635
|
+
trace_id: activeTraceId,
|
|
636
|
+
kind: 'daemon.reply.lifecycle.failed_to_start',
|
|
637
|
+
message: 'Failed to start gateway channel reply lifecycle',
|
|
638
|
+
level: 'warn',
|
|
639
|
+
jid: chatJid,
|
|
640
|
+
agent_id: agentId,
|
|
641
|
+
details: {
|
|
642
|
+
error: err?.message || String(err),
|
|
643
|
+
},
|
|
644
|
+
});
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
const maybeFlushChannelReplyPreview = async () => {
|
|
649
|
+
if (!useGatewayChannelReplyLifecycle || !gwChannelReplyClient)
|
|
650
|
+
return;
|
|
651
|
+
if (!channelReplyPreviewText.trim())
|
|
652
|
+
return;
|
|
653
|
+
if (channelReplyFlushInFlight)
|
|
654
|
+
return;
|
|
655
|
+
const now = Date.now();
|
|
656
|
+
if (now - channelReplyLastFlushAt < CHANNEL_REPLY_STREAM_FLUSH_MS)
|
|
657
|
+
return;
|
|
658
|
+
const handle = await ensureChannelReplyHandle('Thinking...');
|
|
659
|
+
if (!handle)
|
|
660
|
+
return;
|
|
661
|
+
channelReplyFlushInFlight = true;
|
|
662
|
+
try {
|
|
663
|
+
await gwChannelReplyClient.updateChannelReply(handle, channelReplyPreviewText, activeTraceId);
|
|
664
|
+
channelReplyLastFlushAt = Date.now();
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
logger.debug({ err, chatJid }, 'Failed to flush gateway channel reply preview chunk');
|
|
668
|
+
}
|
|
669
|
+
finally {
|
|
670
|
+
channelReplyFlushInFlight = false;
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
completeAutomationIfNeeded = async (outcome, detail) => {
|
|
674
|
+
if (automationFinalized ||
|
|
675
|
+
!automationMessage ||
|
|
676
|
+
(!automationMessage?.automation_id && !automationMessage?.schedule_id) ||
|
|
677
|
+
(!automationMessage?.automation_run_id && !automationMessage?.schedule_run_id)) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
automationFinalized = true;
|
|
681
|
+
try {
|
|
682
|
+
await completeAutomationRun({
|
|
683
|
+
sendMessage: (jid, notification) => sendFn(jid, notification),
|
|
684
|
+
ensureNotificationSessionExists,
|
|
685
|
+
}, automationMessage, chatJid, outcome, detail);
|
|
686
|
+
}
|
|
687
|
+
catch (completeError) {
|
|
688
|
+
logger.warn({ completeError, automation_id: automationMessage?.automation_id, outcome }, 'Failed to finalize automation run in process fallback');
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
heartbeatTimer = setInterval(() => {
|
|
692
|
+
void emitProgressHeartbeat();
|
|
693
|
+
}, chatJid.startsWith('web:') ? 1000 : 15_000);
|
|
694
|
+
try {
|
|
695
|
+
if (useGatewayChannelReplyLifecycle) {
|
|
696
|
+
await ensureChannelReplyHandle('Thinking...');
|
|
697
|
+
}
|
|
698
|
+
// Get MCP manager for this session
|
|
699
|
+
let mcpManager;
|
|
700
|
+
try {
|
|
701
|
+
mcpManager = await getMcpManager();
|
|
702
|
+
}
|
|
703
|
+
catch (err) {
|
|
704
|
+
logger.warn({ err }, 'Failed to get MCP manager, continuing without MCP');
|
|
705
|
+
}
|
|
706
|
+
const daemon = new AgentCortex(agentId, { ...session, task_id: taskId }.session_id, {
|
|
707
|
+
mcpManager,
|
|
708
|
+
onStateChange: async (state) => {
|
|
709
|
+
const eventData = {
|
|
710
|
+
phase: state.activity?.phase,
|
|
711
|
+
action: state.activity?.action,
|
|
712
|
+
target: state.activity?.target,
|
|
713
|
+
elapsed_ms: state.activity?.elapsed_ms,
|
|
714
|
+
status: state.status,
|
|
715
|
+
};
|
|
716
|
+
lastProgressEvent = { ...eventData };
|
|
717
|
+
logAutomationProgress(eventData);
|
|
718
|
+
if (shouldMirrorTaskThread) {
|
|
719
|
+
const statusType = state.status === 'error'
|
|
720
|
+
? 'failed'
|
|
721
|
+
: state.status === 'interrupted'
|
|
722
|
+
? 'idle'
|
|
723
|
+
: state.status === 'idle'
|
|
724
|
+
? 'idle'
|
|
725
|
+
: 'active';
|
|
726
|
+
mirrorTaskThreadChunk(appServerChunk('thread/status/changed', {
|
|
727
|
+
threadId: session.session_id,
|
|
728
|
+
status: { type: statusType },
|
|
729
|
+
}));
|
|
730
|
+
}
|
|
731
|
+
// Broadcast computer_state for all status transitions so
|
|
732
|
+
// consumers get real-time status updates.
|
|
733
|
+
if (chatJid.startsWith('web:')) {
|
|
734
|
+
broadcastToChat(chatJid, {
|
|
735
|
+
type: 'computer_state',
|
|
736
|
+
chat_jid: chatJid,
|
|
737
|
+
...state,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
if (chatJid.startsWith('web:') &&
|
|
741
|
+
(state.status === 'interrupted' || state.status === 'error')) {
|
|
742
|
+
if (streamState.nextSeq > 1) {
|
|
743
|
+
broadcastToChat(chatJid, {
|
|
744
|
+
type: 'stream_end',
|
|
745
|
+
stream_id: streamState.streamId,
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
broadcastToChat(chatJid, { type: 'progress_end' });
|
|
749
|
+
}
|
|
750
|
+
// Forward streaming text deltas to SSE clients
|
|
751
|
+
if (eventData.phase === 'stream_event' &&
|
|
752
|
+
eventData.action === 'speaking' &&
|
|
753
|
+
typeof eventData.target === 'string') {
|
|
754
|
+
if (useGatewayChannelReplyLifecycle) {
|
|
755
|
+
channelReplyPreviewText += eventData.target;
|
|
756
|
+
await maybeFlushChannelReplyPreview();
|
|
757
|
+
}
|
|
758
|
+
const frame = appendStreamChunk(streamState, eventData.target);
|
|
759
|
+
if (frame) {
|
|
760
|
+
broadcastToChat(chatJid, {
|
|
761
|
+
type: 'stream_delta',
|
|
762
|
+
...frame,
|
|
763
|
+
});
|
|
764
|
+
mirrorTaskThreadChunk({
|
|
765
|
+
type: 'text-delta',
|
|
766
|
+
id: streamState.streamId,
|
|
767
|
+
delta: frame.text,
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const progressInfo = parseProgressEvent(eventData);
|
|
772
|
+
if (!progressInfo)
|
|
773
|
+
return;
|
|
774
|
+
const now = Date.now();
|
|
775
|
+
const targetKey = typeof eventData.target === 'string'
|
|
776
|
+
? eventData.target.slice(0, 120)
|
|
777
|
+
: '';
|
|
778
|
+
const progressKey = `${progressKeyFromEvent(eventData)}|${targetKey}`;
|
|
779
|
+
const isWeb = chatJid.startsWith('web:');
|
|
780
|
+
const throttleMs = isWeb ? 1000 : 10_000;
|
|
781
|
+
const shouldSend = !lastProgressSentAt ||
|
|
782
|
+
progressKey !== lastProgressKey ||
|
|
783
|
+
now - lastProgressSentAt >= throttleMs;
|
|
784
|
+
if (!shouldSend)
|
|
785
|
+
return;
|
|
786
|
+
lastProgressSentAt = now;
|
|
787
|
+
lastProgressKey = progressKey;
|
|
788
|
+
await emitProgress(progressInfo);
|
|
789
|
+
},
|
|
790
|
+
onFile: async (filePath, caption) => {
|
|
791
|
+
try {
|
|
792
|
+
await routeOutboundFile(channels, chatJid, filePath, caption);
|
|
793
|
+
}
|
|
794
|
+
catch (err) {
|
|
795
|
+
logger.warn({ err, chatJid, filePath }, 'Failed to send file');
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
onReply: async (text) => {
|
|
799
|
+
// Stop heartbeat immediately — the run is complete and
|
|
800
|
+
// we don't want stale progress events re-triggering the
|
|
801
|
+
// "thinking" state on the frontend after stream_end.
|
|
802
|
+
if (heartbeatTimer) {
|
|
803
|
+
clearInterval(heartbeatTimer);
|
|
804
|
+
heartbeatTimer = null;
|
|
805
|
+
}
|
|
806
|
+
if (shouldMirrorTaskThread) {
|
|
807
|
+
const remainingText = text.startsWith(streamState.fullText)
|
|
808
|
+
? text.slice(streamState.fullText.length)
|
|
809
|
+
: streamState.fullText.trim()
|
|
810
|
+
? ''
|
|
811
|
+
: text;
|
|
812
|
+
if (remainingText) {
|
|
813
|
+
const frame = appendStreamChunk(streamState, remainingText);
|
|
814
|
+
if (frame) {
|
|
815
|
+
mirrorTaskThreadChunk({
|
|
816
|
+
type: 'text-delta',
|
|
817
|
+
id: streamState.streamId,
|
|
818
|
+
delta: frame.text,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
mirrorTaskThreadChunk({
|
|
823
|
+
type: 'text-end',
|
|
824
|
+
id: streamState.streamId,
|
|
825
|
+
});
|
|
826
|
+
mirrorTaskThreadChunk(appServerChunk('turn/completed', {
|
|
827
|
+
threadId: session.session_id,
|
|
828
|
+
turnId,
|
|
829
|
+
turn: { id: turnId, status: 'completed' },
|
|
830
|
+
}));
|
|
831
|
+
updateSessionStatus(agentId, sessionId, 'idle');
|
|
832
|
+
}
|
|
833
|
+
// Finalize streaming: emit stream_end so consumers
|
|
834
|
+
// know the streaming phase is complete.
|
|
835
|
+
if (chatJid.startsWith('web:') && streamState.nextSeq > 1) {
|
|
836
|
+
broadcastToChat(chatJid, {
|
|
837
|
+
type: 'stream_end',
|
|
838
|
+
stream_id: streamState.streamId,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
// Clear progress indicators
|
|
842
|
+
if (chatJid.startsWith('web:')) {
|
|
843
|
+
broadcastToChat(chatJid, { type: 'progress_end' });
|
|
844
|
+
}
|
|
845
|
+
else if (statusMessageId) {
|
|
846
|
+
try {
|
|
847
|
+
await routeEditMessage(channels, chatJid, statusMessageId, 'Done, sending final reply...');
|
|
848
|
+
}
|
|
849
|
+
catch {
|
|
850
|
+
// Safe to ignore; final answer will still be delivered.
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
// Deliver the final message through the channel.
|
|
854
|
+
// Pass stream_id as message_id so the frontend can correlate
|
|
855
|
+
// the final message with the streaming placeholder.
|
|
856
|
+
const suppressReply = shouldSuppressAutomationReply(latestMsg, 'succeeded', text);
|
|
857
|
+
emitGatewayTrace({
|
|
858
|
+
trace_id: activeTraceId,
|
|
859
|
+
kind: 'daemon.reply.ready',
|
|
860
|
+
message: suppressReply
|
|
861
|
+
? 'Agent produced reply but delivery is suppressed by automation policy'
|
|
862
|
+
: 'Agent produced final reply',
|
|
863
|
+
jid: chatJid,
|
|
864
|
+
agent_id: agentId,
|
|
865
|
+
details: {
|
|
866
|
+
suppressed: suppressReply,
|
|
867
|
+
reply_excerpt: text.slice(0, 200),
|
|
868
|
+
},
|
|
869
|
+
});
|
|
870
|
+
if (suppressReply) {
|
|
871
|
+
if (useGatewayChannelReplyLifecycle && gwChannelReplyClient) {
|
|
872
|
+
const handle = await ensureChannelReplyHandle('Thinking...');
|
|
873
|
+
if (handle) {
|
|
874
|
+
try {
|
|
875
|
+
await gwChannelReplyClient.finalizeChannelReply(handle, activeTraceId);
|
|
876
|
+
emitGatewayTrace({
|
|
877
|
+
trace_id: activeTraceId,
|
|
878
|
+
kind: 'daemon.reply.dispatched',
|
|
879
|
+
message: 'Reply lifecycle finalized without outbound channel message',
|
|
880
|
+
jid: chatJid,
|
|
881
|
+
agent_id: agentId,
|
|
882
|
+
details: {
|
|
883
|
+
suppressed: true,
|
|
884
|
+
reply_handle: handle,
|
|
885
|
+
mode: 'channel_reply_lifecycle',
|
|
886
|
+
},
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
catch (lifecycleErr) {
|
|
890
|
+
logger.warn({ lifecycleErr, chatJid }, 'Failed to finalize suppressed gateway channel reply lifecycle');
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
await completeAutomationIfNeeded('succeeded', text);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (!suppressReply) {
|
|
898
|
+
if (useGatewayChannelReplyLifecycle && gwChannelReplyClient) {
|
|
899
|
+
const handle = await ensureChannelReplyHandle('Thinking...');
|
|
900
|
+
if (handle) {
|
|
901
|
+
try {
|
|
902
|
+
if (channelReplyPreviewText && channelReplyPreviewText !== text) {
|
|
903
|
+
channelReplyPreviewText = text;
|
|
904
|
+
}
|
|
905
|
+
await gwChannelReplyClient.updateChannelReply(handle, text, activeTraceId);
|
|
906
|
+
await gwChannelReplyClient.finalizeChannelReply(handle, activeTraceId);
|
|
907
|
+
persistOutboundLocally(text);
|
|
908
|
+
emitGatewayTrace({
|
|
909
|
+
trace_id: activeTraceId,
|
|
910
|
+
kind: 'daemon.reply.dispatched',
|
|
911
|
+
message: 'Reply dispatched via gateway channel reply lifecycle',
|
|
912
|
+
jid: chatJid,
|
|
913
|
+
agent_id: agentId,
|
|
914
|
+
details: {
|
|
915
|
+
message_id: streamState.streamId,
|
|
916
|
+
reply_handle: handle,
|
|
917
|
+
mode: 'channel_reply_lifecycle',
|
|
918
|
+
},
|
|
919
|
+
});
|
|
920
|
+
await completeAutomationIfNeeded('succeeded', text);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
catch (lifecycleErr) {
|
|
924
|
+
logger.warn({ lifecycleErr, chatJid }, 'Gateway channel reply lifecycle dispatch failed; falling back to channel_reply');
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
await sendFn(chatJid, text, {
|
|
929
|
+
message_id: streamState.streamId,
|
|
930
|
+
trace_id: activeTraceId,
|
|
931
|
+
task_id: taskId,
|
|
932
|
+
});
|
|
933
|
+
emitGatewayTrace({
|
|
934
|
+
trace_id: activeTraceId,
|
|
935
|
+
kind: 'daemon.reply.dispatched',
|
|
936
|
+
message: 'Reply dispatched to outbound channel router',
|
|
937
|
+
jid: chatJid,
|
|
938
|
+
agent_id: agentId,
|
|
939
|
+
details: {
|
|
940
|
+
message_id: streamState.streamId,
|
|
941
|
+
},
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
await completeAutomationIfNeeded('succeeded', text);
|
|
945
|
+
},
|
|
946
|
+
onError: async (text, error) => {
|
|
947
|
+
if (heartbeatTimer) {
|
|
948
|
+
clearInterval(heartbeatTimer);
|
|
949
|
+
heartbeatTimer = null;
|
|
950
|
+
}
|
|
951
|
+
// Automation task threads mirror the underlying Codex/App Server stream.
|
|
952
|
+
// A daemon watchdog timeout is run metadata, not a Codex chat event.
|
|
953
|
+
// Keep it out of the mirrored thread so the chat history remains the
|
|
954
|
+
// single source of truth from the actual runtime.
|
|
955
|
+
const shouldMirrorSyntheticError = shouldMirrorTaskThread && !isAutomationMessage;
|
|
956
|
+
if (shouldMirrorSyntheticError) {
|
|
957
|
+
const errorText = text || error?.message || 'Task failed';
|
|
958
|
+
if (!streamState.fullText.trim() && errorText) {
|
|
959
|
+
const frame = appendStreamChunk(streamState, errorText);
|
|
960
|
+
if (frame) {
|
|
961
|
+
mirrorTaskThreadChunk({
|
|
962
|
+
type: 'text-delta',
|
|
963
|
+
id: streamState.streamId,
|
|
964
|
+
delta: frame.text,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
mirrorTaskThreadChunk({
|
|
969
|
+
type: 'text-end',
|
|
970
|
+
id: streamState.streamId,
|
|
971
|
+
});
|
|
972
|
+
mirrorTaskThreadChunk(appServerChunk('turn/failed', {
|
|
973
|
+
threadId: session.session_id,
|
|
974
|
+
turnId,
|
|
975
|
+
turn: { id: turnId, status: 'failed' },
|
|
976
|
+
error: { message: error?.message || text },
|
|
977
|
+
}));
|
|
978
|
+
updateSessionStatus(agentId, sessionId, 'error');
|
|
979
|
+
}
|
|
980
|
+
if (!isAutomationMessage && chatJid.startsWith('web:') && streamState.nextSeq > 1) {
|
|
981
|
+
broadcastToChat(chatJid, {
|
|
982
|
+
type: 'stream_end',
|
|
983
|
+
stream_id: streamState.streamId,
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
if (!isAutomationMessage && chatJid.startsWith('web:')) {
|
|
987
|
+
broadcastToChat(chatJid, { type: 'progress_end' });
|
|
988
|
+
}
|
|
989
|
+
emitGatewayTrace({
|
|
990
|
+
trace_id: activeTraceId,
|
|
991
|
+
kind: 'daemon.reply.error',
|
|
992
|
+
message: 'Agent reported an error response',
|
|
993
|
+
level: 'error',
|
|
994
|
+
jid: chatJid,
|
|
995
|
+
agent_id: agentId,
|
|
996
|
+
details: {
|
|
997
|
+
error: error?.message || text,
|
|
998
|
+
},
|
|
999
|
+
});
|
|
1000
|
+
if (!isAutomationMessage && useGatewayChannelReplyLifecycle && gwChannelReplyClient) {
|
|
1001
|
+
const handle = await ensureChannelReplyHandle('Thinking...');
|
|
1002
|
+
if (handle) {
|
|
1003
|
+
try {
|
|
1004
|
+
await gwChannelReplyClient.failChannelReply(handle, text, activeTraceId);
|
|
1005
|
+
persistOutboundLocally(text);
|
|
1006
|
+
await completeAutomationIfNeeded('failed', error.message || text);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
catch (lifecycleErr) {
|
|
1010
|
+
logger.warn({ lifecycleErr, chatJid }, 'Gateway channel reply lifecycle error-dispatch failed; falling back to channel_reply');
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (!isAutomationMessage) {
|
|
1015
|
+
await sendFn(chatJid, text, {
|
|
1016
|
+
message_id: streamState.streamId,
|
|
1017
|
+
trace_id: activeTraceId,
|
|
1018
|
+
task_id: taskId,
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
await completeAutomationIfNeeded('failed', error.message || text);
|
|
1022
|
+
},
|
|
1023
|
+
});
|
|
1024
|
+
activeDaemons.set(chatJid, daemon);
|
|
1025
|
+
try {
|
|
1026
|
+
if (shouldMirrorTaskThread) {
|
|
1027
|
+
updateSessionStatus(agentId, sessionId, 'running');
|
|
1028
|
+
mirrorTaskThreadChunk({
|
|
1029
|
+
type: 'start',
|
|
1030
|
+
messageId: streamState.streamId,
|
|
1031
|
+
});
|
|
1032
|
+
mirrorTaskThreadChunk(appServerChunk('turn/started', {
|
|
1033
|
+
threadId: session.session_id,
|
|
1034
|
+
turnId,
|
|
1035
|
+
turn: { id: turnId, status: 'running' },
|
|
1036
|
+
}));
|
|
1037
|
+
}
|
|
1038
|
+
// Use the latest message content as the prompt
|
|
1039
|
+
if (isAutomationMessage) {
|
|
1040
|
+
logger.info({
|
|
1041
|
+
...canonicalAutomationLogFields({
|
|
1042
|
+
chat_jid: chatJid,
|
|
1043
|
+
task_id: taskId,
|
|
1044
|
+
automation_id: automationMessage?.automation_id,
|
|
1045
|
+
schedule_id: automationMessage?.schedule_id,
|
|
1046
|
+
automation_run_id: automationMessage?.automation_run_id,
|
|
1047
|
+
schedule_run_id: automationMessage?.schedule_run_id,
|
|
1048
|
+
agent_id: agentId,
|
|
1049
|
+
}),
|
|
1050
|
+
prompt_excerpt: excerptText(latestMsg?.content || '', 160),
|
|
1051
|
+
context_excerpt: buildTaskContextExcerpt(runnableMessages),
|
|
1052
|
+
event_style: classifyObservableLogEvent('automation.started_real_work').emphasis,
|
|
1053
|
+
}, 'Automation started');
|
|
1054
|
+
}
|
|
1055
|
+
emitGatewayTrace({
|
|
1056
|
+
trace_id: activeTraceId,
|
|
1057
|
+
kind: 'daemon.run.executing',
|
|
1058
|
+
message: 'Dispatching prompt to agent runtime',
|
|
1059
|
+
jid: chatJid,
|
|
1060
|
+
agent_id: agentId,
|
|
1061
|
+
details: {
|
|
1062
|
+
task_id: taskId,
|
|
1063
|
+
model: latestMsg?.model || null,
|
|
1064
|
+
},
|
|
1065
|
+
});
|
|
1066
|
+
const runtimeOptions = latestMsg?.metadata?.runtime_options &&
|
|
1067
|
+
typeof latestMsg.metadata.runtime_options === 'object'
|
|
1068
|
+
? latestMsg.metadata.runtime_options
|
|
1069
|
+
: {};
|
|
1070
|
+
const permissionMode = runtimeOptions.permissionMode === 'default' ||
|
|
1071
|
+
runtimeOptions.permissionMode === 'read-only' ||
|
|
1072
|
+
runtimeOptions.permissionMode === 'workspace-write' ||
|
|
1073
|
+
runtimeOptions.permissionMode === 'full-access' ||
|
|
1074
|
+
runtimeOptions.permissionMode === 'custom'
|
|
1075
|
+
? runtimeOptions.permissionMode
|
|
1076
|
+
: undefined;
|
|
1077
|
+
const effort = runtimeOptions.effort === 'low' ||
|
|
1078
|
+
runtimeOptions.effort === 'medium' ||
|
|
1079
|
+
runtimeOptions.effort === 'high' ||
|
|
1080
|
+
runtimeOptions.effort === 'xhigh'
|
|
1081
|
+
? runtimeOptions.effort
|
|
1082
|
+
: undefined;
|
|
1083
|
+
const networkAccess = runtimeOptions.networkAccess === true;
|
|
1084
|
+
if (chatJid.startsWith('web:')) {
|
|
1085
|
+
const stream = await daemon.runNativeStream(taskId, aiMessages, {
|
|
1086
|
+
model: latestMsg?.model,
|
|
1087
|
+
effort,
|
|
1088
|
+
permissionMode,
|
|
1089
|
+
networkAccess,
|
|
1090
|
+
trustedDataHosts: CODEX_TRUSTED_DATA_HOSTS,
|
|
1091
|
+
});
|
|
1092
|
+
const reader = stream.getReader();
|
|
1093
|
+
const decoder = new TextDecoder();
|
|
1094
|
+
let pending = '';
|
|
1095
|
+
while (true) {
|
|
1096
|
+
const { done, value } = await reader.read();
|
|
1097
|
+
if (done)
|
|
1098
|
+
break;
|
|
1099
|
+
pending += decoder.decode(value, { stream: true });
|
|
1100
|
+
const lines = pending.split('\n');
|
|
1101
|
+
pending = lines.pop() || '';
|
|
1102
|
+
for (const rawLine of lines) {
|
|
1103
|
+
const line = rawLine.trim();
|
|
1104
|
+
if (!line.startsWith('data:'))
|
|
1105
|
+
continue;
|
|
1106
|
+
const payload = line.slice(5).trim();
|
|
1107
|
+
if (!payload)
|
|
1108
|
+
continue;
|
|
1109
|
+
try {
|
|
1110
|
+
const parsedPayload = JSON.parse(payload);
|
|
1111
|
+
captureCodexStreamPayload({
|
|
1112
|
+
agentId,
|
|
1113
|
+
threadId: sessionId,
|
|
1114
|
+
runtimeThreadId: session.session_id,
|
|
1115
|
+
payload: parsedPayload,
|
|
1116
|
+
});
|
|
1117
|
+
broadcastToChat(chatJid, parsedPayload);
|
|
1118
|
+
}
|
|
1119
|
+
catch {
|
|
1120
|
+
// Ignore malformed chunks and keep stream processing alive.
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
const tail = pending.trim();
|
|
1125
|
+
if (tail.startsWith('data:')) {
|
|
1126
|
+
const payload = tail.slice(5).trim();
|
|
1127
|
+
if (payload) {
|
|
1128
|
+
try {
|
|
1129
|
+
const parsedPayload = JSON.parse(payload);
|
|
1130
|
+
captureCodexStreamPayload({
|
|
1131
|
+
agentId,
|
|
1132
|
+
threadId: sessionId,
|
|
1133
|
+
runtimeThreadId: session.session_id,
|
|
1134
|
+
payload: parsedPayload,
|
|
1135
|
+
});
|
|
1136
|
+
broadcastToChat(chatJid, parsedPayload);
|
|
1137
|
+
}
|
|
1138
|
+
catch {
|
|
1139
|
+
// Ignore malformed final chunk.
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
// Mark stream dispatch completion after all chunks are broadcast to
|
|
1144
|
+
// active web clients. `/chat` waits on this marker before closing
|
|
1145
|
+
// the SSE response to avoid cutting off trailing streamed chunks.
|
|
1146
|
+
storeSessionUiEvent(agentId, sessionId, {
|
|
1147
|
+
id: `${taskId}:stream-dispatched:${Date.now()}`,
|
|
1148
|
+
timestamp: new Date().toISOString(),
|
|
1149
|
+
task_id: taskId,
|
|
1150
|
+
chunk: {
|
|
1151
|
+
type: 'stream-dispatched',
|
|
1152
|
+
},
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
await daemon.run(taskId, runtimePrompt, {
|
|
1157
|
+
model: latestMsg?.model,
|
|
1158
|
+
effort,
|
|
1159
|
+
permissionMode,
|
|
1160
|
+
networkAccess,
|
|
1161
|
+
trustedDataHosts: CODEX_TRUSTED_DATA_HOSTS,
|
|
1162
|
+
timeoutMs: isAutomationMessage ? automationTaskTimeoutMs() : undefined,
|
|
1163
|
+
resume: isAutomationMessage ? false : undefined,
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
emitGatewayTrace({
|
|
1167
|
+
trace_id: activeTraceId,
|
|
1168
|
+
kind: 'daemon.run.completed',
|
|
1169
|
+
message: 'Agent runtime completed successfully',
|
|
1170
|
+
jid: chatJid,
|
|
1171
|
+
agent_id: agentId,
|
|
1172
|
+
details: {
|
|
1173
|
+
task_id: taskId,
|
|
1174
|
+
},
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
finally {
|
|
1178
|
+
if (activeDaemons.get(chatJid) === daemon) {
|
|
1179
|
+
activeDaemons.delete(chatJid);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
finally {
|
|
1184
|
+
if (heartbeatTimer) {
|
|
1185
|
+
clearInterval(heartbeatTimer);
|
|
1186
|
+
heartbeatTimer = null;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return true;
|
|
1190
|
+
}
|
|
1191
|
+
catch (err) {
|
|
1192
|
+
logger.error({
|
|
1193
|
+
err,
|
|
1194
|
+
chat_jid: chatJid,
|
|
1195
|
+
automation_id: automationMessage?.automation_id,
|
|
1196
|
+
schedule_id: automationMessage?.schedule_id,
|
|
1197
|
+
automation_run_id: automationMessage?.automation_run_id,
|
|
1198
|
+
schedule_run_id: automationMessage?.schedule_run_id,
|
|
1199
|
+
}, 'Agent failed');
|
|
1200
|
+
await completeAutomationIfNeeded('failed', err?.message || 'Agent failed');
|
|
1201
|
+
emitGatewayTrace({
|
|
1202
|
+
trace_id: activeTraceId,
|
|
1203
|
+
kind: 'daemon.run.failed',
|
|
1204
|
+
message: 'Agent runtime failed',
|
|
1205
|
+
level: 'error',
|
|
1206
|
+
jid: chatJid,
|
|
1207
|
+
agent_id: agentId,
|
|
1208
|
+
details: {
|
|
1209
|
+
error: err?.message || String(err),
|
|
1210
|
+
},
|
|
1211
|
+
});
|
|
1212
|
+
await sendFn(chatJid, `❌ **Agent Error**\n\`\`\`\n${err.message}\n\`\`\``, { trace_id: activeTraceId, task_id: latestMsg?.task_id });
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
catch (err) {
|
|
1217
|
+
logger.error({ err, chatJid }, 'processMessages: unexpected error');
|
|
1218
|
+
return false;
|
|
1219
|
+
}
|
|
1220
|
+
finally {
|
|
1221
|
+
routeSetTyping(channels, chatJid, false);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
/** Resolve the workspace directory for a group. */
|
|
1225
|
+
function getFactoryPath(group) {
|
|
1226
|
+
return path.join(SUPEN_HOME, 'factory', group.folder);
|
|
1227
|
+
}
|
|
1228
|
+
export function getAvailableProjects() {
|
|
1229
|
+
const allChats = getAllChats();
|
|
1230
|
+
return allChats.map((chat) => ({
|
|
1231
|
+
jid: chat.jid,
|
|
1232
|
+
name: chat.name || chat.jid,
|
|
1233
|
+
lastActivity: chat.last_message_time,
|
|
1234
|
+
isRegistered: !!registeredProjects[chat.jid],
|
|
1235
|
+
}));
|
|
1236
|
+
}
|
|
1237
|
+
/** @internal - for tests only. */
|
|
1238
|
+
export function _setRegisteredProjects(groups) {
|
|
1239
|
+
registeredProjects = groups;
|
|
1240
|
+
}
|
|
1241
|
+
function registerProject(jid, group) {
|
|
1242
|
+
// Ensure a chats row exists for this JID before registering,
|
|
1243
|
+
// so that subsequent message storage doesn't fail with FK constraint.
|
|
1244
|
+
storeChatMetadata(jid, new Date().toISOString(), group.name);
|
|
1245
|
+
setRegisteredProject(jid, group);
|
|
1246
|
+
registeredProjects[jid] = group;
|
|
1247
|
+
scheduleSupabasePush();
|
|
1248
|
+
logger.info({ jid, folder: group.folder }, 'Group registered');
|
|
1249
|
+
}
|
|
1250
|
+
async function startMessageLoop() {
|
|
1251
|
+
if (messageLoopRunning) {
|
|
1252
|
+
logger.debug('Message loop already running, skipping duplicate start');
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
messageLoopRunning = true;
|
|
1256
|
+
logger.debug('Starting message loop');
|
|
1257
|
+
// Track what the message loop has already SEEN (to avoid re-enqueuing).
|
|
1258
|
+
// This is separate from lastAgentTimestamp which tracks what processMessages
|
|
1259
|
+
// has actually PROCESSED.
|
|
1260
|
+
const lastLoopTimestamp = {};
|
|
1261
|
+
while (messageLoopRunning) {
|
|
1262
|
+
try {
|
|
1263
|
+
const jids = Object.keys(registeredProjects);
|
|
1264
|
+
const lastGlobalTs = Object.values(lastLoopTimestamp).sort().reverse()[0] ||
|
|
1265
|
+
Object.values(lastAgentTimestamp).sort().reverse()[0] ||
|
|
1266
|
+
'';
|
|
1267
|
+
const { messages } = getNewMessages(jids, lastGlobalTs, ASSISTANT_NAME);
|
|
1268
|
+
for (const msg of messages) {
|
|
1269
|
+
const chatJid = msg.chat_jid;
|
|
1270
|
+
const group = registeredProjects[chatJid];
|
|
1271
|
+
if (!group) {
|
|
1272
|
+
logger.debug({ chatJid }, 'Skipping message from unregistered group');
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
// Track that the loop has seen this message (prevents re-enqueue)
|
|
1276
|
+
if (!lastLoopTimestamp[chatJid] ||
|
|
1277
|
+
msg.timestamp > lastLoopTimestamp[chatJid]) {
|
|
1278
|
+
lastLoopTimestamp[chatJid] = msg.timestamp;
|
|
1279
|
+
}
|
|
1280
|
+
if (msg.is_from_me)
|
|
1281
|
+
continue;
|
|
1282
|
+
const triggerMatch = TRIGGER_PATTERN.test(msg.content);
|
|
1283
|
+
if (group.isMain || !group.requiresTrigger || triggerMatch) {
|
|
1284
|
+
logger.info({ chatJid, sender: msg.sender_name }, 'Trigger matched, checking locks');
|
|
1285
|
+
scheduleRun(chatJid);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
catch (err) {
|
|
1290
|
+
logger.error({ err }, 'Error in message loop');
|
|
1291
|
+
}
|
|
1292
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
function loadState() {
|
|
1296
|
+
const groups = getAllRegisteredProjects();
|
|
1297
|
+
for (const [jid, group] of Object.entries(groups)) {
|
|
1298
|
+
registeredProjects[jid] = group;
|
|
1299
|
+
}
|
|
1300
|
+
logger.info({ count: Object.keys(registeredProjects).length }, 'Groups loaded');
|
|
1301
|
+
const dbSessions = getAllSessions();
|
|
1302
|
+
for (const sess of dbSessions) {
|
|
1303
|
+
sessions[sess.agent_id] = sess.session_id;
|
|
1304
|
+
}
|
|
1305
|
+
logger.info({ count: Object.keys(sessions).length }, 'Sessions loaded');
|
|
1306
|
+
for (const jid of Object.keys(registeredProjects)) {
|
|
1307
|
+
const timestamp = getRouterState(jid);
|
|
1308
|
+
if (timestamp) {
|
|
1309
|
+
lastAgentTimestamp[jid] = timestamp;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
function handleChatMetadata(chatJid, timestamp, name, channel, isGroup) {
|
|
1314
|
+
storeChatMetadata(chatJid, timestamp, name, channel, isGroup);
|
|
1315
|
+
}
|
|
1316
|
+
function recoverPendingMessages() {
|
|
1317
|
+
for (const [chatJid, group] of Object.entries(registeredProjects)) {
|
|
1318
|
+
const sinceTimestamp = lastAgentTimestamp[chatJid] || '';
|
|
1319
|
+
const pending = getMessagesSince(chatJid, sinceTimestamp, ASSISTANT_NAME);
|
|
1320
|
+
if (pending.length > 0) {
|
|
1321
|
+
logger.info({ group: group.name, pendingCount: pending.length }, 'Recovery: replaying unprocessed messages');
|
|
1322
|
+
scheduleRun(chatJid);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
let sendFn;
|
|
1327
|
+
let createChannelFn;
|
|
1328
|
+
function ensureNotificationSessionExists(targetJid, fallbackAgentId) {
|
|
1329
|
+
const resolved = resolveFromChatJid(targetJid);
|
|
1330
|
+
if (!resolved)
|
|
1331
|
+
return;
|
|
1332
|
+
ensureSession({
|
|
1333
|
+
agent_id: resolved.agentId || fallbackAgentId || 'unknown',
|
|
1334
|
+
session_id: resolved.sessionId,
|
|
1335
|
+
channel: targetJid.startsWith('web:') ? 'http' : targetJid.split(':')[0] || 'unknown',
|
|
1336
|
+
source_ref: targetJid,
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
async function main() {
|
|
1340
|
+
await acquireDaemonLock({
|
|
1341
|
+
interactive: process.env.SUPEN_DAEMON_LOCK_PROMPT === '1' ||
|
|
1342
|
+
process.env.npm_lifecycle_event === 'dev',
|
|
1343
|
+
terminateExisting: process.env.SUPEN_DAEMON_LOCK_TAKEOVER === '1' ||
|
|
1344
|
+
process.env.npm_lifecycle_event === 'dev',
|
|
1345
|
+
});
|
|
1346
|
+
const productName = process.env.SUPEN_PRODUCT_NAME || 'Supen';
|
|
1347
|
+
logger.info(`${productName} Daemon starting`);
|
|
1348
|
+
initStore();
|
|
1349
|
+
cleanupStaleSessions();
|
|
1350
|
+
cleanupStaleAutomationExecutions();
|
|
1351
|
+
logger.info('Database initialized');
|
|
1352
|
+
// Ensure default agent and session exist
|
|
1353
|
+
ensureSession({
|
|
1354
|
+
agent_id: 'supen-agent',
|
|
1355
|
+
session_id: 'supen-session',
|
|
1356
|
+
channel: 'web',
|
|
1357
|
+
agent_name: 'Supen Agent',
|
|
1358
|
+
space_id: process.env.SUPEN_SPACE_ID || 'local',
|
|
1359
|
+
});
|
|
1360
|
+
logger.info('Default agent and session ensured');
|
|
1361
|
+
if (isSupabaseConfigured()) {
|
|
1362
|
+
await pullFromSupabase();
|
|
1363
|
+
startPeriodicSupabasePush();
|
|
1364
|
+
}
|
|
1365
|
+
loadState();
|
|
1366
|
+
const channelOpts = {
|
|
1367
|
+
onMessage: (chatJid, msg) => {
|
|
1368
|
+
const traceId = readTraceIdFromMetadata(msg.metadata) || createDaemonTraceId();
|
|
1369
|
+
if (!msg.metadata || typeof msg.metadata !== 'object' || Array.isArray(msg.metadata)) {
|
|
1370
|
+
msg.metadata = { trace_id: traceId };
|
|
1371
|
+
}
|
|
1372
|
+
else if (!readTraceIdFromMetadata(msg.metadata)) {
|
|
1373
|
+
msg.metadata = { ...msg.metadata, trace_id: traceId };
|
|
1374
|
+
}
|
|
1375
|
+
emitGatewayTrace({
|
|
1376
|
+
trace_id: traceId,
|
|
1377
|
+
kind: 'daemon.message.received',
|
|
1378
|
+
message: 'Daemon accepted inbound message from gateway',
|
|
1379
|
+
channel: typeof msg.metadata?.channel === 'string' ? msg.metadata.channel : undefined,
|
|
1380
|
+
jid: chatJid,
|
|
1381
|
+
agent_id: msg.agent_id,
|
|
1382
|
+
details: {
|
|
1383
|
+
sender: msg.sender,
|
|
1384
|
+
sender_name: msg.sender_name,
|
|
1385
|
+
content_excerpt: (msg.content || '').slice(0, 160),
|
|
1386
|
+
},
|
|
1387
|
+
});
|
|
1388
|
+
storeMessage(msg);
|
|
1389
|
+
// Event-driven: immediately process incoming messages instead of waiting for poll
|
|
1390
|
+
if (!msg.is_from_me) {
|
|
1391
|
+
scheduleRun(chatJid);
|
|
1392
|
+
emitGatewayTrace({
|
|
1393
|
+
trace_id: traceId,
|
|
1394
|
+
kind: 'daemon.message.queued',
|
|
1395
|
+
message: 'Message queued for processing',
|
|
1396
|
+
jid: chatJid,
|
|
1397
|
+
agent_id: msg.agent_id,
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
},
|
|
1401
|
+
onSessionStop: (agentId, sessionId, actor) => requestSessionStop(agentId, sessionId, actor),
|
|
1402
|
+
onChatMetadata: (chatJid, timestamp, name, channel, isGroup) => handleChatMetadata(chatJid, timestamp, name, channel, isGroup),
|
|
1403
|
+
registeredProjects: () => registeredProjects,
|
|
1404
|
+
onGroupRegistered: (jid, group) => registerProject(jid, group),
|
|
1405
|
+
};
|
|
1406
|
+
// ── Gateway uplink (core infrastructure, N:1 — many nodes → one gateway) ──
|
|
1407
|
+
const gateway = new Gateway({
|
|
1408
|
+
onMessage: (chatJid, msg) => channelOpts.onMessage(chatJid, msg),
|
|
1409
|
+
onChatMetadata: (chatJid, timestamp, name, channel, isGroup) => channelOpts.onChatMetadata(chatJid, timestamp, name, channel, isGroup),
|
|
1410
|
+
onEnrollmentSuccess: async (daemonId) => {
|
|
1411
|
+
// After enrollment, publish the daemon's gateway routing metadata.
|
|
1412
|
+
try {
|
|
1413
|
+
await gatewayRoutingConfig.sync({ force: true });
|
|
1414
|
+
logger.info({ daemonId }, 'Gateway routing config sent');
|
|
1415
|
+
}
|
|
1416
|
+
catch (err) {
|
|
1417
|
+
logger.error({ err }, 'Failed to send gateway routing config');
|
|
1418
|
+
}
|
|
1419
|
+
},
|
|
1420
|
+
});
|
|
1421
|
+
setGatewayInstance(gateway);
|
|
1422
|
+
const gatewayRoutingConfig = createGatewayRoutingConfigSync({
|
|
1423
|
+
gateway,
|
|
1424
|
+
loadCommands: () => listCommands({ channel: 'gateway' }),
|
|
1425
|
+
logger,
|
|
1426
|
+
});
|
|
1427
|
+
await gateway.connect();
|
|
1428
|
+
setInterval(() => {
|
|
1429
|
+
gatewayRoutingConfig.sync().catch((err) => {
|
|
1430
|
+
logger.warn({ err }, 'Failed to refresh gateway routing config');
|
|
1431
|
+
});
|
|
1432
|
+
}, 5_000).unref?.();
|
|
1433
|
+
// Add to channels so outbound routing (sendMessage/ownsJid) works
|
|
1434
|
+
channels.push(gateway);
|
|
1435
|
+
// ── Consumer channels ──
|
|
1436
|
+
const CHANNEL_CONNECT_TIMEOUT = 15_000;
|
|
1437
|
+
const enabledFromConfig = getEnabledChannelsFromConfig();
|
|
1438
|
+
const registeredChannelNames = getRegisteredChannelNames();
|
|
1439
|
+
const toConnect = enabledFromConfig.length > 0
|
|
1440
|
+
? registeredChannelNames.filter((n) => enabledFromConfig.includes(n))
|
|
1441
|
+
: registeredChannelNames;
|
|
1442
|
+
for (const name of toConnect) {
|
|
1443
|
+
const factory = getChannelFactory(name);
|
|
1444
|
+
if (factory) {
|
|
1445
|
+
const channel = factory(channelOpts);
|
|
1446
|
+
if (channel) {
|
|
1447
|
+
try {
|
|
1448
|
+
logger.info({ channel: name }, 'Connecting channel');
|
|
1449
|
+
await Promise.race([
|
|
1450
|
+
channel.connect(),
|
|
1451
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Channel ${name} connect timed out after ${CHANNEL_CONNECT_TIMEOUT / 1000}s`)), CHANNEL_CONNECT_TIMEOUT)),
|
|
1452
|
+
]);
|
|
1453
|
+
channels.push(channel);
|
|
1454
|
+
}
|
|
1455
|
+
catch (err) {
|
|
1456
|
+
logger.error({ channel: name, err }, 'Failed to connect channel — skipping');
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
sendFn = async (jid, text, options) => {
|
|
1462
|
+
const messageId = options?.message_id || `bot-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
1463
|
+
if (!jid.startsWith('task:')) {
|
|
1464
|
+
// Check if this is a gateway session - use gateway's routing which handles both
|
|
1465
|
+
// regular gateway sessions and channel-based sessions (via sendReplyForChat)
|
|
1466
|
+
const gw = getGatewayInstance();
|
|
1467
|
+
if (jid.startsWith('gateway:') && gw) {
|
|
1468
|
+
await gw.sendReplyForChat(jid, text, options?.message_id, options?.trace_id);
|
|
1469
|
+
}
|
|
1470
|
+
else {
|
|
1471
|
+
await routeOutbound(channels, jid, text, {
|
|
1472
|
+
embeds: options?.embeds,
|
|
1473
|
+
message_id: messageId,
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
const ts = new Date().toISOString();
|
|
1478
|
+
lastAgentTimestamp[jid] = ts;
|
|
1479
|
+
setRouterState(jid, ts);
|
|
1480
|
+
// Store bot response so conversation history includes assistant turns
|
|
1481
|
+
if (text.trim() && !jid.startsWith('task:')) {
|
|
1482
|
+
storeMessage({
|
|
1483
|
+
id: messageId,
|
|
1484
|
+
chat_jid: jid,
|
|
1485
|
+
sender: ASSISTANT_NAME,
|
|
1486
|
+
sender_name: ASSISTANT_NAME,
|
|
1487
|
+
content: text,
|
|
1488
|
+
timestamp: ts,
|
|
1489
|
+
is_from_me: true,
|
|
1490
|
+
is_bot_message: true,
|
|
1491
|
+
...(options?.task_id ? { task_id: options.task_id } : {}),
|
|
1492
|
+
...(options?.trace_id ? { metadata: { trace_id: options.trace_id } } : {}),
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
createChannelFn = async (fromJid, channelName) => {
|
|
1497
|
+
for (const ch of channels) {
|
|
1498
|
+
if (ch.ownsJid(fromJid) && ch.createChannel) {
|
|
1499
|
+
return ch.createChannel(fromJid, channelName);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
return null;
|
|
1503
|
+
};
|
|
1504
|
+
recoverPendingMessages();
|
|
1505
|
+
// --- Auto-enable default skills ---
|
|
1506
|
+
// SECURITY: We do NOT grant approveLevel3 here. Any skill listed in
|
|
1507
|
+
// defaultEnabled that requires Level 3 will be skipped at startup with a
|
|
1508
|
+
// warning. Level 3 skills require explicit operator approval via the CLI.
|
|
1509
|
+
try {
|
|
1510
|
+
const skillHub = new SkillHub(SKILLS_CONFIG);
|
|
1511
|
+
const defaultSkills = SKILLS_CONFIG.defaultEnabled;
|
|
1512
|
+
const ctx = { actor: 'system-init', isAdmin: true, approveLevel3: false };
|
|
1513
|
+
for (const skillName of defaultSkills) {
|
|
1514
|
+
try {
|
|
1515
|
+
if (!skillHub.getInstalled(skillName)) {
|
|
1516
|
+
skillHub.installSkill(skillName, ctx);
|
|
1517
|
+
}
|
|
1518
|
+
const installed = skillHub.getInstalled(skillName);
|
|
1519
|
+
if (!installed)
|
|
1520
|
+
continue;
|
|
1521
|
+
// Issue #50: skip Level 3 skills — they must be explicitly approved
|
|
1522
|
+
if (installed.permissionLevel === 3) {
|
|
1523
|
+
logger.warn({ skill: skillName }, 'Skipping auto-enable of Level 3 default skill — explicit approval required. Use `supen-node skills enable --approve` to enable it.');
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
if (!installed.enabled) {
|
|
1527
|
+
skillHub.enableSkill(skillName, ctx);
|
|
1528
|
+
logger.info(`Auto-enabled default skill: ${skillName}`);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
catch (err) {
|
|
1532
|
+
logger.warn(`Failed to auto-enable skill ${skillName}: ${err.message}`);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
catch (err) {
|
|
1537
|
+
logger.warn(`Failed to initialize default skills: ${err.message}`);
|
|
1538
|
+
}
|
|
1539
|
+
startMessageLoop();
|
|
1540
|
+
startAutomationEventListener({
|
|
1541
|
+
enqueueMessage: channelOpts.onMessage,
|
|
1542
|
+
});
|
|
1543
|
+
const shutdown = async (signal) => {
|
|
1544
|
+
logger.info({ signal }, 'Shutdown signal received');
|
|
1545
|
+
// Wait for all active agents to finish (up to 10s)
|
|
1546
|
+
const activePromises = Array.from(activeAgentLocks.values());
|
|
1547
|
+
if (activePromises.length > 0) {
|
|
1548
|
+
logger.info({ draining: activePromises.length }, 'Waiting for active agents to finish...');
|
|
1549
|
+
await Promise.race([
|
|
1550
|
+
Promise.allSettled(activePromises),
|
|
1551
|
+
new Promise((r) => setTimeout(r, 10000)),
|
|
1552
|
+
]);
|
|
1553
|
+
}
|
|
1554
|
+
await stopAutomationEventListener();
|
|
1555
|
+
await Promise.allSettled(channels.map((ch) => ch.disconnect()));
|
|
1556
|
+
messageLoopRunning = false;
|
|
1557
|
+
process.exit(0);
|
|
1558
|
+
};
|
|
1559
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
1560
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
1561
|
+
logger.info(`${productName} Daemon running (trigger: @${ASSISTANT_NAME})`);
|
|
1562
|
+
}
|
|
1563
|
+
export class SupenDaemon {
|
|
1564
|
+
constructor(config = {}) {
|
|
1565
|
+
if (config.productName) {
|
|
1566
|
+
process.env.SUPEN_PRODUCT_NAME = config.productName;
|
|
1567
|
+
}
|
|
1568
|
+
configureSupenDaemon({ dataDir: config.dataDir });
|
|
1569
|
+
}
|
|
1570
|
+
async start() {
|
|
1571
|
+
initializeDataDirs();
|
|
1572
|
+
await main();
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
import url from 'node:url';
|
|
1576
|
+
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
|
1577
|
+
new SupenDaemon().start().catch((err) => {
|
|
1578
|
+
console.error('Fatal daemon error:', err);
|
|
1579
|
+
process.exit(1);
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
//# sourceMappingURL=index.js.map
|