@xopcai/xopc 0.0.112 → 0.0.114
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/dist/browser-ext/icons/icon-128.png +0 -0
- package/dist/browser-ext/icons/icon-16.png +0 -0
- package/dist/browser-ext/icons/icon-32.png +0 -0
- package/dist/browser-ext/icons/icon-48.png +0 -0
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/delivery-to.d.ts +3 -3
- package/dist/extensions/weixin/src/delivery-to.js.map +1 -1
- package/dist/gateway/static/root/apple-touch-icon.png +0 -0
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-EhRp-ZMa.js → agent-avatar-dicebear-Fp-p7bP1.js} +1 -1
- package/dist/gateway/static/root/assets/agent-browser-settings-page-CWh7trjd.js +6 -0
- package/dist/gateway/static/root/assets/agent-defaults-tabbed-page-kC8OuLoj.js +1 -0
- package/dist/gateway/static/root/assets/agents-DJWG26Si.js +71 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-BIy7KEsM.js → app-management-settings-panel-v24i25Oj.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-DGQG8NwA.js → appearance-settings-ycbdJRuZ.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-0V3DXZ7J.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-uuQPupqx.js → archive-plugin-CJzSADJo.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-DBkv0cAv.js → attachment-load-BTAB4zGS.js} +1 -1
- package/dist/gateway/static/root/assets/{binary-plugins-DxxN_VAR.js → binary-plugins-DXR1-mct.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-CwnUlRmO.js → block-editor-CzQri9h2.js} +44 -44
- package/dist/gateway/static/root/assets/channels-settings-CIKOkYdO.js +1 -0
- package/dist/gateway/static/root/assets/{config-api-BD-NcD5d.js → config-api-DRqjEvAx.js} +1 -1
- package/dist/gateway/static/root/assets/connectors-settings-ClrDS5jH.js +2 -0
- package/dist/gateway/static/root/assets/{cron-api-C5DoipZA.js → cron-api-BXaZXv1o.js} +1 -1
- package/dist/gateway/static/root/assets/cron-page-Chi_4u4P.js +3 -0
- package/dist/gateway/static/root/assets/dreaming-settings-B0_rs2tj.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-BDHA9o_e.js → extension-debug-page-DSkqMYo7.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DmDLVSPE.js → extension-page-h2dGDbMN.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-1124aHw_.js → extension-settings-page-hN99RhOi.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-DOe56Wmy.js → fetch-D9QqUtzZ.js} +1 -1
- package/dist/gateway/static/root/assets/{format-interval-label-tsMSvBSR.js → format-interval-label-D6E687rG.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-CsD4FC9D.js → gateway-config-api-CLt_SKTC.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-HQ8FBhY2.js → gateway-settings-1PDi4lI0.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-startup-retry-C1zg7pH3.js → gateway-startup-retry-N7Z_sz7p.js} +1 -1
- package/dist/gateway/static/root/assets/goal-detail-page-D_wwXL-t.js +1 -0
- package/dist/gateway/static/root/assets/goals-page-DZ2toZ8y.js +2 -0
- package/dist/gateway/static/root/assets/{goals-settings-B5nI6lgz.js → goals-settings-LFlwtIKE.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-CIv42nJD.js → heartbeat-config-api-DGPadTw-.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-DFcVuJ3M.js → heartbeat-settings-BHHAs8ZX.js} +1 -1
- package/dist/gateway/static/root/assets/index-DefIdW9q.js +122 -0
- package/dist/gateway/static/root/assets/index-DzmWBkq-.css +1 -0
- package/dist/gateway/static/root/assets/{interval-presets-DGP3S7OB.js → interval-presets-CEklPQ_r.js} +1 -1
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-fETaDYQP.js → keyboard-shortcuts-settings-Ce_aQ8Iv.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-j_D2QTuQ.js +2 -0
- package/dist/gateway/static/root/assets/{markdown-split-CgeBJMOD.js → markdown-split-BMz3xPXO.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-C30GzlV2.js → media-plugins-BalOSoz9.js} +1 -1
- package/dist/gateway/static/root/assets/messages-BN_WiUg0.js +3 -0
- package/dist/gateway/static/root/assets/models-hub-panel-CMH_TmZz.js +2 -0
- package/dist/gateway/static/root/assets/note-detail-page-O18BrQPq.js +2 -0
- package/dist/gateway/static/root/assets/{note-image-lightbox-t1g0qZYh.js → note-image-lightbox-C5D1Q5mY.js} +1 -1
- package/dist/gateway/static/root/assets/notes-page-B_ZYCmY9.js +1 -0
- package/dist/gateway/static/root/assets/{preview-open-alternatives-DeWp9x5g.js → preview-open-alternatives-CYFR5U9l.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-CwKgWixY.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-BOaAOJI3.js → save-bar-controls-BQ79Z5IP.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-BLWLKqgw.js → schedule-field-Bgk6Emue.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-plqRFYT8.js → schema-form-DzS36TMa.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-C4fcD-7B.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-Ad2OJ86w.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-BP5zRrDI.js → settings-collapsible-section-CdjFnSAy.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-Bv8MZplG.js → settings-form-section-DZLy_exu.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-D2BMY5wo.js +1 -0
- package/dist/gateway/static/root/assets/setup-status-panel-aWwxl_E1.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-4OX1YCgp.js → share-preview-page-_qRDr4g_.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-DUE83uIf.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-mWKJcrog.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-0Sx1Q58n.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-CGy4f_s2.js → system-settings-panel-hqaBF5aG.js} +1 -1
- package/dist/gateway/static/root/assets/use-agent-defaults-form-zbVIQLOA.js +1 -0
- package/dist/gateway/static/root/assets/{use-channel-catalog-CjGHnDvN.js → use-channel-catalog-CbXq97rT.js} +1 -1
- package/dist/gateway/static/root/assets/{use-settings-tab-guard-BMMFa0oA.js → use-settings-tab-guard-DwS3drWK.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-D6HR0hR2.js +1 -0
- package/dist/gateway/static/root/assets/{voice-player-store-D5vzWwls.js → voice-player-store-DSuOd9zy.js} +1 -1
- package/dist/gateway/static/root/assets/workflow-page.utils-C0Uq3xyq.js +1 -0
- package/dist/gateway/static/root/assets/workflows-page-DW0fmWhQ.js +18 -0
- package/dist/gateway/static/root/favicon-16x16.png +0 -0
- package/dist/gateway/static/root/favicon-32x32.png +0 -0
- package/dist/gateway/static/root/favicon-icon.svg +1 -0
- package/dist/gateway/static/root/favicon.ico +0 -0
- package/dist/gateway/static/root/favicon.png +0 -0
- package/dist/gateway/static/root/favicon.svg +24 -17
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/gateway/static/root/logo-dark.svg +23 -15
- package/dist/gateway/static/root/logo.svg +23 -16
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.d.ts +8 -0
- package/dist/src/agent/agent-manager.js +57 -22
- package/dist/src/agent/agent-manager.js.map +1 -1
- package/dist/src/agent/bootstrap/project-agents-context.d.ts +2 -0
- package/dist/src/agent/bootstrap/project-agents-context.js +32 -0
- package/dist/src/agent/bootstrap/project-agents-context.js.map +1 -0
- package/dist/src/agent/goals/persistent-goal-service.js +2 -4
- package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
- package/dist/src/agent/inbound/inbound-loop.js +5 -1
- package/dist/src/agent/inbound/inbound-loop.js.map +1 -1
- package/dist/src/agent/inbound/turn-dispatcher.d.ts +2 -2
- package/dist/src/agent/inbound/turn-dispatcher.js +2 -2
- package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-materialize.d.ts +27 -0
- package/dist/src/agent/mcp/bundle-mcp-materialize.js +49 -1
- package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-runtime.js +78 -3
- package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-types.d.ts +19 -0
- package/dist/src/agent/service/btw-query.js +11 -3
- package/dist/src/agent/service/btw-query.js.map +1 -1
- package/dist/src/agent/service/parse-outbound-session-key.js +13 -8
- package/dist/src/agent/service/parse-outbound-session-key.js.map +1 -1
- package/dist/src/agent/service/process-direct-one-shot.d.ts +2 -2
- package/dist/src/agent/service/process-direct-one-shot.js +1 -1
- package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.d.ts +2 -2
- package/dist/src/agent/service/process-direct-streaming.js +10 -5
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.d.ts +1 -1
- package/dist/src/agent/service.js +9 -5
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/service.types.d.ts +5 -0
- package/dist/src/agent/skills/filesystem-watcher.js +4 -3
- package/dist/src/agent/skills/filesystem-watcher.js.map +1 -1
- package/dist/src/agent/skills/index.js +2 -1
- package/dist/src/agent/skills/index.js.map +1 -1
- package/dist/src/agent/skills/skill-manage-ops.js +2 -1
- package/dist/src/agent/skills/skill-manage-ops.js.map +1 -1
- package/dist/src/agent/skills/types.d.ts +1 -1
- package/dist/src/agent/skills/watcher.js +2 -5
- package/dist/src/agent/skills/watcher.js.map +1 -1
- package/dist/src/agent/skills/workspace-skills-dir.d.ts +1 -0
- package/dist/src/agent/skills/workspace-skills-dir.js +11 -0
- package/dist/src/agent/skills/workspace-skills-dir.js.map +1 -0
- package/dist/src/agent/tool-manuals/browser.d.ts +1 -1
- package/dist/src/agent/tool-manuals/browser.js +62 -3
- package/dist/src/agent/tool-manuals/browser.js.map +1 -1
- package/dist/src/agent/tools/browser/tool/browser-use-tool.d.ts +4 -1
- package/dist/src/agent/tools/browser/tool/browser-use-tool.js +13 -4
- package/dist/src/agent/tools/browser/tool/browser-use-tool.js.map +1 -1
- package/dist/src/agent/tools/browser/tool/schemas.d.ts +0 -2
- package/dist/src/agent/tools/browser/tool/schemas.js +1 -2
- package/dist/src/agent/tools/browser/tool/schemas.js.map +1 -1
- package/dist/src/agent/tools/factory.js +1 -10
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/skill-manage-tool.js +1 -1
- package/dist/src/agent/tools/skill-manage-tool.js.map +1 -1
- package/dist/src/agent/workflow/subagent-runner.js +18 -1
- package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
- package/dist/src/ai-assist/text-assist.d.ts +3 -0
- package/dist/src/ai-assist/text-assist.js +25 -7
- package/dist/src/ai-assist/text-assist.js.map +1 -1
- package/dist/src/browser/actions/extension-registry-bridge.js +91 -7
- package/dist/src/browser/actions/extension-registry-bridge.js.map +1 -1
- package/dist/src/browser/actions/registry.js +383 -7
- package/dist/src/browser/actions/registry.js.map +1 -1
- package/dist/src/browser/actions/types.d.ts +11 -3
- package/dist/src/browser/index.js +2 -2
- package/dist/src/browser/pipeline/output.d.ts +18 -16
- package/dist/src/browser/pipeline/output.js +28 -20
- package/dist/src/browser/pipeline/output.js.map +1 -1
- package/dist/src/browser/pipeline/runner.d.ts +7 -14
- package/dist/src/browser/pipeline/runner.js +349 -102
- package/dist/src/browser/pipeline/runner.js.map +1 -1
- package/dist/src/browser/pipeline/schema.d.ts +1 -0
- package/dist/src/browser/pipeline/schema.js +35 -58
- package/dist/src/browser/pipeline/schema.js.map +1 -1
- package/dist/src/browser/pipeline/source.d.ts +2 -0
- package/dist/src/browser/pipeline/source.js +10 -1
- package/dist/src/browser/pipeline/source.js.map +1 -1
- package/dist/src/browser/pipeline/template.d.ts +10 -2
- package/dist/src/browser/pipeline/template.js +50 -8
- package/dist/src/browser/pipeline/template.js.map +1 -1
- package/dist/src/channels/plugins/types.adapters.d.ts +2 -2
- package/dist/src/chat-commands/builtins/config.js +3 -5
- package/dist/src/chat-commands/builtins/config.js.map +1 -1
- package/dist/src/chat-commands/context.d.ts +5 -0
- package/dist/src/chat-commands/context.js +18 -9
- package/dist/src/chat-commands/context.js.map +1 -1
- package/dist/src/chat-commands/index.d.ts +1 -1
- package/dist/src/chat-commands/index.js +2 -2
- package/dist/src/chat-commands/index.js.map +1 -1
- package/dist/src/chat-commands/processor.js +22 -21
- package/dist/src/chat-commands/processor.js.map +1 -1
- package/dist/src/chat-commands/session-key.d.ts +3 -15
- package/dist/src/chat-commands/session-key.js +3 -49
- package/dist/src/chat-commands/session-key.js.map +1 -1
- package/dist/src/chat-commands/types.d.ts +4 -0
- package/dist/src/cli/commands/browser-cli-helpers.js +11 -5
- package/dist/src/cli/commands/browser-cli-helpers.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/doctor/checks/session-integrity.js +8 -8
- package/dist/src/cli/commands/doctor/checks/session-integrity.js.map +1 -1
- package/dist/src/config/rules.js +5 -0
- package/dist/src/config/rules.js.map +1 -1
- package/dist/src/connectors/builtin-catalog.js +180 -1
- package/dist/src/connectors/builtin-catalog.js.map +1 -1
- package/dist/src/connectors/health.js +18 -9
- package/dist/src/connectors/health.js.map +1 -1
- package/dist/src/connectors/types.d.ts +19 -0
- package/dist/src/connectors/usage.js +12 -4
- package/dist/src/connectors/usage.js.map +1 -1
- package/dist/src/cron/executor.js.map +1 -1
- package/dist/src/cron/types.d.ts +2 -2
- package/dist/src/extensions/types/tui.d.ts +2 -0
- package/dist/src/gateway/heartbeat/service.js +13 -1
- package/dist/src/gateway/heartbeat/service.js.map +1 -1
- package/dist/src/gateway/hono/app.js +13 -4
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/oauth-async.js +1 -1
- package/dist/src/gateway/hono/routes/agent-stream.js +6 -6
- package/dist/src/gateway/hono/routes/agent-stream.js.map +1 -1
- package/dist/src/gateway/hono/routes/config-patch/gateway.d.ts +1 -1
- package/dist/src/gateway/hono/routes/config-patch/gateway.js +6 -0
- package/dist/src/gateway/hono/routes/config-patch/gateway.js.map +1 -1
- package/dist/src/gateway/hono/routes/goals.js +16 -3
- package/dist/src/gateway/hono/routes/goals.js.map +1 -1
- package/dist/src/gateway/hono/routes/logs.js +1 -0
- package/dist/src/gateway/hono/routes/logs.js.map +1 -1
- package/dist/src/gateway/hono/routes/mcp.js +27 -8
- package/dist/src/gateway/hono/routes/mcp.js.map +1 -1
- package/dist/src/gateway/hono/routes/notes.js +14 -2
- package/dist/src/gateway/hono/routes/notes.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +62 -4
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/hono/routes/workspace.js +3 -66
- package/dist/src/gateway/hono/routes/workspace.js.map +1 -1
- package/dist/src/gateway/hono/sse.d.ts +2 -2
- package/dist/src/gateway/hono/sse.js +7 -15
- package/dist/src/gateway/hono/sse.js.map +1 -1
- package/dist/src/gateway/resolve-webchat-session-key.d.ts +2 -6
- package/dist/src/gateway/resolve-webchat-session-key.js +5 -31
- package/dist/src/gateway/resolve-webchat-session-key.js.map +1 -1
- package/dist/src/gateway/service/agent-runner.d.ts +2 -3
- package/dist/src/gateway/service/agent-runner.js +9 -22
- package/dist/src/gateway/service/agent-runner.js.map +1 -1
- package/dist/src/gateway/service/run-gateway-agent.js +9 -7
- package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
- package/dist/src/gateway/service/sessions-api.d.ts +9 -0
- package/dist/src/gateway/service/sessions-api.js +11 -0
- package/dist/src/gateway/service/sessions-api.js.map +1 -1
- package/dist/src/gateway/service.d.ts +1 -1
- package/dist/src/gateway/service.js +19 -5
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/workspace-file-search.d.ts +9 -0
- package/dist/src/gateway/workspace-file-search.js +76 -0
- package/dist/src/gateway/workspace-file-search.js.map +1 -0
- package/dist/src/goals/goal-notifications.d.ts +2 -0
- package/dist/src/goals/goal-notifications.js +16 -11
- package/dist/src/goals/goal-notifications.js.map +1 -1
- package/dist/src/goals/goal-service.js +1 -6
- package/dist/src/goals/goal-service.js.map +1 -1
- package/dist/src/project-trust/trust-store.js +2 -0
- package/dist/src/project-trust/trust-store.js.map +1 -1
- package/dist/src/session/index.d.ts +1 -1
- package/dist/src/session/index.js +2 -2
- package/dist/src/session/init-session-turn.js +6 -9
- package/dist/src/session/init-session-turn.js.map +1 -1
- package/dist/src/session/manager.d.ts +5 -1
- package/dist/src/session/manager.js +5 -2
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/reset-type.d.ts +0 -2
- package/dist/src/session/reset-type.js +3 -9
- package/dist/src/session/reset-type.js.map +1 -1
- package/dist/src/session/resolve-session.js +9 -20
- package/dist/src/session/resolve-session.js.map +1 -1
- package/dist/src/session/store-reader.d.ts +5 -0
- package/dist/src/session/store-reader.js +1 -0
- package/dist/src/session/store.d.ts +11 -3
- package/dist/src/session/store.js +29 -13
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/session/types.d.ts +2 -4
- package/dist/src/session/types.js.map +1 -1
- package/dist/src/storage/sqlite/index.d.ts +2 -2
- package/dist/src/storage/sqlite/index.js +3 -3
- package/dist/src/storage/sqlite/migrations/010_session_id_identity.sql +30 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +2 -2
- package/dist/src/storage/sqlite/migrations/runner.js +4 -4
- package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/runner.ts +2 -2
- package/dist/src/storage/sqlite/row-mappers.d.ts +4 -4
- package/dist/src/storage/sqlite/row-mappers.js +11 -14
- package/dist/src/storage/sqlite/row-mappers.js.map +1 -1
- package/dist/src/storage/sqlite/schema.js +3 -3
- package/dist/src/storage/sqlite/schema.js.map +1 -1
- package/dist/src/storage/sqlite/schema.sql +37 -10
- package/dist/src/storage/sqlite/session-metadata.d.ts +2 -7
- package/dist/src/storage/sqlite/session-metadata.js +18 -72
- package/dist/src/storage/sqlite/session-metadata.js.map +1 -1
- package/dist/src/storage/sqlite/session-repository.d.ts +6 -5
- package/dist/src/storage/sqlite/session-repository.js +34 -40
- package/dist/src/storage/sqlite/session-repository.js.map +1 -1
- package/dist/src/storage/sqlite/transcript-repository.d.ts +2 -2
- package/dist/src/storage/sqlite/transcript-repository.js +52 -52
- package/dist/src/storage/sqlite/transcript-repository.js.map +1 -1
- package/dist/src/tui/backends/embedded-backend.d.ts +11 -4
- package/dist/src/tui/backends/embedded-backend.js +27 -1
- package/dist/src/tui/backends/embedded-backend.js.map +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.d.ts +4 -1
- package/dist/src/tui/backends/gateway-sse-backend.js +21 -2
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
- package/dist/src/tui/extension-host/autocomplete.js +13 -10
- package/dist/src/tui/extension-host/autocomplete.js.map +1 -1
- package/dist/src/tui/extension-host/runtime.d.ts +7 -0
- package/dist/src/tui/extension-host/runtime.js +6 -4
- package/dist/src/tui/extension-host/runtime.js.map +1 -1
- package/dist/src/tui/tui-backend.d.ts +9 -0
- package/dist/src/tui/tui-commands.js +85 -6
- package/dist/src/tui/tui-commands.js.map +1 -1
- package/dist/src/tui/tui-file-autocomplete.d.ts +5 -0
- package/dist/src/tui/tui-file-autocomplete.js +26 -0
- package/dist/src/tui/tui-file-autocomplete.js.map +1 -0
- package/dist/src/tui/tui-picker-overlay.js +1 -1
- package/dist/src/tui/tui.js +29 -11
- package/dist/src/tui/tui.js.map +1 -1
- package/dist/src/utils/logger/context.d.ts +2 -2
- package/dist/src/utils/logger/context.js +2 -1
- package/dist/src/utils/logger/context.js.map +1 -1
- package/dist/src/utils/logger/log-store.js +1 -0
- package/dist/src/utils/logger/log-store.js.map +1 -1
- package/dist/src/utils/logger/pino-record.js +3 -0
- package/dist/src/utils/logger/pino-record.js.map +1 -1
- package/dist/src/utils/logger/types.d.ts +5 -0
- package/dist/src/utils/logger/types.js.map +1 -1
- package/dist/src/workflows/service/workflow-session-bridge.js +13 -1
- package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agent-browser-settings-page-H4foVCiS.js +0 -6
- package/dist/gateway/static/root/assets/agent-defaults-tabbed-page-DDNw2oNU.js +0 -1
- package/dist/gateway/static/root/assets/agents-E_X2kzS1.js +0 -71
- package/dist/gateway/static/root/assets/apps-page-BZLvInLr.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-BHvUKlzw.js +0 -1
- package/dist/gateway/static/root/assets/connectors-settings-VBzXcEXL.js +0 -2
- package/dist/gateway/static/root/assets/cron-page-BLTpdm34.js +0 -3
- package/dist/gateway/static/root/assets/dreaming-settings-DSVhXTPq.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-BG3Onph0.js +0 -1
- package/dist/gateway/static/root/assets/goals-page-DgeKn8zL.js +0 -2
- package/dist/gateway/static/root/assets/index-5A_ZaABy.css +0 -1
- package/dist/gateway/static/root/assets/index-BTmbp2AU.js +0 -121
- package/dist/gateway/static/root/assets/logs-page-B1-b27lK.js +0 -2
- package/dist/gateway/static/root/assets/messages-BUUfPaZ9.js +0 -3
- package/dist/gateway/static/root/assets/models-hub-panel-DPcTS1-u.js +0 -2
- package/dist/gateway/static/root/assets/note-detail-page-xeGb4owe.js +0 -2
- package/dist/gateway/static/root/assets/notes-page-C_TdnKBe.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-BtkPmJIN.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-DOUjxzJm.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-qK_bnTrw.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-DmDfE1ww.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-BEFwNcIZ.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-CbKmJZot.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-YdtnUHtK.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-DwW1IzQ0.js +0 -2
- package/dist/gateway/static/root/assets/use-agent-defaults-form-BMuMvzl_.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-DnRbhMNq.js +0 -1
- package/dist/gateway/static/root/assets/workflow-page.utils-tjGpRVnw.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-DqqHKGj2.js +0 -18
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","names":[],"sources":["../../../src/cron/executor.ts"],"sourcesContent":["// Cron job executor with timeout, retry logic and agent integration\nimport type { WorkflowRunView } from '../workflows/domain/index.js';\nimport type {\n CronRunOutcome,\n CronWorkflowRunStarter,\n HeartbeatWakeSink,\n JobData,\n JobExecution,\n JobExecutor,\n JobExecutorDeps,\n} from './types.js';\nimport type { OutboundMessage } from '../channels/transport-types.js';\nimport { createLogger } from '../utils/logger.js';\nimport {\n getChannelPlugin,\n} from '../channels/plugins/registry.js';\nimport { getCronPayloadText } from './job-content.js';\nimport type { SessionStore } from '../session/store.js';\nimport type { CronRunLogStore } from './run-log-store.js';\nimport {\n DEFAULT_ACK_MAX_CHARS,\n NO_REPLY,\n shouldSilence,\n stripHeartbeatToken,\n} from '../heartbeat/tokens.js';\nimport { DEFAULT_AGENT_ID, normalizeAgentId } from '../agent/agent-scope.js';\nimport { buildSessionKey } from '../routing/session-key.js';\nimport {\n buildWorkflowRunCronSummary,\n buildWorkflowRunDeliveryText,\n isWorkflowRunCronSuccess,\n resolveWorkflowCronWaitMs,\n waitForWorkflowRunView,\n} from './workflow-run-completion.js';\n\nconst log = createLogger('CronExecutor');\n\n// Error backoff schedule in ms\nconst ERROR_BACKOFF_MS = [\n 30_000, // 1st error → 30s\n 60_000, // 2nd error → 1min\n 5 * 60_000, // 3rd error → 5min\n 15 * 60_000, // 4th error → 15min\n 60 * 60_000, // 5th+ error → 60min\n];\n\nfunction errorBackoffMs(consecutiveErrors: number): number {\n const idx = Math.min(consecutiveErrors - 1, ERROR_BACKOFF_MS.length - 1);\n return ERROR_BACKOFF_MS[Math.max(0, idx)];\n}\n\nfunction resolveIsolatedCronJobModel(job: JobData): string | undefined {\n if (job.payload?.kind === 'agentTurn') {\n const m = job.payload.model?.trim();\n if (m) return m;\n }\n return undefined;\n}\n\nasync function postCronWebhook(url: string, payload: unknown): Promise<void> {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`Webhook delivery failed: HTTP ${res.status}${text ? ` ${text.slice(0, 200)}` : ''}`);\n }\n}\n\nexport class DefaultJobExecutor implements JobExecutor {\n private history: Map<string, JobExecution[]> = new Map();\n private runningJobs = new Map<string, AbortController>();\n private agentService: any = null;\n private messageBus: any = null;\n private heartbeatService: HeartbeatWakeSink | null = null;\n private sessionStore: SessionStore | undefined;\n private runLogStore: CronRunLogStore | null = null;\n private getDefaultCronAgentId: (() => string) | null = null;\n private workflowRunService: CronWorkflowRunStarter | null = null;\n private goalRunner: JobExecutorDeps['goalRunner'] | null = null;\n\n setRunLogStore(store: CronRunLogStore | null): void {\n this.runLogStore = store;\n }\n\n setDeps(deps: JobExecutorDeps): void {\n this.agentService = deps.agentService;\n this.messageBus = deps.messageBus;\n this.heartbeatService = deps.heartbeatService ?? null;\n if (deps.sessionStore !== undefined) {\n this.sessionStore = deps.sessionStore;\n }\n this.getDefaultCronAgentId = deps.getDefaultCronAgentId ?? null;\n this.workflowRunService = deps.workflowRunService ?? null;\n this.goalRunner = deps.goalRunner ?? null;\n }\n\n private async buildCronOutboundMessage(\n channel: string,\n to: string,\n content: string,\n ): Promise<OutboundMessage> {\n const plugin = getChannelPlugin(channel);\n if (plugin?.cronDelivery) {\n const { chatId, accountId, metadata } = await plugin.cronDelivery.normalizeDeliveryTarget(\n to,\n this.sessionStore,\n );\n const meta =\n accountId || metadata\n ? { ...(metadata ?? {}), ...(accountId ? { accountId } : {}) }\n : undefined;\n return {\n channel,\n chat_id: chatId,\n content,\n type: 'message',\n ...(meta && Object.keys(meta).length > 0 ? { metadata: meta } : {}),\n };\n }\n return {\n channel,\n chat_id: to,\n content,\n type: 'message',\n };\n }\n\n async execute(job: JobData, signal: AbortSignal, deps?: JobExecutorDeps): Promise<void> {\n // Set deps if provided\n if (deps) {\n this.setDeps(deps);\n }\n\n const executionId = crypto.randomUUID();\n const execution: JobExecution = {\n id: executionId,\n jobId: job.id,\n status: 'running',\n startedAt: new Date().toISOString(),\n retryCount: 0,\n sessionId: job.sessionKey,\n sessionKey: job.sessionKey,\n sessionType: job.sessionKey ? 'cron' : undefined,\n };\n\n // Record execution start\n this.addToHistory(job.id, execution);\n if (this.runLogStore) {\n await this.runLogStore.upsert(execution);\n }\n this.runningJobs.set(job.id, new AbortController());\n\n log.info(\n { jobId: job.id, executionId, preview: getCronPayloadText(job).slice(0, 100) },\n 'Job executing'\n );\n\n let result: CronRunOutcome;\n\n try {\n // Check for cancellation\n if (signal.aborted) {\n throw new Error('Job was cancelled before execution');\n }\n\n // Execute the job\n result = await this.performJob(job, signal);\n\n // Mark as success/failed\n execution.status = result.status === 'ok' ? 'success' : result.status === 'skipped' ? 'cancelled' : 'failed';\n execution.endedAt = new Date().toISOString();\n execution.duration = Date.now() - new Date(execution.startedAt).getTime();\n execution.summary = result.summary;\n execution.error = result.error;\n execution.sessionId = result.sessionId;\n execution.sessionKey = result.sessionKey;\n execution.sessionType = result.sessionType;\n execution.model = result.model;\n execution.workflowRunId = result.workflowRunId;\n\n if (result.status === 'ok') {\n log.info(\n { jobId: job.id, executionId, duration: execution.duration },\n 'Job completed'\n );\n } else if (result.status === 'skipped') {\n log.warn({ jobId: job.id, executionId, reason: result.error }, 'Job skipped');\n } else {\n log.error(\n { jobId: job.id, executionId, errorMessage: result.error, phase: 'cron.execute' },\n `Job failed: ${result.error ?? 'unknown'}`,\n );\n }\n\n if (result.status === 'ok' && job.wakeMode === 'next-heartbeat' && this.heartbeatService) {\n try {\n this.heartbeatService.requestNow({ reason: `cron:${job.id}` });\n } catch (e) {\n log.warn({ jobId: job.id, err: e }, 'Heartbeat wake after cron failed');\n }\n }\n } catch (error) {\n execution.status = 'failed';\n execution.endedAt = new Date().toISOString();\n execution.duration = Date.now() - new Date(execution.startedAt).getTime();\n execution.error = error instanceof Error ? error.message : String(error);\n\n log.error(\n {\n err: error,\n errorMessage: execution.error,\n jobId: job.id,\n executionId,\n phase: 'cron.execute',\n },\n `Job execution error: ${execution.error}`,\n );\n\n result = {\n status: 'error',\n error: execution.error,\n };\n } finally {\n this.runningJobs.delete(job.id);\n }\n\n if (this.runLogStore) {\n await this.runLogStore.upsert(execution);\n }\n\n return;\n }\n\n /**\n * Perform the actual job work - integrate with AgentService\n */\n protected async performJob(job: JobData, signal: AbortSignal): Promise<CronRunOutcome> {\n const timeout =\n job.payload.kind === 'agentTurn' && job.payload.timeoutSeconds\n ? job.payload.timeoutSeconds * 1000\n : 180_000;\n const sessionTarget = job.sessionTarget;\n\n // Check for abort before starting\n if (signal.aborted) {\n return { status: 'skipped', error: 'Job was aborted before execution' };\n }\n\n try {\n if (job.payload.kind === 'goalContinue') {\n return await this.executeGoalContinue(job);\n }\n\n if (job.payload.kind === 'workflowRun') {\n return await this.executeWorkflowRun(job, signal);\n }\n\n // If no agent service, fall back to basic execution\n if (!this.agentService || !this.messageBus) {\n log.warn({ jobId: job.id }, 'No agent service configured, using basic execution');\n return this.basicExecute(job, signal, timeout);\n }\n\n if (sessionTarget === 'main') {\n return await this.executeMainSession(job, signal, timeout);\n } else {\n return await this.executeIsolated(job, signal, timeout);\n }\n } catch (error) {\n return {\n status: 'error',\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n private async executeGoalContinue(job: JobData): Promise<CronRunOutcome> {\n if (job.payload.kind !== 'goalContinue') {\n return { status: 'error', error: 'Cron job payload is not goalContinue' };\n }\n if (!this.goalRunner) {\n return { status: 'error', error: 'Goal runner is not configured for cron' };\n }\n const item = this.goalRunner.enqueue(job.payload.goalId, {\n message: job.payload.message,\n maxRetries: job.payload.maxRetries ?? 0,\n source: 'cron',\n });\n return {\n status: 'ok',\n summary: `Queued goal ${job.payload.goalId} (${item.status})`,\n sessionId: item.sessionKey,\n sessionKey: item.sessionKey,\n sessionType: 'goal',\n };\n }\n\n private async executeWorkflowRun(job: JobData, signal: AbortSignal): Promise<CronRunOutcome> {\n if (signal.aborted) {\n return { status: 'skipped', error: 'Job was aborted before workflow run start' };\n }\n\n if (job.payload.kind !== 'workflowRun') {\n return { status: 'error', error: 'Cron job payload is not workflowRun' };\n }\n\n if (!this.workflowRunService) {\n return { status: 'error', error: 'Workflow run service is not configured for cron' };\n }\n\n const fallbackAgentId = this.getDefaultCronAgentId?.() ?? DEFAULT_AGENT_ID;\n const agentId = normalizeAgentId(job.payload.agentId || job.agentId || fallbackAgentId);\n const fireId = job.payload.source?.fireId || crypto.randomUUID();\n const source = {\n kind: 'cron' as const,\n scheduleId: job.payload.source?.scheduleId || job.id,\n fireId,\n scheduledAtMs: job.payload.source?.scheduledAtMs ?? Date.now(),\n };\n const result = await this.workflowRunService.startWorkflowRun({\n agentId,\n definitionId: job.payload.definitionId,\n input: job.payload.input,\n inputEnvelope: job.payload.inputEnvelope,\n goal: job.payload.goal,\n source,\n idempotencyKey: `cron:${job.id}:${fireId}`,\n });\n\n if (result.ok === false) {\n return {\n status: 'error',\n error: result.message,\n };\n }\n\n log.info(\n {\n jobId: job.id,\n workflowRunId: result.runId,\n definitionId: job.payload.definitionId,\n sessionKey: result.sessionKey,\n },\n 'Workflow run started from cron',\n );\n\n const waitForCompletion = job.payload.waitForCompletion !== false;\n if (!waitForCompletion || !this.workflowRunService.readWorkflowRunView) {\n return {\n status: 'ok',\n summary: `Started workflow run ${result.runId}`,\n sessionId: result.sessionKey,\n sessionKey: result.sessionKey,\n sessionType: 'workflow',\n workflowRunId: result.runId,\n };\n }\n\n return this.waitForWorkflowRunOutcome({\n job,\n agentId,\n sessionKey: result.sessionKey,\n initialRunId: result.runId,\n signal,\n });\n }\n\n private async waitForWorkflowRunOutcome(params: {\n job: JobData;\n agentId: string;\n sessionKey: string;\n initialRunId: string;\n signal: AbortSignal;\n }): Promise<CronRunOutcome> {\n const waitMs = resolveWorkflowCronWaitMs(180_000);\n const maxWorkflowRetries =\n params.job.payload.kind === 'workflowRun'\n ? Math.max(0, Math.floor(params.job.payload.maxRetries ?? 0))\n : 0;\n let runId = params.initialRunId;\n let attempt = 0;\n\n while (attempt <= maxWorkflowRetries) {\n const waitResult = await waitForWorkflowRunView({\n readView: (id) => this.workflowRunService!.readWorkflowRunView!(params.agentId, id),\n runId,\n signal: params.signal,\n timeoutMs: waitMs,\n });\n\n if (waitResult.kind === 'aborted') {\n return { status: 'skipped', error: 'Job was aborted while waiting for workflow run' };\n }\n\n if (waitResult.kind === 'timeout') {\n return {\n status: 'error',\n error: `Workflow run ${runId} did not finish within ${waitMs}ms`,\n summary: waitResult.lastView ? buildWorkflowRunCronSummary(waitResult.lastView) : undefined,\n sessionId: params.sessionKey,\n sessionKey: params.sessionKey,\n sessionType: 'workflow',\n workflowRunId: runId,\n };\n }\n\n const view = waitResult.view;\n const summary = buildWorkflowRunCronSummary(view);\n const succeeded = isWorkflowRunCronSuccess(view);\n\n if (succeeded) {\n const baseOutcome: CronRunOutcome = {\n status: 'ok',\n summary,\n sessionId: params.sessionKey,\n sessionKey: params.sessionKey,\n sessionType: 'workflow',\n workflowRunId: runId,\n };\n await this.deliverWorkflowRunOutcome(params.job, view, baseOutcome);\n return baseOutcome;\n }\n\n const canRetry =\n attempt < maxWorkflowRetries &&\n view.run.status === 'failed' &&\n Boolean(this.workflowRunService?.retryWorkflowRun);\n\n if (!canRetry) {\n return {\n status: 'error',\n summary,\n error: summary,\n sessionId: params.sessionKey,\n sessionKey: params.sessionKey,\n sessionType: 'workflow',\n workflowRunId: runId,\n };\n }\n\n const retryResult = await this.workflowRunService!.retryWorkflowRun!({\n agentId: params.agentId,\n runId,\n });\n if (retryResult.ok === false) {\n return {\n status: 'error',\n error: retryResult.message,\n summary,\n sessionId: params.sessionKey,\n sessionKey: params.sessionKey,\n sessionType: 'workflow',\n workflowRunId: runId,\n };\n }\n\n runId = retryResult.runId;\n attempt += 1;\n log.info(\n { jobId: params.job.id, attempt, workflowRunId: runId },\n 'Retrying failed workflow run from cron',\n );\n }\n\n return {\n status: 'error',\n error: 'Workflow run retries exhausted',\n sessionId: params.sessionKey,\n sessionKey: params.sessionKey,\n sessionType: 'workflow',\n workflowRunId: runId,\n };\n }\n\n private async deliverWorkflowRunOutcome(\n job: JobData,\n view: WorkflowRunView,\n outcome: CronRunOutcome,\n ): Promise<void> {\n const delivery = job.delivery;\n if (!delivery || delivery.mode === 'none' || delivery.channel === 'local' || !delivery.to?.trim()) {\n return;\n }\n\n try {\n const text = buildWorkflowRunDeliveryText(view);\n if (delivery.mode === 'webhook') {\n await postCronWebhook(delivery.to, {\n jobId: job.id,\n jobName: job.name,\n payloadKind: job.payload.kind,\n workflowRunId: view.run.id,\n status: view.run.status,\n text,\n run: view.run,\n });\n log.info({ jobId: job.id, workflowRunId: view.run.id }, 'Delivered workflow run webhook from cron');\n return;\n }\n if (!this.messageBus || !delivery.channel) return;\n const outbound = await this.buildCronOutboundMessage(delivery.channel!, delivery.to!, text);\n await this.messageBus.publishOutbound(outbound);\n log.info(\n { jobId: job.id, channel: delivery.channel, to: outbound.chat_id, workflowRunId: view.run.id },\n 'Delivered workflow run result from cron',\n );\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ jobId: job.id, err, workflowRunId: view.run.id }, `Workflow cron delivery failed: ${em}`);\n if (outcome.status === 'ok') {\n outcome.summary = `${outcome.summary ?? ''} (delivery failed: ${em})`.trim();\n }\n }\n }\n\n /**\n * Execute in main session - sends system event\n */\n private async executeMainSession(\n job: JobData,\n signal: AbortSignal,\n timeout: number\n ): Promise<CronRunOutcome> {\n const text = getCronPayloadText(job);\n\n if (!text || !text.trim()) {\n return { status: 'skipped', error: 'Main session job requires non-empty message' };\n }\n\n const delivery = job.delivery;\n const channel = delivery?.channel ?? 'local';\n const to = delivery?.to ?? '';\n const actualMessage = text;\n\n // Create timeout promise\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(`Job timed out after ${timeout}ms`)), timeout);\n });\n\n // Create execution promise\n const executePromise = (async () => {\n // Check for abort\n if (signal.aborted) {\n throw new Error('Job was aborted');\n }\n\n if (!delivery || delivery.mode === 'none' || channel === 'local') {\n log.info(\n { jobId: job.id, messageLength: actualMessage.length },\n 'Cron main session: no outbound publish'\n );\n return {\n status: 'ok' as const,\n summary: actualMessage.slice(0, 200),\n };\n }\n\n if (delivery.mode === 'webhook') {\n if (!to.trim()) {\n throw new Error('Webhook delivery requires delivery.to');\n }\n await postCronWebhook(to, {\n jobId: job.id,\n jobName: job.name,\n payloadKind: job.payload.kind,\n text: actualMessage,\n });\n return {\n status: 'ok' as const,\n summary: actualMessage.slice(0, 200),\n };\n }\n\n if (!this.messageBus) {\n return { status: 'error' as const, error: 'MessageBus not available' };\n }\n\n const outbound = await this.buildCronOutboundMessage(channel, to, actualMessage);\n\n await this.messageBus.publishOutbound(outbound);\n\n log.info(\n { jobId: job.id, channel, to: outbound.chat_id, messageLength: actualMessage.length },\n 'Sent message to main session'\n );\n\n return {\n status: 'ok' as const,\n summary: actualMessage.slice(0, 200),\n };\n })();\n\n // Race against timeout\n return await Promise.race([executePromise, timeoutPromise]);\n }\n\n /**\n * Execute in isolated mode - runs agent independently\n */\n private async executeIsolated(\n job: JobData,\n signal: AbortSignal,\n timeout: number\n ): Promise<CronRunOutcome> {\n const message = getCronPayloadText(job);\n\n if (!message || !message.trim()) {\n return { status: 'skipped', error: 'Isolated job requires non-empty message' };\n }\n\n const explicitSessionKey =\n job.sessionTarget.startsWith('session:')\n ? job.sessionTarget.slice('session:'.length).trim()\n : job.sessionTarget === 'current'\n ? job.sessionKey?.trim()\n : job.sessionTarget === 'isolated'\n ? job.sessionKey?.trim()\n : undefined;\n const aid = job.agentId?.trim();\n const fallbackAgentId = this.getDefaultCronAgentId?.() ?? DEFAULT_AGENT_ID;\n const sessionKey = explicitSessionKey || buildSessionKey({\n agentId: normalizeAgentId(aid || fallbackAgentId),\n source: 'cron',\n accountId: 'default',\n peerKind: 'dm',\n peerId: `${job.id}-${crypto.randomUUID().slice(0, 8)}`,\n });\n\n // Create timeout promise\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(`Job timed out after ${timeout}ms`)), timeout);\n });\n\n // Create execution promise\n const executePromise = (async () => {\n // Check for abort\n if (signal.aborted) {\n throw new Error('Job was aborted');\n }\n\n await this.agentService.sessionConfig.applyCronJobWorkingDirectory(sessionKey, job.workingDirectory);\n\n const jobModel = resolveIsolatedCronJobModel(job);\n if (jobModel) {\n const ok = await this.agentService.sessionConfig.applyCronJobModelOverride(sessionKey, jobModel);\n if (!ok) {\n log.warn({ jobId: job.id, sessionKey, model: jobModel }, 'Cron job model invalid; using agent default');\n }\n } else {\n await this.agentService.sessionConfig.applyCronJobModelOverride(sessionKey, undefined);\n }\n\n const response = await this.agentService.turnDispatcher.processDirect(message, sessionKey);\n\n const model = this.agentService.getModelForSession(sessionKey);\n\n log.info(\n { jobId: job.id, sessionKey, responseLength: response.length, model },\n 'Agent execution completed'\n );\n\n // Handle delivery (`local` channel or `mode: none` = no outbound; transcript in SessionStore)\n const delivery = job.delivery;\n const outboundChannel = delivery?.channel;\n const shouldPublish =\n delivery &&\n delivery.mode !== 'none' &&\n delivery.to;\n\n if (shouldPublish) {\n if (shouldSilence(response, DEFAULT_ACK_MAX_CHARS) || response.trim() === NO_REPLY) {\n return {\n status: 'ok' as const,\n summary: response.slice(0, 200),\n sessionId: sessionKey,\n sessionKey,\n sessionType: 'cron',\n model,\n };\n }\n const { stripped } = stripHeartbeatToken(response);\n const outboundText = stripped || response.trim();\n\n if (delivery.mode === 'webhook') {\n await postCronWebhook(delivery.to, {\n jobId: job.id,\n jobName: job.name,\n payloadKind: job.payload.kind,\n sessionKey,\n model,\n text: outboundText,\n });\n log.info({ jobId: job.id }, 'Delivered agent response webhook');\n return {\n status: 'ok' as const,\n summary: response.slice(0, 200),\n sessionId: sessionKey,\n sessionKey,\n sessionType: 'cron',\n model,\n };\n }\n if (outboundChannel === 'local') {\n return {\n status: 'ok' as const,\n summary: response.slice(0, 200),\n sessionId: sessionKey,\n sessionKey,\n sessionType: 'cron',\n model,\n };\n }\n const targetChannel = outboundChannel || 'cli';\n const outbound = await this.buildCronOutboundMessage(targetChannel, delivery.to, outboundText);\n\n await this.messageBus.publishOutbound(outbound);\n\n log.info(\n { jobId: job.id, channel: targetChannel, to: outbound.chat_id },\n 'Delivered agent response'\n );\n\n return {\n status: 'ok' as const,\n summary: response.slice(0, 200),\n sessionId: sessionKey,\n sessionKey,\n sessionType: 'cron',\n model,\n };\n }\n\n // No outbound delivery: transcript is in SessionStore under `sessionKey`.\n return {\n status: 'ok' as const,\n summary: response.slice(0, 200),\n sessionId: sessionKey,\n sessionKey,\n sessionType: 'cron',\n model,\n };\n })();\n\n // Race against timeout\n try {\n return await Promise.race([executePromise, timeoutPromise]);\n } finally {\n const { retireSessionMcpRuntimeForSessionKey } = await import('../agent/mcp/bundle-mcp-tools.js');\n await retireSessionMcpRuntimeForSessionKey({\n sessionKey,\n reason: 'cron-isolated-end',\n }).catch(() => {});\n }\n }\n\n /**\n * Basic execution without agent service (fallback)\n */\n private async basicExecute(\n job: JobData,\n signal: AbortSignal,\n timeout: number\n ): Promise<CronRunOutcome> {\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(`Job timed out after ${timeout}ms`));\n }, timeout);\n\n // Listen for abort signal\n const abortHandler = () => {\n clearTimeout(timeoutId);\n reject(new Error('Job was aborted'));\n };\n signal.addEventListener('abort', abortHandler);\n\n // Simulate basic work\n setTimeout(() => {\n clearTimeout(timeoutId);\n signal.removeEventListener('abort', abortHandler);\n\n if (signal.aborted) {\n reject(new Error('Job was aborted'));\n } else {\n resolve({\n status: 'ok',\n summary: `Executed: ${getCronPayloadText(job).slice(0, 100)}`,\n });\n }\n }, 100);\n });\n }\n\n /**\n * Cancel a running job\n */\n cancelJob(jobId: string): boolean {\n const controller = this.runningJobs.get(jobId);\n if (controller) {\n controller.abort();\n this.runningJobs.delete(jobId);\n return true;\n }\n return false;\n }\n\n /**\n * Get execution history for a job\n */\n getHistory(jobId: string, limit = 10): JobExecution[] {\n const history = this.history.get(jobId) || [];\n return history.slice(-limit);\n }\n\n /**\n * Get currently running executions\n */\n getRunningExecutions(): JobExecution[] {\n const result: JobExecution[] = [];\n for (const [jobId] of this.runningJobs) {\n const history = this.history.get(jobId);\n if (history) {\n const running = history.find((e) => e.status === 'running');\n if (running) result.push(running);\n }\n }\n return result;\n }\n\n /**\n * Check if a job is currently running\n */\n isRunning(jobId: string): boolean {\n return this.runningJobs.has(jobId);\n }\n\n /**\n * Get consecutive error count for a job\n */\n getConsecutiveErrors(jobId: string): number {\n const history = this.history.get(jobId) || [];\n // Find last execution\n for (let i = history.length - 1; i >= 0; i--) {\n if (history[i].status !== 'running') {\n if (history[i].status === 'failed' || history[i].status === 'cancelled') {\n // Count consecutive errors before this\n let count = 0;\n for (let j = i - 1; j >= 0; j--) {\n if (history[j].status === 'failed' || history[j].status === 'cancelled') {\n count++;\n } else {\n break;\n }\n }\n return count + 1;\n }\n return 0;\n }\n }\n return 0;\n }\n\n /**\n * Calculate backoff delay for a job\n */\n calculateBackoff(jobId: string): number {\n const errors = this.getConsecutiveErrors(jobId);\n return errorBackoffMs(errors);\n }\n\n /**\n * Clear old history entries\n */\n cleanupHistory(maxAgeDays = 7): void {\n const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;\n\n for (const [jobId, executions] of this.history) {\n this.history.set(\n jobId,\n executions.filter((e) => new Date(e.startedAt).getTime() > cutoff)\n );\n }\n }\n\n private addToHistory(jobId: string, execution: JobExecution): void {\n const existing = this.history.get(jobId) || [];\n existing.push(execution);\n // Keep last 100 executions per job\n if (existing.length > 100) {\n existing.shift();\n }\n this.history.set(jobId, existing);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"executor.js","names":[],"sources":["../../../src/cron/executor.ts"],"sourcesContent":["// Cron job executor with timeout, retry logic and agent integration\nimport type { WorkflowRunView } from '../workflows/domain/index.js';\nimport type {\n CronRunOutcome,\n CronWorkflowRunStarter,\n HeartbeatWakeSink,\n JobData,\n JobExecution,\n JobExecutor,\n JobExecutorDeps,\n} from './types.js';\nimport type { OutboundMessage } from '../channels/transport-types.js';\nimport { createLogger } from '../utils/logger.js';\nimport {\n getChannelPlugin,\n} from '../channels/plugins/registry.js';\nimport { getCronPayloadText } from './job-content.js';\nimport type { CronRunLogStore } from './run-log-store.js';\nimport {\n DEFAULT_ACK_MAX_CHARS,\n NO_REPLY,\n shouldSilence,\n stripHeartbeatToken,\n} from '../heartbeat/tokens.js';\nimport { DEFAULT_AGENT_ID, normalizeAgentId } from '../agent/agent-scope.js';\nimport { buildSessionKey } from '../routing/session-key.js';\nimport {\n buildWorkflowRunCronSummary,\n buildWorkflowRunDeliveryText,\n isWorkflowRunCronSuccess,\n resolveWorkflowCronWaitMs,\n waitForWorkflowRunView,\n} from './workflow-run-completion.js';\n\nconst log = createLogger('CronExecutor');\n\n// Error backoff schedule in ms\nconst ERROR_BACKOFF_MS = [\n 30_000, // 1st error → 30s\n 60_000, // 2nd error → 1min\n 5 * 60_000, // 3rd error → 5min\n 15 * 60_000, // 4th error → 15min\n 60 * 60_000, // 5th+ error → 60min\n];\n\nfunction errorBackoffMs(consecutiveErrors: number): number {\n const idx = Math.min(consecutiveErrors - 1, ERROR_BACKOFF_MS.length - 1);\n return ERROR_BACKOFF_MS[Math.max(0, idx)];\n}\n\nfunction resolveIsolatedCronJobModel(job: JobData): string | undefined {\n if (job.payload?.kind === 'agentTurn') {\n const m = job.payload.model?.trim();\n if (m) return m;\n }\n return undefined;\n}\n\nasync function postCronWebhook(url: string, payload: unknown): Promise<void> {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`Webhook delivery failed: HTTP ${res.status}${text ? ` ${text.slice(0, 200)}` : ''}`);\n }\n}\n\nexport class DefaultJobExecutor implements JobExecutor {\n private history: Map<string, JobExecution[]> = new Map();\n private runningJobs = new Map<string, AbortController>();\n private agentService: any = null;\n private messageBus: any = null;\n private heartbeatService: HeartbeatWakeSink | null = null;\n private sessionStore: JobExecutorDeps['sessionStore'] | undefined;\n private runLogStore: CronRunLogStore | null = null;\n private getDefaultCronAgentId: (() => string) | null = null;\n private workflowRunService: CronWorkflowRunStarter | null = null;\n private goalRunner: JobExecutorDeps['goalRunner'] | null = null;\n\n setRunLogStore(store: CronRunLogStore | null): void {\n this.runLogStore = store;\n }\n\n setDeps(deps: JobExecutorDeps): void {\n this.agentService = deps.agentService;\n this.messageBus = deps.messageBus;\n this.heartbeatService = deps.heartbeatService ?? null;\n if (deps.sessionStore !== undefined) {\n this.sessionStore = deps.sessionStore;\n }\n this.getDefaultCronAgentId = deps.getDefaultCronAgentId ?? null;\n this.workflowRunService = deps.workflowRunService ?? null;\n this.goalRunner = deps.goalRunner ?? null;\n }\n\n private async buildCronOutboundMessage(\n channel: string,\n to: string,\n content: string,\n ): Promise<OutboundMessage> {\n const plugin = getChannelPlugin(channel);\n if (plugin?.cronDelivery) {\n const { chatId, accountId, metadata } = await plugin.cronDelivery.normalizeDeliveryTarget(\n to,\n this.sessionStore,\n );\n const meta =\n accountId || metadata\n ? { ...(metadata ?? {}), ...(accountId ? { accountId } : {}) }\n : undefined;\n return {\n channel,\n chat_id: chatId,\n content,\n type: 'message',\n ...(meta && Object.keys(meta).length > 0 ? { metadata: meta } : {}),\n };\n }\n return {\n channel,\n chat_id: to,\n content,\n type: 'message',\n };\n }\n\n async execute(job: JobData, signal: AbortSignal, deps?: JobExecutorDeps): Promise<void> {\n // Set deps if provided\n if (deps) {\n this.setDeps(deps);\n }\n\n const executionId = crypto.randomUUID();\n const execution: JobExecution = {\n id: executionId,\n jobId: job.id,\n status: 'running',\n startedAt: new Date().toISOString(),\n retryCount: 0,\n sessionId: job.sessionKey,\n sessionKey: job.sessionKey,\n sessionType: job.sessionKey ? 'cron' : undefined,\n };\n\n // Record execution start\n this.addToHistory(job.id, execution);\n if (this.runLogStore) {\n await this.runLogStore.upsert(execution);\n }\n this.runningJobs.set(job.id, new AbortController());\n\n log.info(\n { jobId: job.id, executionId, preview: getCronPayloadText(job).slice(0, 100) },\n 'Job executing'\n );\n\n let result: CronRunOutcome;\n\n try {\n // Check for cancellation\n if (signal.aborted) {\n throw new Error('Job was cancelled before execution');\n }\n\n // Execute the job\n result = await this.performJob(job, signal);\n\n // Mark as success/failed\n execution.status = result.status === 'ok' ? 'success' : result.status === 'skipped' ? 'cancelled' : 'failed';\n execution.endedAt = new Date().toISOString();\n execution.duration = Date.now() - new Date(execution.startedAt).getTime();\n execution.summary = result.summary;\n execution.error = result.error;\n execution.sessionId = result.sessionId;\n execution.sessionKey = result.sessionKey;\n execution.sessionType = result.sessionType;\n execution.model = result.model;\n execution.workflowRunId = result.workflowRunId;\n\n if (result.status === 'ok') {\n log.info(\n { jobId: job.id, executionId, duration: execution.duration },\n 'Job completed'\n );\n } else if (result.status === 'skipped') {\n log.warn({ jobId: job.id, executionId, reason: result.error }, 'Job skipped');\n } else {\n log.error(\n { jobId: job.id, executionId, errorMessage: result.error, phase: 'cron.execute' },\n `Job failed: ${result.error ?? 'unknown'}`,\n );\n }\n\n if (result.status === 'ok' && job.wakeMode === 'next-heartbeat' && this.heartbeatService) {\n try {\n this.heartbeatService.requestNow({ reason: `cron:${job.id}` });\n } catch (e) {\n log.warn({ jobId: job.id, err: e }, 'Heartbeat wake after cron failed');\n }\n }\n } catch (error) {\n execution.status = 'failed';\n execution.endedAt = new Date().toISOString();\n execution.duration = Date.now() - new Date(execution.startedAt).getTime();\n execution.error = error instanceof Error ? error.message : String(error);\n\n log.error(\n {\n err: error,\n errorMessage: execution.error,\n jobId: job.id,\n executionId,\n phase: 'cron.execute',\n },\n `Job execution error: ${execution.error}`,\n );\n\n result = {\n status: 'error',\n error: execution.error,\n };\n } finally {\n this.runningJobs.delete(job.id);\n }\n\n if (this.runLogStore) {\n await this.runLogStore.upsert(execution);\n }\n\n return;\n }\n\n /**\n * Perform the actual job work - integrate with AgentService\n */\n protected async performJob(job: JobData, signal: AbortSignal): Promise<CronRunOutcome> {\n const timeout =\n job.payload.kind === 'agentTurn' && job.payload.timeoutSeconds\n ? job.payload.timeoutSeconds * 1000\n : 180_000;\n const sessionTarget = job.sessionTarget;\n\n // Check for abort before starting\n if (signal.aborted) {\n return { status: 'skipped', error: 'Job was aborted before execution' };\n }\n\n try {\n if (job.payload.kind === 'goalContinue') {\n return await this.executeGoalContinue(job);\n }\n\n if (job.payload.kind === 'workflowRun') {\n return await this.executeWorkflowRun(job, signal);\n }\n\n // If no agent service, fall back to basic execution\n if (!this.agentService || !this.messageBus) {\n log.warn({ jobId: job.id }, 'No agent service configured, using basic execution');\n return this.basicExecute(job, signal, timeout);\n }\n\n if (sessionTarget === 'main') {\n return await this.executeMainSession(job, signal, timeout);\n } else {\n return await this.executeIsolated(job, signal, timeout);\n }\n } catch (error) {\n return {\n status: 'error',\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n private async executeGoalContinue(job: JobData): Promise<CronRunOutcome> {\n if (job.payload.kind !== 'goalContinue') {\n return { status: 'error', error: 'Cron job payload is not goalContinue' };\n }\n if (!this.goalRunner) {\n return { status: 'error', error: 'Goal runner is not configured for cron' };\n }\n const item = this.goalRunner.enqueue(job.payload.goalId, {\n message: job.payload.message,\n maxRetries: job.payload.maxRetries ?? 0,\n source: 'cron',\n });\n return {\n status: 'ok',\n summary: `Queued goal ${job.payload.goalId} (${item.status})`,\n sessionId: item.sessionKey,\n sessionKey: item.sessionKey,\n sessionType: 'goal',\n };\n }\n\n private async executeWorkflowRun(job: JobData, signal: AbortSignal): Promise<CronRunOutcome> {\n if (signal.aborted) {\n return { status: 'skipped', error: 'Job was aborted before workflow run start' };\n }\n\n if (job.payload.kind !== 'workflowRun') {\n return { status: 'error', error: 'Cron job payload is not workflowRun' };\n }\n\n if (!this.workflowRunService) {\n return { status: 'error', error: 'Workflow run service is not configured for cron' };\n }\n\n const fallbackAgentId = this.getDefaultCronAgentId?.() ?? DEFAULT_AGENT_ID;\n const agentId = normalizeAgentId(job.payload.agentId || job.agentId || fallbackAgentId);\n const fireId = job.payload.source?.fireId || crypto.randomUUID();\n const source = {\n kind: 'cron' as const,\n scheduleId: job.payload.source?.scheduleId || job.id,\n fireId,\n scheduledAtMs: job.payload.source?.scheduledAtMs ?? Date.now(),\n };\n const result = await this.workflowRunService.startWorkflowRun({\n agentId,\n definitionId: job.payload.definitionId,\n input: job.payload.input,\n inputEnvelope: job.payload.inputEnvelope,\n goal: job.payload.goal,\n source,\n idempotencyKey: `cron:${job.id}:${fireId}`,\n });\n\n if (result.ok === false) {\n return {\n status: 'error',\n error: result.message,\n };\n }\n\n log.info(\n {\n jobId: job.id,\n workflowRunId: result.runId,\n definitionId: job.payload.definitionId,\n sessionKey: result.sessionKey,\n },\n 'Workflow run started from cron',\n );\n\n const waitForCompletion = job.payload.waitForCompletion !== false;\n if (!waitForCompletion || !this.workflowRunService.readWorkflowRunView) {\n return {\n status: 'ok',\n summary: `Started workflow run ${result.runId}`,\n sessionId: result.sessionKey,\n sessionKey: result.sessionKey,\n sessionType: 'workflow',\n workflowRunId: result.runId,\n };\n }\n\n return this.waitForWorkflowRunOutcome({\n job,\n agentId,\n sessionKey: result.sessionKey,\n initialRunId: result.runId,\n signal,\n });\n }\n\n private async waitForWorkflowRunOutcome(params: {\n job: JobData;\n agentId: string;\n sessionKey: string;\n initialRunId: string;\n signal: AbortSignal;\n }): Promise<CronRunOutcome> {\n const waitMs = resolveWorkflowCronWaitMs(180_000);\n const maxWorkflowRetries =\n params.job.payload.kind === 'workflowRun'\n ? Math.max(0, Math.floor(params.job.payload.maxRetries ?? 0))\n : 0;\n let runId = params.initialRunId;\n let attempt = 0;\n\n while (attempt <= maxWorkflowRetries) {\n const waitResult = await waitForWorkflowRunView({\n readView: (id) => this.workflowRunService!.readWorkflowRunView!(params.agentId, id),\n runId,\n signal: params.signal,\n timeoutMs: waitMs,\n });\n\n if (waitResult.kind === 'aborted') {\n return { status: 'skipped', error: 'Job was aborted while waiting for workflow run' };\n }\n\n if (waitResult.kind === 'timeout') {\n return {\n status: 'error',\n error: `Workflow run ${runId} did not finish within ${waitMs}ms`,\n summary: waitResult.lastView ? buildWorkflowRunCronSummary(waitResult.lastView) : undefined,\n sessionId: params.sessionKey,\n sessionKey: params.sessionKey,\n sessionType: 'workflow',\n workflowRunId: runId,\n };\n }\n\n const view = waitResult.view;\n const summary = buildWorkflowRunCronSummary(view);\n const succeeded = isWorkflowRunCronSuccess(view);\n\n if (succeeded) {\n const baseOutcome: CronRunOutcome = {\n status: 'ok',\n summary,\n sessionId: params.sessionKey,\n sessionKey: params.sessionKey,\n sessionType: 'workflow',\n workflowRunId: runId,\n };\n await this.deliverWorkflowRunOutcome(params.job, view, baseOutcome);\n return baseOutcome;\n }\n\n const canRetry =\n attempt < maxWorkflowRetries &&\n view.run.status === 'failed' &&\n Boolean(this.workflowRunService?.retryWorkflowRun);\n\n if (!canRetry) {\n return {\n status: 'error',\n summary,\n error: summary,\n sessionId: params.sessionKey,\n sessionKey: params.sessionKey,\n sessionType: 'workflow',\n workflowRunId: runId,\n };\n }\n\n const retryResult = await this.workflowRunService!.retryWorkflowRun!({\n agentId: params.agentId,\n runId,\n });\n if (retryResult.ok === false) {\n return {\n status: 'error',\n error: retryResult.message,\n summary,\n sessionId: params.sessionKey,\n sessionKey: params.sessionKey,\n sessionType: 'workflow',\n workflowRunId: runId,\n };\n }\n\n runId = retryResult.runId;\n attempt += 1;\n log.info(\n { jobId: params.job.id, attempt, workflowRunId: runId },\n 'Retrying failed workflow run from cron',\n );\n }\n\n return {\n status: 'error',\n error: 'Workflow run retries exhausted',\n sessionId: params.sessionKey,\n sessionKey: params.sessionKey,\n sessionType: 'workflow',\n workflowRunId: runId,\n };\n }\n\n private async deliverWorkflowRunOutcome(\n job: JobData,\n view: WorkflowRunView,\n outcome: CronRunOutcome,\n ): Promise<void> {\n const delivery = job.delivery;\n if (!delivery || delivery.mode === 'none' || delivery.channel === 'local' || !delivery.to?.trim()) {\n return;\n }\n\n try {\n const text = buildWorkflowRunDeliveryText(view);\n if (delivery.mode === 'webhook') {\n await postCronWebhook(delivery.to, {\n jobId: job.id,\n jobName: job.name,\n payloadKind: job.payload.kind,\n workflowRunId: view.run.id,\n status: view.run.status,\n text,\n run: view.run,\n });\n log.info({ jobId: job.id, workflowRunId: view.run.id }, 'Delivered workflow run webhook from cron');\n return;\n }\n if (!this.messageBus || !delivery.channel) return;\n const outbound = await this.buildCronOutboundMessage(delivery.channel!, delivery.to!, text);\n await this.messageBus.publishOutbound(outbound);\n log.info(\n { jobId: job.id, channel: delivery.channel, to: outbound.chat_id, workflowRunId: view.run.id },\n 'Delivered workflow run result from cron',\n );\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ jobId: job.id, err, workflowRunId: view.run.id }, `Workflow cron delivery failed: ${em}`);\n if (outcome.status === 'ok') {\n outcome.summary = `${outcome.summary ?? ''} (delivery failed: ${em})`.trim();\n }\n }\n }\n\n /**\n * Execute in main session - sends system event\n */\n private async executeMainSession(\n job: JobData,\n signal: AbortSignal,\n timeout: number\n ): Promise<CronRunOutcome> {\n const text = getCronPayloadText(job);\n\n if (!text || !text.trim()) {\n return { status: 'skipped', error: 'Main session job requires non-empty message' };\n }\n\n const delivery = job.delivery;\n const channel = delivery?.channel ?? 'local';\n const to = delivery?.to ?? '';\n const actualMessage = text;\n\n // Create timeout promise\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(`Job timed out after ${timeout}ms`)), timeout);\n });\n\n // Create execution promise\n const executePromise = (async () => {\n // Check for abort\n if (signal.aborted) {\n throw new Error('Job was aborted');\n }\n\n if (!delivery || delivery.mode === 'none' || channel === 'local') {\n log.info(\n { jobId: job.id, messageLength: actualMessage.length },\n 'Cron main session: no outbound publish'\n );\n return {\n status: 'ok' as const,\n summary: actualMessage.slice(0, 200),\n };\n }\n\n if (delivery.mode === 'webhook') {\n if (!to.trim()) {\n throw new Error('Webhook delivery requires delivery.to');\n }\n await postCronWebhook(to, {\n jobId: job.id,\n jobName: job.name,\n payloadKind: job.payload.kind,\n text: actualMessage,\n });\n return {\n status: 'ok' as const,\n summary: actualMessage.slice(0, 200),\n };\n }\n\n if (!this.messageBus) {\n return { status: 'error' as const, error: 'MessageBus not available' };\n }\n\n const outbound = await this.buildCronOutboundMessage(channel, to, actualMessage);\n\n await this.messageBus.publishOutbound(outbound);\n\n log.info(\n { jobId: job.id, channel, to: outbound.chat_id, messageLength: actualMessage.length },\n 'Sent message to main session'\n );\n\n return {\n status: 'ok' as const,\n summary: actualMessage.slice(0, 200),\n };\n })();\n\n // Race against timeout\n return await Promise.race([executePromise, timeoutPromise]);\n }\n\n /**\n * Execute in isolated mode - runs agent independently\n */\n private async executeIsolated(\n job: JobData,\n signal: AbortSignal,\n timeout: number\n ): Promise<CronRunOutcome> {\n const message = getCronPayloadText(job);\n\n if (!message || !message.trim()) {\n return { status: 'skipped', error: 'Isolated job requires non-empty message' };\n }\n\n const explicitSessionKey =\n job.sessionTarget.startsWith('session:')\n ? job.sessionTarget.slice('session:'.length).trim()\n : job.sessionTarget === 'current'\n ? job.sessionKey?.trim()\n : job.sessionTarget === 'isolated'\n ? job.sessionKey?.trim()\n : undefined;\n const aid = job.agentId?.trim();\n const fallbackAgentId = this.getDefaultCronAgentId?.() ?? DEFAULT_AGENT_ID;\n const sessionKey = explicitSessionKey || buildSessionKey({\n agentId: normalizeAgentId(aid || fallbackAgentId),\n source: 'cron',\n accountId: 'default',\n peerKind: 'dm',\n peerId: `${job.id}-${crypto.randomUUID().slice(0, 8)}`,\n });\n\n // Create timeout promise\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(`Job timed out after ${timeout}ms`)), timeout);\n });\n\n // Create execution promise\n const executePromise = (async () => {\n // Check for abort\n if (signal.aborted) {\n throw new Error('Job was aborted');\n }\n\n await this.agentService.sessionConfig.applyCronJobWorkingDirectory(sessionKey, job.workingDirectory);\n\n const jobModel = resolveIsolatedCronJobModel(job);\n if (jobModel) {\n const ok = await this.agentService.sessionConfig.applyCronJobModelOverride(sessionKey, jobModel);\n if (!ok) {\n log.warn({ jobId: job.id, sessionKey, model: jobModel }, 'Cron job model invalid; using agent default');\n }\n } else {\n await this.agentService.sessionConfig.applyCronJobModelOverride(sessionKey, undefined);\n }\n\n const response = await this.agentService.turnDispatcher.processDirect(message, sessionKey);\n\n const model = this.agentService.getModelForSession(sessionKey);\n\n log.info(\n { jobId: job.id, sessionKey, responseLength: response.length, model },\n 'Agent execution completed'\n );\n\n // Handle delivery (`local` channel or `mode: none` = no outbound; transcript in SessionStore)\n const delivery = job.delivery;\n const outboundChannel = delivery?.channel;\n const shouldPublish =\n delivery &&\n delivery.mode !== 'none' &&\n delivery.to;\n\n if (shouldPublish) {\n if (shouldSilence(response, DEFAULT_ACK_MAX_CHARS) || response.trim() === NO_REPLY) {\n return {\n status: 'ok' as const,\n summary: response.slice(0, 200),\n sessionId: sessionKey,\n sessionKey,\n sessionType: 'cron',\n model,\n };\n }\n const { stripped } = stripHeartbeatToken(response);\n const outboundText = stripped || response.trim();\n\n if (delivery.mode === 'webhook') {\n await postCronWebhook(delivery.to, {\n jobId: job.id,\n jobName: job.name,\n payloadKind: job.payload.kind,\n sessionKey,\n model,\n text: outboundText,\n });\n log.info({ jobId: job.id }, 'Delivered agent response webhook');\n return {\n status: 'ok' as const,\n summary: response.slice(0, 200),\n sessionId: sessionKey,\n sessionKey,\n sessionType: 'cron',\n model,\n };\n }\n if (outboundChannel === 'local') {\n return {\n status: 'ok' as const,\n summary: response.slice(0, 200),\n sessionId: sessionKey,\n sessionKey,\n sessionType: 'cron',\n model,\n };\n }\n const targetChannel = outboundChannel || 'cli';\n const outbound = await this.buildCronOutboundMessage(targetChannel, delivery.to, outboundText);\n\n await this.messageBus.publishOutbound(outbound);\n\n log.info(\n { jobId: job.id, channel: targetChannel, to: outbound.chat_id },\n 'Delivered agent response'\n );\n\n return {\n status: 'ok' as const,\n summary: response.slice(0, 200),\n sessionId: sessionKey,\n sessionKey,\n sessionType: 'cron',\n model,\n };\n }\n\n // No outbound delivery: transcript is in SessionStore under `sessionKey`.\n return {\n status: 'ok' as const,\n summary: response.slice(0, 200),\n sessionId: sessionKey,\n sessionKey,\n sessionType: 'cron',\n model,\n };\n })();\n\n // Race against timeout\n try {\n return await Promise.race([executePromise, timeoutPromise]);\n } finally {\n const { retireSessionMcpRuntimeForSessionKey } = await import('../agent/mcp/bundle-mcp-tools.js');\n await retireSessionMcpRuntimeForSessionKey({\n sessionKey,\n reason: 'cron-isolated-end',\n }).catch(() => {});\n }\n }\n\n /**\n * Basic execution without agent service (fallback)\n */\n private async basicExecute(\n job: JobData,\n signal: AbortSignal,\n timeout: number\n ): Promise<CronRunOutcome> {\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(`Job timed out after ${timeout}ms`));\n }, timeout);\n\n // Listen for abort signal\n const abortHandler = () => {\n clearTimeout(timeoutId);\n reject(new Error('Job was aborted'));\n };\n signal.addEventListener('abort', abortHandler);\n\n // Simulate basic work\n setTimeout(() => {\n clearTimeout(timeoutId);\n signal.removeEventListener('abort', abortHandler);\n\n if (signal.aborted) {\n reject(new Error('Job was aborted'));\n } else {\n resolve({\n status: 'ok',\n summary: `Executed: ${getCronPayloadText(job).slice(0, 100)}`,\n });\n }\n }, 100);\n });\n }\n\n /**\n * Cancel a running job\n */\n cancelJob(jobId: string): boolean {\n const controller = this.runningJobs.get(jobId);\n if (controller) {\n controller.abort();\n this.runningJobs.delete(jobId);\n return true;\n }\n return false;\n }\n\n /**\n * Get execution history for a job\n */\n getHistory(jobId: string, limit = 10): JobExecution[] {\n const history = this.history.get(jobId) || [];\n return history.slice(-limit);\n }\n\n /**\n * Get currently running executions\n */\n getRunningExecutions(): JobExecution[] {\n const result: JobExecution[] = [];\n for (const [jobId] of this.runningJobs) {\n const history = this.history.get(jobId);\n if (history) {\n const running = history.find((e) => e.status === 'running');\n if (running) result.push(running);\n }\n }\n return result;\n }\n\n /**\n * Check if a job is currently running\n */\n isRunning(jobId: string): boolean {\n return this.runningJobs.has(jobId);\n }\n\n /**\n * Get consecutive error count for a job\n */\n getConsecutiveErrors(jobId: string): number {\n const history = this.history.get(jobId) || [];\n // Find last execution\n for (let i = history.length - 1; i >= 0; i--) {\n if (history[i].status !== 'running') {\n if (history[i].status === 'failed' || history[i].status === 'cancelled') {\n // Count consecutive errors before this\n let count = 0;\n for (let j = i - 1; j >= 0; j--) {\n if (history[j].status === 'failed' || history[j].status === 'cancelled') {\n count++;\n } else {\n break;\n }\n }\n return count + 1;\n }\n return 0;\n }\n }\n return 0;\n }\n\n /**\n * Calculate backoff delay for a job\n */\n calculateBackoff(jobId: string): number {\n const errors = this.getConsecutiveErrors(jobId);\n return errorBackoffMs(errors);\n }\n\n /**\n * Clear old history entries\n */\n cleanupHistory(maxAgeDays = 7): void {\n const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;\n\n for (const [jobId, executions] of this.history) {\n this.history.set(\n jobId,\n executions.filter((e) => new Date(e.startedAt).getTime() > cutoff)\n );\n }\n }\n\n private addToHistory(jobId: string, execution: JobExecution): void {\n const existing = this.history.get(jobId) || [];\n existing.push(execution);\n // Keep last 100 executions per job\n if (existing.length > 100) {\n existing.shift();\n }\n this.history.set(jobId, existing);\n }\n}\n"],"mappings":"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"}
|
package/dist/src/cron/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SessionListReader } from '../session/store-reader.js';
|
|
2
2
|
import type { WorkflowRunInputEnvelope, WorkflowRunSource, WorkflowRunView } from '../workflows/domain/index.js';
|
|
3
3
|
import type { StartWorkflowRunServiceParams, WorkflowRunServiceResult } from '../workflows/service/workflow-run-service.types.js';
|
|
4
4
|
export type { CronRunHistoryRow, CronRunOutcome, CronRunStatus, CronUsageSummary, JobExecution, } from './execution-types.js';
|
|
@@ -139,7 +139,7 @@ export interface JobExecutorDeps {
|
|
|
139
139
|
agentService?: any;
|
|
140
140
|
messageBus?: any;
|
|
141
141
|
heartbeatService?: HeartbeatWakeSink;
|
|
142
|
-
sessionStore?:
|
|
142
|
+
sessionStore?: SessionListReader;
|
|
143
143
|
getDefaultCronAgentId?: () => string;
|
|
144
144
|
workflowRunService?: CronWorkflowRunStarter;
|
|
145
145
|
goalRunner?: {
|
|
@@ -124,7 +124,19 @@ var HeartbeatService = class {
|
|
|
124
124
|
reasons: reasonSummary
|
|
125
125
|
}, "Heartbeat: invoking agent");
|
|
126
126
|
if (sessionKey === "heartbeat:main") try {
|
|
127
|
-
await this.deps.sessionStore.saveMessages(sessionKey, []
|
|
127
|
+
await this.deps.sessionStore.saveMessages(sessionKey, [], { metadata: {
|
|
128
|
+
sourceChannel: "heartbeat",
|
|
129
|
+
sourceChatId: "main",
|
|
130
|
+
sessionType: "heartbeat",
|
|
131
|
+
customData: { heartbeatTarget: "main" },
|
|
132
|
+
routing: {
|
|
133
|
+
agentId: "main",
|
|
134
|
+
source: "heartbeat",
|
|
135
|
+
accountId: "default",
|
|
136
|
+
peerKind: "direct",
|
|
137
|
+
peerId: "main"
|
|
138
|
+
}
|
|
139
|
+
} });
|
|
128
140
|
} catch (err) {
|
|
129
141
|
log.warn({
|
|
130
142
|
err,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","names":[],"sources":["../../../../src/gateway/heartbeat/service.ts"],"sourcesContent":["import { readFile } from 'fs/promises';\n\nimport type { AgentService } from '../../agent/service.js';\nimport type { Config } from '../../config/schema.js';\nimport { CronService } from '../../cron/service.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { SessionStore } from '../../session/store.js';\nimport { appendCronEventLines } from '../../heartbeat/event-prompt.js';\nimport { isWithinActiveHours } from '../../heartbeat/active-hours.js';\nimport { isHeartbeatContentEmpty } from '../../heartbeat/content-check.js';\nimport {\n DEFAULT_ACK_MAX_CHARS,\n stripHeartbeatToken,\n shouldSilence,\n} from '../../heartbeat/tokens.js';\nimport { createHeartbeatWake } from '../../heartbeat/wake.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { resolveHeartbeatMdPath } from '../workspace-heartbeat-path.js';\n\nconst log = createLogger('HeartbeatService');\n\nconst DEFAULT_PROMPT =\n 'Read HEARTBEAT.md if it exists. Follow it strictly. If nothing needs attention, reply HEARTBEAT_OK.';\n\nexport interface HeartbeatRunnerConfig {\n enabled: boolean;\n intervalMs: number;\n target?: string;\n targetChatId?: string;\n prompt?: string;\n ackMaxChars?: number;\n isolatedSession?: boolean;\n activeHours?: {\n start: string;\n end: string;\n timezone?: string;\n };\n}\n\nfunction mapConfigToRunner(cfg: Config | undefined): HeartbeatRunnerConfig {\n const h = cfg?.gateway?.heartbeat;\n return {\n enabled: h?.enabled ?? true,\n intervalMs: h?.intervalMs ?? 1_800_000,\n target: h?.target,\n targetChatId: h?.targetChatId,\n prompt: h?.prompt,\n ackMaxChars: h?.ackMaxChars,\n isolatedSession: h?.isolatedSession,\n activeHours: h?.activeHours,\n };\n}\n\n/** Map persisted gateway config to runner options (gateway start / reload). */\nexport function heartbeatRunnerConfigFromConfig(cfg: Config): HeartbeatRunnerConfig {\n return mapConfigToRunner(cfg);\n}\n\nexport interface HeartbeatServiceDeps {\n agentService: AgentService;\n messageBus: MessageBus;\n cronService: CronService;\n sessionStore: SessionStore;\n /** Current app config (for HEARTBEAT.md path under the default agent `profile/` directory). */\n getConfig: () => Config;\n}\n\nexport class HeartbeatService {\n private intervalId: ReturnType<typeof setInterval> | null = null;\n private wake: ReturnType<typeof createHeartbeatWake>;\n private lastHeartbeatText = '';\n private lastHeartbeatAt = 0;\n private runnerConfig: HeartbeatRunnerConfig | null = null;\n\n constructor(private deps: HeartbeatServiceDeps) {\n this.wake = createHeartbeatWake((reasons) => this.runHeartbeatOnce(reasons));\n }\n\n start(config: HeartbeatRunnerConfig): void {\n if (!config.enabled) {\n log.info('Heartbeat disabled');\n this.runnerConfig = null;\n return;\n }\n\n this.runnerConfig = config;\n log.info({ intervalMs: config.intervalMs }, 'Heartbeat timer started (interval wake)');\n\n this.intervalId = setInterval(() => {\n this.wake.request({ reason: 'interval' });\n }, config.intervalMs);\n this.intervalId.unref?.();\n }\n\n /** Cron, exec completion, manual triggers, etc. */\n requestNow(opts?: { reason?: string }): void {\n this.wake.request({ reason: opts?.reason ?? 'manual' });\n }\n\n stop(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n this.wake.stop();\n this.runnerConfig = null;\n log.info('Heartbeat stopped');\n }\n\n updateConfig(config: Config): void {\n const mapped = mapConfigToRunner(config);\n this.stop();\n if (mapped.enabled) {\n this.start(mapped);\n }\n log.info('Heartbeat config updated');\n }\n\n isRunning(): boolean {\n return this.intervalId !== null;\n }\n\n private async runHeartbeatOnce(reasons: string[]): Promise<void> {\n const reasonSummary = [...new Set(reasons)].join(', ') || 'unknown';\n\n const cfg = this.runnerConfig;\n if (!cfg?.enabled) {\n log.debug({ reasons: reasonSummary }, 'Heartbeat: skip (disabled)');\n return;\n }\n\n try {\n const metrics = await this.deps.cronService.getMetrics();\n log.trace(\n { runningJobs: metrics.runningJobs, enabledJobs: metrics.enabledJobs },\n 'Heartbeat: cron metrics',\n );\n } catch {\n /* optional */\n }\n\n if (cfg.activeHours && !isWithinActiveHours(cfg.activeHours)) {\n log.debug({ reasons: reasonSummary }, 'Heartbeat: skip (outside active hours)');\n return;\n }\n\n const heartbeatPath = resolveHeartbeatMdPath(this.deps.getConfig());\n if (!heartbeatPath) {\n log.debug({ reasons: reasonSummary }, 'Heartbeat: skip (no HEARTBEAT path)');\n return;\n }\n let heartbeatContent: string | undefined;\n try {\n const raw = await readFile(heartbeatPath, 'utf-8');\n if (isHeartbeatContentEmpty(raw)) {\n log.debug({ path: heartbeatPath, reasons: reasonSummary }, 'Heartbeat: skip (HEARTBEAT.md empty)');\n return;\n }\n heartbeatContent = raw.trim();\n } catch {\n log.debug({ path: heartbeatPath, reasons: reasonSummary }, 'Heartbeat: HEARTBEAT.md missing; continuing');\n }\n\n const sessionKey = cfg.isolatedSession\n ? `heartbeat:isolated:${Date.now()}`\n : 'heartbeat:main';\n\n let basePrompt = (cfg.prompt?.trim() || DEFAULT_PROMPT).trim();\n if (heartbeatContent) {\n basePrompt = `${basePrompt}\\n\\n---\\nHEARTBEAT.md:\\n${heartbeatContent}\\n---`;\n }\n basePrompt = appendCronEventLines(basePrompt, reasons);\n const prompt = `${basePrompt}\\n\\nCurrent time: ${new Date().toISOString()}`;\n\n const ackMax = cfg.ackMaxChars ?? DEFAULT_ACK_MAX_CHARS;\n\n log.debug({ sessionKey, reasons: reasonSummary }, 'Heartbeat: invoking agent');\n\n // `heartbeat:main` reuses one session key; each run would otherwise append to the transcript\n // until the model rejects the request (context window exceeded). Heartbeat prompts are\n // self-contained (HEARTBEAT.md + this turn's text), so we start from an empty history.\n if (sessionKey === 'heartbeat:main') {\n try {\n await this.deps.sessionStore.saveMessages(sessionKey, []);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Heartbeat: failed to reset main session transcript');\n }\n }\n\n let reply: string;\n try {\n reply = await this.deps.agentService.turnDispatcher.processDirect(prompt, sessionKey);\n } catch (error) {\n log.error({ err: error }, 'Heartbeat: agent call failed');\n return;\n }\n\n if (!reply?.trim()) {\n log.debug({ reasons: reasonSummary }, 'Heartbeat: skip (empty model reply)');\n return;\n }\n\n if (shouldSilence(reply, ackMax)) {\n log.info(\n { ackMax, replyChars: reply.length, reasons: reasonSummary },\n 'Heartbeat: not sent — silent (HEARTBEAT_OK / short ack)',\n );\n return;\n }\n\n const { stripped } = stripHeartbeatToken(reply);\n const finalText = stripped || reply.trim();\n\n if (this.isDuplicate(finalText)) {\n log.info(\n { finalTextChars: finalText.length, reasons: reasonSummary },\n 'Heartbeat: not sent — duplicate within 24h',\n );\n return;\n }\n\n const target = cfg.target?.trim();\n const targetChatId = cfg.targetChatId?.trim();\n const hasDeliveryTarget = Boolean(target && targetChatId);\n\n if (hasDeliveryTarget) {\n await this.deps.messageBus.publishOutbound({\n channel: target!,\n chat_id: targetChatId!,\n content: finalText,\n type: 'message',\n });\n log.info(\n {\n reasons: reasonSummary,\n channel: target,\n chatId: targetChatId,\n contentChars: finalText.length,\n },\n 'Heartbeat: sent — outbound queued',\n );\n } else {\n log.info(\n { reasons: reasonSummary, finalTextChars: finalText.length },\n 'Heartbeat: not sent — no delivery target (set gateway.heartbeat.target + targetChatId)',\n );\n }\n\n this.lastHeartbeatText = finalText;\n this.lastHeartbeatAt = Date.now();\n }\n\n private isDuplicate(text: string): boolean {\n const DEDUP_WINDOW_MS = 24 * 60 * 60 * 1000;\n return (\n text.trim() === this.lastHeartbeatText.trim() &&\n Date.now() - this.lastHeartbeatAt < DEDUP_WINDOW_MS\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.js","names":[],"sources":["../../../../src/gateway/heartbeat/service.ts"],"sourcesContent":["import { readFile } from 'fs/promises';\n\nimport type { AgentService } from '../../agent/service.js';\nimport type { Config } from '../../config/schema.js';\nimport { CronService } from '../../cron/service.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { SessionStore } from '../../session/store.js';\nimport { appendCronEventLines } from '../../heartbeat/event-prompt.js';\nimport { isWithinActiveHours } from '../../heartbeat/active-hours.js';\nimport { isHeartbeatContentEmpty } from '../../heartbeat/content-check.js';\nimport {\n DEFAULT_ACK_MAX_CHARS,\n stripHeartbeatToken,\n shouldSilence,\n} from '../../heartbeat/tokens.js';\nimport { createHeartbeatWake } from '../../heartbeat/wake.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { resolveHeartbeatMdPath } from '../workspace-heartbeat-path.js';\n\nconst log = createLogger('HeartbeatService');\n\nconst DEFAULT_PROMPT =\n 'Read HEARTBEAT.md if it exists. Follow it strictly. If nothing needs attention, reply HEARTBEAT_OK.';\n\nexport interface HeartbeatRunnerConfig {\n enabled: boolean;\n intervalMs: number;\n target?: string;\n targetChatId?: string;\n prompt?: string;\n ackMaxChars?: number;\n isolatedSession?: boolean;\n activeHours?: {\n start: string;\n end: string;\n timezone?: string;\n };\n}\n\nfunction mapConfigToRunner(cfg: Config | undefined): HeartbeatRunnerConfig {\n const h = cfg?.gateway?.heartbeat;\n return {\n enabled: h?.enabled ?? true,\n intervalMs: h?.intervalMs ?? 1_800_000,\n target: h?.target,\n targetChatId: h?.targetChatId,\n prompt: h?.prompt,\n ackMaxChars: h?.ackMaxChars,\n isolatedSession: h?.isolatedSession,\n activeHours: h?.activeHours,\n };\n}\n\n/** Map persisted gateway config to runner options (gateway start / reload). */\nexport function heartbeatRunnerConfigFromConfig(cfg: Config): HeartbeatRunnerConfig {\n return mapConfigToRunner(cfg);\n}\n\nexport interface HeartbeatServiceDeps {\n agentService: AgentService;\n messageBus: MessageBus;\n cronService: CronService;\n sessionStore: SessionStore;\n /** Current app config (for HEARTBEAT.md path under the default agent `profile/` directory). */\n getConfig: () => Config;\n}\n\nexport class HeartbeatService {\n private intervalId: ReturnType<typeof setInterval> | null = null;\n private wake: ReturnType<typeof createHeartbeatWake>;\n private lastHeartbeatText = '';\n private lastHeartbeatAt = 0;\n private runnerConfig: HeartbeatRunnerConfig | null = null;\n\n constructor(private deps: HeartbeatServiceDeps) {\n this.wake = createHeartbeatWake((reasons) => this.runHeartbeatOnce(reasons));\n }\n\n start(config: HeartbeatRunnerConfig): void {\n if (!config.enabled) {\n log.info('Heartbeat disabled');\n this.runnerConfig = null;\n return;\n }\n\n this.runnerConfig = config;\n log.info({ intervalMs: config.intervalMs }, 'Heartbeat timer started (interval wake)');\n\n this.intervalId = setInterval(() => {\n this.wake.request({ reason: 'interval' });\n }, config.intervalMs);\n this.intervalId.unref?.();\n }\n\n /** Cron, exec completion, manual triggers, etc. */\n requestNow(opts?: { reason?: string }): void {\n this.wake.request({ reason: opts?.reason ?? 'manual' });\n }\n\n stop(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n this.wake.stop();\n this.runnerConfig = null;\n log.info('Heartbeat stopped');\n }\n\n updateConfig(config: Config): void {\n const mapped = mapConfigToRunner(config);\n this.stop();\n if (mapped.enabled) {\n this.start(mapped);\n }\n log.info('Heartbeat config updated');\n }\n\n isRunning(): boolean {\n return this.intervalId !== null;\n }\n\n private async runHeartbeatOnce(reasons: string[]): Promise<void> {\n const reasonSummary = [...new Set(reasons)].join(', ') || 'unknown';\n\n const cfg = this.runnerConfig;\n if (!cfg?.enabled) {\n log.debug({ reasons: reasonSummary }, 'Heartbeat: skip (disabled)');\n return;\n }\n\n try {\n const metrics = await this.deps.cronService.getMetrics();\n log.trace(\n { runningJobs: metrics.runningJobs, enabledJobs: metrics.enabledJobs },\n 'Heartbeat: cron metrics',\n );\n } catch {\n /* optional */\n }\n\n if (cfg.activeHours && !isWithinActiveHours(cfg.activeHours)) {\n log.debug({ reasons: reasonSummary }, 'Heartbeat: skip (outside active hours)');\n return;\n }\n\n const heartbeatPath = resolveHeartbeatMdPath(this.deps.getConfig());\n if (!heartbeatPath) {\n log.debug({ reasons: reasonSummary }, 'Heartbeat: skip (no HEARTBEAT path)');\n return;\n }\n let heartbeatContent: string | undefined;\n try {\n const raw = await readFile(heartbeatPath, 'utf-8');\n if (isHeartbeatContentEmpty(raw)) {\n log.debug({ path: heartbeatPath, reasons: reasonSummary }, 'Heartbeat: skip (HEARTBEAT.md empty)');\n return;\n }\n heartbeatContent = raw.trim();\n } catch {\n log.debug({ path: heartbeatPath, reasons: reasonSummary }, 'Heartbeat: HEARTBEAT.md missing; continuing');\n }\n\n const sessionKey = cfg.isolatedSession\n ? `heartbeat:isolated:${Date.now()}`\n : 'heartbeat:main';\n\n let basePrompt = (cfg.prompt?.trim() || DEFAULT_PROMPT).trim();\n if (heartbeatContent) {\n basePrompt = `${basePrompt}\\n\\n---\\nHEARTBEAT.md:\\n${heartbeatContent}\\n---`;\n }\n basePrompt = appendCronEventLines(basePrompt, reasons);\n const prompt = `${basePrompt}\\n\\nCurrent time: ${new Date().toISOString()}`;\n\n const ackMax = cfg.ackMaxChars ?? DEFAULT_ACK_MAX_CHARS;\n\n log.debug({ sessionKey, reasons: reasonSummary }, 'Heartbeat: invoking agent');\n\n // `heartbeat:main` reuses one session key; each run would otherwise append to the transcript\n // until the model rejects the request (context window exceeded). Heartbeat prompts are\n // self-contained (HEARTBEAT.md + this turn's text), so we start from an empty history.\n if (sessionKey === 'heartbeat:main') {\n try {\n await this.deps.sessionStore.saveMessages(sessionKey, [], {\n metadata: {\n sourceChannel: 'heartbeat',\n sourceChatId: 'main',\n sessionType: 'heartbeat',\n customData: { heartbeatTarget: 'main' },\n routing: {\n agentId: 'main',\n source: 'heartbeat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: 'main',\n },\n },\n });\n } catch (err) {\n log.warn({ err, sessionKey }, 'Heartbeat: failed to reset main session transcript');\n }\n }\n\n let reply: string;\n try {\n reply = await this.deps.agentService.turnDispatcher.processDirect(prompt, sessionKey);\n } catch (error) {\n log.error({ err: error }, 'Heartbeat: agent call failed');\n return;\n }\n\n if (!reply?.trim()) {\n log.debug({ reasons: reasonSummary }, 'Heartbeat: skip (empty model reply)');\n return;\n }\n\n if (shouldSilence(reply, ackMax)) {\n log.info(\n { ackMax, replyChars: reply.length, reasons: reasonSummary },\n 'Heartbeat: not sent — silent (HEARTBEAT_OK / short ack)',\n );\n return;\n }\n\n const { stripped } = stripHeartbeatToken(reply);\n const finalText = stripped || reply.trim();\n\n if (this.isDuplicate(finalText)) {\n log.info(\n { finalTextChars: finalText.length, reasons: reasonSummary },\n 'Heartbeat: not sent — duplicate within 24h',\n );\n return;\n }\n\n const target = cfg.target?.trim();\n const targetChatId = cfg.targetChatId?.trim();\n const hasDeliveryTarget = Boolean(target && targetChatId);\n\n if (hasDeliveryTarget) {\n await this.deps.messageBus.publishOutbound({\n channel: target!,\n chat_id: targetChatId!,\n content: finalText,\n type: 'message',\n });\n log.info(\n {\n reasons: reasonSummary,\n channel: target,\n chatId: targetChatId,\n contentChars: finalText.length,\n },\n 'Heartbeat: sent — outbound queued',\n );\n } else {\n log.info(\n { reasons: reasonSummary, finalTextChars: finalText.length },\n 'Heartbeat: not sent — no delivery target (set gateway.heartbeat.target + targetChatId)',\n );\n }\n\n this.lastHeartbeatText = finalText;\n this.lastHeartbeatAt = Date.now();\n }\n\n private isDuplicate(text: string): boolean {\n const DEDUP_WINDOW_MS = 24 * 60 * 60 * 1000;\n return (\n text.trim() === this.lastHeartbeatText.trim() &&\n Date.now() - this.lastHeartbeatAt < DEDUP_WINDOW_MS\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -142,8 +142,18 @@ function createHonoApp(config) {
|
|
|
142
142
|
}
|
|
143
143
|
return next();
|
|
144
144
|
}));
|
|
145
|
+
const DEFAULT_API_BODY_MAX = 1 * 1024 * 1024;
|
|
146
|
+
const SKILL_UPLOAD_BODY_MAX = 10 * 1024 * 1024;
|
|
147
|
+
const NOTE_MEDIA_BODY_MAX = 25 * 1024 * 1024;
|
|
148
|
+
const VOICE_TRANSCRIBE_BODY_MAX = 35 * 1024 * 1024;
|
|
149
|
+
const WEBCHAT_AGENT_BODY_MAX = maxWebchatAgentRequestBodyBytes();
|
|
150
|
+
const isNoteMediaUploadRequest = (path, method, contentType) => {
|
|
151
|
+
if (method !== "POST") return false;
|
|
152
|
+
if (/^\/api\/notes\/[^/]+\/media$/.test(path)) return true;
|
|
153
|
+
return path === "/api/notes" && contentType?.includes("multipart/form-data") === true;
|
|
154
|
+
};
|
|
145
155
|
app.use("/api/skills/upload", bodyLimit({
|
|
146
|
-
maxSize:
|
|
156
|
+
maxSize: SKILL_UPLOAD_BODY_MAX,
|
|
147
157
|
onError: (c) => {
|
|
148
158
|
log.warn({
|
|
149
159
|
path: c.req.path,
|
|
@@ -155,10 +165,9 @@ function createHonoApp(config) {
|
|
|
155
165
|
}, 413);
|
|
156
166
|
}
|
|
157
167
|
}));
|
|
158
|
-
const DEFAULT_API_BODY_MAX = 1 * 1024 * 1024;
|
|
159
|
-
const WEBCHAT_AGENT_BODY_MAX = maxWebchatAgentRequestBodyBytes();
|
|
160
168
|
app.use("/api/*", async (c, next) => {
|
|
161
|
-
const
|
|
169
|
+
const contentType = c.req.header("content-type");
|
|
170
|
+
const maxSize = c.req.path === "/api/agent" ? WEBCHAT_AGENT_BODY_MAX : c.req.path === "/api/skills/upload" ? SKILL_UPLOAD_BODY_MAX : c.req.path === "/api/voice/transcribe" ? VOICE_TRANSCRIBE_BODY_MAX : isNoteMediaUploadRequest(c.req.path, c.req.method, contentType) ? NOTE_MEDIA_BODY_MAX : DEFAULT_API_BODY_MAX;
|
|
162
171
|
const maxSizeMb = Math.ceil(maxSize / (1024 * 1024));
|
|
163
172
|
return bodyLimit({
|
|
164
173
|
maxSize,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","names":[],"sources":["../../../../src/gateway/hono/app.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context } from 'hono';\nimport { cors } from 'hono/cors';\nimport { createMiddleware } from 'hono/factory';\nimport { bodyLimit } from 'hono/body-limit';\nimport { getConnInfo } from '@hono/node-server/conninfo';\n\nimport { resolveGatewayEffectiveHost } from '../../config/gateway-bind.js';\nimport { createLogger } from '../../utils/logger.js';\nimport type { GatewayService } from '../service.js';\nimport { resolveAllowedBrowserOrigins, resolveGatewayServiceListenPort } from '../host.js';\nimport { loadTunnelState } from '../../tunnel/tunnel-state.js';\nimport { maxWebchatAgentRequestBodyBytes } from '../chat-limits.js';\nimport { buildGatewayConsoleCspHeader } from '../security/csp.js';\nimport { checkBrowserOrigin } from '../security/origin-check.js';\nimport { isLoopbackIpAddress, isTrustedProxyAddress } from '../client-ip.js';\nimport { resolveReverseProxyPublicUrl } from '../public-url.js';\nimport { auth } from './middleware/auth.js';\nimport { operatorScopes } from './middleware/scopes.js';\nimport { createStrictRateLimitMiddleware } from './middleware/strict-rate-limit.js';\nimport { logContextMiddleware } from './middleware/log-context.js';\nimport { logger } from './middleware/logger.js';\nimport { routeErrorMiddleware } from './middleware/route-errors.js';\nimport { registerPublicExtensionAssetRoutes } from './routes/auth-registry-extensions.js';\nimport { registerAuthenticatedRoutes } from './routes/index.js';\nimport { registerPublicGatewayRoutes } from './routes/public-gateway.js';\nimport { resetLazyRouteBundlesForTests } from './routes/lazy-fallback.js';\nimport { prewarmStaticUiCache } from './lib/static-ui.js';\nimport { registerSiteShareMiddleware } from '../../share/site-share-router.js';\nconst log = createLogger('Gateway:App');\n\nexport interface HonoAppConfig {\n service: GatewayService;\n token?: string;\n}\n\n/**\n * Extension sandbox HTML under `/api/extensions/:id/assets/*` ships its own CSP\n * (`frame-ancestors 'self'`). The global gateway middleware must not overwrite it\n * with `frame-ancestors 'none'` / `X-Frame-Options: DENY`, or the console cannot embed iframes.\n */\nexport function isExtensionGatewayUiAssetPath(path: string): boolean {\n return /^\\/api\\/extensions\\/[^/]+\\/assets\\//.test(path);\n}\n\nexport function createHonoApp(config: HonoAppConfig): Hono {\n if (process.env.VITEST) {\n resetLazyRouteBundlesForTests();\n }\n const { service, token } = config;\n const app = new Hono();\n\n const gatewayPort = resolveGatewayServiceListenPort(service);\n\n const resolveBrowserOrigins = (): string[] =>\n resolveAllowedBrowserOrigins({\n configuredOrigins: service.currentConfig.gateway.corsOrigins,\n port: gatewayPort,\n bindHost: resolveGatewayEffectiveHost(service.currentConfig),\n tunnelPublicUrl: loadTunnelState()?.publicUrl,\n reverseProxyPublicUrl: resolveReverseProxyPublicUrl(service.currentConfig),\n });\n\n /**\n * TCP source for the in-flight request, normalized for trusted-proxy checks.\n * Returns undefined when the runtime doesn't expose conninfo (tests, mocks).\n */\n const resolveRequestRemoteAddress = (c: Context): string | undefined => {\n try {\n return getConnInfo(c).remote.address;\n } catch {\n return undefined;\n }\n };\n\n /**\n * A request's TCP source qualifies as a \"trusted proxy hop\" when it's\n * loopback (the user's own machine, where any reverse proxy lives) or\n * listed in `gateway.trustedProxies`. We use this signal to safely\n * auto-allow same-host Origins through CSRF without requiring a manual\n * `corsOrigins` entry for every reverse-proxy hostname.\n */\n const isRequestFromTrustedProxy = (c: Context): boolean => {\n const remote = resolveRequestRemoteAddress(c);\n if (!remote) return false;\n if (isLoopbackIpAddress(remote)) return true;\n return isTrustedProxyAddress(remote, service.currentConfig.gateway?.trustedProxies);\n };\n\n app.use(logContextMiddleware());\n app.use(logger({\n trustedProxies: service.currentConfig.gateway?.trustedProxies,\n allowRealIpFallback: service.currentConfig.gateway?.allowRealIpFallback === true,\n }));\n\n // Site-share middleware runs BEFORE CORS/CSP — it owns the request when the\n // Host header matches `*.<publicHostSuffix>` (default `*.share.xopc.ai`) or\n // the path starts with `/site/:token/`. Otherwise it falls through.\n registerSiteShareMiddleware(app, service);\n app.use(\n cors({\n origin: (origin) => {\n const allowed = resolveBrowserOrigins();\n if (!origin) {\n return allowed[0] ?? `http://127.0.0.1:${gatewayPort}`;\n }\n const normalized = origin.toLowerCase();\n const hit = allowed.find((entry) => entry.toLowerCase() === normalized);\n if (hit) return origin;\n return allowed.includes('*') ? '*' : '';\n },\n allowMethods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'X-Session-Id', 'Last-Event-ID'],\n credentials: true,\n maxAge: 86400,\n }),\n );\n\n // Build CSP header once at startup (no inline script hashes needed for SPA)\n const gatewayConsoleCsp = buildGatewayConsoleCspHeader();\n\n // Security headers middleware\n app.use(createMiddleware(async (c, next) => {\n await next();\n if (isExtensionGatewayUiAssetPath(c.req.path)) {\n return;\n }\n c.header('X-Frame-Options', 'DENY');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Referrer-Policy', 'strict-origin-when-cross-origin');\n c.header('X-XSS-Protection', '1; mode=block');\n // microphone=(self): allow same-origin chat voice (composer). microphone=() breaks packaged Electron loading the gateway SPA.\n c.header('Permissions-Policy', 'camera=(), microphone=(self), geolocation=()');\n c.header('Content-Security-Policy', gatewayConsoleCsp);\n }));\n\n // Browser Origin check middleware for API routes (CSRF protection).\n // Non-browser requests (no Origin header) pass through — they are\n // authenticated by the token middleware instead.\n const allowHostHeaderOriginFallback =\n service.currentConfig.gateway?.dangerouslyAllowHostHeaderOriginFallback === true;\n app.use('/api/*', createMiddleware(async (c, next) => {\n // Sandboxed extension iframes (no allow-same-origin) send `Origin: null`.\n // `checkBrowserOrigin` rejects that; these routes rely on CSP instead\n // (`registerPublicExtensionAssetRoutes`).\n if (isExtensionGatewayUiAssetPath(c.req.path)) {\n return next();\n }\n\n const origin = c.req.header('origin');\n if (!origin || origin.trim().toLowerCase() === 'null') {\n // Native apps / opaque origins — authenticated via Bearer token\n return next();\n }\n\n const result = checkBrowserOrigin({\n requestHost: c.req.header('host'),\n origin,\n allowedOrigins: resolveBrowserOrigins(),\n allowHostHeaderOriginFallback,\n autoAllowSameHostFromTrustedProxy: isRequestFromTrustedProxy(c),\n isLocalClient: false,\n });\n\n if (!result.ok) {\n log.warn(\n {\n origin,\n requestHost: c.req.header('host'),\n reason: 'reason' in result ? result.reason : 'unknown',\n path: c.req.path,\n method: c.req.method,\n },\n `Browser origin check failed: ${origin} not in allowed list`,\n );\n return c.json({ error: 'Forbidden', message: 'Origin not allowed' }, 403);\n }\n\n return next();\n }));\n\n app.use('/api/skills/upload', bodyLimit({\n maxSize: 10 * 1024 * 1024,\n onError: (c) => {\n log.warn({ path: c.req.path, maxSizeMb: 10 }, 'Request body too large: skills upload exceeds 10MB limit');\n return c.json({ error: 'Skill package too large', maxSize: '10MB' }, 413);\n },\n }));\n\n const DEFAULT_API_BODY_MAX = 1 * 1024 * 1024;\n const WEBCHAT_AGENT_BODY_MAX = maxWebchatAgentRequestBodyBytes();\n\n app.use('/api/*', async (c, next) => {\n const maxSize = c.req.path === '/api/agent' ? WEBCHAT_AGENT_BODY_MAX : DEFAULT_API_BODY_MAX;\n const maxSizeMb = Math.ceil(maxSize / (1024 * 1024));\n return bodyLimit({\n maxSize,\n onError: (ctx) => {\n log.warn({ path: ctx.req.path, maxSizeMb }, `Request body too large: exceeds ${maxSizeMb}MB limit`);\n return ctx.json({ error: 'Request body too large', maxSize: `${maxSizeMb}MB` }, 413);\n },\n })(c, next);\n });\n\n registerPublicGatewayRoutes(app, service);\n\n // Extension UI assets are served without auth: sandboxed iframes (no allow-same-origin)\n // have an opaque origin of `null` and cannot forward the ?token= from the parent HTML URL.\n // Security is enforced by the strict CSP (frame-ancestors 'self') on every response.\n registerPublicExtensionAssetRoutes(app, service);\n\n const authenticated = new Hono();\n authenticated.use(routeErrorMiddleware());\n authenticated.use(\n auth({\n token,\n getGatewayAuth: () => service.currentConfig.gateway?.auth,\n getResolvedAuth: () => {\n if (typeof service.getResolvedAuth === 'function') {\n return service.getResolvedAuth();\n }\n return token ? { mode: 'token', token } : { mode: 'none' };\n },\n getTrustedProxyContext: () => ({\n trustedProxies: service.currentConfig.gateway?.trustedProxies,\n allowRealIpFallback: service.currentConfig.gateway?.allowRealIpFallback === true,\n }),\n }),\n );\n authenticated.use(operatorScopes());\n\n const strictRateLimitMiddleware = createStrictRateLimitMiddleware({\n getTrustedProxyContext: () => ({\n trustedProxies: service.currentConfig.gateway?.trustedProxies,\n allowRealIpFallback: service.currentConfig.gateway?.allowRealIpFallback === true,\n }),\n });\n\n const sseConfig = {\n service,\n maxSseConnections: service.currentConfig.gateway.maxSseConnections,\n };\n\n registerAuthenticatedRoutes(app, authenticated, {\n service,\n strictRateLimitMiddleware,\n sseConfig,\n });\n\n const prewarm = prewarmStaticUiCache();\n if (prewarm.loaded > 0) {\n log.debug({ loaded: prewarm.loaded, missing: prewarm.missing }, 'Static UI cache prewarmed');\n }\n\n app.route('/', authenticated);\n\n app.notFound((c) => {\n const isApiRoute = c.req.path.startsWith('/api/');\n const fields = { path: c.req.path, method: c.req.method };\n if (isApiRoute) {\n log.warn(fields, 'Route not found');\n } else {\n log.debug(fields, 'Route not found');\n }\n return c.json({ error: 'Not found' }, 404);\n });\n\n app.onError((err, c) => {\n log.error(\n {\n err,\n phase: 'gateway.http.unhandled',\n path: c.req.path,\n method: c.req.method,\n userAgent: c.req.header('user-agent'),\n },\n `Hono error on ${c.req.method} ${c.req.path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return c.json({ error: 'Internal server error' }, 500);\n });\n\n return app;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app.js","names":[],"sources":["../../../../src/gateway/hono/app.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context } from 'hono';\nimport { cors } from 'hono/cors';\nimport { createMiddleware } from 'hono/factory';\nimport { bodyLimit } from 'hono/body-limit';\nimport { getConnInfo } from '@hono/node-server/conninfo';\n\nimport { resolveGatewayEffectiveHost } from '../../config/gateway-bind.js';\nimport { createLogger } from '../../utils/logger.js';\nimport type { GatewayService } from '../service.js';\nimport { resolveAllowedBrowserOrigins, resolveGatewayServiceListenPort } from '../host.js';\nimport { loadTunnelState } from '../../tunnel/tunnel-state.js';\nimport { maxWebchatAgentRequestBodyBytes } from '../chat-limits.js';\nimport { buildGatewayConsoleCspHeader } from '../security/csp.js';\nimport { checkBrowserOrigin } from '../security/origin-check.js';\nimport { isLoopbackIpAddress, isTrustedProxyAddress } from '../client-ip.js';\nimport { resolveReverseProxyPublicUrl } from '../public-url.js';\nimport { auth } from './middleware/auth.js';\nimport { operatorScopes } from './middleware/scopes.js';\nimport { createStrictRateLimitMiddleware } from './middleware/strict-rate-limit.js';\nimport { logContextMiddleware } from './middleware/log-context.js';\nimport { logger } from './middleware/logger.js';\nimport { routeErrorMiddleware } from './middleware/route-errors.js';\nimport { registerPublicExtensionAssetRoutes } from './routes/auth-registry-extensions.js';\nimport { registerAuthenticatedRoutes } from './routes/index.js';\nimport { registerPublicGatewayRoutes } from './routes/public-gateway.js';\nimport { resetLazyRouteBundlesForTests } from './routes/lazy-fallback.js';\nimport { prewarmStaticUiCache } from './lib/static-ui.js';\nimport { registerSiteShareMiddleware } from '../../share/site-share-router.js';\nconst log = createLogger('Gateway:App');\n\nexport interface HonoAppConfig {\n service: GatewayService;\n token?: string;\n}\n\n/**\n * Extension sandbox HTML under `/api/extensions/:id/assets/*` ships its own CSP\n * (`frame-ancestors 'self'`). The global gateway middleware must not overwrite it\n * with `frame-ancestors 'none'` / `X-Frame-Options: DENY`, or the console cannot embed iframes.\n */\nexport function isExtensionGatewayUiAssetPath(path: string): boolean {\n return /^\\/api\\/extensions\\/[^/]+\\/assets\\//.test(path);\n}\n\nexport function createHonoApp(config: HonoAppConfig): Hono {\n if (process.env.VITEST) {\n resetLazyRouteBundlesForTests();\n }\n const { service, token } = config;\n const app = new Hono();\n\n const gatewayPort = resolveGatewayServiceListenPort(service);\n\n const resolveBrowserOrigins = (): string[] =>\n resolveAllowedBrowserOrigins({\n configuredOrigins: service.currentConfig.gateway.corsOrigins,\n port: gatewayPort,\n bindHost: resolveGatewayEffectiveHost(service.currentConfig),\n tunnelPublicUrl: loadTunnelState()?.publicUrl,\n reverseProxyPublicUrl: resolveReverseProxyPublicUrl(service.currentConfig),\n });\n\n /**\n * TCP source for the in-flight request, normalized for trusted-proxy checks.\n * Returns undefined when the runtime doesn't expose conninfo (tests, mocks).\n */\n const resolveRequestRemoteAddress = (c: Context): string | undefined => {\n try {\n return getConnInfo(c).remote.address;\n } catch {\n return undefined;\n }\n };\n\n /**\n * A request's TCP source qualifies as a \"trusted proxy hop\" when it's\n * loopback (the user's own machine, where any reverse proxy lives) or\n * listed in `gateway.trustedProxies`. We use this signal to safely\n * auto-allow same-host Origins through CSRF without requiring a manual\n * `corsOrigins` entry for every reverse-proxy hostname.\n */\n const isRequestFromTrustedProxy = (c: Context): boolean => {\n const remote = resolveRequestRemoteAddress(c);\n if (!remote) return false;\n if (isLoopbackIpAddress(remote)) return true;\n return isTrustedProxyAddress(remote, service.currentConfig.gateway?.trustedProxies);\n };\n\n app.use(logContextMiddleware());\n app.use(logger({\n trustedProxies: service.currentConfig.gateway?.trustedProxies,\n allowRealIpFallback: service.currentConfig.gateway?.allowRealIpFallback === true,\n }));\n\n // Site-share middleware runs BEFORE CORS/CSP — it owns the request when the\n // Host header matches `*.<publicHostSuffix>` (default `*.share.xopc.ai`) or\n // the path starts with `/site/:token/`. Otherwise it falls through.\n registerSiteShareMiddleware(app, service);\n app.use(\n cors({\n origin: (origin) => {\n const allowed = resolveBrowserOrigins();\n if (!origin) {\n return allowed[0] ?? `http://127.0.0.1:${gatewayPort}`;\n }\n const normalized = origin.toLowerCase();\n const hit = allowed.find((entry) => entry.toLowerCase() === normalized);\n if (hit) return origin;\n return allowed.includes('*') ? '*' : '';\n },\n allowMethods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'X-Session-Id', 'Last-Event-ID'],\n credentials: true,\n maxAge: 86400,\n }),\n );\n\n // Build CSP header once at startup (no inline script hashes needed for SPA)\n const gatewayConsoleCsp = buildGatewayConsoleCspHeader();\n\n // Security headers middleware\n app.use(createMiddleware(async (c, next) => {\n await next();\n if (isExtensionGatewayUiAssetPath(c.req.path)) {\n return;\n }\n c.header('X-Frame-Options', 'DENY');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Referrer-Policy', 'strict-origin-when-cross-origin');\n c.header('X-XSS-Protection', '1; mode=block');\n // microphone=(self): allow same-origin chat voice (composer). microphone=() breaks packaged Electron loading the gateway SPA.\n c.header('Permissions-Policy', 'camera=(), microphone=(self), geolocation=()');\n c.header('Content-Security-Policy', gatewayConsoleCsp);\n }));\n\n // Browser Origin check middleware for API routes (CSRF protection).\n // Non-browser requests (no Origin header) pass through — they are\n // authenticated by the token middleware instead.\n const allowHostHeaderOriginFallback =\n service.currentConfig.gateway?.dangerouslyAllowHostHeaderOriginFallback === true;\n app.use('/api/*', createMiddleware(async (c, next) => {\n // Sandboxed extension iframes (no allow-same-origin) send `Origin: null`.\n // `checkBrowserOrigin` rejects that; these routes rely on CSP instead\n // (`registerPublicExtensionAssetRoutes`).\n if (isExtensionGatewayUiAssetPath(c.req.path)) {\n return next();\n }\n\n const origin = c.req.header('origin');\n if (!origin || origin.trim().toLowerCase() === 'null') {\n // Native apps / opaque origins — authenticated via Bearer token\n return next();\n }\n\n const result = checkBrowserOrigin({\n requestHost: c.req.header('host'),\n origin,\n allowedOrigins: resolveBrowserOrigins(),\n allowHostHeaderOriginFallback,\n autoAllowSameHostFromTrustedProxy: isRequestFromTrustedProxy(c),\n isLocalClient: false,\n });\n\n if (!result.ok) {\n log.warn(\n {\n origin,\n requestHost: c.req.header('host'),\n reason: 'reason' in result ? result.reason : 'unknown',\n path: c.req.path,\n method: c.req.method,\n },\n `Browser origin check failed: ${origin} not in allowed list`,\n );\n return c.json({ error: 'Forbidden', message: 'Origin not allowed' }, 403);\n }\n\n return next();\n }));\n\n const DEFAULT_API_BODY_MAX = 1 * 1024 * 1024;\n const SKILL_UPLOAD_BODY_MAX = 10 * 1024 * 1024;\n const NOTE_MEDIA_BODY_MAX = 25 * 1024 * 1024;\n const VOICE_TRANSCRIBE_BODY_MAX = 35 * 1024 * 1024;\n const WEBCHAT_AGENT_BODY_MAX = maxWebchatAgentRequestBodyBytes();\n\n const isNoteMediaUploadRequest = (path: string, method: string, contentType: string | undefined): boolean => {\n if (method !== 'POST') return false;\n if (/^\\/api\\/notes\\/[^/]+\\/media$/.test(path)) return true;\n return path === '/api/notes' && contentType?.includes('multipart/form-data') === true;\n };\n\n app.use('/api/skills/upload', bodyLimit({\n maxSize: SKILL_UPLOAD_BODY_MAX,\n onError: (c) => {\n log.warn({ path: c.req.path, maxSizeMb: 10 }, 'Request body too large: skills upload exceeds 10MB limit');\n return c.json({ error: 'Skill package too large', maxSize: '10MB' }, 413);\n },\n }));\n\n app.use('/api/*', async (c, next) => {\n const contentType = c.req.header('content-type');\n const maxSize = c.req.path === '/api/agent'\n ? WEBCHAT_AGENT_BODY_MAX\n : c.req.path === '/api/skills/upload'\n ? SKILL_UPLOAD_BODY_MAX\n : c.req.path === '/api/voice/transcribe'\n ? VOICE_TRANSCRIBE_BODY_MAX\n : isNoteMediaUploadRequest(c.req.path, c.req.method, contentType)\n ? NOTE_MEDIA_BODY_MAX\n : DEFAULT_API_BODY_MAX;\n const maxSizeMb = Math.ceil(maxSize / (1024 * 1024));\n return bodyLimit({\n maxSize,\n onError: (ctx) => {\n log.warn({ path: ctx.req.path, maxSizeMb }, `Request body too large: exceeds ${maxSizeMb}MB limit`);\n return ctx.json({ error: 'Request body too large', maxSize: `${maxSizeMb}MB` }, 413);\n },\n })(c, next);\n });\n\n registerPublicGatewayRoutes(app, service);\n\n // Extension UI assets are served without auth: sandboxed iframes (no allow-same-origin)\n // have an opaque origin of `null` and cannot forward the ?token= from the parent HTML URL.\n // Security is enforced by the strict CSP (frame-ancestors 'self') on every response.\n registerPublicExtensionAssetRoutes(app, service);\n\n const authenticated = new Hono();\n authenticated.use(routeErrorMiddleware());\n authenticated.use(\n auth({\n token,\n getGatewayAuth: () => service.currentConfig.gateway?.auth,\n getResolvedAuth: () => {\n if (typeof service.getResolvedAuth === 'function') {\n return service.getResolvedAuth();\n }\n return token ? { mode: 'token', token } : { mode: 'none' };\n },\n getTrustedProxyContext: () => ({\n trustedProxies: service.currentConfig.gateway?.trustedProxies,\n allowRealIpFallback: service.currentConfig.gateway?.allowRealIpFallback === true,\n }),\n }),\n );\n authenticated.use(operatorScopes());\n\n const strictRateLimitMiddleware = createStrictRateLimitMiddleware({\n getTrustedProxyContext: () => ({\n trustedProxies: service.currentConfig.gateway?.trustedProxies,\n allowRealIpFallback: service.currentConfig.gateway?.allowRealIpFallback === true,\n }),\n });\n\n const sseConfig = {\n service,\n maxSseConnections: service.currentConfig.gateway.maxSseConnections,\n };\n\n registerAuthenticatedRoutes(app, authenticated, {\n service,\n strictRateLimitMiddleware,\n sseConfig,\n });\n\n const prewarm = prewarmStaticUiCache();\n if (prewarm.loaded > 0) {\n log.debug({ loaded: prewarm.loaded, missing: prewarm.missing }, 'Static UI cache prewarmed');\n }\n\n app.route('/', authenticated);\n\n app.notFound((c) => {\n const isApiRoute = c.req.path.startsWith('/api/');\n const fields = { path: c.req.path, method: c.req.method };\n if (isApiRoute) {\n log.warn(fields, 'Route not found');\n } else {\n log.debug(fields, 'Route not found');\n }\n return c.json({ error: 'Not found' }, 404);\n });\n\n app.onError((err, c) => {\n log.error(\n {\n err,\n phase: 'gateway.http.unhandled',\n path: c.req.path,\n method: c.req.method,\n userAgent: c.req.header('user-agent'),\n },\n `Hono error on ${c.req.method} ${c.req.path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return c.json({ error: 'Internal server error' }, 500);\n });\n\n return app;\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createLogger } from "../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
3
|
import { CredentialResolver, init_credentials } from "../../auth/credentials.js";
|
|
4
|
-
import { anthropicOAuthProvider } from "../../auth/oauth/anthropic.js";
|
|
5
4
|
import { isLoopbackHost } from "../host.js";
|
|
6
5
|
import { resolveReverseProxyPublicUrl } from "../public-url.js";
|
|
6
|
+
import { anthropicOAuthProvider } from "../../auth/oauth/anthropic.js";
|
|
7
7
|
import { minimaxOAuthProvider } from "../../auth/oauth/minimax.js";
|
|
8
8
|
import { minimaxCnOAuthProvider } from "../../auth/oauth/minimax-cn.js";
|
|
9
9
|
import { kimiCodingOAuthProvider } from "../../auth/oauth/kimi-coding.js";
|
|
@@ -31,24 +31,24 @@ function registerAgentStreamRoutes(authenticated, deps) {
|
|
|
31
31
|
message: "Invalid JSON body"
|
|
32
32
|
}
|
|
33
33
|
}, 400);
|
|
34
|
-
const
|
|
34
|
+
const sessionKey = typeof body.sessionKey === "string" ? body.sessionKey.trim() : "";
|
|
35
35
|
const message = typeof body.message === "string" ? body.message : "";
|
|
36
|
-
if (!
|
|
36
|
+
if (!sessionKey) return c.json({
|
|
37
37
|
ok: false,
|
|
38
38
|
error: {
|
|
39
39
|
code: "BAD_REQUEST",
|
|
40
|
-
message: "Missing
|
|
40
|
+
message: "Missing sessionKey"
|
|
41
41
|
}
|
|
42
42
|
}, 400);
|
|
43
|
-
const result = await service.steerWebchatAgent(
|
|
43
|
+
const result = await service.steerWebchatAgent(sessionKey, message);
|
|
44
44
|
if (result.ok === false) {
|
|
45
45
|
logRouteWarn(log, c, `Agent steer failed: ${result.code}`, "gateway.route.agent", {
|
|
46
|
-
|
|
46
|
+
sessionKey,
|
|
47
47
|
code: result.code
|
|
48
48
|
});
|
|
49
49
|
const code = result.code;
|
|
50
50
|
const status = code === "BAD_REQUEST" ? 400 : code === "NO_ACTIVE_RUN" ? 409 : 500;
|
|
51
|
-
const msg = code === "NO_ACTIVE_RUN" ? "No active agent run for this
|
|
51
|
+
const msg = code === "NO_ACTIVE_RUN" ? "No active agent run for this session" : code === "STEER_FAILED" ? "Steer failed" : "Message required";
|
|
52
52
|
return c.json({
|
|
53
53
|
ok: false,
|
|
54
54
|
error: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-stream.js","names":[],"sources":["../../../../../src/gateway/hono/routes/agent-stream.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport {\n createAgentResumeHandler,\n createAgentSSEHandler,\n createEventsSSEHandler,\n createSendHandler,\n} from '../sse.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\nimport { createGatewayRouteLogger, logRouteWarn } from '../lib/route-logger.js';\n\nconst log = createGatewayRouteLogger('AgentStream');\n\nexport function registerAgentStreamRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware, sseConfig } = deps;\n\n authenticated.post('/api/agent', strictRateLimitMiddleware, createAgentSSEHandler(sseConfig));\n\n authenticated.post('/api/agent/resume', strictRateLimitMiddleware, createAgentResumeHandler(sseConfig));\n\n authenticated.post('/api/agent/abort', strictRateLimitMiddleware, async (c) => {\n const body = await c.req.json().catch(() => null);\n const runId =\n body && typeof body === 'object' && typeof (body as { runId?: unknown }).runId === 'string'\n ? (body as { runId: string }).runId.trim()\n : '';\n if (!runId) {\n return c.json(\n { ok: false, error: { code: 'BAD_REQUEST', message: 'Missing runId' } },\n 400,\n );\n }\n const aborted = service.abortAgentRun(runId);\n return c.json({ ok: true, payload: { aborted } });\n });\n\n authenticated.post('/api/agent/steer', strictRateLimitMiddleware, async (c) => {\n const body = await c.req.json().catch(() => null);\n if (!body || typeof body !== 'object') {\n return c.json(\n { ok: false, error: { code: 'BAD_REQUEST', message: 'Invalid JSON body' } },\n 400,\n );\n }\n const
|
|
1
|
+
{"version":3,"file":"agent-stream.js","names":[],"sources":["../../../../../src/gateway/hono/routes/agent-stream.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport {\n createAgentResumeHandler,\n createAgentSSEHandler,\n createEventsSSEHandler,\n createSendHandler,\n} from '../sse.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\nimport { createGatewayRouteLogger, logRouteWarn } from '../lib/route-logger.js';\n\nconst log = createGatewayRouteLogger('AgentStream');\n\nexport function registerAgentStreamRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware, sseConfig } = deps;\n\n authenticated.post('/api/agent', strictRateLimitMiddleware, createAgentSSEHandler(sseConfig));\n\n authenticated.post('/api/agent/resume', strictRateLimitMiddleware, createAgentResumeHandler(sseConfig));\n\n authenticated.post('/api/agent/abort', strictRateLimitMiddleware, async (c) => {\n const body = await c.req.json().catch(() => null);\n const runId =\n body && typeof body === 'object' && typeof (body as { runId?: unknown }).runId === 'string'\n ? (body as { runId: string }).runId.trim()\n : '';\n if (!runId) {\n return c.json(\n { ok: false, error: { code: 'BAD_REQUEST', message: 'Missing runId' } },\n 400,\n );\n }\n const aborted = service.abortAgentRun(runId);\n return c.json({ ok: true, payload: { aborted } });\n });\n\n authenticated.post('/api/agent/steer', strictRateLimitMiddleware, async (c) => {\n const body = await c.req.json().catch(() => null);\n if (!body || typeof body !== 'object') {\n return c.json(\n { ok: false, error: { code: 'BAD_REQUEST', message: 'Invalid JSON body' } },\n 400,\n );\n }\n const sessionKey =\n typeof (body as { sessionKey?: unknown }).sessionKey === 'string'\n ? (body as { sessionKey: string }).sessionKey.trim()\n : '';\n const message =\n typeof (body as { message?: unknown }).message === 'string'\n ? (body as { message: string }).message\n : '';\n if (!sessionKey) {\n return c.json(\n { ok: false, error: { code: 'BAD_REQUEST', message: 'Missing sessionKey' } },\n 400,\n );\n }\n const result = await service.steerWebchatAgent(sessionKey, message);\n if (result.ok === false) {\n logRouteWarn(log, c, `Agent steer failed: ${result.code}`, 'gateway.route.agent', { sessionKey, code: result.code });\n const code = result.code;\n const status = code === 'BAD_REQUEST' ? 400 : code === 'NO_ACTIVE_RUN' ? 409 : 500;\n const msg =\n code === 'NO_ACTIVE_RUN'\n ? 'No active agent run for this session'\n : code === 'STEER_FAILED'\n ? 'Steer failed'\n : 'Message required';\n return c.json({ ok: false, error: { code, message: msg } }, status);\n }\n return c.json({ ok: true, payload: { steered: true } });\n });\n\n authenticated.post('/api/clarify/:requestId', strictRateLimitMiddleware, async (c) => {\n const requestId = c.req.param('requestId')?.trim() ?? '';\n if (!requestId) {\n return c.json(\n { ok: false, error: { code: 'BAD_REQUEST', message: 'Missing requestId' } },\n 400,\n );\n }\n const body = await c.req.json().catch(() => null);\n const skip =\n body &&\n typeof body === 'object' &&\n (body as { skip?: unknown }).skip === true;\n const rawAnswer =\n body && typeof body === 'object' && typeof (body as { answer?: unknown }).answer === 'string'\n ? (body as { answer: string }).answer\n : '';\n const answer = typeof rawAnswer === 'string' ? rawAnswer.trim() : '';\n if (!skip && !answer) {\n return c.json(\n { ok: false, error: { code: 'BAD_REQUEST', message: 'Missing answer field' } },\n 400,\n );\n }\n const handled = service.submitClarifyResponse(requestId, skip ? '' : answer);\n if (!handled) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'No pending clarification with this ID' } },\n 404,\n );\n }\n return c.json({ ok: true, payload: { received: true } });\n });\n\n authenticated.post('/api/send', strictRateLimitMiddleware, createSendHandler(sseConfig));\n\n authenticated.get('/api/events', createEventsSSEHandler(sseConfig));\n}\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Covers heartbeat, bind/customBindHost/port, tailscale, auth (mode + token +
|
|
5
5
|
* password + rateLimit + trustedProxy), trustedProxies, allowRealIpFallback,
|
|
6
|
-
* dangerouslyAllowHostHeaderOriginFallback, security, share, publicUrl,
|
|
6
|
+
* dangerouslyAllowHostHeaderOriginFallback, security, share, siteShare, publicUrl,
|
|
7
7
|
* corsOrigins, maxSseConnections, and channelConnectDefer{Mode,Ids,SkipIds}.
|
|
8
8
|
*
|
|
9
9
|
* Validation policy: each subsection that can reject rejects with a 400 and a
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { init_public_url, validatePublicUrl } from "../../../../config/public-url.js";
|
|
2
2
|
import { isValidIPv4 } from "../../../../config/gateway-bind.js";
|
|
3
3
|
import { mergeShareConfigPatch } from "../../../../share/share-config.js";
|
|
4
|
+
import { mergeSiteShareConfigPatch } from "../../../../share/site-share-config.js";
|
|
4
5
|
import { isMaskedSecretPatchValue } from "../../lib/mask-secret-length.js";
|
|
5
6
|
import { PATCH_OK, patchError } from "./result.js";
|
|
6
7
|
//#region src/gateway/hono/routes/config-patch/gateway.ts
|
|
@@ -221,6 +222,11 @@ function applyGatewayPatch(config, body) {
|
|
|
221
222
|
const shareResult = mergeShareConfigPatch(config, body.gateway.share);
|
|
222
223
|
if (shareResult.ok === false) return patchError(shareResult.message);
|
|
223
224
|
}
|
|
225
|
+
if (body.gateway?.siteShare !== void 0) {
|
|
226
|
+
if (typeof body.gateway.siteShare !== "object" || body.gateway.siteShare === null || Array.isArray(body.gateway.siteShare)) return patchError("gateway.siteShare must be an object");
|
|
227
|
+
const siteShareResult = mergeSiteShareConfigPatch(config, body.gateway.siteShare);
|
|
228
|
+
if (siteShareResult.ok === false) return patchError(siteShareResult.message);
|
|
229
|
+
}
|
|
224
230
|
if (body.gateway?.publicUrl !== void 0) {
|
|
225
231
|
const gw = ensureGateway(config);
|
|
226
232
|
if (body.gateway.publicUrl === null || body.gateway.publicUrl === "") delete gw.publicUrl;
|