@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,2956 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem-based data store for Supen.
|
|
3
|
+
*
|
|
4
|
+
* Replaces SQLite (db.ts) with plain files:
|
|
5
|
+
* - agents/{id}/agent.json → AgentRecord
|
|
6
|
+
* - sessions/{sid}/ → session.json + messages.jsonl
|
|
7
|
+
* - router-state.json → key-value pairs
|
|
8
|
+
* - registered groups → agent.json `sources` field (future)
|
|
9
|
+
*
|
|
10
|
+
* Design philosophy: the filesystem IS the database.
|
|
11
|
+
* See docs/data-management-design.md
|
|
12
|
+
*/
|
|
13
|
+
import crypto from 'crypto';
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { CronExpressionParser } from 'cron-parser';
|
|
17
|
+
import { AGENTS_DIR, SUPEN_HOME, TIMEZONE, getAgentModelConfig } from './config.js';
|
|
18
|
+
import { computeNextAutomationRun } from './automation-timing.js';
|
|
19
|
+
import { logger } from './logger.js';
|
|
20
|
+
import { agentHomePaths, ensureSpaceLayout, spacePaths, spacesDir, } from './storage-paths.js';
|
|
21
|
+
import { resolveTaskArtifactPaths } from './task-artifacts.js';
|
|
22
|
+
const jsonlAppendDirCache = new Set();
|
|
23
|
+
const getGlobalUsagePath = () => path.join(SUPEN_HOME, 'global-usage.json');
|
|
24
|
+
export const AUTOMATION_TASK_AGENT_ID = 'automation-task';
|
|
25
|
+
import { isValidGroupFolder } from './utils.js';
|
|
26
|
+
import { assertSafePathSegment, resolveWithin } from './security.js';
|
|
27
|
+
const DEFAULT_SPACE_ID = 'local';
|
|
28
|
+
function defaultSpaceId() {
|
|
29
|
+
return (process.env.SUPEN_SPACE_ID || '').trim() || DEFAULT_SPACE_ID;
|
|
30
|
+
}
|
|
31
|
+
const AUTOMATION_STALE_EXECUTION_MS = 10 * 60 * 1000;
|
|
32
|
+
const JSONL_TAIL_MAX_READ_BYTES = 16 * 1024 * 1024;
|
|
33
|
+
const SESSION_UI_EVENT_RECOVERY_LIMIT = 2_000;
|
|
34
|
+
const TASK_UI_EVENT_HISTORY_LIMIT = 5_000;
|
|
35
|
+
// ═══════════════════════════════════════════════════════════════
|
|
36
|
+
// Helpers
|
|
37
|
+
// ═══════════════════════════════════════════════════════════════
|
|
38
|
+
function readJson(filePath) {
|
|
39
|
+
try {
|
|
40
|
+
if (!fs.existsSync(filePath))
|
|
41
|
+
return undefined;
|
|
42
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function writeJson(filePath, data) {
|
|
49
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
50
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
51
|
+
}
|
|
52
|
+
function appendJsonl(filePath, record) {
|
|
53
|
+
const dir = path.dirname(filePath);
|
|
54
|
+
if (!jsonlAppendDirCache.has(dir) || !fs.existsSync(dir)) {
|
|
55
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
56
|
+
jsonlAppendDirCache.add(dir);
|
|
57
|
+
}
|
|
58
|
+
fs.appendFileSync(filePath, JSON.stringify(record) + '\n', 'utf-8');
|
|
59
|
+
}
|
|
60
|
+
function readJsonl(filePath) {
|
|
61
|
+
try {
|
|
62
|
+
if (!fs.existsSync(filePath))
|
|
63
|
+
return [];
|
|
64
|
+
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
65
|
+
if (!content)
|
|
66
|
+
return [];
|
|
67
|
+
return content.split('\n').map((line) => JSON.parse(line));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function listJsonFiles(dir) {
|
|
74
|
+
try {
|
|
75
|
+
if (!fs.existsSync(dir))
|
|
76
|
+
return [];
|
|
77
|
+
return fs
|
|
78
|
+
.readdirSync(dir)
|
|
79
|
+
.filter((f) => f.endsWith('.json'))
|
|
80
|
+
.map((f) => f.replace(/\.json$/, ''));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Read last N lines from a JSONL file (efficient tail). */
|
|
87
|
+
function readJsonlTail(filePath, limit) {
|
|
88
|
+
try {
|
|
89
|
+
if (limit <= 0)
|
|
90
|
+
return [];
|
|
91
|
+
if (!fs.existsSync(filePath))
|
|
92
|
+
return [];
|
|
93
|
+
const stat = fs.statSync(filePath);
|
|
94
|
+
if (stat.size <= 0)
|
|
95
|
+
return [];
|
|
96
|
+
const fd = fs.openSync(filePath, 'r');
|
|
97
|
+
try {
|
|
98
|
+
const chunkSize = 64 * 1024;
|
|
99
|
+
let pos = stat.size;
|
|
100
|
+
let newlineCount = 0;
|
|
101
|
+
let bytesScanned = 0;
|
|
102
|
+
const chunks = [];
|
|
103
|
+
while (pos > 0 && newlineCount <= limit && bytesScanned < JSONL_TAIL_MAX_READ_BYTES) {
|
|
104
|
+
const bytesToRead = Math.min(chunkSize, pos, JSONL_TAIL_MAX_READ_BYTES - bytesScanned);
|
|
105
|
+
pos -= bytesToRead;
|
|
106
|
+
bytesScanned += bytesToRead;
|
|
107
|
+
const chunk = Buffer.allocUnsafe(bytesToRead);
|
|
108
|
+
const bytesRead = fs.readSync(fd, chunk, 0, bytesToRead, pos);
|
|
109
|
+
if (bytesRead <= 0)
|
|
110
|
+
break;
|
|
111
|
+
const view = bytesRead === chunk.length ? chunk : chunk.subarray(0, bytesRead);
|
|
112
|
+
for (let i = 0; i < view.length; i += 1) {
|
|
113
|
+
if (view[i] === 0x0a)
|
|
114
|
+
newlineCount += 1;
|
|
115
|
+
}
|
|
116
|
+
chunks.unshift(view);
|
|
117
|
+
}
|
|
118
|
+
if (chunks.length === 0)
|
|
119
|
+
return [];
|
|
120
|
+
const content = Buffer.concat(chunks).toString('utf-8').trim();
|
|
121
|
+
if (!content)
|
|
122
|
+
return [];
|
|
123
|
+
const tail = content.split('\n').slice(-limit);
|
|
124
|
+
const out = [];
|
|
125
|
+
for (const line of tail) {
|
|
126
|
+
if (!line.trim())
|
|
127
|
+
continue;
|
|
128
|
+
try {
|
|
129
|
+
out.push(JSON.parse(line));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Skip malformed lines to preserve best-effort retrieval.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
fs.closeSync(fd);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/** Read matching JSONL records from the tail until the caller has enough context. */
|
|
146
|
+
function readJsonlTailMatchingUntil(filePath, matches, shouldStop) {
|
|
147
|
+
try {
|
|
148
|
+
if (!fs.existsSync(filePath))
|
|
149
|
+
return [];
|
|
150
|
+
const stat = fs.statSync(filePath);
|
|
151
|
+
if (stat.size <= 0)
|
|
152
|
+
return [];
|
|
153
|
+
const fd = fs.openSync(filePath, 'r');
|
|
154
|
+
try {
|
|
155
|
+
const chunkSize = 64 * 1024;
|
|
156
|
+
let pos = stat.size;
|
|
157
|
+
let carry = Buffer.alloc(0);
|
|
158
|
+
let bytesScanned = 0;
|
|
159
|
+
const matched = [];
|
|
160
|
+
while (pos > 0 && bytesScanned < JSONL_TAIL_MAX_READ_BYTES) {
|
|
161
|
+
const bytesToRead = Math.min(chunkSize, pos, JSONL_TAIL_MAX_READ_BYTES - bytesScanned);
|
|
162
|
+
pos -= bytesToRead;
|
|
163
|
+
bytesScanned += bytesToRead;
|
|
164
|
+
const chunk = Buffer.allocUnsafe(bytesToRead);
|
|
165
|
+
const bytesRead = fs.readSync(fd, chunk, 0, bytesToRead, pos);
|
|
166
|
+
if (bytesRead <= 0)
|
|
167
|
+
break;
|
|
168
|
+
const view = bytesRead === chunk.length ? chunk : chunk.subarray(0, bytesRead);
|
|
169
|
+
const content = carry.length > 0 ? Buffer.concat([view, carry]) : view;
|
|
170
|
+
const lines = [];
|
|
171
|
+
let lineStart = 0;
|
|
172
|
+
for (let i = 0; i < content.length; i += 1) {
|
|
173
|
+
if (content[i] !== 0x0a)
|
|
174
|
+
continue;
|
|
175
|
+
lines.push(content.subarray(lineStart, i));
|
|
176
|
+
lineStart = i + 1;
|
|
177
|
+
}
|
|
178
|
+
lines.push(content.subarray(lineStart));
|
|
179
|
+
if (pos > 0) {
|
|
180
|
+
carry = lines.shift() || Buffer.alloc(0);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
carry = Buffer.alloc(0);
|
|
184
|
+
}
|
|
185
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
186
|
+
const line = lines[i]?.toString('utf-8').trim();
|
|
187
|
+
if (!line)
|
|
188
|
+
continue;
|
|
189
|
+
try {
|
|
190
|
+
const record = JSON.parse(line);
|
|
191
|
+
if (!matches(record))
|
|
192
|
+
continue;
|
|
193
|
+
matched.push(record);
|
|
194
|
+
if (shouldStop(record)) {
|
|
195
|
+
return matched.reverse();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Skip malformed lines to preserve best-effort retrieval.
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return matched.reverse();
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
fs.closeSync(fd);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function listDirs(parentDir) {
|
|
214
|
+
try {
|
|
215
|
+
if (!fs.existsSync(parentDir))
|
|
216
|
+
return [];
|
|
217
|
+
return fs
|
|
218
|
+
.readdirSync(parentDir, { withFileTypes: true })
|
|
219
|
+
.filter((d) => d.isDirectory())
|
|
220
|
+
.map((d) => d.name);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// ═══════════════════════════════════════════════════════════════
|
|
227
|
+
// Path helpers
|
|
228
|
+
// ═══════════════════════════════════════════════════════════════
|
|
229
|
+
function listSpaceIds() {
|
|
230
|
+
return listDirs(spacesDir());
|
|
231
|
+
}
|
|
232
|
+
function normalizeAgentNameValue(name, agentId) {
|
|
233
|
+
if (typeof name !== 'string')
|
|
234
|
+
return undefined;
|
|
235
|
+
const trimmed = name.trim();
|
|
236
|
+
if (!trimmed)
|
|
237
|
+
return undefined;
|
|
238
|
+
const lowered = trimmed.toLowerCase();
|
|
239
|
+
if (lowered === 'unknown' ||
|
|
240
|
+
lowered === 'undefined' ||
|
|
241
|
+
lowered === 'null' ||
|
|
242
|
+
lowered === 'n/a' ||
|
|
243
|
+
lowered === 'none') {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
if (trimmed === agentId)
|
|
247
|
+
return agentId;
|
|
248
|
+
return trimmed;
|
|
249
|
+
}
|
|
250
|
+
function normalizeAgentRecord(record) {
|
|
251
|
+
const normalizedName = normalizeAgentNameValue(record.name, record.agent_id) || record.agent_id;
|
|
252
|
+
return {
|
|
253
|
+
...record,
|
|
254
|
+
name: normalizedName,
|
|
255
|
+
space_id: record.space_id || defaultSpaceId(),
|
|
256
|
+
skills: record.skills || { mode: 'all', allowed: ['*'] },
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function resolveAgentRecord(agentId) {
|
|
260
|
+
const home = agentHomePaths(agentId);
|
|
261
|
+
const record = readJson(home.config);
|
|
262
|
+
if (!record)
|
|
263
|
+
return undefined;
|
|
264
|
+
return {
|
|
265
|
+
record,
|
|
266
|
+
baseDir: home.base,
|
|
267
|
+
configPath: home.config,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function findAgentRecord(agentId) {
|
|
271
|
+
const resolved = resolveAgentRecord(agentId);
|
|
272
|
+
return resolved ? normalizeAgentRecord(resolved.record) : undefined;
|
|
273
|
+
}
|
|
274
|
+
export function getAgentStorage(agentId) {
|
|
275
|
+
const resolved = resolveAgentRecord(agentId);
|
|
276
|
+
if (!resolved)
|
|
277
|
+
return undefined;
|
|
278
|
+
return {
|
|
279
|
+
agent: normalizeAgentRecord(resolved.record),
|
|
280
|
+
baseDir: resolved.baseDir,
|
|
281
|
+
configPath: resolved.configPath,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function agentDir(agentId) {
|
|
285
|
+
const resolved = resolveAgentRecord(agentId);
|
|
286
|
+
if (resolved)
|
|
287
|
+
return resolved.baseDir;
|
|
288
|
+
return agentHomePaths(agentId).base;
|
|
289
|
+
}
|
|
290
|
+
function agentJsonPath(agentId) {
|
|
291
|
+
const resolved = resolveAgentRecord(agentId);
|
|
292
|
+
if (resolved)
|
|
293
|
+
return resolved.configPath;
|
|
294
|
+
return agentHomePaths(agentId).config;
|
|
295
|
+
}
|
|
296
|
+
function sessionsRootDir() {
|
|
297
|
+
return path.join(SUPEN_HOME, 'sessions');
|
|
298
|
+
}
|
|
299
|
+
function archivedSessionsRootDir() {
|
|
300
|
+
return path.join(SUPEN_HOME, 'archived_sessions');
|
|
301
|
+
}
|
|
302
|
+
function rootSessionDir(sessionId) {
|
|
303
|
+
const safeSessionId = assertSafePathSegment(sessionId, 'session_id');
|
|
304
|
+
return resolveWithin(sessionsRootDir(), safeSessionId);
|
|
305
|
+
}
|
|
306
|
+
function rootArchivedSessionDir(sessionId) {
|
|
307
|
+
const safeSessionId = assertSafePathSegment(sessionId, 'session_id');
|
|
308
|
+
return resolveWithin(archivedSessionsRootDir(), safeSessionId);
|
|
309
|
+
}
|
|
310
|
+
function sessionDir(agentId, sessionId) {
|
|
311
|
+
assertSafePathSegment(agentId, 'agent_id');
|
|
312
|
+
return rootSessionDir(sessionId);
|
|
313
|
+
}
|
|
314
|
+
function resolveSessionDir(agentId, sessionId) {
|
|
315
|
+
assertSafePathSegment(agentId, 'agent_id');
|
|
316
|
+
const livePath = rootSessionDir(sessionId);
|
|
317
|
+
if (fs.existsSync(livePath)) {
|
|
318
|
+
return livePath;
|
|
319
|
+
}
|
|
320
|
+
const archivedPath = rootArchivedSessionDir(sessionId);
|
|
321
|
+
if (fs.existsSync(archivedPath)) {
|
|
322
|
+
return archivedPath;
|
|
323
|
+
}
|
|
324
|
+
return sessionDir(agentId, sessionId);
|
|
325
|
+
}
|
|
326
|
+
function sessionJsonPath(agentId, sessionId) {
|
|
327
|
+
return path.join(resolveSessionDir(agentId, sessionId), 'session.json');
|
|
328
|
+
}
|
|
329
|
+
function messagesPath(agentId, sessionId) {
|
|
330
|
+
return path.join(resolveSessionDir(agentId, sessionId), 'messages.jsonl');
|
|
331
|
+
}
|
|
332
|
+
function taskMetadataDir(sessionId, taskId) {
|
|
333
|
+
if (!taskId || !taskId.trim())
|
|
334
|
+
return null;
|
|
335
|
+
return path.join(resolveTaskArtifactPaths(sessionId, taskId).taskRoot, '.supen');
|
|
336
|
+
}
|
|
337
|
+
function taskUiEventsPath(sessionId, taskId) {
|
|
338
|
+
const dir = taskMetadataDir(sessionId, taskId);
|
|
339
|
+
return dir ? path.join(dir, 'events.jsonl') : null;
|
|
340
|
+
}
|
|
341
|
+
function taskMetadataPath(sessionId, taskId) {
|
|
342
|
+
const dir = taskMetadataDir(sessionId, taskId);
|
|
343
|
+
return dir ? path.join(dir, 'task.json') : null;
|
|
344
|
+
}
|
|
345
|
+
function writeTaskMetadata(agentId, sessionId, taskId) {
|
|
346
|
+
const metadataPath = taskMetadataPath(sessionId, taskId);
|
|
347
|
+
if (!metadataPath || fs.existsSync(metadataPath))
|
|
348
|
+
return;
|
|
349
|
+
writeJson(metadataPath, {
|
|
350
|
+
agent_id: agentId,
|
|
351
|
+
session_id: sessionId,
|
|
352
|
+
task_id: taskId,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function listTaskUiEventsForSession(agentId, sessionId, limit = TASK_UI_EVENT_HISTORY_LIMIT) {
|
|
356
|
+
const tasksRoot = spacePaths('local').tasks;
|
|
357
|
+
const events = [];
|
|
358
|
+
const boundedLimit = Math.max(1, limit);
|
|
359
|
+
for (const taskFolder of listDirs(tasksRoot)) {
|
|
360
|
+
const metadataDir = path.join(tasksRoot, taskFolder, '.supen');
|
|
361
|
+
const metadata = readJson(path.join(metadataDir, 'task.json'));
|
|
362
|
+
if (metadata?.agent_id !== agentId || metadata.session_id !== sessionId)
|
|
363
|
+
continue;
|
|
364
|
+
events.push(...readJsonlTail(path.join(metadataDir, 'events.jsonl'), boundedLimit));
|
|
365
|
+
}
|
|
366
|
+
return events
|
|
367
|
+
.sort((a, b) => String(a.timestamp || '').localeCompare(String(b.timestamp || '')))
|
|
368
|
+
.slice(-boundedLimit);
|
|
369
|
+
}
|
|
370
|
+
function automationsDir() {
|
|
371
|
+
return path.join(SUPEN_HOME, 'automations');
|
|
372
|
+
}
|
|
373
|
+
function automationDir(automationId) {
|
|
374
|
+
const safeAutomationId = assertSafePathSegment(automationId, 'automation_id');
|
|
375
|
+
return resolveWithin(automationsDir(), safeAutomationId);
|
|
376
|
+
}
|
|
377
|
+
function automationJsonPath(automationId) {
|
|
378
|
+
return path.join(automationDir(automationId), 'automation.json');
|
|
379
|
+
}
|
|
380
|
+
function automationStateJsonPath(automationId) {
|
|
381
|
+
return path.join(automationDir(automationId), 'state.json');
|
|
382
|
+
}
|
|
383
|
+
function automationRunsDir(automationId) {
|
|
384
|
+
return resolveWithin(automationDir(automationId), 'runs');
|
|
385
|
+
}
|
|
386
|
+
function automationRunJsonPath(automationId, runId) {
|
|
387
|
+
const safeRunId = assertSafePathSegment(runId, 'automation_run_id');
|
|
388
|
+
return resolveWithin(automationRunsDir(automationId), `${safeRunId}.json`);
|
|
389
|
+
}
|
|
390
|
+
function automationEventsDir(automationId) {
|
|
391
|
+
return resolveWithin(automationDir(automationId), 'events');
|
|
392
|
+
}
|
|
393
|
+
function automationEventJsonPath(automationId, eventId) {
|
|
394
|
+
const safeEventId = assertSafePathSegment(eventId, 'automation_event_id');
|
|
395
|
+
return resolveWithin(automationEventsDir(automationId), `${safeEventId}.json`);
|
|
396
|
+
}
|
|
397
|
+
function automationTaskId(automationId) {
|
|
398
|
+
return `task-automation-${automationId}`;
|
|
399
|
+
}
|
|
400
|
+
function automationChatJid(agentId, sessionId) {
|
|
401
|
+
return agentId === AUTOMATION_TASK_AGENT_ID
|
|
402
|
+
? `task:${sessionId}`
|
|
403
|
+
: `web:${agentId}:${sessionId}`;
|
|
404
|
+
}
|
|
405
|
+
const getRouterStatePath = () => path.join(SUPEN_HOME, 'router-state.json');
|
|
406
|
+
const getRegisteredGroupsPath = () => path.join(SUPEN_HOME, 'registered-groups.json');
|
|
407
|
+
// ═══════════════════════════════════════════════════════════════
|
|
408
|
+
// Init (no-op — dirs created on demand)
|
|
409
|
+
// ═══════════════════════════════════════════════════════════════
|
|
410
|
+
export function initStore() {
|
|
411
|
+
fs.mkdirSync(AGENTS_DIR, { recursive: true });
|
|
412
|
+
fs.mkdirSync(sessionsRootDir(), { recursive: true });
|
|
413
|
+
fs.mkdirSync(archivedSessionsRootDir(), { recursive: true });
|
|
414
|
+
fs.mkdirSync(spacesDir(), { recursive: true });
|
|
415
|
+
logger.info({ dir: AGENTS_DIR }, 'Store initialized (filesystem)');
|
|
416
|
+
}
|
|
417
|
+
// ═══════════════════════════════════════════════════════════════
|
|
418
|
+
// Agents
|
|
419
|
+
// ═══════════════════════════════════════════════════════════════
|
|
420
|
+
export function ensureAgent(input) {
|
|
421
|
+
const existing = resolveAgentRecord(input.agent_id);
|
|
422
|
+
const now = new Date().toISOString();
|
|
423
|
+
const incomingName = normalizeAgentNameValue(input.name, input.agent_id);
|
|
424
|
+
if (existing) {
|
|
425
|
+
const existingName = normalizeAgentNameValue(existing.record.name, existing.record.agent_id) ||
|
|
426
|
+
existing.record.agent_id;
|
|
427
|
+
const updated = {
|
|
428
|
+
...existing.record,
|
|
429
|
+
...(existing.record.space_id || input.space_id
|
|
430
|
+
? { space_id: existing.record.space_id || input.space_id || defaultSpaceId() }
|
|
431
|
+
: {}),
|
|
432
|
+
name: incomingName || existingName,
|
|
433
|
+
tags: input.tags || existing.record.tags,
|
|
434
|
+
channels: input.channels || existing.record.channels,
|
|
435
|
+
skills: input.skills || existing.record.skills,
|
|
436
|
+
updated_at: now,
|
|
437
|
+
};
|
|
438
|
+
writeJson(existing.configPath, updated);
|
|
439
|
+
return normalizeAgentRecord(updated);
|
|
440
|
+
}
|
|
441
|
+
const spaceId = input.space_id || defaultSpaceId();
|
|
442
|
+
ensureSpaceLayout(spaceId);
|
|
443
|
+
const record = {
|
|
444
|
+
agent_id: input.agent_id,
|
|
445
|
+
space_id: spaceId,
|
|
446
|
+
name: incomingName || input.agent_id,
|
|
447
|
+
tags: input.tags,
|
|
448
|
+
channels: input.channels,
|
|
449
|
+
skills: input.skills || { mode: 'all', allowed: ['*'] },
|
|
450
|
+
created_at: now,
|
|
451
|
+
updated_at: now,
|
|
452
|
+
};
|
|
453
|
+
writeJson(agentHomePaths(input.agent_id).config, record);
|
|
454
|
+
return record;
|
|
455
|
+
}
|
|
456
|
+
export function createAgent(input) {
|
|
457
|
+
if (findAgentRecord(input.agent_id)) {
|
|
458
|
+
throw new Error(`Agent "${input.agent_id}" already exists`);
|
|
459
|
+
}
|
|
460
|
+
const spaceId = input.space_id || defaultSpaceId();
|
|
461
|
+
ensureSpaceLayout(spaceId);
|
|
462
|
+
const now = new Date().toISOString();
|
|
463
|
+
const record = {
|
|
464
|
+
agent_id: input.agent_id,
|
|
465
|
+
space_id: spaceId,
|
|
466
|
+
name: input.name || input.agent_id,
|
|
467
|
+
tags: input.tags,
|
|
468
|
+
channels: input.channels,
|
|
469
|
+
skills: input.skills || { mode: 'all', allowed: ['*'] },
|
|
470
|
+
created_at: now,
|
|
471
|
+
updated_at: now,
|
|
472
|
+
};
|
|
473
|
+
writeJson(agentHomePaths(input.agent_id).config, record);
|
|
474
|
+
return record;
|
|
475
|
+
}
|
|
476
|
+
export function updateAgent(agentId, updates) {
|
|
477
|
+
const existing = resolveAgentRecord(agentId);
|
|
478
|
+
if (!existing)
|
|
479
|
+
return undefined;
|
|
480
|
+
const now = new Date().toISOString();
|
|
481
|
+
const updated = {
|
|
482
|
+
...existing.record,
|
|
483
|
+
...updates,
|
|
484
|
+
agent_id: existing.record.agent_id,
|
|
485
|
+
created_at: existing.record.created_at,
|
|
486
|
+
...(existing.record.space_id || updates.space_id
|
|
487
|
+
? {
|
|
488
|
+
space_id: typeof updates.space_id === 'string' && updates.space_id.trim()
|
|
489
|
+
? updates.space_id
|
|
490
|
+
: existing.record.space_id || defaultSpaceId(),
|
|
491
|
+
}
|
|
492
|
+
: {}),
|
|
493
|
+
updated_at: now,
|
|
494
|
+
};
|
|
495
|
+
writeJson(existing.configPath, updated);
|
|
496
|
+
return normalizeAgentRecord(updated);
|
|
497
|
+
}
|
|
498
|
+
export function deleteAgent(agentId) {
|
|
499
|
+
const dir = agentDir(agentId);
|
|
500
|
+
if (!fs.existsSync(dir))
|
|
501
|
+
return false;
|
|
502
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
export function getAgent(agentId) {
|
|
506
|
+
return findAgentRecord(agentId);
|
|
507
|
+
}
|
|
508
|
+
export function getAllAgents() {
|
|
509
|
+
const agents = [];
|
|
510
|
+
for (const agentId of listDirs(AGENTS_DIR)) {
|
|
511
|
+
const record = readJson(agentHomePaths(agentId).config);
|
|
512
|
+
if (record)
|
|
513
|
+
agents.push(normalizeAgentRecord(record));
|
|
514
|
+
}
|
|
515
|
+
return agents.sort((a, b) => (b.updated_at || '').localeCompare(a.updated_at || ''));
|
|
516
|
+
}
|
|
517
|
+
// ═══════════════════════════════════════════════════════════════
|
|
518
|
+
// Sessions
|
|
519
|
+
// ═══════════════════════════════════════════════════════════════
|
|
520
|
+
export function ensureSession(input) {
|
|
521
|
+
const existingAgent = findAgentRecord(input.agent_id);
|
|
522
|
+
const resolvedSpaceId = input.space_id || existingAgent?.space_id || defaultSpaceId();
|
|
523
|
+
ensureAgent({
|
|
524
|
+
agent_id: input.agent_id,
|
|
525
|
+
name: input.agent_name,
|
|
526
|
+
space_id: resolvedSpaceId,
|
|
527
|
+
});
|
|
528
|
+
const jsonPath = sessionJsonPath(input.agent_id, input.session_id);
|
|
529
|
+
const existing = readJson(jsonPath);
|
|
530
|
+
const now = new Date().toISOString();
|
|
531
|
+
const normalizedCreatedAt = typeof input.created_at === 'string' && input.created_at.trim()
|
|
532
|
+
? input.created_at
|
|
533
|
+
: now;
|
|
534
|
+
const normalizedUpdatedAt = typeof input.updated_at === 'string' && input.updated_at.trim()
|
|
535
|
+
? input.updated_at
|
|
536
|
+
: undefined;
|
|
537
|
+
if (existing) {
|
|
538
|
+
const existingSpaceId = existing.space_id || resolvedSpaceId;
|
|
539
|
+
const nextSpaceId = input.space_id ||
|
|
540
|
+
(existingSpaceId === DEFAULT_SPACE_ID && resolvedSpaceId !== DEFAULT_SPACE_ID
|
|
541
|
+
? resolvedSpaceId
|
|
542
|
+
: existingSpaceId);
|
|
543
|
+
const nextChannel = input.channel;
|
|
544
|
+
const nextChannelSession = input.channel_session ?? existing.channel_session;
|
|
545
|
+
const nextBackendDriverId = input.backend_driver_id || existing.backend_driver_id;
|
|
546
|
+
const nextSourceRef = input.source_ref || existing.source_ref;
|
|
547
|
+
const nextKnowledgeId = input.knowledge_id || existing.knowledge_id;
|
|
548
|
+
const nextKnowledgeName = input.knowledge_name || existing.knowledge_name;
|
|
549
|
+
const nextEnvironmentId = input.environment_id || existing.environment_id;
|
|
550
|
+
const nextEnvironmentSnapshot = input.environment_snapshot || existing.environment_snapshot;
|
|
551
|
+
const nextTaskWorkspaceFolder = input.task_workspace_folder ?? existing.task_workspace_folder;
|
|
552
|
+
const nextTitle = existing.title || input.title?.trim() || undefined;
|
|
553
|
+
const nextStatus = input.status || existing.status;
|
|
554
|
+
const changed = nextSpaceId !== existing.space_id ||
|
|
555
|
+
nextChannel !== existing.channel ||
|
|
556
|
+
nextChannelSession !== existing.channel_session ||
|
|
557
|
+
nextBackendDriverId !== existing.backend_driver_id ||
|
|
558
|
+
nextSourceRef !== existing.source_ref ||
|
|
559
|
+
nextKnowledgeId !== existing.knowledge_id ||
|
|
560
|
+
nextKnowledgeName !== existing.knowledge_name ||
|
|
561
|
+
nextEnvironmentId !== existing.environment_id ||
|
|
562
|
+
JSON.stringify(nextEnvironmentSnapshot ?? null) !== JSON.stringify(existing.environment_snapshot ?? null) ||
|
|
563
|
+
nextTaskWorkspaceFolder !== existing.task_workspace_folder ||
|
|
564
|
+
nextTitle !== existing.title ||
|
|
565
|
+
nextStatus !== existing.status;
|
|
566
|
+
const updated = {
|
|
567
|
+
...existing,
|
|
568
|
+
space_id: nextSpaceId,
|
|
569
|
+
channel: nextChannel,
|
|
570
|
+
channel_session: nextChannelSession,
|
|
571
|
+
backend_driver_id: nextBackendDriverId,
|
|
572
|
+
source_ref: nextSourceRef,
|
|
573
|
+
knowledge_id: nextKnowledgeId,
|
|
574
|
+
knowledge_name: nextKnowledgeName,
|
|
575
|
+
environment_id: nextEnvironmentId,
|
|
576
|
+
environment_snapshot: nextEnvironmentSnapshot,
|
|
577
|
+
task_workspace_folder: nextTaskWorkspaceFolder,
|
|
578
|
+
title: nextTitle,
|
|
579
|
+
status: nextStatus,
|
|
580
|
+
updated_at: normalizedUpdatedAt || (changed ? now : existing.updated_at),
|
|
581
|
+
};
|
|
582
|
+
if (changed || normalizedUpdatedAt) {
|
|
583
|
+
writeJson(jsonPath, updated);
|
|
584
|
+
}
|
|
585
|
+
return updated;
|
|
586
|
+
}
|
|
587
|
+
const record = {
|
|
588
|
+
session_id: input.session_id,
|
|
589
|
+
agent_id: input.agent_id,
|
|
590
|
+
space_id: resolvedSpaceId,
|
|
591
|
+
channel: input.channel,
|
|
592
|
+
channel_session: input.channel_session,
|
|
593
|
+
backend_driver_id: input.backend_driver_id,
|
|
594
|
+
source_ref: input.source_ref,
|
|
595
|
+
knowledge_id: input.knowledge_id,
|
|
596
|
+
knowledge_name: input.knowledge_name,
|
|
597
|
+
environment_id: input.environment_id,
|
|
598
|
+
environment_snapshot: input.environment_snapshot,
|
|
599
|
+
task_workspace_folder: input.task_workspace_folder ?? null,
|
|
600
|
+
title: input.title?.trim() || undefined,
|
|
601
|
+
status: input.status || 'idle',
|
|
602
|
+
created_at: normalizedCreatedAt,
|
|
603
|
+
updated_at: normalizedUpdatedAt || normalizedCreatedAt,
|
|
604
|
+
};
|
|
605
|
+
writeJson(jsonPath, record);
|
|
606
|
+
return record;
|
|
607
|
+
}
|
|
608
|
+
export function getSession(sessionId) {
|
|
609
|
+
return (readJson(path.join(rootSessionDir(sessionId), 'session.json')) ||
|
|
610
|
+
readJson(path.join(rootArchivedSessionDir(sessionId), 'session.json')));
|
|
611
|
+
}
|
|
612
|
+
export function getSessionForAgent(agentId, sessionId) {
|
|
613
|
+
const session = readJson(sessionJsonPath(agentId, sessionId));
|
|
614
|
+
return session?.agent_id === agentId ? session : undefined;
|
|
615
|
+
}
|
|
616
|
+
function ensureAutomationTasksForAgent(agentId) {
|
|
617
|
+
for (const automation of listAutomations(agentId)) {
|
|
618
|
+
ensureAutomationTask(automation);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
export function getSessionsForAgent(agentId) {
|
|
622
|
+
ensureAutomationTasksForAgent(agentId);
|
|
623
|
+
const sessions = [];
|
|
624
|
+
const sessionsBase = sessionsRootDir();
|
|
625
|
+
if (fs.existsSync(sessionsBase)) {
|
|
626
|
+
for (const sid of listDirs(sessionsBase)) {
|
|
627
|
+
const session = readJson(path.join(sessionsBase, sid, 'session.json'));
|
|
628
|
+
if (session?.agent_id === agentId)
|
|
629
|
+
sessions.push(session);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return sessions.sort((a, b) => (b.updated_at || b.created_at || '').localeCompare(a.updated_at || a.created_at || ''));
|
|
633
|
+
}
|
|
634
|
+
function touchSessionActivity(agentId, sessionId, timestamp = new Date().toISOString()) {
|
|
635
|
+
const jsonPath = sessionJsonPath(agentId, sessionId);
|
|
636
|
+
const session = readJson(jsonPath);
|
|
637
|
+
if (!session)
|
|
638
|
+
return;
|
|
639
|
+
const nextUpdatedAt = Number.isNaN(new Date(timestamp).getTime())
|
|
640
|
+
? new Date().toISOString()
|
|
641
|
+
: timestamp;
|
|
642
|
+
if (session.updated_at && session.updated_at > nextUpdatedAt)
|
|
643
|
+
return;
|
|
644
|
+
session.space_id = session.space_id || DEFAULT_SPACE_ID;
|
|
645
|
+
session.updated_at = nextUpdatedAt;
|
|
646
|
+
writeJson(jsonPath, session);
|
|
647
|
+
}
|
|
648
|
+
export function getArchivedSessionsForAgent(agentId) {
|
|
649
|
+
const sessionsBase = archivedSessionsRootDir();
|
|
650
|
+
const sessions = [];
|
|
651
|
+
if (fs.existsSync(sessionsBase)) {
|
|
652
|
+
for (const sid of listDirs(sessionsBase)) {
|
|
653
|
+
const jsonPath = path.join(sessionsBase, sid, 'session.json');
|
|
654
|
+
const session = readJson(jsonPath);
|
|
655
|
+
if (session?.agent_id === agentId) {
|
|
656
|
+
sessions.push({ ...session, status: 'archived' });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return sessions.sort((a, b) => (b.updated_at || b.created_at || '').localeCompare(a.updated_at || a.created_at || ''));
|
|
661
|
+
}
|
|
662
|
+
export function getAllSessions() {
|
|
663
|
+
const sessions = [];
|
|
664
|
+
for (const sid of listDirs(sessionsRootDir())) {
|
|
665
|
+
const session = readJson(path.join(sessionsRootDir(), sid, 'session.json'));
|
|
666
|
+
if (session)
|
|
667
|
+
sessions.push(session);
|
|
668
|
+
}
|
|
669
|
+
return sessions.sort((a, b) => {
|
|
670
|
+
const cmp = a.agent_id.localeCompare(b.agent_id);
|
|
671
|
+
return cmp !== 0 ? cmp : a.session_id.localeCompare(b.session_id);
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
export function ensureAutomationTask(automation) {
|
|
675
|
+
const taskId = automation.task_id || automationTaskId(automation.id);
|
|
676
|
+
const agentId = automation.kind === 'event'
|
|
677
|
+
? AUTOMATION_TASK_AGENT_ID
|
|
678
|
+
: automation.agent_id;
|
|
679
|
+
const session = ensureSession({
|
|
680
|
+
agent_id: agentId,
|
|
681
|
+
session_id: taskId,
|
|
682
|
+
channel: 'automation',
|
|
683
|
+
source_ref: `automation:${automation.id}`,
|
|
684
|
+
title: automation.name,
|
|
685
|
+
task_workspace_folder: automation.cwds?.[0] ?? null,
|
|
686
|
+
});
|
|
687
|
+
if (session.agent_id !== agentId) {
|
|
688
|
+
const updated = {
|
|
689
|
+
...session,
|
|
690
|
+
agent_id: agentId,
|
|
691
|
+
channel: 'automation',
|
|
692
|
+
source_ref: `automation:${automation.id}`,
|
|
693
|
+
updated_at: new Date().toISOString(),
|
|
694
|
+
};
|
|
695
|
+
ensureAgent({ agent_id: agentId, name: agentId === AUTOMATION_TASK_AGENT_ID ? 'Automation Task' : undefined });
|
|
696
|
+
writeJson(sessionJsonPath(agentId, taskId), updated);
|
|
697
|
+
return updated;
|
|
698
|
+
}
|
|
699
|
+
return session;
|
|
700
|
+
}
|
|
701
|
+
export function automationExecutionIdentity(automationId) {
|
|
702
|
+
return `supen-automation:${automationId}`;
|
|
703
|
+
}
|
|
704
|
+
function automationRunContextPath(automationId, runId) {
|
|
705
|
+
const safeRunId = assertSafePathSegment(runId, 'automation_run_id');
|
|
706
|
+
return resolveWithin(automationRunsDir(automationId), `${safeRunId}.context.json`);
|
|
707
|
+
}
|
|
708
|
+
export function writeAutomationRunContext(input) {
|
|
709
|
+
const memoryPath = path.join(automationDir(input.automation.id), 'memory.md');
|
|
710
|
+
const executionIdentity = automationExecutionIdentity(input.automation.id);
|
|
711
|
+
const contextPath = automationRunContextPath(input.automation.id, input.runId);
|
|
712
|
+
writeJson(contextPath, {
|
|
713
|
+
version: 1,
|
|
714
|
+
kind: input.kind,
|
|
715
|
+
generated_at: input.nowIso,
|
|
716
|
+
automation: {
|
|
717
|
+
id: input.automation.id,
|
|
718
|
+
name: input.automation.name,
|
|
719
|
+
execution_identity: executionIdentity,
|
|
720
|
+
prompt: input.automation.prompt,
|
|
721
|
+
action: input.automation.action ?? null,
|
|
722
|
+
trigger: input.automation.trigger ?? null,
|
|
723
|
+
memory_path: memoryPath,
|
|
724
|
+
previous_triggered_at: input.previousTriggeredAt ?? null,
|
|
725
|
+
model: input.automation.model ?? null,
|
|
726
|
+
permission_mode: input.automation.permission_mode ?? null,
|
|
727
|
+
network_access: input.automation.network_access === true,
|
|
728
|
+
cwds: input.automation.cwds ?? [],
|
|
729
|
+
},
|
|
730
|
+
run: {
|
|
731
|
+
id: input.runId,
|
|
732
|
+
},
|
|
733
|
+
event: input.eventEnvelope ?? null,
|
|
734
|
+
});
|
|
735
|
+
return contextPath;
|
|
736
|
+
}
|
|
737
|
+
function readTemplatePath(root, pathExpression) {
|
|
738
|
+
const segments = pathExpression
|
|
739
|
+
.split('.')
|
|
740
|
+
.map((segment) => segment.trim())
|
|
741
|
+
.filter(Boolean);
|
|
742
|
+
let current = root;
|
|
743
|
+
for (const segment of segments) {
|
|
744
|
+
if (!current || typeof current !== 'object')
|
|
745
|
+
return undefined;
|
|
746
|
+
current = current[segment];
|
|
747
|
+
}
|
|
748
|
+
return current;
|
|
749
|
+
}
|
|
750
|
+
function stringifyTemplateValue(value) {
|
|
751
|
+
if (value === null || value === undefined)
|
|
752
|
+
return '';
|
|
753
|
+
if (typeof value === 'string')
|
|
754
|
+
return value;
|
|
755
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
756
|
+
return String(value);
|
|
757
|
+
return JSON.stringify(value);
|
|
758
|
+
}
|
|
759
|
+
export function renderAutomationActionCommand(command, eventEnvelope) {
|
|
760
|
+
const root = {
|
|
761
|
+
event: eventEnvelope ?? null,
|
|
762
|
+
payload: eventEnvelope && typeof eventEnvelope.payload === 'object'
|
|
763
|
+
? eventEnvelope.payload
|
|
764
|
+
: null,
|
|
765
|
+
};
|
|
766
|
+
return command.replace(/\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}/g, (_match, pathExpression) => stringifyTemplateValue(readTemplatePath(root, String(pathExpression))));
|
|
767
|
+
}
|
|
768
|
+
export function buildAutomationRunChatMessage(input) {
|
|
769
|
+
const rawCommand = input.automation.action?.command?.trim();
|
|
770
|
+
const command = rawCommand?.startsWith('/')
|
|
771
|
+
? renderAutomationActionCommand(rawCommand, input.eventEnvelope).trim()
|
|
772
|
+
: '';
|
|
773
|
+
const opening = command || 'Please execute the automation described in the context file.';
|
|
774
|
+
return [
|
|
775
|
+
opening,
|
|
776
|
+
'',
|
|
777
|
+
`Automation context file: ${input.contextPath}`,
|
|
778
|
+
`Automation id: ${input.automation.id}`,
|
|
779
|
+
`Run id: ${input.runId}`,
|
|
780
|
+
'Read the context JSON file before acting. Execute exactly once.',
|
|
781
|
+
'Use the event payload in that context when present. Do not ask for repeated instructions.',
|
|
782
|
+
].join('\n');
|
|
783
|
+
}
|
|
784
|
+
export function buildAutomationTriggerMessage(input) {
|
|
785
|
+
const taskId = input.automation.task_id ||
|
|
786
|
+
automationTaskId(input.automation.id);
|
|
787
|
+
const memoryPath = path.join(automationDir(input.automation.id), 'memory.md');
|
|
788
|
+
const lastRun = input.previousTriggeredAt || 'never';
|
|
789
|
+
const executionIdentity = automationExecutionIdentity(input.automation.id);
|
|
790
|
+
const runId = input.runId || crypto.randomUUID();
|
|
791
|
+
const contextPath = writeAutomationRunContext({
|
|
792
|
+
automation: input.automation,
|
|
793
|
+
runId,
|
|
794
|
+
kind: 'scheduled',
|
|
795
|
+
nowIso: input.nowIso,
|
|
796
|
+
previousTriggeredAt: input.previousTriggeredAt,
|
|
797
|
+
});
|
|
798
|
+
const content = buildAutomationRunChatMessage({
|
|
799
|
+
automation: input.automation,
|
|
800
|
+
runId,
|
|
801
|
+
contextPath,
|
|
802
|
+
});
|
|
803
|
+
return {
|
|
804
|
+
id: crypto.randomUUID(),
|
|
805
|
+
chat_jid: automationChatJid(input.automation.agent_id, taskId),
|
|
806
|
+
sender: 'system',
|
|
807
|
+
sender_name: 'Automation',
|
|
808
|
+
content,
|
|
809
|
+
timestamp: input.nowIso,
|
|
810
|
+
is_from_me: false,
|
|
811
|
+
agent_id: input.automation.agent_id,
|
|
812
|
+
session_id: taskId,
|
|
813
|
+
task_id: taskId,
|
|
814
|
+
automation_id: input.automation.id,
|
|
815
|
+
automation_run_id: runId,
|
|
816
|
+
model: input.automation.model,
|
|
817
|
+
metadata: {
|
|
818
|
+
origin: 'automation',
|
|
819
|
+
automation_id: input.automation.id,
|
|
820
|
+
automation_name: input.automation.name,
|
|
821
|
+
automation_execution_identity: executionIdentity,
|
|
822
|
+
automation_memory_path: memoryPath,
|
|
823
|
+
automation_context_path: contextPath,
|
|
824
|
+
automation_last_run: lastRun,
|
|
825
|
+
automation_prompt: input.automation.prompt,
|
|
826
|
+
runtime_options: {
|
|
827
|
+
...(input.automation.permission_mode
|
|
828
|
+
? { permissionMode: input.automation.permission_mode }
|
|
829
|
+
: {}),
|
|
830
|
+
...(input.automation.network_access ? { networkAccess: true } : {}),
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
export function updateSessionStatus(agentId, sessionId, status) {
|
|
836
|
+
const jsonPath = sessionJsonPath(agentId, sessionId);
|
|
837
|
+
const session = readJson(jsonPath);
|
|
838
|
+
if (session) {
|
|
839
|
+
session.space_id = session.space_id || DEFAULT_SPACE_ID;
|
|
840
|
+
session.status = status;
|
|
841
|
+
session.updated_at = new Date().toISOString();
|
|
842
|
+
writeJson(jsonPath, session);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
export function updateSessionSdkId(agentId, sessionId, sdkSessionId) {
|
|
846
|
+
const jsonPath = sessionJsonPath(agentId, sessionId);
|
|
847
|
+
const session = readJson(jsonPath);
|
|
848
|
+
if (session) {
|
|
849
|
+
session.space_id = session.space_id || DEFAULT_SPACE_ID;
|
|
850
|
+
session.sdk_session_id = sdkSessionId;
|
|
851
|
+
session.updated_at = new Date().toISOString();
|
|
852
|
+
writeJson(jsonPath, session);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
export function clearSessionSdkId(agentId, sessionId) {
|
|
856
|
+
const jsonPath = sessionJsonPath(agentId, sessionId);
|
|
857
|
+
const session = readJson(jsonPath);
|
|
858
|
+
if (session) {
|
|
859
|
+
session.space_id = session.space_id || DEFAULT_SPACE_ID;
|
|
860
|
+
delete session.sdk_session_id;
|
|
861
|
+
session.updated_at = new Date().toISOString();
|
|
862
|
+
writeJson(jsonPath, session);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
export function updateSessionBackendDriverId(agentId, sessionId, backendDriverId) {
|
|
866
|
+
const jsonPath = sessionJsonPath(agentId, sessionId);
|
|
867
|
+
const session = readJson(jsonPath);
|
|
868
|
+
if (session) {
|
|
869
|
+
session.space_id = session.space_id || DEFAULT_SPACE_ID;
|
|
870
|
+
session.backend_driver_id = backendDriverId;
|
|
871
|
+
session.updated_at = new Date().toISOString();
|
|
872
|
+
writeJson(jsonPath, session);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
export function updateSessionUsage(agentId, sessionId, tokensIn, tokensOut) {
|
|
876
|
+
const now = new Date().toISOString();
|
|
877
|
+
// 1. Update Session Usage
|
|
878
|
+
const sessPath = sessionJsonPath(agentId, sessionId);
|
|
879
|
+
const session = readJson(sessPath);
|
|
880
|
+
if (session) {
|
|
881
|
+
session.space_id = session.space_id || DEFAULT_SPACE_ID;
|
|
882
|
+
session.tokens_in = (session.tokens_in || 0) + tokensIn;
|
|
883
|
+
session.tokens_out = (session.tokens_out || 0) + tokensOut;
|
|
884
|
+
session.updated_at = now;
|
|
885
|
+
writeJson(sessPath, session);
|
|
886
|
+
}
|
|
887
|
+
// 2. Update Agent Usage (Aggregate)
|
|
888
|
+
const aPath = agentJsonPath(agentId);
|
|
889
|
+
const agent = readJson(aPath);
|
|
890
|
+
if (agent) {
|
|
891
|
+
agent.tokens_in = (agent.tokens_in || 0) + tokensIn;
|
|
892
|
+
agent.tokens_out = (agent.tokens_out || 0) + tokensOut;
|
|
893
|
+
agent.updated_at = now;
|
|
894
|
+
writeJson(aPath, agent);
|
|
895
|
+
}
|
|
896
|
+
appendGlobalUsage(agentId, sessionId, tokensIn, tokensOut);
|
|
897
|
+
}
|
|
898
|
+
export function appendGlobalUsage(agentId, sessionId, tokensIn, tokensOut) {
|
|
899
|
+
const now = new Date().toISOString();
|
|
900
|
+
// This ensures deletion of agents/sessions does not impact the historical total.
|
|
901
|
+
try {
|
|
902
|
+
const today = now.split('T')[0];
|
|
903
|
+
const models = getAgentModelConfig(agentId, true);
|
|
904
|
+
const modelId = models.length > 0 ? models[0].id : 'unknown';
|
|
905
|
+
const rawLedger = readJson(getGlobalUsagePath());
|
|
906
|
+
let ledger;
|
|
907
|
+
if (!rawLedger || typeof rawLedger.total !== 'object') {
|
|
908
|
+
ledger = {
|
|
909
|
+
total: {
|
|
910
|
+
tokens_in: rawLedger?.tokens_in || 0,
|
|
911
|
+
tokens_out: rawLedger?.tokens_out || 0,
|
|
912
|
+
estimated_cost_usd: rawLedger?.estimated_cost_usd || 0,
|
|
913
|
+
updated_at: rawLedger?.updated_at || now,
|
|
914
|
+
},
|
|
915
|
+
daily: {},
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
else {
|
|
919
|
+
ledger = rawLedger;
|
|
920
|
+
}
|
|
921
|
+
const increment = getUsageStats({
|
|
922
|
+
tokens_in: tokensIn,
|
|
923
|
+
tokens_out: tokensOut,
|
|
924
|
+
agent_id: agentId,
|
|
925
|
+
});
|
|
926
|
+
if (!ledger.daily)
|
|
927
|
+
ledger.daily = {};
|
|
928
|
+
if (!ledger.daily[today]) {
|
|
929
|
+
ledger.daily[today] = {
|
|
930
|
+
total: { tokens_in: 0, tokens_out: 0, estimated_cost_usd: 0 },
|
|
931
|
+
models: {},
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
const day = ledger.daily[today];
|
|
935
|
+
if (!day.models[modelId]) {
|
|
936
|
+
day.models[modelId] = {
|
|
937
|
+
total: { tokens_in: 0, tokens_out: 0, estimated_cost_usd: 0 },
|
|
938
|
+
sessions: {},
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
const model = day.models[modelId];
|
|
942
|
+
if (!model.sessions[sessionId]) {
|
|
943
|
+
model.sessions[sessionId] = {
|
|
944
|
+
agent_id: agentId,
|
|
945
|
+
tokens_in: 0,
|
|
946
|
+
tokens_out: 0,
|
|
947
|
+
estimated_cost_usd: 0,
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
const sessionEntry = model.sessions[sessionId];
|
|
951
|
+
const applyIncrement = (target) => {
|
|
952
|
+
target.tokens_in += tokensIn;
|
|
953
|
+
target.tokens_out += tokensOut;
|
|
954
|
+
target.tokens_total = (target.tokens_in || 0) + (target.tokens_out || 0);
|
|
955
|
+
target.estimated_cost_usd = Number((target.estimated_cost_usd + increment.estimated_cost_usd).toFixed(6));
|
|
956
|
+
};
|
|
957
|
+
applyIncrement(ledger.total);
|
|
958
|
+
ledger.total.updated_at = now;
|
|
959
|
+
applyIncrement(day.total);
|
|
960
|
+
applyIncrement(model.total);
|
|
961
|
+
applyIncrement(sessionEntry);
|
|
962
|
+
writeJson(getGlobalUsagePath(), ledger);
|
|
963
|
+
}
|
|
964
|
+
catch (e) {
|
|
965
|
+
logger.warn({
|
|
966
|
+
error: e,
|
|
967
|
+
message: e instanceof Error ? e.message : String(e),
|
|
968
|
+
stack: e instanceof Error ? e.stack : undefined,
|
|
969
|
+
}, 'Failed to update global usage file');
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
export function getUsageStats(record) {
|
|
973
|
+
const tokens_in = record.tokens_in || 0;
|
|
974
|
+
const tokens_out = record.tokens_out || 0;
|
|
975
|
+
let estimated_cost_usd = 0;
|
|
976
|
+
if (record.agent_id) {
|
|
977
|
+
const models = getAgentModelConfig(record.agent_id, true);
|
|
978
|
+
const model = models.length > 0 ? models[0] : null;
|
|
979
|
+
if (model?.pricing) {
|
|
980
|
+
estimated_cost_usd =
|
|
981
|
+
(tokens_in / 1_000_000) * (model.pricing.input_usd_per_1m || 0) +
|
|
982
|
+
(tokens_out / 1_000_000) * (model.pricing.output_usd_per_1m || 0);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return {
|
|
986
|
+
tokens_in,
|
|
987
|
+
tokens_out,
|
|
988
|
+
tokens_total: tokens_in + tokens_out,
|
|
989
|
+
estimated_cost_usd: Number(estimated_cost_usd.toFixed(6)),
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
/** Get global usage across all agents and sessions. */
|
|
993
|
+
export function getGlobalUsage() {
|
|
994
|
+
const ledger = readJson(getGlobalUsagePath());
|
|
995
|
+
if (ledger && ledger.total) {
|
|
996
|
+
return {
|
|
997
|
+
tokens_in: ledger.total.tokens_in,
|
|
998
|
+
tokens_out: ledger.total.tokens_out,
|
|
999
|
+
tokens_total: ledger.total.tokens_in + ledger.total.tokens_out,
|
|
1000
|
+
estimated_cost_usd: ledger.total.estimated_cost_usd,
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
// Fallback: calculate from current agents (migration path)
|
|
1004
|
+
let tokens_in = 0;
|
|
1005
|
+
let tokens_out = 0;
|
|
1006
|
+
let total_cost = 0;
|
|
1007
|
+
for (const agent of getAllAgents()) {
|
|
1008
|
+
const stats = getUsageStats(agent);
|
|
1009
|
+
tokens_in += stats.tokens_in;
|
|
1010
|
+
tokens_out += stats.tokens_out;
|
|
1011
|
+
total_cost += stats.estimated_cost_usd;
|
|
1012
|
+
}
|
|
1013
|
+
return {
|
|
1014
|
+
tokens_in,
|
|
1015
|
+
tokens_out,
|
|
1016
|
+
tokens_total: tokens_in + tokens_out,
|
|
1017
|
+
estimated_cost_usd: Number(total_cost.toFixed(6)),
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
/** Get the full detailed daily usage ledger. */
|
|
1021
|
+
export function getDailyUsage() {
|
|
1022
|
+
const ledger = readJson(getGlobalUsagePath());
|
|
1023
|
+
return ledger?.daily || {};
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Reset sessions stuck as "running" — called at startup to recover from crashes.
|
|
1027
|
+
* A "running" session cannot survive a process restart, so these are stale.
|
|
1028
|
+
*/
|
|
1029
|
+
export function cleanupStaleSessions() {
|
|
1030
|
+
let cleaned = 0;
|
|
1031
|
+
for (const agentId of listDirs(AGENTS_DIR)) {
|
|
1032
|
+
for (const session of getSessionsForAgent(agentId)) {
|
|
1033
|
+
if (session.status === 'running') {
|
|
1034
|
+
finalizeInterruptedSessionUiStream(agentId, session.session_id);
|
|
1035
|
+
updateSessionStatus(agentId, session.session_id, 'idle');
|
|
1036
|
+
cleaned++;
|
|
1037
|
+
logger.info({ agentId, sessionId: session.session_id }, 'Reset stale running session to idle');
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
return cleaned;
|
|
1042
|
+
}
|
|
1043
|
+
function finalizeInterruptedSessionUiStream(agentId, sessionId) {
|
|
1044
|
+
const events = getSessionUiEvents(agentId, sessionId, SESSION_UI_EVENT_RECOVERY_LIMIT);
|
|
1045
|
+
if (events.length === 0)
|
|
1046
|
+
return;
|
|
1047
|
+
let openTaskId;
|
|
1048
|
+
let messageOpen = false;
|
|
1049
|
+
let stepOpen = false;
|
|
1050
|
+
const openTextIds = new Set();
|
|
1051
|
+
const openReasoningIds = new Set();
|
|
1052
|
+
for (const event of events) {
|
|
1053
|
+
const chunk = event.chunk;
|
|
1054
|
+
if (!chunk || typeof chunk !== 'object')
|
|
1055
|
+
continue;
|
|
1056
|
+
const type = typeof chunk.type === 'string' ? chunk.type : '';
|
|
1057
|
+
if (type === 'start') {
|
|
1058
|
+
messageOpen = true;
|
|
1059
|
+
stepOpen = false;
|
|
1060
|
+
openTaskId = event.task_id;
|
|
1061
|
+
openTextIds.clear();
|
|
1062
|
+
openReasoningIds.clear();
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (!messageOpen)
|
|
1066
|
+
continue;
|
|
1067
|
+
if (type === 'start-step') {
|
|
1068
|
+
stepOpen = true;
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
if (type === 'finish-step') {
|
|
1072
|
+
stepOpen = false;
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
if (type === 'text-start') {
|
|
1076
|
+
openTextIds.add(String(chunk.id || 'text-default'));
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
if (type === 'text-end') {
|
|
1080
|
+
openTextIds.delete(String(chunk.id || 'text-default'));
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
if (type === 'reasoning-start') {
|
|
1084
|
+
openReasoningIds.add(String(chunk.id || 'reasoning-default'));
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
if (type === 'reasoning-end') {
|
|
1088
|
+
openReasoningIds.delete(String(chunk.id || 'reasoning-default'));
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
if (type === 'finish' || type === 'error') {
|
|
1092
|
+
messageOpen = false;
|
|
1093
|
+
stepOpen = false;
|
|
1094
|
+
openTaskId = undefined;
|
|
1095
|
+
openTextIds.clear();
|
|
1096
|
+
openReasoningIds.clear();
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (!messageOpen)
|
|
1100
|
+
return;
|
|
1101
|
+
const timestamp = new Date().toISOString();
|
|
1102
|
+
const taskId = openTaskId || `stale-${Date.now()}`;
|
|
1103
|
+
const chunks = [
|
|
1104
|
+
...Array.from(openTextIds).map((id) => ({ type: 'text-end', id })),
|
|
1105
|
+
...Array.from(openReasoningIds).map((id) => ({ type: 'reasoning-end', id })),
|
|
1106
|
+
...(stepOpen ? [{ type: 'finish-step' }] : []),
|
|
1107
|
+
{
|
|
1108
|
+
type: 'error',
|
|
1109
|
+
errorText: 'Response interrupted because the server restarted before this turn finished.',
|
|
1110
|
+
},
|
|
1111
|
+
{ type: 'finish', finishReason: 'error' },
|
|
1112
|
+
];
|
|
1113
|
+
chunks.forEach((chunk, index) => {
|
|
1114
|
+
storeSessionUiEvent(agentId, sessionId, {
|
|
1115
|
+
id: `${taskId}:stale-interrupted:${timestamp}:${index}`,
|
|
1116
|
+
timestamp,
|
|
1117
|
+
task_id: taskId,
|
|
1118
|
+
chunk,
|
|
1119
|
+
});
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
export function updateSessionMetadata(agentId, sessionId, updates) {
|
|
1123
|
+
const jsonPath = sessionJsonPath(agentId, sessionId);
|
|
1124
|
+
const session = readJson(jsonPath);
|
|
1125
|
+
if (!session)
|
|
1126
|
+
return false;
|
|
1127
|
+
session.space_id = session.space_id || DEFAULT_SPACE_ID;
|
|
1128
|
+
if (updates.title !== undefined)
|
|
1129
|
+
session.title = updates.title;
|
|
1130
|
+
if (updates.knowledge_id !== undefined)
|
|
1131
|
+
session.knowledge_id = updates.knowledge_id;
|
|
1132
|
+
if (updates.knowledge_name !== undefined)
|
|
1133
|
+
session.knowledge_name = updates.knowledge_name;
|
|
1134
|
+
session.updated_at = new Date().toISOString();
|
|
1135
|
+
writeJson(jsonPath, session);
|
|
1136
|
+
return true;
|
|
1137
|
+
}
|
|
1138
|
+
export function archiveSessionForAgent(agentId, sessionId) {
|
|
1139
|
+
const dir = sessionDir(agentId, sessionId);
|
|
1140
|
+
if (!fs.existsSync(dir))
|
|
1141
|
+
return false;
|
|
1142
|
+
const session = readJson(path.join(dir, 'session.json'));
|
|
1143
|
+
if (session && session.agent_id !== agentId)
|
|
1144
|
+
return false;
|
|
1145
|
+
const archiveDir = archivedSessionsRootDir();
|
|
1146
|
+
fs.mkdirSync(archiveDir, { recursive: true });
|
|
1147
|
+
const targetDir = rootArchivedSessionDir(sessionId);
|
|
1148
|
+
if (fs.existsSync(targetDir))
|
|
1149
|
+
return false; // already archived
|
|
1150
|
+
fs.renameSync(dir, targetDir);
|
|
1151
|
+
deleteSchedulesForSession(agentId, sessionId);
|
|
1152
|
+
return true;
|
|
1153
|
+
}
|
|
1154
|
+
function deleteSchedulesForSession(agentId, sessionId) {
|
|
1155
|
+
if (sessionId.startsWith('sched-')) {
|
|
1156
|
+
deleteSchedule(sessionId.slice(6));
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
const schedules = getSchedulesForAgent(agentId);
|
|
1160
|
+
for (const s of schedules) {
|
|
1161
|
+
if (s.target_jid && s.target_jid.endsWith(':' + sessionId)) {
|
|
1162
|
+
deleteSchedule(s.id);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
export function restoreSessionForAgent(agentId, sessionId) {
|
|
1168
|
+
const archivePath = rootArchivedSessionDir(sessionId);
|
|
1169
|
+
if (!fs.existsSync(archivePath))
|
|
1170
|
+
return false;
|
|
1171
|
+
const restoreDir = sessionDir(agentId, sessionId);
|
|
1172
|
+
fs.mkdirSync(path.dirname(restoreDir), { recursive: true });
|
|
1173
|
+
if (fs.existsSync(restoreDir))
|
|
1174
|
+
return false;
|
|
1175
|
+
fs.renameSync(archivePath, restoreDir);
|
|
1176
|
+
return true;
|
|
1177
|
+
}
|
|
1178
|
+
export function deleteSession(sessionId) {
|
|
1179
|
+
const liveDir = rootSessionDir(sessionId);
|
|
1180
|
+
if (fs.existsSync(liveDir)) {
|
|
1181
|
+
fs.rmSync(liveDir, { recursive: true, force: true });
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
const archivedDir = rootArchivedSessionDir(sessionId);
|
|
1185
|
+
if (fs.existsSync(archivedDir)) {
|
|
1186
|
+
fs.rmSync(archivedDir, { recursive: true, force: true });
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
export function deleteSessionForAgent(agentId, sessionId, fromArchived = false) {
|
|
1191
|
+
const dir = fromArchived
|
|
1192
|
+
? rootArchivedSessionDir(sessionId)
|
|
1193
|
+
: sessionDir(agentId, sessionId);
|
|
1194
|
+
if (!fs.existsSync(dir))
|
|
1195
|
+
return false;
|
|
1196
|
+
const session = readJson(path.join(dir, 'session.json'));
|
|
1197
|
+
if (session && session.agent_id !== agentId)
|
|
1198
|
+
return false;
|
|
1199
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1200
|
+
deleteSchedulesForSession(agentId, sessionId);
|
|
1201
|
+
return true;
|
|
1202
|
+
}
|
|
1203
|
+
/** Convert internal JSONL format → NewMessage for API compatibility */
|
|
1204
|
+
function storedToNewMessage(m, chatJid) {
|
|
1205
|
+
return {
|
|
1206
|
+
id: m.id,
|
|
1207
|
+
chat_jid: chatJid,
|
|
1208
|
+
task_id: m.task_id,
|
|
1209
|
+
sender: m.sender || 'unknown',
|
|
1210
|
+
sender_name: m.sender_name || 'Unknown',
|
|
1211
|
+
content: m.text,
|
|
1212
|
+
timestamp: m.ts,
|
|
1213
|
+
is_from_me: m.is_from_me ?? m.role === 'bot',
|
|
1214
|
+
is_bot_message: m.role === 'bot',
|
|
1215
|
+
attachments: m.attachments,
|
|
1216
|
+
model: m.model,
|
|
1217
|
+
metadata: m.metadata,
|
|
1218
|
+
automation_id: m.automation_id,
|
|
1219
|
+
automation_run_id: m.automation_run_id,
|
|
1220
|
+
automation_notify_target_jid: m.automation_notify_target_jid,
|
|
1221
|
+
automation_notify_policy: m.automation_notify_policy,
|
|
1222
|
+
schedule_id: m.schedule_id,
|
|
1223
|
+
schedule_run_id: m.schedule_run_id,
|
|
1224
|
+
schedule_notify_target_jid: m.schedule_notify_target_jid,
|
|
1225
|
+
schedule_notify_policy: m.schedule_notify_policy,
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
/** Resolve agent+session from chat_jid (format: "web:agent_id:session_id" or similar). */
|
|
1229
|
+
export function resolveFromChatJid(chatJid) {
|
|
1230
|
+
const taskMatch = chatJid.match(/^task:(.+)$/);
|
|
1231
|
+
if (taskMatch) {
|
|
1232
|
+
return { agentId: AUTOMATION_TASK_AGENT_ID, sessionId: taskMatch[1] };
|
|
1233
|
+
}
|
|
1234
|
+
// web:agent_id:session_id
|
|
1235
|
+
const webMatch = chatJid.match(/^web:(.+?):(.+)$/);
|
|
1236
|
+
if (webMatch)
|
|
1237
|
+
return { agentId: webMatch[1], sessionId: webMatch[2] };
|
|
1238
|
+
// acp:agent_id:session_id
|
|
1239
|
+
const acpMatch = chatJid.match(/^acp:(.+?):(.+)$/);
|
|
1240
|
+
if (acpMatch)
|
|
1241
|
+
return { agentId: acpMatch[1], sessionId: acpMatch[2] };
|
|
1242
|
+
// gateway:agent_id:session_id
|
|
1243
|
+
const gatewayMatch = chatJid.match(/^gateway:(.+?):(.+)$/);
|
|
1244
|
+
if (gatewayMatch)
|
|
1245
|
+
return { agentId: gatewayMatch[1], sessionId: gatewayMatch[2] };
|
|
1246
|
+
// feishu:app_id:chat_id
|
|
1247
|
+
const feishuMatch = chatJid.match(/^feishu:(.+?):(.+)$/);
|
|
1248
|
+
if (feishuMatch)
|
|
1249
|
+
return { agentId: feishuMatch[1], sessionId: feishuMatch[2] };
|
|
1250
|
+
// For other channels, try to find the session across agents
|
|
1251
|
+
for (const sid of listDirs(sessionsRootDir())) {
|
|
1252
|
+
const session = readJson(path.join(sessionsRootDir(), sid, 'session.json'));
|
|
1253
|
+
if (session?.source_ref === chatJid) {
|
|
1254
|
+
return { agentId: session.agent_id, sessionId: session.session_id };
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
return null;
|
|
1258
|
+
}
|
|
1259
|
+
export function storeMessage(msg) {
|
|
1260
|
+
// Determine agent_id and session_id
|
|
1261
|
+
let agentId = msg.agent_id;
|
|
1262
|
+
let sessionId = msg.session_id;
|
|
1263
|
+
if (!agentId || !sessionId) {
|
|
1264
|
+
const resolved = resolveFromChatJid(msg.chat_jid);
|
|
1265
|
+
if (resolved) {
|
|
1266
|
+
agentId = agentId || resolved.agentId;
|
|
1267
|
+
sessionId = sessionId || resolved.sessionId;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
if (!agentId || !sessionId) {
|
|
1271
|
+
logger.warn({ chat_jid: msg.chat_jid }, 'Cannot store message: unable to resolve agent/session from chat_jid');
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const channelSession = typeof msg.metadata?.channel_thread === 'string'
|
|
1275
|
+
? msg.metadata.channel_thread
|
|
1276
|
+
: typeof msg.metadata?.channel_session === 'string'
|
|
1277
|
+
? msg.metadata.channel_session
|
|
1278
|
+
: undefined;
|
|
1279
|
+
if (channelSession) {
|
|
1280
|
+
const existingSession = getSessionForAgent(agentId, sessionId);
|
|
1281
|
+
if (existingSession) {
|
|
1282
|
+
ensureSession({
|
|
1283
|
+
agent_id: agentId,
|
|
1284
|
+
session_id: sessionId,
|
|
1285
|
+
channel: existingSession.channel,
|
|
1286
|
+
channel_session: channelSession,
|
|
1287
|
+
source_ref: existingSession.source_ref || msg.chat_jid,
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
// Ensure session directory exists
|
|
1292
|
+
const msgPath = messagesPath(agentId, sessionId);
|
|
1293
|
+
const stored = {
|
|
1294
|
+
id: msg.id,
|
|
1295
|
+
ts: msg.timestamp,
|
|
1296
|
+
role: msg.is_bot_message ? 'bot' : 'user',
|
|
1297
|
+
task_id: msg.task_id,
|
|
1298
|
+
sender: msg.sender,
|
|
1299
|
+
sender_name: msg.sender_name,
|
|
1300
|
+
text: msg.content,
|
|
1301
|
+
is_from_me: msg.is_from_me,
|
|
1302
|
+
attachments: msg.attachments,
|
|
1303
|
+
model: msg.model,
|
|
1304
|
+
metadata: msg.metadata,
|
|
1305
|
+
automation_id: msg.automation_id,
|
|
1306
|
+
automation_run_id: msg.automation_run_id,
|
|
1307
|
+
automation_notify_target_jid: msg.automation_notify_target_jid,
|
|
1308
|
+
automation_notify_policy: msg.automation_notify_policy,
|
|
1309
|
+
schedule_id: msg.schedule_id,
|
|
1310
|
+
schedule_run_id: msg.schedule_run_id,
|
|
1311
|
+
schedule_notify_target_jid: msg.schedule_notify_target_jid,
|
|
1312
|
+
schedule_notify_policy: msg.schedule_notify_policy,
|
|
1313
|
+
};
|
|
1314
|
+
appendJsonl(msgPath, stored);
|
|
1315
|
+
touchSessionActivity(agentId, sessionId, msg.timestamp);
|
|
1316
|
+
}
|
|
1317
|
+
/** Alias for storeMessage — both had identical implementations in db.ts */
|
|
1318
|
+
export const storeMessageDirect = storeMessage;
|
|
1319
|
+
export function getRecentMessages(chatJid, limit = 10) {
|
|
1320
|
+
const resolved = resolveFromChatJid(chatJid);
|
|
1321
|
+
if (!resolved)
|
|
1322
|
+
return [];
|
|
1323
|
+
const messages = readJsonlTail(messagesPath(resolved.agentId, resolved.sessionId), limit);
|
|
1324
|
+
return messages.map((m) => storedToNewMessage(m, chatJid));
|
|
1325
|
+
}
|
|
1326
|
+
export function getMessagesSince(chatJid, sinceTimestamp, _botPrefix) {
|
|
1327
|
+
const resolved = resolveFromChatJid(chatJid);
|
|
1328
|
+
if (!resolved)
|
|
1329
|
+
return [];
|
|
1330
|
+
const all = readJsonl(messagesPath(resolved.agentId, resolved.sessionId));
|
|
1331
|
+
return all
|
|
1332
|
+
.filter((m) => {
|
|
1333
|
+
const isNew = m.ts > sinceTimestamp;
|
|
1334
|
+
const isNotBot = m.role !== 'bot';
|
|
1335
|
+
const hasText = m.text && m.text.trim() !== '';
|
|
1336
|
+
const keep = isNew && isNotBot && hasText;
|
|
1337
|
+
logger.debug({
|
|
1338
|
+
msgId: m.id,
|
|
1339
|
+
role: m.role,
|
|
1340
|
+
text: m.text,
|
|
1341
|
+
isNew,
|
|
1342
|
+
isNotBot,
|
|
1343
|
+
hasText,
|
|
1344
|
+
keep,
|
|
1345
|
+
}, 'getMessagesSince: filtering message');
|
|
1346
|
+
return keep;
|
|
1347
|
+
})
|
|
1348
|
+
.map((m) => storedToNewMessage(m, chatJid));
|
|
1349
|
+
}
|
|
1350
|
+
export function getNewMessages(jids, lastTimestamp, botPrefix) {
|
|
1351
|
+
if (jids.length === 0)
|
|
1352
|
+
return { messages: [], newTimestamp: lastTimestamp };
|
|
1353
|
+
let newTimestamp = lastTimestamp;
|
|
1354
|
+
const messages = [];
|
|
1355
|
+
for (const jid of jids) {
|
|
1356
|
+
const msgs = getMessagesSince(jid, lastTimestamp, botPrefix);
|
|
1357
|
+
for (const m of msgs) {
|
|
1358
|
+
messages.push(m);
|
|
1359
|
+
if (m.timestamp > newTimestamp)
|
|
1360
|
+
newTimestamp = m.timestamp;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
messages.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
1364
|
+
return { messages, newTimestamp };
|
|
1365
|
+
}
|
|
1366
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1367
|
+
// Automations
|
|
1368
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1369
|
+
export class AutomationInputError extends Error {
|
|
1370
|
+
code;
|
|
1371
|
+
constructor(code, message) {
|
|
1372
|
+
super(message);
|
|
1373
|
+
this.name = 'AutomationInputError';
|
|
1374
|
+
this.code = code;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
export class ScheduleInputError extends AutomationInputError {
|
|
1378
|
+
constructor(code, message) {
|
|
1379
|
+
super(code, message);
|
|
1380
|
+
this.name = 'ScheduleInputError';
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
function normalizeAutomationTimestamp(value, fieldName) {
|
|
1384
|
+
const trimmed = value.trim();
|
|
1385
|
+
if (!trimmed) {
|
|
1386
|
+
throw new AutomationInputError(`missing_${fieldName}`, `${fieldName} is required`);
|
|
1387
|
+
}
|
|
1388
|
+
const parsed = new Date(trimmed);
|
|
1389
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
1390
|
+
throw new AutomationInputError(`invalid_${fieldName}`, `${fieldName} must be a valid ISO timestamp`);
|
|
1391
|
+
}
|
|
1392
|
+
return parsed.toISOString();
|
|
1393
|
+
}
|
|
1394
|
+
function normalizeOptionalAutomationTimestamp(value, fieldName) {
|
|
1395
|
+
if (value == null)
|
|
1396
|
+
return null;
|
|
1397
|
+
return normalizeAutomationTimestamp(value, fieldName);
|
|
1398
|
+
}
|
|
1399
|
+
function validateAutomationCounter(name, value) {
|
|
1400
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
1401
|
+
throw new AutomationInputError(`invalid_${name}`, `${name} must be a non-negative integer`);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function buildAutomationResultPreview(text) {
|
|
1405
|
+
const trimmed = text?.trim();
|
|
1406
|
+
if (!trimmed)
|
|
1407
|
+
return null;
|
|
1408
|
+
return trimmed.length <= 280 ? trimmed : `${trimmed.slice(0, 277)}...`;
|
|
1409
|
+
}
|
|
1410
|
+
const ONE_SHOT_RRULE = 'FREQ=MINUTELY;COUNT=1';
|
|
1411
|
+
function isRRuleExpression(value) {
|
|
1412
|
+
if (typeof value !== 'string')
|
|
1413
|
+
return false;
|
|
1414
|
+
const trimmed = value.trim().toUpperCase();
|
|
1415
|
+
return trimmed.startsWith('FREQ=') || trimmed.startsWith('RRULE:');
|
|
1416
|
+
}
|
|
1417
|
+
function computeCronNextRun(cron, currentDate) {
|
|
1418
|
+
try {
|
|
1419
|
+
const interval = CronExpressionParser.parse(cron, {
|
|
1420
|
+
tz: TIMEZONE,
|
|
1421
|
+
currentDate,
|
|
1422
|
+
});
|
|
1423
|
+
return interval.next().toISOString();
|
|
1424
|
+
}
|
|
1425
|
+
catch {
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
function computeScheduleExpressionNextRun(expression, timezone, currentDate) {
|
|
1430
|
+
if (isRRuleExpression(expression)) {
|
|
1431
|
+
return computeNextAutomationRun({
|
|
1432
|
+
rrule: expression,
|
|
1433
|
+
timezone,
|
|
1434
|
+
currentDate,
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
return computeCronNextRun(expression, currentDate);
|
|
1438
|
+
}
|
|
1439
|
+
function computeScheduleExpressionNextRunOrThrow(input) {
|
|
1440
|
+
const nextRun = computeScheduleExpressionNextRun(input.expression, input.timezone, input.currentDate);
|
|
1441
|
+
if (!nextRun) {
|
|
1442
|
+
throw new AutomationInputError(isRRuleExpression(input.expression) ? 'invalid_rrule' : 'invalid_cron', 'schedule expression must produce a valid next_run');
|
|
1443
|
+
}
|
|
1444
|
+
return nextRun;
|
|
1445
|
+
}
|
|
1446
|
+
function computeAutomationNextRunOrThrow(input) {
|
|
1447
|
+
const nextRun = computeNextAutomationRun(input);
|
|
1448
|
+
if (!nextRun) {
|
|
1449
|
+
throw new AutomationInputError('invalid_rrule', 'rrule and timezone must produce a valid next_run');
|
|
1450
|
+
}
|
|
1451
|
+
return nextRun;
|
|
1452
|
+
}
|
|
1453
|
+
function computeStoredAutomationNextRun(automation, currentDate) {
|
|
1454
|
+
if (automation.kind !== 'calendar')
|
|
1455
|
+
return null;
|
|
1456
|
+
const expression = automation.rrule;
|
|
1457
|
+
if (!expression?.trim())
|
|
1458
|
+
return null;
|
|
1459
|
+
if (!automation.timezone?.trim())
|
|
1460
|
+
return null;
|
|
1461
|
+
return computeScheduleExpressionNextRun(expression, automation.timezone, currentDate);
|
|
1462
|
+
}
|
|
1463
|
+
function requireObjectRecord(value, field) {
|
|
1464
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1465
|
+
throw new AutomationInputError(`invalid_${field}`, `${field} must be an object`);
|
|
1466
|
+
}
|
|
1467
|
+
return value;
|
|
1468
|
+
}
|
|
1469
|
+
function requireStringValue(value, field) {
|
|
1470
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
1471
|
+
throw new AutomationInputError(`missing_${field}`, `${field} is required`);
|
|
1472
|
+
}
|
|
1473
|
+
return value.trim();
|
|
1474
|
+
}
|
|
1475
|
+
function normalizeEventAutomationTrigger(trigger) {
|
|
1476
|
+
const triggerRecord = requireObjectRecord(trigger, 'trigger');
|
|
1477
|
+
const connection = requireObjectRecord(triggerRecord.connection, 'trigger_connection');
|
|
1478
|
+
const subscription = requireObjectRecord(triggerRecord.subscription, 'trigger_subscription');
|
|
1479
|
+
const type = requireStringValue(triggerRecord.type, 'trigger_type');
|
|
1480
|
+
if (type !== 'supabase') {
|
|
1481
|
+
throw new AutomationInputError('invalid_trigger_type', 'trigger.type must be supabase');
|
|
1482
|
+
}
|
|
1483
|
+
const source = typeof triggerRecord.source === 'string' && triggerRecord.source.trim()
|
|
1484
|
+
? triggerRecord.source.trim()
|
|
1485
|
+
: 'postgres_changes';
|
|
1486
|
+
if (source !== 'postgres_changes') {
|
|
1487
|
+
throw new AutomationInputError('invalid_trigger_source', 'trigger.source must be postgres_changes');
|
|
1488
|
+
}
|
|
1489
|
+
const event = requireStringValue(subscription.event, 'trigger_subscription_event').toUpperCase();
|
|
1490
|
+
if (!['*', 'INSERT', 'UPDATE', 'DELETE'].includes(event)) {
|
|
1491
|
+
throw new AutomationInputError('invalid_trigger_subscription_event', 'trigger.subscription.event must be one of: *, INSERT, UPDATE, DELETE');
|
|
1492
|
+
}
|
|
1493
|
+
return {
|
|
1494
|
+
type: 'supabase',
|
|
1495
|
+
source,
|
|
1496
|
+
connection: {
|
|
1497
|
+
url_env: requireStringValue(connection.url_env, 'trigger_connection_url_env'),
|
|
1498
|
+
key_env: requireStringValue(connection.key_env, 'trigger_connection_key_env'),
|
|
1499
|
+
},
|
|
1500
|
+
subscription: {
|
|
1501
|
+
schema: requireStringValue(subscription.schema, 'trigger_subscription_schema'),
|
|
1502
|
+
table: requireStringValue(subscription.table, 'trigger_subscription_table'),
|
|
1503
|
+
event: event,
|
|
1504
|
+
...(typeof subscription.filter === 'string' && subscription.filter.trim()
|
|
1505
|
+
? { filter: subscription.filter.trim() }
|
|
1506
|
+
: {}),
|
|
1507
|
+
},
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
function normalizeEventAutomationAction(action, defaultPrompt) {
|
|
1511
|
+
const actionRecord = action
|
|
1512
|
+
? requireObjectRecord(action, 'action')
|
|
1513
|
+
: { type: 'skill', command: defaultPrompt };
|
|
1514
|
+
const type = requireStringValue(actionRecord.type, 'action_type');
|
|
1515
|
+
if (type !== 'skill') {
|
|
1516
|
+
throw new AutomationInputError('invalid_action_type', 'action.type must be skill');
|
|
1517
|
+
}
|
|
1518
|
+
return {
|
|
1519
|
+
type: 'skill',
|
|
1520
|
+
command: requireStringValue(actionRecord.command ?? defaultPrompt, 'action_command'),
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
function normalizeAutomationRecord(automation) {
|
|
1524
|
+
const id = assertSafePathSegment(automation.id, 'automation_id');
|
|
1525
|
+
const taskId = typeof automation.task_id === 'string' && automation.task_id.trim()
|
|
1526
|
+
? assertSafePathSegment(automation.task_id.trim(), 'task_id')
|
|
1527
|
+
: automationTaskId(id);
|
|
1528
|
+
const kind = automation.kind === 'event' ? 'event' : 'calendar';
|
|
1529
|
+
const agentId = kind === 'event'
|
|
1530
|
+
? AUTOMATION_TASK_AGENT_ID
|
|
1531
|
+
: automation.agent_id.trim();
|
|
1532
|
+
const normalized = {
|
|
1533
|
+
...automation,
|
|
1534
|
+
version: 2,
|
|
1535
|
+
id,
|
|
1536
|
+
kind,
|
|
1537
|
+
name: automation.name.trim(),
|
|
1538
|
+
agent_id: agentId,
|
|
1539
|
+
prompt: automation.prompt.trim(),
|
|
1540
|
+
task_id: taskId,
|
|
1541
|
+
status: automation.status === 'PAUSED' ? 'PAUSED' : 'ACTIVE',
|
|
1542
|
+
...(kind === 'calendar'
|
|
1543
|
+
? {
|
|
1544
|
+
rrule: automation.rrule?.trim(),
|
|
1545
|
+
timezone: automation.timezone?.trim(),
|
|
1546
|
+
exdate: automation.exdate?.filter(Boolean),
|
|
1547
|
+
trigger: undefined,
|
|
1548
|
+
action: undefined,
|
|
1549
|
+
}
|
|
1550
|
+
: {
|
|
1551
|
+
trigger: normalizeEventAutomationTrigger(automation.trigger),
|
|
1552
|
+
action: normalizeEventAutomationAction(automation.action, automation.prompt),
|
|
1553
|
+
rrule: undefined,
|
|
1554
|
+
timezone: undefined,
|
|
1555
|
+
exdate: undefined,
|
|
1556
|
+
}),
|
|
1557
|
+
network_access: automation.network_access === true,
|
|
1558
|
+
last_triggered_at: automation.last_triggered_at ?? null,
|
|
1559
|
+
last_message_id: automation.last_message_id ?? null,
|
|
1560
|
+
};
|
|
1561
|
+
return normalized;
|
|
1562
|
+
}
|
|
1563
|
+
function normalizeAutomationStateRecord(state) {
|
|
1564
|
+
return {
|
|
1565
|
+
...state,
|
|
1566
|
+
execution_status: state.execution_status ?? 'idle',
|
|
1567
|
+
claimed_at: state.claimed_at ?? null,
|
|
1568
|
+
started_at: state.started_at ?? null,
|
|
1569
|
+
next_run: state.next_run ?? null,
|
|
1570
|
+
last_run: state.last_run ?? null,
|
|
1571
|
+
pending_event_count: state.pending_event_count ?? 0,
|
|
1572
|
+
pending_event_at: state.pending_event_at ?? null,
|
|
1573
|
+
pending_event_payload: state.pending_event_payload ?? null,
|
|
1574
|
+
last_result_preview: state.last_result_preview ?? null,
|
|
1575
|
+
last_error: state.last_error ?? null,
|
|
1576
|
+
run_count: state.run_count ?? 0,
|
|
1577
|
+
failure_count: state.failure_count ?? 0,
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
function normalizeAutomationRunRecord(run) {
|
|
1581
|
+
return {
|
|
1582
|
+
...run,
|
|
1583
|
+
claimed_at: run.claimed_at ?? null,
|
|
1584
|
+
started_at: run.started_at ?? null,
|
|
1585
|
+
finished_at: run.finished_at ?? null,
|
|
1586
|
+
result_preview: run.result_preview ?? null,
|
|
1587
|
+
full_result: run.full_result ?? null,
|
|
1588
|
+
error: run.error ?? null,
|
|
1589
|
+
no_op_detected: run.no_op_detected ?? false,
|
|
1590
|
+
no_op_reason: run.no_op_reason ?? null,
|
|
1591
|
+
notification_sent: run.notification_sent ?? false,
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
function normalizeAutomationEventRecord(event) {
|
|
1595
|
+
const receivedAt = normalizeAutomationTimestamp(event.received_at, 'received_at');
|
|
1596
|
+
return {
|
|
1597
|
+
event_id: assertSafePathSegment(event.event_id, 'automation_event_id'),
|
|
1598
|
+
automation_id: assertSafePathSegment(event.automation_id, 'automation_id'),
|
|
1599
|
+
status: event.status === 'running' || event.status === 'succeeded' || event.status === 'failed'
|
|
1600
|
+
? event.status
|
|
1601
|
+
: 'queued',
|
|
1602
|
+
payload: event.payload && typeof event.payload === 'object' ? event.payload : {},
|
|
1603
|
+
received_at: receivedAt,
|
|
1604
|
+
claimed_at: normalizeOptionalAutomationTimestamp(event.claimed_at, 'claimed_at'),
|
|
1605
|
+
finished_at: normalizeOptionalAutomationTimestamp(event.finished_at, 'finished_at'),
|
|
1606
|
+
run_id: event.run_id ? assertSafePathSegment(event.run_id, 'automation_run_id') : null,
|
|
1607
|
+
error: event.error ?? null,
|
|
1608
|
+
created_at: event.created_at
|
|
1609
|
+
? normalizeAutomationTimestamp(event.created_at, 'created_at')
|
|
1610
|
+
: receivedAt,
|
|
1611
|
+
updated_at: event.updated_at
|
|
1612
|
+
? normalizeAutomationTimestamp(event.updated_at, 'updated_at')
|
|
1613
|
+
: receivedAt,
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
function isStaleAutomationExecution(executionStatus, marker, nowMs) {
|
|
1617
|
+
if (!marker)
|
|
1618
|
+
return false;
|
|
1619
|
+
if (executionStatus !== 'running' && executionStatus !== 'claimed')
|
|
1620
|
+
return false;
|
|
1621
|
+
const startedMs = new Date(marker).getTime();
|
|
1622
|
+
if (Number.isNaN(startedMs))
|
|
1623
|
+
return false;
|
|
1624
|
+
return nowMs - startedMs >= AUTOMATION_STALE_EXECUTION_MS;
|
|
1625
|
+
}
|
|
1626
|
+
function getAutomationBundle(automationId) {
|
|
1627
|
+
const automation = readJson(automationJsonPath(automationId));
|
|
1628
|
+
if (!automation)
|
|
1629
|
+
return undefined;
|
|
1630
|
+
const state = readJson(automationStateJsonPath(automationId));
|
|
1631
|
+
if (!state) {
|
|
1632
|
+
throw new AutomationInputError('missing_state', `Automation state is missing for ${automationId}`);
|
|
1633
|
+
}
|
|
1634
|
+
return {
|
|
1635
|
+
automation: normalizeAutomationRecord(automation),
|
|
1636
|
+
state: normalizeAutomationStateRecord(state),
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
function locateAutomationRun(runId) {
|
|
1640
|
+
for (const automationId of listDirs(automationsDir())) {
|
|
1641
|
+
const run = readJson(automationRunJsonPath(automationId, runId));
|
|
1642
|
+
if (run) {
|
|
1643
|
+
return {
|
|
1644
|
+
automationId,
|
|
1645
|
+
run: normalizeAutomationRunRecord(run),
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
return undefined;
|
|
1650
|
+
}
|
|
1651
|
+
export function getAutomationRunById(runId) {
|
|
1652
|
+
return locateAutomationRun(runId)?.run;
|
|
1653
|
+
}
|
|
1654
|
+
export function createAutomation(input) {
|
|
1655
|
+
if (!input.agent_id?.trim()) {
|
|
1656
|
+
throw new AutomationInputError('missing_agent_id', 'agent_id is required');
|
|
1657
|
+
}
|
|
1658
|
+
if (!input.name?.trim()) {
|
|
1659
|
+
throw new AutomationInputError('missing_name', 'name is required');
|
|
1660
|
+
}
|
|
1661
|
+
if (!input.prompt?.trim()) {
|
|
1662
|
+
throw new AutomationInputError('missing_prompt', 'prompt is required');
|
|
1663
|
+
}
|
|
1664
|
+
if (!input.rrule?.trim()) {
|
|
1665
|
+
throw new AutomationInputError('missing_rrule', 'rrule is required');
|
|
1666
|
+
}
|
|
1667
|
+
if (!input.timezone?.trim()) {
|
|
1668
|
+
throw new AutomationInputError('missing_timezone', 'timezone is required');
|
|
1669
|
+
}
|
|
1670
|
+
ensureAgent({ agent_id: input.agent_id });
|
|
1671
|
+
const id = crypto.randomUUID();
|
|
1672
|
+
const taskId = automationTaskId(id);
|
|
1673
|
+
const now = Date.now();
|
|
1674
|
+
const status = input.status ?? 'ACTIVE';
|
|
1675
|
+
const nextRun = input.next_run !== undefined
|
|
1676
|
+
? normalizeOptionalAutomationTimestamp(input.next_run, 'next_run')
|
|
1677
|
+
: status === 'ACTIVE'
|
|
1678
|
+
? computeAutomationNextRunOrThrow({
|
|
1679
|
+
rrule: input.rrule,
|
|
1680
|
+
timezone: input.timezone,
|
|
1681
|
+
})
|
|
1682
|
+
: null;
|
|
1683
|
+
const automation = normalizeAutomationRecord({
|
|
1684
|
+
version: 2,
|
|
1685
|
+
id,
|
|
1686
|
+
kind: 'calendar',
|
|
1687
|
+
name: input.name.trim(),
|
|
1688
|
+
agent_id: input.agent_id.trim(),
|
|
1689
|
+
task_id: taskId,
|
|
1690
|
+
prompt: input.prompt.trim(),
|
|
1691
|
+
status,
|
|
1692
|
+
rrule: input.rrule.trim(),
|
|
1693
|
+
timezone: input.timezone.trim(),
|
|
1694
|
+
exdate: input.exdate?.filter(Boolean),
|
|
1695
|
+
session_mode: input.session_mode,
|
|
1696
|
+
model: input.model,
|
|
1697
|
+
reasoning_effort: input.reasoning_effort,
|
|
1698
|
+
permission_mode: input.permission_mode,
|
|
1699
|
+
network_access: input.network_access === true,
|
|
1700
|
+
execution_environment: input.execution_environment,
|
|
1701
|
+
cwds: input.cwds?.filter(Boolean),
|
|
1702
|
+
last_triggered_at: null,
|
|
1703
|
+
last_message_id: null,
|
|
1704
|
+
created_at: now,
|
|
1705
|
+
updated_at: now,
|
|
1706
|
+
});
|
|
1707
|
+
const state = normalizeAutomationStateRecord({
|
|
1708
|
+
automation_id: id,
|
|
1709
|
+
execution_status: 'idle',
|
|
1710
|
+
claimed_at: null,
|
|
1711
|
+
started_at: null,
|
|
1712
|
+
next_run: nextRun,
|
|
1713
|
+
last_run: null,
|
|
1714
|
+
last_result_preview: null,
|
|
1715
|
+
last_error: null,
|
|
1716
|
+
run_count: 0,
|
|
1717
|
+
failure_count: 0,
|
|
1718
|
+
updated_at: now,
|
|
1719
|
+
});
|
|
1720
|
+
writeJson(automationJsonPath(id), automation);
|
|
1721
|
+
writeJson(automationStateJsonPath(id), state);
|
|
1722
|
+
ensureAutomationTask(automation);
|
|
1723
|
+
return automation;
|
|
1724
|
+
}
|
|
1725
|
+
export function createEventAutomation(input) {
|
|
1726
|
+
if (!input.name?.trim()) {
|
|
1727
|
+
throw new AutomationInputError('missing_name', 'name is required');
|
|
1728
|
+
}
|
|
1729
|
+
if (!input.prompt?.trim()) {
|
|
1730
|
+
throw new AutomationInputError('missing_prompt', 'prompt is required');
|
|
1731
|
+
}
|
|
1732
|
+
ensureAgent({ agent_id: AUTOMATION_TASK_AGENT_ID, name: 'Automation Task' });
|
|
1733
|
+
const id = crypto.randomUUID();
|
|
1734
|
+
const now = Date.now();
|
|
1735
|
+
const automation = normalizeAutomationRecord({
|
|
1736
|
+
version: 2,
|
|
1737
|
+
id,
|
|
1738
|
+
kind: 'event',
|
|
1739
|
+
name: input.name.trim(),
|
|
1740
|
+
agent_id: AUTOMATION_TASK_AGENT_ID,
|
|
1741
|
+
task_id: automationTaskId(id),
|
|
1742
|
+
prompt: input.prompt.trim(),
|
|
1743
|
+
status: input.status ?? 'ACTIVE',
|
|
1744
|
+
trigger: input.trigger,
|
|
1745
|
+
action: input.action,
|
|
1746
|
+
permission_mode: input.permission_mode,
|
|
1747
|
+
network_access: input.network_access === true,
|
|
1748
|
+
execution_environment: input.execution_environment,
|
|
1749
|
+
cwds: input.cwds?.filter(Boolean),
|
|
1750
|
+
last_triggered_at: null,
|
|
1751
|
+
last_message_id: null,
|
|
1752
|
+
created_at: now,
|
|
1753
|
+
updated_at: now,
|
|
1754
|
+
});
|
|
1755
|
+
const state = normalizeAutomationStateRecord({
|
|
1756
|
+
automation_id: id,
|
|
1757
|
+
execution_status: 'idle',
|
|
1758
|
+
claimed_at: null,
|
|
1759
|
+
started_at: null,
|
|
1760
|
+
next_run: null,
|
|
1761
|
+
last_run: null,
|
|
1762
|
+
last_result_preview: null,
|
|
1763
|
+
last_error: null,
|
|
1764
|
+
run_count: 0,
|
|
1765
|
+
failure_count: 0,
|
|
1766
|
+
updated_at: now,
|
|
1767
|
+
});
|
|
1768
|
+
writeJson(automationJsonPath(id), automation);
|
|
1769
|
+
writeJson(automationStateJsonPath(id), state);
|
|
1770
|
+
ensureAutomationTask(automation);
|
|
1771
|
+
return automation;
|
|
1772
|
+
}
|
|
1773
|
+
export function getAutomationById(id) {
|
|
1774
|
+
const automation = readJson(automationJsonPath(id));
|
|
1775
|
+
return automation ? normalizeAutomationRecord(automation) : undefined;
|
|
1776
|
+
}
|
|
1777
|
+
export function getAutomationState(id) {
|
|
1778
|
+
const state = readJson(automationStateJsonPath(id));
|
|
1779
|
+
return state ? normalizeAutomationStateRecord(state) : undefined;
|
|
1780
|
+
}
|
|
1781
|
+
export function listAutomations(agentId) {
|
|
1782
|
+
const records = [];
|
|
1783
|
+
for (const automationId of listDirs(automationsDir())) {
|
|
1784
|
+
const automation = getAutomationById(automationId);
|
|
1785
|
+
if (!automation)
|
|
1786
|
+
continue;
|
|
1787
|
+
if (agentId && automation.agent_id !== agentId)
|
|
1788
|
+
continue;
|
|
1789
|
+
records.push(automation);
|
|
1790
|
+
}
|
|
1791
|
+
return records.sort((left, right) => right.updated_at - left.updated_at);
|
|
1792
|
+
}
|
|
1793
|
+
export function updateAutomation(id, updates) {
|
|
1794
|
+
const bundle = getAutomationBundle(id);
|
|
1795
|
+
if (!bundle)
|
|
1796
|
+
return undefined;
|
|
1797
|
+
if (updates.name !== undefined && !updates.name.trim()) {
|
|
1798
|
+
throw new AutomationInputError('missing_name', 'name is required');
|
|
1799
|
+
}
|
|
1800
|
+
if (updates.prompt !== undefined && !updates.prompt.trim()) {
|
|
1801
|
+
throw new AutomationInputError('missing_prompt', 'prompt is required');
|
|
1802
|
+
}
|
|
1803
|
+
const now = Date.now();
|
|
1804
|
+
const automation = normalizeAutomationRecord({
|
|
1805
|
+
...bundle.automation,
|
|
1806
|
+
...(updates.kind !== undefined ? { kind: updates.kind } : {}),
|
|
1807
|
+
...(updates.name !== undefined ? { name: updates.name.trim() } : {}),
|
|
1808
|
+
...(updates.prompt !== undefined ? { prompt: updates.prompt.trim() } : {}),
|
|
1809
|
+
...(updates.rrule !== undefined ? { rrule: updates.rrule.trim() } : {}),
|
|
1810
|
+
...(updates.timezone !== undefined ? { timezone: updates.timezone.trim() } : {}),
|
|
1811
|
+
...(updates.exdate !== undefined ? { exdate: updates.exdate.filter(Boolean) } : {}),
|
|
1812
|
+
...(updates.status !== undefined ? { status: updates.status } : {}),
|
|
1813
|
+
...(updates.session_mode !== undefined ? { session_mode: updates.session_mode } : {}),
|
|
1814
|
+
...(updates.model !== undefined ? { model: updates.model } : {}),
|
|
1815
|
+
...(updates.reasoning_effort !== undefined
|
|
1816
|
+
? { reasoning_effort: updates.reasoning_effort }
|
|
1817
|
+
: {}),
|
|
1818
|
+
...(updates.permission_mode !== undefined
|
|
1819
|
+
? { permission_mode: updates.permission_mode }
|
|
1820
|
+
: {}),
|
|
1821
|
+
...(updates.network_access !== undefined
|
|
1822
|
+
? { network_access: updates.network_access === true }
|
|
1823
|
+
: {}),
|
|
1824
|
+
...(updates.execution_environment !== undefined
|
|
1825
|
+
? { execution_environment: updates.execution_environment }
|
|
1826
|
+
: {}),
|
|
1827
|
+
...(updates.cwds !== undefined ? { cwds: updates.cwds.filter(Boolean) } : {}),
|
|
1828
|
+
...(updates.trigger !== undefined ? { trigger: updates.trigger } : {}),
|
|
1829
|
+
...(updates.action !== undefined ? { action: updates.action } : {}),
|
|
1830
|
+
updated_at: now,
|
|
1831
|
+
});
|
|
1832
|
+
const hasNextRunUpdate = Object.prototype.hasOwnProperty.call(updates, 'next_run');
|
|
1833
|
+
const isCalendarAutomation = automation.kind === 'calendar';
|
|
1834
|
+
const scheduleExpressionChanged = isCalendarAutomation && (updates.rrule !== undefined || updates.timezone !== undefined);
|
|
1835
|
+
const wasPaused = bundle.automation.status === 'PAUSED';
|
|
1836
|
+
const isActive = automation.status === 'ACTIVE';
|
|
1837
|
+
let nextRun = bundle.state.next_run;
|
|
1838
|
+
if (!isCalendarAutomation) {
|
|
1839
|
+
nextRun = null;
|
|
1840
|
+
}
|
|
1841
|
+
else if (hasNextRunUpdate) {
|
|
1842
|
+
nextRun = normalizeOptionalAutomationTimestamp(updates.next_run, 'next_run');
|
|
1843
|
+
}
|
|
1844
|
+
else if (!isActive) {
|
|
1845
|
+
nextRun = null;
|
|
1846
|
+
}
|
|
1847
|
+
if (isCalendarAutomation &&
|
|
1848
|
+
isActive &&
|
|
1849
|
+
(scheduleExpressionChanged || wasPaused || nextRun === null) &&
|
|
1850
|
+
(!hasNextRunUpdate || nextRun === null)) {
|
|
1851
|
+
nextRun = computeAutomationNextRunOrThrow({
|
|
1852
|
+
rrule: automation.rrule || '',
|
|
1853
|
+
timezone: automation.timezone || '',
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
const state = normalizeAutomationStateRecord({
|
|
1857
|
+
...bundle.state,
|
|
1858
|
+
next_run: nextRun,
|
|
1859
|
+
updated_at: now,
|
|
1860
|
+
});
|
|
1861
|
+
writeJson(automationJsonPath(id), automation);
|
|
1862
|
+
writeJson(automationStateJsonPath(id), state);
|
|
1863
|
+
return automation;
|
|
1864
|
+
}
|
|
1865
|
+
export function updateAutomationState(id, updates) {
|
|
1866
|
+
const bundle = getAutomationBundle(id);
|
|
1867
|
+
if (!bundle)
|
|
1868
|
+
return undefined;
|
|
1869
|
+
if (updates.run_count !== undefined) {
|
|
1870
|
+
validateAutomationCounter('run_count', updates.run_count);
|
|
1871
|
+
}
|
|
1872
|
+
if (updates.failure_count !== undefined) {
|
|
1873
|
+
validateAutomationCounter('failure_count', updates.failure_count);
|
|
1874
|
+
}
|
|
1875
|
+
if (updates.pending_event_count !== undefined) {
|
|
1876
|
+
validateAutomationCounter('pending_event_count', updates.pending_event_count);
|
|
1877
|
+
}
|
|
1878
|
+
const state = normalizeAutomationStateRecord({
|
|
1879
|
+
...bundle.state,
|
|
1880
|
+
...(updates.execution_status !== undefined
|
|
1881
|
+
? { execution_status: updates.execution_status }
|
|
1882
|
+
: {}),
|
|
1883
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'claimed_at')
|
|
1884
|
+
? {
|
|
1885
|
+
claimed_at: normalizeOptionalAutomationTimestamp(updates.claimed_at, 'claimed_at'),
|
|
1886
|
+
}
|
|
1887
|
+
: {}),
|
|
1888
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'started_at')
|
|
1889
|
+
? {
|
|
1890
|
+
started_at: normalizeOptionalAutomationTimestamp(updates.started_at, 'started_at'),
|
|
1891
|
+
}
|
|
1892
|
+
: {}),
|
|
1893
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'next_run')
|
|
1894
|
+
? {
|
|
1895
|
+
next_run: normalizeOptionalAutomationTimestamp(updates.next_run, 'next_run'),
|
|
1896
|
+
}
|
|
1897
|
+
: {}),
|
|
1898
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'last_run')
|
|
1899
|
+
? {
|
|
1900
|
+
last_run: normalizeOptionalAutomationTimestamp(updates.last_run, 'last_run'),
|
|
1901
|
+
}
|
|
1902
|
+
: {}),
|
|
1903
|
+
...(updates.pending_event_count !== undefined
|
|
1904
|
+
? { pending_event_count: updates.pending_event_count }
|
|
1905
|
+
: {}),
|
|
1906
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'pending_event_at')
|
|
1907
|
+
? {
|
|
1908
|
+
pending_event_at: normalizeOptionalAutomationTimestamp(updates.pending_event_at, 'pending_event_at'),
|
|
1909
|
+
}
|
|
1910
|
+
: {}),
|
|
1911
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'pending_event_payload')
|
|
1912
|
+
? { pending_event_payload: updates.pending_event_payload ?? null }
|
|
1913
|
+
: {}),
|
|
1914
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'last_result_preview')
|
|
1915
|
+
? { last_result_preview: updates.last_result_preview ?? null }
|
|
1916
|
+
: {}),
|
|
1917
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'last_error')
|
|
1918
|
+
? { last_error: updates.last_error ?? null }
|
|
1919
|
+
: {}),
|
|
1920
|
+
...(updates.run_count !== undefined ? { run_count: updates.run_count } : {}),
|
|
1921
|
+
...(updates.failure_count !== undefined
|
|
1922
|
+
? { failure_count: updates.failure_count }
|
|
1923
|
+
: {}),
|
|
1924
|
+
updated_at: Date.now(),
|
|
1925
|
+
});
|
|
1926
|
+
writeJson(automationStateJsonPath(id), state);
|
|
1927
|
+
return state;
|
|
1928
|
+
}
|
|
1929
|
+
export function markAutomationTriggered(input) {
|
|
1930
|
+
const bundle = getAutomationBundle(input.automationId);
|
|
1931
|
+
if (!bundle)
|
|
1932
|
+
return undefined;
|
|
1933
|
+
const nextRun = bundle.automation.status === 'ACTIVE'
|
|
1934
|
+
? computeStoredAutomationNextRun(bundle.automation, input.triggeredAt)
|
|
1935
|
+
: null;
|
|
1936
|
+
const automation = updateAutomation(input.automationId, {
|
|
1937
|
+
next_run: nextRun,
|
|
1938
|
+
});
|
|
1939
|
+
if (!automation)
|
|
1940
|
+
return undefined;
|
|
1941
|
+
const updated = normalizeAutomationRecord({
|
|
1942
|
+
...automation,
|
|
1943
|
+
last_triggered_at: input.triggeredAt,
|
|
1944
|
+
last_message_id: input.messageId,
|
|
1945
|
+
updated_at: Date.now(),
|
|
1946
|
+
});
|
|
1947
|
+
writeJson(automationJsonPath(input.automationId), updated);
|
|
1948
|
+
const state = input.keepExecutionStatus ? getAutomationState(input.automationId) : undefined;
|
|
1949
|
+
updateAutomationState(input.automationId, {
|
|
1950
|
+
execution_status: input.keepExecutionStatus
|
|
1951
|
+
? state?.execution_status ?? 'idle'
|
|
1952
|
+
: 'idle',
|
|
1953
|
+
claimed_at: input.keepExecutionStatus ? state?.claimed_at ?? null : null,
|
|
1954
|
+
started_at: input.keepExecutionStatus ? state?.started_at ?? null : null,
|
|
1955
|
+
next_run: nextRun,
|
|
1956
|
+
last_run: input.triggeredAt,
|
|
1957
|
+
});
|
|
1958
|
+
if (automation.kind === 'calendar' && automation.status === 'ACTIVE' && nextRun === null) {
|
|
1959
|
+
updateAutomation(input.automationId, { status: 'PAUSED' });
|
|
1960
|
+
}
|
|
1961
|
+
return updated;
|
|
1962
|
+
}
|
|
1963
|
+
export function deleteAutomation(id) {
|
|
1964
|
+
const dir = automationDir(id);
|
|
1965
|
+
if (fs.existsSync(dir)) {
|
|
1966
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1967
|
+
}
|
|
1968
|
+
const sessionId = `sched-${id}`;
|
|
1969
|
+
for (const agentId of listDirs(AGENTS_DIR)) {
|
|
1970
|
+
deleteSessionForAgent(agentId, sessionId);
|
|
1971
|
+
deleteSessionForAgent(agentId, sessionId, true);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
export function createAutomationRun(automationId, input = {}) {
|
|
1975
|
+
const bundle = getAutomationBundle(automationId);
|
|
1976
|
+
if (!bundle) {
|
|
1977
|
+
throw new AutomationInputError('missing_automation', `Automation not found: ${automationId}`);
|
|
1978
|
+
}
|
|
1979
|
+
const runId = crypto.randomUUID();
|
|
1980
|
+
const triggeredAt = input.triggered_at !== undefined
|
|
1981
|
+
? normalizeAutomationTimestamp(input.triggered_at, 'triggered_at')
|
|
1982
|
+
: new Date().toISOString();
|
|
1983
|
+
const claimedAt = input.claimed_at !== undefined
|
|
1984
|
+
? normalizeOptionalAutomationTimestamp(input.claimed_at, 'claimed_at')
|
|
1985
|
+
: bundle.state.claimed_at;
|
|
1986
|
+
const startedAt = input.started_at !== undefined
|
|
1987
|
+
? normalizeOptionalAutomationTimestamp(input.started_at, 'started_at')
|
|
1988
|
+
: triggeredAt;
|
|
1989
|
+
const run = normalizeAutomationRunRecord({
|
|
1990
|
+
run_id: runId,
|
|
1991
|
+
automation_id: bundle.automation.id,
|
|
1992
|
+
agent_id: bundle.automation.agent_id,
|
|
1993
|
+
status: 'running',
|
|
1994
|
+
triggered_at: triggeredAt,
|
|
1995
|
+
claimed_at: claimedAt,
|
|
1996
|
+
started_at: startedAt,
|
|
1997
|
+
finished_at: null,
|
|
1998
|
+
session_id: input.session_id,
|
|
1999
|
+
task_id: input.task_id,
|
|
2000
|
+
prompt: bundle.automation.prompt,
|
|
2001
|
+
model: bundle.automation.model,
|
|
2002
|
+
reasoning_effort: bundle.automation.reasoning_effort,
|
|
2003
|
+
cwd: input.cwd ?? bundle.automation.cwds?.[0],
|
|
2004
|
+
result_preview: null,
|
|
2005
|
+
full_result: null,
|
|
2006
|
+
error: null,
|
|
2007
|
+
no_op_detected: false,
|
|
2008
|
+
no_op_reason: null,
|
|
2009
|
+
notification_sent: false,
|
|
2010
|
+
created_at: triggeredAt,
|
|
2011
|
+
updated_at: triggeredAt,
|
|
2012
|
+
});
|
|
2013
|
+
writeJson(automationRunJsonPath(automationId, runId), run);
|
|
2014
|
+
updateAutomationState(automationId, {
|
|
2015
|
+
execution_status: 'running',
|
|
2016
|
+
claimed_at: claimedAt,
|
|
2017
|
+
started_at: startedAt,
|
|
2018
|
+
next_run: computeStoredAutomationNextRun(bundle.automation, startedAt ?? undefined),
|
|
2019
|
+
pending_event_count: 0,
|
|
2020
|
+
pending_event_at: null,
|
|
2021
|
+
pending_event_payload: null,
|
|
2022
|
+
});
|
|
2023
|
+
return run;
|
|
2024
|
+
}
|
|
2025
|
+
export function touchAutomationRun(automationRunId, now = new Date().toISOString()) {
|
|
2026
|
+
const located = locateAutomationRun(automationRunId);
|
|
2027
|
+
if (!located)
|
|
2028
|
+
return false;
|
|
2029
|
+
const updated = normalizeAutomationRunRecord({
|
|
2030
|
+
...located.run,
|
|
2031
|
+
updated_at: normalizeAutomationTimestamp(now, 'updated_at'),
|
|
2032
|
+
});
|
|
2033
|
+
writeJson(automationRunJsonPath(located.automationId, automationRunId), updated);
|
|
2034
|
+
return true;
|
|
2035
|
+
}
|
|
2036
|
+
export function enqueueAutomationEvent(input) {
|
|
2037
|
+
const automation = getAutomationById(input.automationId);
|
|
2038
|
+
if (!automation) {
|
|
2039
|
+
throw new AutomationInputError('missing_automation', `Automation not found: ${input.automationId}`);
|
|
2040
|
+
}
|
|
2041
|
+
let receivedAt = normalizeAutomationTimestamp(input.receivedAt ?? new Date().toISOString(), 'received_at');
|
|
2042
|
+
const latestExistingEvent = listAutomationEvents({
|
|
2043
|
+
automation_id: automation.id,
|
|
2044
|
+
limit: 10_000,
|
|
2045
|
+
}).at(-1);
|
|
2046
|
+
const latestExistingMs = Date.parse(latestExistingEvent?.received_at || '');
|
|
2047
|
+
const receivedAtMs = Date.parse(receivedAt);
|
|
2048
|
+
if (Number.isFinite(latestExistingMs) &&
|
|
2049
|
+
Number.isFinite(receivedAtMs) &&
|
|
2050
|
+
latestExistingMs >= receivedAtMs) {
|
|
2051
|
+
receivedAt = new Date(latestExistingMs + 1).toISOString();
|
|
2052
|
+
}
|
|
2053
|
+
const event = normalizeAutomationEventRecord({
|
|
2054
|
+
event_id: crypto.randomUUID(),
|
|
2055
|
+
automation_id: automation.id,
|
|
2056
|
+
status: 'queued',
|
|
2057
|
+
payload: input.payload,
|
|
2058
|
+
received_at: receivedAt,
|
|
2059
|
+
claimed_at: null,
|
|
2060
|
+
finished_at: null,
|
|
2061
|
+
run_id: null,
|
|
2062
|
+
error: null,
|
|
2063
|
+
created_at: receivedAt,
|
|
2064
|
+
updated_at: receivedAt,
|
|
2065
|
+
});
|
|
2066
|
+
writeJson(automationEventJsonPath(automation.id, event.event_id), event);
|
|
2067
|
+
const queuedEvents = listAutomationEvents({
|
|
2068
|
+
automation_id: automation.id,
|
|
2069
|
+
status: 'queued',
|
|
2070
|
+
limit: 10_000,
|
|
2071
|
+
});
|
|
2072
|
+
updateAutomationState(automation.id, {
|
|
2073
|
+
pending_event_count: queuedEvents.length,
|
|
2074
|
+
pending_event_at: queuedEvents.at(0)?.received_at ?? receivedAt,
|
|
2075
|
+
pending_event_payload: queuedEvents.at(0)?.payload ?? input.payload,
|
|
2076
|
+
});
|
|
2077
|
+
return event;
|
|
2078
|
+
}
|
|
2079
|
+
export function listAutomationEvents(input) {
|
|
2080
|
+
const automationId = assertSafePathSegment(input.automation_id, 'automation_id');
|
|
2081
|
+
const dir = automationEventsDir(automationId);
|
|
2082
|
+
if (!fs.existsSync(dir))
|
|
2083
|
+
return [];
|
|
2084
|
+
const events = [];
|
|
2085
|
+
for (const file of fs.readdirSync(dir)) {
|
|
2086
|
+
if (!file.endsWith('.json'))
|
|
2087
|
+
continue;
|
|
2088
|
+
const event = readJson(automationEventJsonPath(automationId, file.replace(/\.json$/, '')));
|
|
2089
|
+
if (!event)
|
|
2090
|
+
continue;
|
|
2091
|
+
const normalized = normalizeAutomationEventRecord(event);
|
|
2092
|
+
if (input.status && normalized.status !== input.status)
|
|
2093
|
+
continue;
|
|
2094
|
+
events.push(normalized);
|
|
2095
|
+
}
|
|
2096
|
+
const limit = typeof input.limit === 'number' && Number.isFinite(input.limit) && input.limit > 0
|
|
2097
|
+
? Math.floor(input.limit)
|
|
2098
|
+
: 100;
|
|
2099
|
+
return events
|
|
2100
|
+
.sort((left, right) => {
|
|
2101
|
+
const byReceived = left.received_at.localeCompare(right.received_at);
|
|
2102
|
+
return byReceived !== 0 ? byReceived : left.event_id.localeCompare(right.event_id);
|
|
2103
|
+
})
|
|
2104
|
+
.slice(0, limit);
|
|
2105
|
+
}
|
|
2106
|
+
export function claimNextAutomationEvent(input) {
|
|
2107
|
+
const queued = listAutomationEvents({
|
|
2108
|
+
automation_id: input.automationId,
|
|
2109
|
+
status: 'queued',
|
|
2110
|
+
limit: 1,
|
|
2111
|
+
})[0];
|
|
2112
|
+
if (!queued)
|
|
2113
|
+
return undefined;
|
|
2114
|
+
const claimedAt = normalizeAutomationTimestamp(input.claimedAt ?? new Date().toISOString(), 'claimed_at');
|
|
2115
|
+
const updated = normalizeAutomationEventRecord({
|
|
2116
|
+
...queued,
|
|
2117
|
+
status: 'running',
|
|
2118
|
+
claimed_at: claimedAt,
|
|
2119
|
+
run_id: input.runId,
|
|
2120
|
+
updated_at: claimedAt,
|
|
2121
|
+
});
|
|
2122
|
+
writeJson(automationEventJsonPath(input.automationId, queued.event_id), updated);
|
|
2123
|
+
const remainingQueued = listAutomationEvents({
|
|
2124
|
+
automation_id: input.automationId,
|
|
2125
|
+
status: 'queued',
|
|
2126
|
+
limit: 10_000,
|
|
2127
|
+
});
|
|
2128
|
+
updateAutomationState(input.automationId, {
|
|
2129
|
+
pending_event_count: remainingQueued.length,
|
|
2130
|
+
pending_event_at: remainingQueued.at(0)?.received_at ?? null,
|
|
2131
|
+
pending_event_payload: remainingQueued.at(0)?.payload ?? null,
|
|
2132
|
+
});
|
|
2133
|
+
return updated;
|
|
2134
|
+
}
|
|
2135
|
+
export function finalizeAutomationEventForRun(runId, input) {
|
|
2136
|
+
for (const automationId of listDirs(automationsDir())) {
|
|
2137
|
+
for (const event of listAutomationEvents({ automation_id: automationId, limit: 10_000 })) {
|
|
2138
|
+
if (event.run_id !== runId)
|
|
2139
|
+
continue;
|
|
2140
|
+
const finishedAt = normalizeAutomationTimestamp(input.finishedAt ?? new Date().toISOString(), 'finished_at');
|
|
2141
|
+
const updated = normalizeAutomationEventRecord({
|
|
2142
|
+
...event,
|
|
2143
|
+
status: input.status,
|
|
2144
|
+
finished_at: finishedAt,
|
|
2145
|
+
error: input.status === 'failed' ? input.error ?? 'Automation event failed' : null,
|
|
2146
|
+
updated_at: finishedAt,
|
|
2147
|
+
});
|
|
2148
|
+
writeJson(automationEventJsonPath(automationId, event.event_id), updated);
|
|
2149
|
+
return updated;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
return undefined;
|
|
2153
|
+
}
|
|
2154
|
+
export function requeueAutomationEventForRun(runId, now = new Date().toISOString()) {
|
|
2155
|
+
for (const automationId of listDirs(automationsDir())) {
|
|
2156
|
+
for (const event of listAutomationEvents({ automation_id: automationId, limit: 10_000 })) {
|
|
2157
|
+
if (event.run_id !== runId || event.status !== 'running')
|
|
2158
|
+
continue;
|
|
2159
|
+
const nowIso = normalizeAutomationTimestamp(now, 'updated_at');
|
|
2160
|
+
const updated = normalizeAutomationEventRecord({
|
|
2161
|
+
...event,
|
|
2162
|
+
status: 'queued',
|
|
2163
|
+
claimed_at: null,
|
|
2164
|
+
run_id: null,
|
|
2165
|
+
error: null,
|
|
2166
|
+
updated_at: nowIso,
|
|
2167
|
+
});
|
|
2168
|
+
writeJson(automationEventJsonPath(automationId, event.event_id), updated);
|
|
2169
|
+
return updated;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
return undefined;
|
|
2173
|
+
}
|
|
2174
|
+
export function finalizeAutomationRun(runId, input) {
|
|
2175
|
+
const located = locateAutomationRun(runId);
|
|
2176
|
+
if (!located)
|
|
2177
|
+
return undefined;
|
|
2178
|
+
const finishedAt = normalizeAutomationTimestamp(input.finished_at, 'finished_at');
|
|
2179
|
+
const updated = normalizeAutomationRunRecord({
|
|
2180
|
+
...located.run,
|
|
2181
|
+
status: input.status,
|
|
2182
|
+
finished_at: finishedAt,
|
|
2183
|
+
result_preview: input.result_preview !== undefined
|
|
2184
|
+
? buildAutomationResultPreview(input.result_preview)
|
|
2185
|
+
: located.run.result_preview,
|
|
2186
|
+
full_result: input.full_result !== undefined ? input.full_result ?? null : located.run.full_result,
|
|
2187
|
+
error: input.status === 'failed'
|
|
2188
|
+
? input.error ?? located.run.error ?? 'Automation run failed'
|
|
2189
|
+
: null,
|
|
2190
|
+
no_op_detected: input.no_op_detected ?? located.run.no_op_detected,
|
|
2191
|
+
no_op_reason: input.no_op_reason ?? located.run.no_op_reason,
|
|
2192
|
+
notification_sent: input.notification_sent ?? located.run.notification_sent,
|
|
2193
|
+
updated_at: finishedAt,
|
|
2194
|
+
});
|
|
2195
|
+
writeJson(automationRunJsonPath(located.automationId, runId), updated);
|
|
2196
|
+
const automation = getAutomationById(located.automationId);
|
|
2197
|
+
const state = getAutomationState(located.automationId);
|
|
2198
|
+
if (automation && state) {
|
|
2199
|
+
const nextRun = automation.status === 'ACTIVE'
|
|
2200
|
+
? computeStoredAutomationNextRun(automation, finishedAt)
|
|
2201
|
+
: null;
|
|
2202
|
+
updateAutomationState(located.automationId, {
|
|
2203
|
+
execution_status: 'idle',
|
|
2204
|
+
claimed_at: null,
|
|
2205
|
+
started_at: null,
|
|
2206
|
+
next_run: nextRun,
|
|
2207
|
+
last_run: finishedAt,
|
|
2208
|
+
last_result_preview: updated.result_preview,
|
|
2209
|
+
last_error: updated.error,
|
|
2210
|
+
run_count: state.run_count + 1,
|
|
2211
|
+
failure_count: state.failure_count + (updated.status === 'failed' ? 1 : 0),
|
|
2212
|
+
});
|
|
2213
|
+
if (automation.kind === 'calendar' && automation.status === 'ACTIVE' && nextRun === null) {
|
|
2214
|
+
updateAutomation(located.automationId, { status: 'PAUSED' });
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
return updated;
|
|
2218
|
+
}
|
|
2219
|
+
export function listAutomationRuns(input = {}) {
|
|
2220
|
+
const limit = typeof input.limit === 'number' && Number.isFinite(input.limit) && input.limit > 0
|
|
2221
|
+
? Math.floor(input.limit)
|
|
2222
|
+
: 100;
|
|
2223
|
+
const automationIds = typeof input.automation_id === 'string' && input.automation_id.trim()
|
|
2224
|
+
? [input.automation_id.trim()]
|
|
2225
|
+
: listDirs(automationsDir());
|
|
2226
|
+
const runs = [];
|
|
2227
|
+
for (const automationId of automationIds) {
|
|
2228
|
+
const automation = getAutomationById(automationId);
|
|
2229
|
+
if (!automation)
|
|
2230
|
+
continue;
|
|
2231
|
+
if (input.agent_id && automation.agent_id !== input.agent_id)
|
|
2232
|
+
continue;
|
|
2233
|
+
const runsDir = automationRunsDir(automationId);
|
|
2234
|
+
if (!fs.existsSync(runsDir))
|
|
2235
|
+
continue;
|
|
2236
|
+
for (const file of fs.readdirSync(runsDir)) {
|
|
2237
|
+
if (!file.endsWith('.json'))
|
|
2238
|
+
continue;
|
|
2239
|
+
if (file.endsWith('.context.json'))
|
|
2240
|
+
continue;
|
|
2241
|
+
const run = readJson(automationRunJsonPath(automationId, file.replace(/\.json$/, '')));
|
|
2242
|
+
if (!run)
|
|
2243
|
+
continue;
|
|
2244
|
+
const normalized = normalizeAutomationRunRecord(run);
|
|
2245
|
+
if (input.status && normalized.status !== input.status)
|
|
2246
|
+
continue;
|
|
2247
|
+
runs.push(normalized);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
return runs
|
|
2251
|
+
.sort((left, right) => {
|
|
2252
|
+
const leftStamp = left.started_at ?? left.triggered_at ?? left.created_at;
|
|
2253
|
+
const rightStamp = right.started_at ?? right.triggered_at ?? right.created_at;
|
|
2254
|
+
return rightStamp.localeCompare(leftStamp);
|
|
2255
|
+
})
|
|
2256
|
+
.slice(0, limit);
|
|
2257
|
+
}
|
|
2258
|
+
export function finalizeRunningAutomationRunForSession(agentId, sessionId, input = {}) {
|
|
2259
|
+
const normalizedAgentId = agentId.trim();
|
|
2260
|
+
const normalizedSessionId = sessionId.trim();
|
|
2261
|
+
if (!normalizedAgentId || !normalizedSessionId)
|
|
2262
|
+
return undefined;
|
|
2263
|
+
const runningRun = listAutomationRuns({
|
|
2264
|
+
agent_id: normalizedAgentId,
|
|
2265
|
+
status: 'running',
|
|
2266
|
+
limit: 100,
|
|
2267
|
+
}).find((run) => run.session_id === normalizedSessionId);
|
|
2268
|
+
if (!runningRun)
|
|
2269
|
+
return undefined;
|
|
2270
|
+
const actor = input.actor?.trim() || 'user';
|
|
2271
|
+
return finalizeAutomationRun(runningRun.run_id, {
|
|
2272
|
+
status: 'failed',
|
|
2273
|
+
finished_at: input.finished_at ?? new Date().toISOString(),
|
|
2274
|
+
error: input.error || `Automation run stopped by ${actor}`,
|
|
2275
|
+
notification_sent: false,
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
function automationToScheduleRecord(automation, state) {
|
|
2279
|
+
const lastStatus = state.last_error != null ? 'failed' : state.last_run ? 'succeeded' : null;
|
|
2280
|
+
return {
|
|
2281
|
+
id: automation.id,
|
|
2282
|
+
agent_id: automation.agent_id,
|
|
2283
|
+
prompt: automation.prompt,
|
|
2284
|
+
cron: automation.rrule,
|
|
2285
|
+
type: 'cron',
|
|
2286
|
+
session: automation.session_mode ?? 'isolated',
|
|
2287
|
+
status: automation.status === 'ACTIVE' ? 'active' : 'paused',
|
|
2288
|
+
target_jid: undefined,
|
|
2289
|
+
created_by: undefined,
|
|
2290
|
+
created_from: undefined,
|
|
2291
|
+
notify_policy: 'none',
|
|
2292
|
+
notify_target_jid: undefined,
|
|
2293
|
+
execution_status: state.execution_status === 'idle' ? 'idle' : 'claimed',
|
|
2294
|
+
claimed_at: state.claimed_at,
|
|
2295
|
+
delete_after_run: false,
|
|
2296
|
+
next_run: state.next_run,
|
|
2297
|
+
last_run: state.last_run,
|
|
2298
|
+
last_status: lastStatus,
|
|
2299
|
+
last_error: state.last_error,
|
|
2300
|
+
last_result_preview: state.last_result_preview,
|
|
2301
|
+
run_count: state.run_count,
|
|
2302
|
+
failure_count: state.failure_count,
|
|
2303
|
+
created_at: new Date(automation.created_at).toISOString(),
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
function automationRunToScheduleRun(run) {
|
|
2307
|
+
return {
|
|
2308
|
+
id: run.run_id,
|
|
2309
|
+
schedule_id: run.automation_id,
|
|
2310
|
+
agent_id: run.agent_id,
|
|
2311
|
+
prompt: run.prompt,
|
|
2312
|
+
status: run.status,
|
|
2313
|
+
session_id: run.session_id,
|
|
2314
|
+
task_id: run.task_id,
|
|
2315
|
+
result_preview: run.result_preview,
|
|
2316
|
+
error: run.error,
|
|
2317
|
+
started_at: run.started_at ?? run.triggered_at,
|
|
2318
|
+
finished_at: run.finished_at,
|
|
2319
|
+
created_at: run.created_at,
|
|
2320
|
+
updated_at: run.updated_at,
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
export function createScheduleRun(scheduleId, input) {
|
|
2324
|
+
const run = createAutomationRun(scheduleId, {
|
|
2325
|
+
triggered_at: input.started_at,
|
|
2326
|
+
started_at: input.started_at,
|
|
2327
|
+
session_id: input.session_id,
|
|
2328
|
+
task_id: input.task_id,
|
|
2329
|
+
});
|
|
2330
|
+
return automationRunToScheduleRun(run);
|
|
2331
|
+
}
|
|
2332
|
+
export function getScheduleRunsForSchedule(scheduleId) {
|
|
2333
|
+
return listAutomationRuns({ automation_id: scheduleId }).map(automationRunToScheduleRun).sort((a, b) => {
|
|
2334
|
+
const aStamp = a.started_at || a.created_at;
|
|
2335
|
+
const bStamp = b.started_at || b.created_at;
|
|
2336
|
+
return bStamp.localeCompare(aStamp);
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
export function listScheduleRuns(input = {}) {
|
|
2340
|
+
const runs = listAutomationRuns({
|
|
2341
|
+
agent_id: input.agent_id,
|
|
2342
|
+
automation_id: input.schedule_id,
|
|
2343
|
+
status: input.status,
|
|
2344
|
+
limit: input.limit,
|
|
2345
|
+
}).map(automationRunToScheduleRun).sort((a, b) => {
|
|
2346
|
+
const aStamp = a.started_at || a.created_at;
|
|
2347
|
+
const bStamp = b.started_at || b.created_at;
|
|
2348
|
+
return bStamp.localeCompare(aStamp);
|
|
2349
|
+
});
|
|
2350
|
+
const limit = typeof input.limit === 'number' && Number.isFinite(input.limit) && input.limit > 0
|
|
2351
|
+
? Math.floor(input.limit)
|
|
2352
|
+
: 100;
|
|
2353
|
+
return runs.slice(0, limit);
|
|
2354
|
+
}
|
|
2355
|
+
export function finalizeScheduleRunRecord(runId, input) {
|
|
2356
|
+
const run = finalizeAutomationRun(runId, {
|
|
2357
|
+
status: input.outcome,
|
|
2358
|
+
finished_at: input.finished_at,
|
|
2359
|
+
result_preview: input.outcome === 'succeeded' ? buildAutomationResultPreview(input.result_text) : null,
|
|
2360
|
+
full_result: input.outcome === 'succeeded' ? input.result_text ?? null : null,
|
|
2361
|
+
error: input.outcome === 'failed' ? input.error ?? 'Schedule run failed' : null,
|
|
2362
|
+
});
|
|
2363
|
+
return run ? automationRunToScheduleRun(run) : undefined;
|
|
2364
|
+
}
|
|
2365
|
+
export function createSchedule(input) {
|
|
2366
|
+
if (!input.agent_id?.trim()) {
|
|
2367
|
+
throw new ScheduleInputError('missing_agent_id', 'agent_id is required');
|
|
2368
|
+
}
|
|
2369
|
+
if (!input.prompt?.trim()) {
|
|
2370
|
+
throw new ScheduleInputError('missing_prompt', 'prompt is required');
|
|
2371
|
+
}
|
|
2372
|
+
const isOneShot = input.type === 'one-shot';
|
|
2373
|
+
const expression = isOneShot ? ONE_SHOT_RRULE : input.cron?.trim();
|
|
2374
|
+
if (!expression) {
|
|
2375
|
+
throw new ScheduleInputError(isOneShot ? 'missing_next_run' : 'missing_cron', isOneShot ? 'next_run is required for one-shot schedules' : 'cron is required');
|
|
2376
|
+
}
|
|
2377
|
+
const nextRun = isOneShot
|
|
2378
|
+
? normalizeAutomationTimestamp(input.next_run, 'next_run')
|
|
2379
|
+
: Object.prototype.hasOwnProperty.call(input, 'next_run') && input.next_run
|
|
2380
|
+
? normalizeAutomationTimestamp(input.next_run, 'next_run')
|
|
2381
|
+
: computeScheduleExpressionNextRunOrThrow({
|
|
2382
|
+
expression,
|
|
2383
|
+
timezone: TIMEZONE,
|
|
2384
|
+
});
|
|
2385
|
+
const automation = createAutomation({
|
|
2386
|
+
agent_id: input.agent_id,
|
|
2387
|
+
name: input.prompt.trim().slice(0, 80) || 'Automation',
|
|
2388
|
+
prompt: input.prompt,
|
|
2389
|
+
rrule: expression,
|
|
2390
|
+
timezone: TIMEZONE,
|
|
2391
|
+
status: 'ACTIVE',
|
|
2392
|
+
next_run: nextRun,
|
|
2393
|
+
session_mode: input.session ?? 'isolated',
|
|
2394
|
+
});
|
|
2395
|
+
const state = getAutomationState(automation.id);
|
|
2396
|
+
if (!state) {
|
|
2397
|
+
throw new ScheduleInputError('missing_state', 'automation state was not created');
|
|
2398
|
+
}
|
|
2399
|
+
return automationToScheduleRecord(automation, state);
|
|
2400
|
+
}
|
|
2401
|
+
export function getScheduleById(id) {
|
|
2402
|
+
const bundle = getAutomationBundle(id);
|
|
2403
|
+
if (bundle) {
|
|
2404
|
+
return bundle.automation.kind === 'calendar'
|
|
2405
|
+
? automationToScheduleRecord(bundle.automation, bundle.state)
|
|
2406
|
+
: undefined;
|
|
2407
|
+
}
|
|
2408
|
+
return undefined;
|
|
2409
|
+
}
|
|
2410
|
+
export function getAllSchedules() {
|
|
2411
|
+
const automationSchedules = listAutomations()
|
|
2412
|
+
.filter((automation) => automation.kind === 'calendar')
|
|
2413
|
+
.map((automation) => {
|
|
2414
|
+
const state = getAutomationState(automation.id);
|
|
2415
|
+
return state ? automationToScheduleRecord(automation, state) : undefined;
|
|
2416
|
+
}).filter((value) => value !== undefined);
|
|
2417
|
+
return automationSchedules.sort((a, b) => (b.created_at || '').localeCompare(a.created_at || ''));
|
|
2418
|
+
}
|
|
2419
|
+
export function getSchedulesForAgent(agentId) {
|
|
2420
|
+
const automationSchedules = listAutomations(agentId)
|
|
2421
|
+
.filter((automation) => automation.kind === 'calendar')
|
|
2422
|
+
.map((automation) => {
|
|
2423
|
+
const state = getAutomationState(automation.id);
|
|
2424
|
+
return state ? automationToScheduleRecord(automation, state) : undefined;
|
|
2425
|
+
}).filter((value) => value !== undefined);
|
|
2426
|
+
return automationSchedules.sort((a, b) => (b.created_at || '').localeCompare(a.created_at || ''));
|
|
2427
|
+
}
|
|
2428
|
+
export function getDueSchedules(forceAll = false) {
|
|
2429
|
+
const now = new Date().toISOString();
|
|
2430
|
+
return getAllSchedules().filter((schedule) => schedule.status === 'active' &&
|
|
2431
|
+
schedule.execution_status !== 'claimed' &&
|
|
2432
|
+
(forceAll || (schedule.next_run != null && schedule.next_run <= now)));
|
|
2433
|
+
}
|
|
2434
|
+
export function claimDueSchedules(nowIso = new Date().toISOString(), forceAll = false) {
|
|
2435
|
+
const claimed = [];
|
|
2436
|
+
recoverStaleAutomationExecutions(nowIso);
|
|
2437
|
+
for (const automation of listAutomations()) {
|
|
2438
|
+
if (automation.kind !== 'calendar')
|
|
2439
|
+
continue;
|
|
2440
|
+
const state = getAutomationState(automation.id);
|
|
2441
|
+
if (!state)
|
|
2442
|
+
continue;
|
|
2443
|
+
if (automation.status !== 'ACTIVE')
|
|
2444
|
+
continue;
|
|
2445
|
+
if (state.execution_status !== 'idle')
|
|
2446
|
+
continue;
|
|
2447
|
+
if (!forceAll && (!state.next_run || state.next_run > nowIso))
|
|
2448
|
+
continue;
|
|
2449
|
+
const updatedState = updateAutomationState(automation.id, {
|
|
2450
|
+
execution_status: 'claimed',
|
|
2451
|
+
claimed_at: nowIso,
|
|
2452
|
+
});
|
|
2453
|
+
if (updatedState) {
|
|
2454
|
+
claimed.push(automationToScheduleRecord(automation, updatedState));
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
return claimed;
|
|
2458
|
+
}
|
|
2459
|
+
export function recoverStaleAutomationExecutions(nowIso = new Date().toISOString()) {
|
|
2460
|
+
const now = new Date(nowIso).getTime();
|
|
2461
|
+
if (Number.isNaN(now))
|
|
2462
|
+
return 0;
|
|
2463
|
+
let recovered = 0;
|
|
2464
|
+
for (const automation of listAutomations()) {
|
|
2465
|
+
const state = getAutomationState(automation.id);
|
|
2466
|
+
if (!state || (state.execution_status !== 'running' && state.execution_status !== 'claimed'))
|
|
2467
|
+
continue;
|
|
2468
|
+
const runningRun = listAutomationRuns({
|
|
2469
|
+
automation_id: automation.id,
|
|
2470
|
+
status: 'running',
|
|
2471
|
+
limit: 1,
|
|
2472
|
+
})[0];
|
|
2473
|
+
const marker = state.execution_status === 'running'
|
|
2474
|
+
? (runningRun?.updated_at ?? state.started_at)
|
|
2475
|
+
: state.claimed_at;
|
|
2476
|
+
if (!isStaleAutomationExecution(state.execution_status, marker, now)) {
|
|
2477
|
+
continue;
|
|
2478
|
+
}
|
|
2479
|
+
const staleRun = runningRun;
|
|
2480
|
+
const nowIsoString = new Date(now).toISOString();
|
|
2481
|
+
const timeoutError = `${automation.id}: automation execution state "${state.execution_status}" stale for at least ${AUTOMATION_STALE_EXECUTION_MS / 1000}s`;
|
|
2482
|
+
const nextRun = computeStoredAutomationNextRun(automation, nowIsoString);
|
|
2483
|
+
if (staleRun) {
|
|
2484
|
+
const finalized = finalizeAutomationRun(staleRun.run_id, {
|
|
2485
|
+
status: 'failed',
|
|
2486
|
+
finished_at: nowIsoString,
|
|
2487
|
+
error: timeoutError,
|
|
2488
|
+
});
|
|
2489
|
+
finalizeAutomationEventForRun(staleRun.run_id, {
|
|
2490
|
+
status: 'failed',
|
|
2491
|
+
finishedAt: nowIsoString,
|
|
2492
|
+
error: timeoutError,
|
|
2493
|
+
});
|
|
2494
|
+
if (!finalized) {
|
|
2495
|
+
logger.warn({ automation_id: automation.id, run_id: staleRun.run_id }, 'Failed to finalize stale automation run, falling back to state reset');
|
|
2496
|
+
updateAutomationState(automation.id, {
|
|
2497
|
+
execution_status: 'idle',
|
|
2498
|
+
claimed_at: null,
|
|
2499
|
+
started_at: null,
|
|
2500
|
+
next_run: nextRun,
|
|
2501
|
+
last_error: timeoutError,
|
|
2502
|
+
failure_count: state.failure_count + 1,
|
|
2503
|
+
last_run: nowIsoString,
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
else {
|
|
2508
|
+
updateAutomationState(automation.id, {
|
|
2509
|
+
execution_status: 'idle',
|
|
2510
|
+
claimed_at: null,
|
|
2511
|
+
started_at: null,
|
|
2512
|
+
next_run: nextRun,
|
|
2513
|
+
last_error: timeoutError,
|
|
2514
|
+
failure_count: state.failure_count + 1,
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2517
|
+
const recoveredState = getAutomationState(automation.id);
|
|
2518
|
+
if (!recoveredState || recoveredState.execution_status !== 'idle') {
|
|
2519
|
+
logger.warn({ automation_id: automation.id, execution_status: state.execution_status }, 'Failed to recover stale automation execution status');
|
|
2520
|
+
continue;
|
|
2521
|
+
}
|
|
2522
|
+
recovered += 1;
|
|
2523
|
+
logger.info({ automation_id: automation.id, execution_status: state.execution_status }, 'Recovered stale automation execution state');
|
|
2524
|
+
}
|
|
2525
|
+
return recovered;
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* Reset automation executions that were in-flight when the daemon process
|
|
2529
|
+
* restarted. The stream owner lived in the old process, so these runs cannot
|
|
2530
|
+
* complete normally even when they have not reached the normal stale timeout.
|
|
2531
|
+
*/
|
|
2532
|
+
export function cleanupStaleAutomationExecutions(nowIso = new Date().toISOString()) {
|
|
2533
|
+
const now = new Date(nowIso).getTime();
|
|
2534
|
+
if (Number.isNaN(now))
|
|
2535
|
+
return 0;
|
|
2536
|
+
let cleaned = 0;
|
|
2537
|
+
for (const automation of listAutomations()) {
|
|
2538
|
+
const state = getAutomationState(automation.id);
|
|
2539
|
+
if (!state || (state.execution_status !== 'running' && state.execution_status !== 'claimed')) {
|
|
2540
|
+
continue;
|
|
2541
|
+
}
|
|
2542
|
+
const orphanedRun = listAutomationRuns({
|
|
2543
|
+
automation_id: automation.id,
|
|
2544
|
+
status: 'running',
|
|
2545
|
+
limit: 1,
|
|
2546
|
+
})[0];
|
|
2547
|
+
const nowIsoString = new Date(now).toISOString();
|
|
2548
|
+
const error = `${automation.id}: automation execution state "${state.execution_status}" was interrupted by daemon restart`;
|
|
2549
|
+
const nextRun = computeStoredAutomationNextRun(automation, nowIsoString);
|
|
2550
|
+
if (orphanedRun) {
|
|
2551
|
+
requeueAutomationEventForRun(orphanedRun.run_id, nowIsoString);
|
|
2552
|
+
finalizeAutomationRun(orphanedRun.run_id, {
|
|
2553
|
+
status: 'failed',
|
|
2554
|
+
finished_at: nowIsoString,
|
|
2555
|
+
error,
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
else {
|
|
2559
|
+
updateAutomationState(automation.id, {
|
|
2560
|
+
execution_status: 'idle',
|
|
2561
|
+
claimed_at: null,
|
|
2562
|
+
started_at: null,
|
|
2563
|
+
next_run: nextRun,
|
|
2564
|
+
last_error: error,
|
|
2565
|
+
failure_count: state.failure_count + 1,
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
const cleanedState = getAutomationState(automation.id);
|
|
2569
|
+
if (!cleanedState || cleanedState.execution_status !== 'idle') {
|
|
2570
|
+
logger.warn({ automation_id: automation.id, execution_status: state.execution_status }, 'Failed to clean up interrupted automation execution');
|
|
2571
|
+
continue;
|
|
2572
|
+
}
|
|
2573
|
+
cleaned += 1;
|
|
2574
|
+
logger.info({ automation_id: automation.id, execution_status: state.execution_status }, 'Reset interrupted automation execution to idle');
|
|
2575
|
+
}
|
|
2576
|
+
return cleaned;
|
|
2577
|
+
}
|
|
2578
|
+
export function updateSchedule(id, updates) {
|
|
2579
|
+
if (Object.prototype.hasOwnProperty.call(updates, 'prompt') &&
|
|
2580
|
+
typeof updates.prompt === 'string' &&
|
|
2581
|
+
!updates.prompt.trim()) {
|
|
2582
|
+
throw new ScheduleInputError('missing_prompt', 'prompt is required');
|
|
2583
|
+
}
|
|
2584
|
+
if (Object.prototype.hasOwnProperty.call(updates, 'run_count') &&
|
|
2585
|
+
typeof updates.run_count === 'number') {
|
|
2586
|
+
validateAutomationCounter('run_count', updates.run_count);
|
|
2587
|
+
}
|
|
2588
|
+
if (Object.prototype.hasOwnProperty.call(updates, 'failure_count') &&
|
|
2589
|
+
typeof updates.failure_count === 'number') {
|
|
2590
|
+
validateAutomationCounter('failure_count', updates.failure_count);
|
|
2591
|
+
}
|
|
2592
|
+
const bundle = getAutomationBundle(id);
|
|
2593
|
+
if (!bundle)
|
|
2594
|
+
return undefined;
|
|
2595
|
+
const existingProjection = automationToScheduleRecord(bundle.automation, bundle.state);
|
|
2596
|
+
const nextType = updates.type ?? existingProjection.type ?? 'cron';
|
|
2597
|
+
const nextStatus = updates.status ?? existingProjection.status;
|
|
2598
|
+
const nextExpression = nextType === 'one-shot'
|
|
2599
|
+
? ONE_SHOT_RRULE
|
|
2600
|
+
: updates.cron?.trim() || bundle.automation.rrule;
|
|
2601
|
+
const hasExplicitNextRun = Object.prototype.hasOwnProperty.call(updates, 'next_run');
|
|
2602
|
+
const hasTimingUpdates = updates.cron !== undefined ||
|
|
2603
|
+
updates.type !== undefined ||
|
|
2604
|
+
hasExplicitNextRun ||
|
|
2605
|
+
(updates.status === 'active' && existingProjection.next_run === null);
|
|
2606
|
+
if (updates.type === 'one-shot' &&
|
|
2607
|
+
existingProjection.type !== 'one-shot' &&
|
|
2608
|
+
!hasExplicitNextRun) {
|
|
2609
|
+
throw new ScheduleInputError('missing_next_run', 'next_run is required when converting a schedule to one-shot');
|
|
2610
|
+
}
|
|
2611
|
+
let derivedNextRun = bundle.state.next_run;
|
|
2612
|
+
if (hasTimingUpdates) {
|
|
2613
|
+
if (hasExplicitNextRun) {
|
|
2614
|
+
derivedNextRun = updates.next_run ?? null;
|
|
2615
|
+
}
|
|
2616
|
+
else if (nextType === 'one-shot') {
|
|
2617
|
+
derivedNextRun = bundle.state.next_run;
|
|
2618
|
+
}
|
|
2619
|
+
else if (nextStatus === 'paused') {
|
|
2620
|
+
derivedNextRun = bundle.state.next_run;
|
|
2621
|
+
}
|
|
2622
|
+
else {
|
|
2623
|
+
derivedNextRun = computeScheduleExpressionNextRunOrThrow({
|
|
2624
|
+
expression: nextExpression,
|
|
2625
|
+
timezone: bundle.automation.timezone,
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
const automation = updateAutomation(id, {
|
|
2630
|
+
...(updates.prompt !== undefined ? { prompt: updates.prompt } : {}),
|
|
2631
|
+
rrule: nextExpression,
|
|
2632
|
+
...(updates.status !== undefined
|
|
2633
|
+
? { status: updates.status === 'active' ? 'ACTIVE' : 'PAUSED' }
|
|
2634
|
+
: {}),
|
|
2635
|
+
next_run: derivedNextRun,
|
|
2636
|
+
...(updates.session !== undefined ? { session_mode: updates.session } : {}),
|
|
2637
|
+
});
|
|
2638
|
+
if (!automation)
|
|
2639
|
+
return undefined;
|
|
2640
|
+
const state = updateAutomationState(id, {
|
|
2641
|
+
...(updates.execution_status !== undefined
|
|
2642
|
+
? {
|
|
2643
|
+
execution_status: updates.execution_status === 'claimed' ? 'claimed' : 'idle',
|
|
2644
|
+
}
|
|
2645
|
+
: {}),
|
|
2646
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'claimed_at')
|
|
2647
|
+
? { claimed_at: updates.claimed_at ?? null }
|
|
2648
|
+
: {}),
|
|
2649
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'last_run')
|
|
2650
|
+
? { last_run: updates.last_run ?? null }
|
|
2651
|
+
: {}),
|
|
2652
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'last_error')
|
|
2653
|
+
? { last_error: updates.last_error ?? null }
|
|
2654
|
+
: {}),
|
|
2655
|
+
...(Object.prototype.hasOwnProperty.call(updates, 'last_result_preview')
|
|
2656
|
+
? { last_result_preview: updates.last_result_preview ?? null }
|
|
2657
|
+
: {}),
|
|
2658
|
+
...(updates.run_count !== undefined ? { run_count: updates.run_count } : {}),
|
|
2659
|
+
...(updates.failure_count !== undefined
|
|
2660
|
+
? { failure_count: updates.failure_count }
|
|
2661
|
+
: {}),
|
|
2662
|
+
});
|
|
2663
|
+
return state ? automationToScheduleRecord(automation, state) : undefined;
|
|
2664
|
+
}
|
|
2665
|
+
export function updateScheduleAfterRun(id, nextRun) {
|
|
2666
|
+
updateAutomationState(id, {
|
|
2667
|
+
execution_status: 'idle',
|
|
2668
|
+
claimed_at: null,
|
|
2669
|
+
started_at: null,
|
|
2670
|
+
next_run: nextRun,
|
|
2671
|
+
last_run: new Date().toISOString(),
|
|
2672
|
+
});
|
|
2673
|
+
if (nextRun === null) {
|
|
2674
|
+
updateAutomation(id, { status: 'PAUSED' });
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
export function finalizeScheduleRun(id, input) {
|
|
2678
|
+
if (input.run_id) {
|
|
2679
|
+
finalizeAutomationRun(input.run_id, {
|
|
2680
|
+
status: input.outcome,
|
|
2681
|
+
finished_at: input.finished_at,
|
|
2682
|
+
result_preview: input.outcome === 'succeeded' ? buildAutomationResultPreview(input.result_text) : null,
|
|
2683
|
+
full_result: input.outcome === 'succeeded' ? input.result_text ?? null : null,
|
|
2684
|
+
error: input.outcome === 'failed' ? input.error ?? 'Schedule run failed' : null,
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2687
|
+
const bundle = getAutomationBundle(id);
|
|
2688
|
+
if (!bundle)
|
|
2689
|
+
return undefined;
|
|
2690
|
+
if (!input.run_id) {
|
|
2691
|
+
const nextRun = bundle.automation.status === 'ACTIVE'
|
|
2692
|
+
? computeStoredAutomationNextRun(bundle.automation, input.finished_at)
|
|
2693
|
+
: null;
|
|
2694
|
+
const updatedState = updateAutomationState(id, {
|
|
2695
|
+
execution_status: 'idle',
|
|
2696
|
+
claimed_at: null,
|
|
2697
|
+
started_at: null,
|
|
2698
|
+
next_run: nextRun,
|
|
2699
|
+
last_run: input.finished_at,
|
|
2700
|
+
last_result_preview: input.outcome === 'succeeded'
|
|
2701
|
+
? buildAutomationResultPreview(input.result_text)
|
|
2702
|
+
: null,
|
|
2703
|
+
last_error: input.outcome === 'failed' ? input.error ?? 'Schedule run failed' : null,
|
|
2704
|
+
run_count: bundle.state.run_count + 1,
|
|
2705
|
+
failure_count: bundle.state.failure_count + (input.outcome === 'failed' ? 1 : 0),
|
|
2706
|
+
});
|
|
2707
|
+
if (bundle.automation.kind === 'calendar' &&
|
|
2708
|
+
bundle.automation.status === 'ACTIVE' &&
|
|
2709
|
+
nextRun === null) {
|
|
2710
|
+
updateAutomation(id, { status: 'PAUSED' });
|
|
2711
|
+
}
|
|
2712
|
+
const refreshed = getAutomationBundle(id);
|
|
2713
|
+
if (refreshed && updatedState) {
|
|
2714
|
+
return automationToScheduleRecord(refreshed.automation, refreshed.state);
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
const refreshed = getAutomationBundle(id);
|
|
2718
|
+
return refreshed
|
|
2719
|
+
? automationToScheduleRecord(refreshed.automation, refreshed.state)
|
|
2720
|
+
: undefined;
|
|
2721
|
+
}
|
|
2722
|
+
export function deleteSchedule(id) {
|
|
2723
|
+
if (getAutomationById(id)) {
|
|
2724
|
+
deleteAutomation(id);
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2728
|
+
// Router state (simple JSON key-value file)
|
|
2729
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2730
|
+
function loadRouterState() {
|
|
2731
|
+
return readJson(getRouterStatePath()) || {};
|
|
2732
|
+
}
|
|
2733
|
+
function saveRouterState(state) {
|
|
2734
|
+
writeJson(getRouterStatePath(), state);
|
|
2735
|
+
}
|
|
2736
|
+
export function getRouterState(key) {
|
|
2737
|
+
return loadRouterState()[key];
|
|
2738
|
+
}
|
|
2739
|
+
export function setRouterState(key, value) {
|
|
2740
|
+
const state = loadRouterState();
|
|
2741
|
+
state[key] = value;
|
|
2742
|
+
saveRouterState(state);
|
|
2743
|
+
}
|
|
2744
|
+
export function getAllRouterState() {
|
|
2745
|
+
return loadRouterState();
|
|
2746
|
+
}
|
|
2747
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2748
|
+
// Registered groups/projects (JSON file)
|
|
2749
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2750
|
+
function loadRegisteredGroups() {
|
|
2751
|
+
return (readJson(getRegisteredGroupsPath()) || {});
|
|
2752
|
+
}
|
|
2753
|
+
function saveRegisteredGroups(groups) {
|
|
2754
|
+
writeJson(getRegisteredGroupsPath(), groups);
|
|
2755
|
+
}
|
|
2756
|
+
export function getRegisteredProject(jid) {
|
|
2757
|
+
const groups = loadRegisteredGroups();
|
|
2758
|
+
const group = groups[jid];
|
|
2759
|
+
if (!group)
|
|
2760
|
+
return undefined;
|
|
2761
|
+
if (!isValidGroupFolder(group.folder)) {
|
|
2762
|
+
logger.warn({ jid, folder: group.folder }, 'Skipping registered group with invalid folder');
|
|
2763
|
+
return undefined;
|
|
2764
|
+
}
|
|
2765
|
+
return {
|
|
2766
|
+
jid,
|
|
2767
|
+
name: group.name,
|
|
2768
|
+
folder: group.folder,
|
|
2769
|
+
agent_id: group.agent_id || group.folder,
|
|
2770
|
+
trigger: group.trigger,
|
|
2771
|
+
added_at: group.added_at,
|
|
2772
|
+
requiresTrigger: group.requiresTrigger,
|
|
2773
|
+
isMain: group.isMain,
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
export function setRegisteredProject(jid, group) {
|
|
2777
|
+
if (!isValidGroupFolder(group.folder)) {
|
|
2778
|
+
throw new Error(`Invalid group folder "${group.folder}" for JID ${jid}`);
|
|
2779
|
+
}
|
|
2780
|
+
const agentId = group.agent_id || group.folder;
|
|
2781
|
+
ensureAgent({ agent_id: agentId, name: group.name });
|
|
2782
|
+
const groups = loadRegisteredGroups();
|
|
2783
|
+
groups[jid] = {
|
|
2784
|
+
...group,
|
|
2785
|
+
agent_id: agentId,
|
|
2786
|
+
};
|
|
2787
|
+
saveRegisteredGroups(groups);
|
|
2788
|
+
}
|
|
2789
|
+
export function getAllRegisteredProjects() {
|
|
2790
|
+
const groups = loadRegisteredGroups();
|
|
2791
|
+
const result = {};
|
|
2792
|
+
for (const [jid, group] of Object.entries(groups)) {
|
|
2793
|
+
if (!isValidGroupFolder(group.folder)) {
|
|
2794
|
+
logger.warn({ jid, folder: group.folder }, 'Skipping registered group with invalid folder');
|
|
2795
|
+
continue;
|
|
2796
|
+
}
|
|
2797
|
+
result[jid] = {
|
|
2798
|
+
name: group.name,
|
|
2799
|
+
folder: group.folder,
|
|
2800
|
+
agent_id: group.agent_id || group.folder,
|
|
2801
|
+
trigger: group.trigger,
|
|
2802
|
+
added_at: group.added_at,
|
|
2803
|
+
requiresTrigger: group.requiresTrigger,
|
|
2804
|
+
isMain: group.isMain,
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
return result;
|
|
2808
|
+
}
|
|
2809
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2810
|
+
// Chat metadata (lightweight — stored in session.json)
|
|
2811
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2812
|
+
export function storeChatMetadata(chatJid, timestamp, name, channel, _isGroup) {
|
|
2813
|
+
// For the filesystem approach, chat metadata is implicitly part of
|
|
2814
|
+
// the session. We ensure the session exists.
|
|
2815
|
+
const resolved = resolveFromChatJid(chatJid);
|
|
2816
|
+
if (!resolved)
|
|
2817
|
+
return;
|
|
2818
|
+
ensureSession({
|
|
2819
|
+
agent_id: resolved.agentId,
|
|
2820
|
+
session_id: resolved.sessionId,
|
|
2821
|
+
channel: channel || 'unknown',
|
|
2822
|
+
source_ref: chatJid,
|
|
2823
|
+
agent_name: name,
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2826
|
+
export function updateChatName(chatJid, name) {
|
|
2827
|
+
// Chat metadata is stored in session.json — update the agent name
|
|
2828
|
+
const resolved = resolveFromChatJid(chatJid);
|
|
2829
|
+
if (!resolved)
|
|
2830
|
+
return;
|
|
2831
|
+
const jsonPath = sessionJsonPath(resolved.agentId, resolved.sessionId);
|
|
2832
|
+
const session = readJson(jsonPath);
|
|
2833
|
+
if (session) {
|
|
2834
|
+
session.name = name;
|
|
2835
|
+
session.space_id = session.space_id || DEFAULT_SPACE_ID;
|
|
2836
|
+
session.updated_at = new Date().toISOString();
|
|
2837
|
+
writeJson(jsonPath, session);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
export function getAllChats() {
|
|
2841
|
+
// Reconstruct from sessions
|
|
2842
|
+
const chats = [];
|
|
2843
|
+
for (const agentId of listDirs(AGENTS_DIR)) {
|
|
2844
|
+
const sessions = getSessionsForAgent(agentId);
|
|
2845
|
+
for (const session of sessions) {
|
|
2846
|
+
chats.push({
|
|
2847
|
+
jid: session.source_ref || `web:${agentId}:${session.session_id}`,
|
|
2848
|
+
name: agentId,
|
|
2849
|
+
last_message_time: session.updated_at,
|
|
2850
|
+
channel: session.channel,
|
|
2851
|
+
is_group: 0,
|
|
2852
|
+
});
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
return chats.sort((a, b) => b.last_message_time.localeCompare(a.last_message_time));
|
|
2856
|
+
}
|
|
2857
|
+
export function getLastGroupSync() {
|
|
2858
|
+
return getRouterState('__group_sync__') || null;
|
|
2859
|
+
}
|
|
2860
|
+
export function setLastGroupSync() {
|
|
2861
|
+
setRouterState('__group_sync__', new Date().toISOString());
|
|
2862
|
+
}
|
|
2863
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2864
|
+
// Interaction events (JSONL per session)
|
|
2865
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2866
|
+
export function storeInteractionEvent(event) {
|
|
2867
|
+
const resolved = resolveFromChatJid(event.chat_jid);
|
|
2868
|
+
if (!resolved)
|
|
2869
|
+
return;
|
|
2870
|
+
const eventsPath = path.join(sessionDir(resolved.agentId, resolved.sessionId), 'interaction-events.jsonl');
|
|
2871
|
+
appendJsonl(eventsPath, event);
|
|
2872
|
+
}
|
|
2873
|
+
export function getRecentInteractionEvents(chatJid, limit = 20) {
|
|
2874
|
+
const resolved = resolveFromChatJid(chatJid);
|
|
2875
|
+
if (!resolved)
|
|
2876
|
+
return [];
|
|
2877
|
+
const eventsPath = path.join(resolveSessionDir(resolved.agentId, resolved.sessionId), 'interaction-events.jsonl');
|
|
2878
|
+
return readJsonlTail(eventsPath, limit);
|
|
2879
|
+
}
|
|
2880
|
+
export function getSessionInteractionEvents(agentId, sessionId, limit = 20) {
|
|
2881
|
+
const eventsPath = path.join(resolveSessionDir(agentId, sessionId), 'interaction-events.jsonl');
|
|
2882
|
+
return limit > 0
|
|
2883
|
+
? readJsonlTail(eventsPath, limit)
|
|
2884
|
+
: readJsonl(eventsPath);
|
|
2885
|
+
}
|
|
2886
|
+
export function storeSessionUiEvent(agentId, sessionId, event) {
|
|
2887
|
+
const taskEventPath = taskUiEventsPath(sessionId, event.task_id);
|
|
2888
|
+
if (taskEventPath) {
|
|
2889
|
+
writeTaskMetadata(agentId, sessionId, event.task_id);
|
|
2890
|
+
appendJsonl(taskEventPath, event);
|
|
2891
|
+
touchSessionActivity(agentId, sessionId, event.timestamp);
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2894
|
+
const eventsPath = path.join(sessionDir(agentId, sessionId), 'events.jsonl');
|
|
2895
|
+
appendJsonl(eventsPath, event);
|
|
2896
|
+
touchSessionActivity(agentId, sessionId, event.timestamp);
|
|
2897
|
+
}
|
|
2898
|
+
export function getSessionUiEvents(agentId, sessionId, limit = 200) {
|
|
2899
|
+
const boundedLimit = limit > 0 ? limit : SESSION_UI_EVENT_RECOVERY_LIMIT;
|
|
2900
|
+
const baseDir = resolveSessionDir(agentId, sessionId);
|
|
2901
|
+
const eventsPath = path.join(baseDir, 'events.jsonl');
|
|
2902
|
+
const taskEvents = listTaskUiEventsForSession(agentId, sessionId, boundedLimit);
|
|
2903
|
+
if (!fs.existsSync(eventsPath) && taskEvents.length > 0) {
|
|
2904
|
+
return taskEvents
|
|
2905
|
+
.sort((a, b) => String(a.timestamp || '').localeCompare(String(b.timestamp || '')))
|
|
2906
|
+
.slice(-boundedLimit);
|
|
2907
|
+
}
|
|
2908
|
+
const sessionEvents = readJsonlTail(eventsPath, boundedLimit);
|
|
2909
|
+
return [...sessionEvents, ...taskEvents]
|
|
2910
|
+
.sort((a, b) => String(a.timestamp || '').localeCompare(String(b.timestamp || '')))
|
|
2911
|
+
.slice(-boundedLimit);
|
|
2912
|
+
}
|
|
2913
|
+
export function getSessionUiEventsForTaskIds(agentId, sessionId, taskIds) {
|
|
2914
|
+
const wanted = new Set(Array.from(taskIds)
|
|
2915
|
+
.map((taskId) => String(taskId || '').trim())
|
|
2916
|
+
.filter(Boolean));
|
|
2917
|
+
if (wanted.size === 0)
|
|
2918
|
+
return [];
|
|
2919
|
+
const taskScopedEvents = [];
|
|
2920
|
+
const missingTaskIds = new Set();
|
|
2921
|
+
for (const taskId of wanted) {
|
|
2922
|
+
const taskEventsPathValue = taskUiEventsPath(sessionId, taskId);
|
|
2923
|
+
if (taskEventsPathValue && fs.existsSync(taskEventsPathValue)) {
|
|
2924
|
+
const taskEvents = readJsonlTail(taskEventsPathValue, TASK_UI_EVENT_HISTORY_LIMIT);
|
|
2925
|
+
taskScopedEvents.push(...taskEvents);
|
|
2926
|
+
}
|
|
2927
|
+
else {
|
|
2928
|
+
missingTaskIds.add(taskId);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
if (missingTaskIds.size === 0) {
|
|
2932
|
+
return taskScopedEvents.sort((a, b) => String(a.timestamp || '').localeCompare(String(b.timestamp || '')));
|
|
2933
|
+
}
|
|
2934
|
+
const eventsPath = path.join(resolveSessionDir(agentId, sessionId), 'events.jsonl');
|
|
2935
|
+
const tasksWithStart = new Set();
|
|
2936
|
+
const sessionEvents = readJsonlTailMatchingUntil(eventsPath, (event) => typeof event.task_id === 'string' && missingTaskIds.has(event.task_id), (event) => {
|
|
2937
|
+
if (event.chunk?.type !== 'start' || typeof event.task_id !== 'string') {
|
|
2938
|
+
return false;
|
|
2939
|
+
}
|
|
2940
|
+
tasksWithStart.add(event.task_id);
|
|
2941
|
+
return tasksWithStart.size >= missingTaskIds.size;
|
|
2942
|
+
});
|
|
2943
|
+
return [...taskScopedEvents, ...sessionEvents].sort((a, b) => String(a.timestamp || '').localeCompare(String(b.timestamp || '')));
|
|
2944
|
+
}
|
|
2945
|
+
// Test-only exports for deterministic helper validation.
|
|
2946
|
+
export const __testOnly = {
|
|
2947
|
+
readJsonlTail,
|
|
2948
|
+
};
|
|
2949
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2950
|
+
// Mind state — REMOVED
|
|
2951
|
+
// These functions are kept as no-ops / stubs for compilation.
|
|
2952
|
+
// The mind state is now defined by Markdown files (SOUL.md, MEMORY.md).
|
|
2953
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2954
|
+
// Intentionally not exported. Mind code should be refactored
|
|
2955
|
+
// to read/write Markdown files directly.
|
|
2956
|
+
//# sourceMappingURL=store.js.map
|