@xopcai/xopc 0.0.110 → 0.0.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -4
- package/README.zh-CN.md +46 -4
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/Combination-BzaXOLd9.js +41 -0
- package/dist/gateway/static/root/assets/agent-avatar-dicebear-zlx9l5Yp.js +1 -0
- package/dist/gateway/static/root/assets/agent-browser-settings-page-B-0zGjt8.js +6 -0
- package/dist/gateway/static/root/assets/agent-defaults-tabbed-page-Bz7JWjRM.js +1 -0
- package/dist/gateway/static/root/assets/agents-DwDnJlXT.js +71 -0
- package/dist/gateway/static/root/assets/app-management-settings-panel-DaQwdeCq.js +1 -0
- package/dist/gateway/static/root/assets/appearance-settings-D2zv6r3P.js +1 -0
- package/dist/gateway/static/root/assets/apps-page-BIppe_jj.js +1 -0
- package/dist/gateway/static/root/assets/archive-plugin-DEmu8eH7.js +1 -0
- package/dist/gateway/static/root/assets/attachment-load-B98lJoN5.js +2 -0
- package/dist/gateway/static/root/assets/{attachment-preview-renderer-CybMSIoS.js → attachment-preview-renderer-DonnL2dL.js} +1 -1
- package/dist/gateway/static/root/assets/binary-plugins-CL9QKmyo.js +2 -0
- package/dist/gateway/static/root/assets/block-editor-BOAhMAhT.js +180 -0
- package/dist/gateway/static/root/assets/channels-settings-Br8iRnXp.js +1 -0
- package/dist/gateway/static/root/assets/cn-JdwAhgNB.js +1 -0
- package/dist/gateway/static/root/assets/code-editor-dZTY2dRW.js +1 -0
- package/dist/gateway/static/root/assets/config-api-0o1WYdz2.js +3 -0
- package/dist/gateway/static/root/assets/connectors-settings-DDvHo9in.js +2 -0
- package/dist/gateway/static/root/assets/cron-api-Dj_UZKST.js +1 -0
- package/dist/gateway/static/root/assets/cron-dreaming-jobs-nCMWCf0p.js +1 -0
- package/dist/gateway/static/root/assets/cron-page-Dv7GII9G.js +1 -0
- package/dist/gateway/static/root/assets/cron-schedule-picker-DH10KgD6.js +1 -0
- package/dist/gateway/static/root/assets/dist-By6Rh2ee.js +1 -0
- package/dist/gateway/static/root/assets/dist-CkobR7mk.js +1 -0
- package/dist/gateway/static/root/assets/dist-DokQNVMQ.js +1 -0
- package/dist/gateway/static/root/assets/dreaming-scene-BHMRVEUS.js +4585 -0
- package/dist/gateway/static/root/assets/dreaming-settings-L2rgZDQl.js +1 -0
- package/dist/gateway/static/root/assets/extension-debug-page-Cjx6pZVz.js +1 -0
- package/dist/gateway/static/root/assets/{extension-page-W1wiG38F.js → extension-page-BSPNL90V.js} +1 -1
- package/dist/gateway/static/root/assets/extension-settings-page-m_UQYkVc.js +1 -0
- package/dist/gateway/static/root/assets/fetch-Bs7in_4F.js +1 -0
- package/dist/gateway/static/root/assets/format-interval-label-CftJ5uXu.js +1 -0
- package/dist/gateway/static/root/assets/gateway-config-api-Clfck-hW.js +1 -0
- package/dist/gateway/static/root/assets/gateway-settings-CS177xni.js +1 -0
- package/dist/gateway/static/root/assets/gateway-startup-retry-BkjVJc38.js +1 -0
- package/dist/gateway/static/root/assets/goal-detail-page-kqJue0sE.js +1 -0
- package/dist/gateway/static/root/assets/goals-page-C0FujUB5.js +2 -0
- package/dist/gateway/static/root/assets/goals-settings-CJnzNSDo.js +1 -0
- package/dist/gateway/static/root/assets/heartbeat-config-api-Cp7CPuIn.js +1 -0
- package/dist/gateway/static/root/assets/heartbeat-settings-ifSI1Wbh.js +1 -0
- package/dist/gateway/static/root/assets/html-workspace-editor-CQLWDiwE.js +1 -0
- package/dist/gateway/static/root/assets/index-COkF54xv.js +121 -0
- package/dist/gateway/static/root/assets/index-DIaSNGEO.css +1 -0
- package/dist/gateway/static/root/assets/interval-presets-B29Ss_2y.js +2 -0
- package/dist/gateway/static/root/assets/keyboard-shortcuts-settings--MI1rYl7.js +1 -0
- package/dist/gateway/static/root/assets/locale-store-DjbfSsfA.js +1 -0
- package/dist/gateway/static/root/assets/logs-page-BwrVNlyW.js +2 -0
- package/dist/gateway/static/root/assets/markdown-editor-AssmZFx4.js +2 -0
- package/dist/gateway/static/root/assets/markdown-split-B_oc9lJe.js +1 -0
- package/dist/gateway/static/root/assets/media-plugins-DLPvB5H3.js +1 -0
- package/dist/gateway/static/root/assets/mermaid-render-BbqDc_LQ.js +134 -0
- package/dist/gateway/static/root/assets/messages-Csef1aW7.js +3 -0
- package/dist/gateway/static/root/assets/middleware-Pd0e8Igk.js +1 -0
- package/dist/gateway/static/root/assets/models-hub-panel-DHGiTIvf.js +2 -0
- package/dist/gateway/static/root/assets/note-detail-page-BM7zlDiT.js +2 -0
- package/dist/gateway/static/root/assets/note-image-lightbox-rSqAgOi9.js +1 -0
- package/dist/gateway/static/root/assets/note-time-hm6JO_Wi.js +1 -0
- package/dist/gateway/static/root/assets/notes-page-C9ACGEty.js +1 -0
- package/dist/gateway/static/root/assets/preview-open-alternatives-Bw_N2oZi.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-BhcWIKuM.js +1 -0
- package/dist/gateway/static/root/assets/save-bar-controls-CNP2jmw0.js +1 -0
- package/dist/gateway/static/root/assets/schedule-field-BPAWui3P.js +1 -0
- package/dist/gateway/static/root/assets/schedule-summary-B-hdSRdR.js +1 -0
- package/dist/gateway/static/root/assets/schema-form-CDPzua9q.js +1 -0
- package/dist/gateway/static/root/assets/sessions-page-DzkpSDZr.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-BYS-rpzn.js +1 -0
- package/dist/gateway/static/root/assets/settings-collapsible-section-C9bff85B.js +1 -0
- package/dist/gateway/static/root/assets/settings-form-section-BsrK7TBF.js +1 -0
- package/dist/gateway/static/root/assets/settings-page-DWK4Zo7s.js +1 -0
- package/dist/gateway/static/root/assets/setup-status-panel-DJiksNu4.js +1 -0
- package/dist/gateway/static/root/assets/share-preview-page-DnutoY_S.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-6oRgJMpn.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-CU9i0nJV.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-ERbdWENm.js +2 -0
- package/dist/gateway/static/root/assets/system-settings-panel-qP1JDtDd.js +1 -0
- package/dist/gateway/static/root/assets/theme-store-CD2wtgZV.js +1 -0
- package/dist/gateway/static/root/assets/use-agent-defaults-form-D3FGVLZb.js +1 -0
- package/dist/gateway/static/root/assets/use-blob-object-url-D4BFuzZB.js +1 -0
- package/dist/gateway/static/root/assets/use-channel-catalog-C4mg1hjD.js +1 -0
- package/dist/gateway/static/root/assets/use-settings-tab-guard-F6C0mpJt.js +1 -0
- package/dist/gateway/static/root/assets/vendor-codemirror-CNP1wPe7.js +45 -0
- package/dist/gateway/static/root/assets/voice-api-key-field-BnDDb6qO.js +1 -0
- package/dist/gateway/static/root/assets/voice-player-store-B2EwIhtY.js +1 -0
- package/dist/gateway/static/root/assets/{workflow-page.utils-EYg_xBQS.js → workflow-page.utils-CRB7WX9l.js} +1 -1
- package/dist/gateway/static/root/assets/workflows-page-2Fptr0je.js +18 -0
- package/dist/gateway/static/root/index.html +12 -10
- package/dist/package.js +1 -1
- package/dist/src/agent/goals/checklist-judge.d.ts +5 -0
- package/dist/src/agent/goals/checklist-judge.js +26 -2
- package/dist/src/agent/goals/checklist-judge.js.map +1 -1
- package/dist/src/agent/goals/evaluate-turn.d.ts +5 -1
- package/dist/src/agent/goals/evaluate-turn.js +29 -3
- package/dist/src/agent/goals/evaluate-turn.js.map +1 -1
- package/dist/src/agent/goals/goal-locale.d.ts +0 -2
- package/dist/src/agent/goals/goal-locale.js +2 -15
- package/dist/src/agent/goals/goal-locale.js.map +1 -1
- package/dist/src/agent/goals/persistent-goal-service.d.ts +9 -0
- package/dist/src/agent/goals/persistent-goal-service.js +2 -1
- package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
- package/dist/src/agent/goals/post-turn.d.ts +7 -0
- package/dist/src/agent/goals/post-turn.js +68 -23
- package/dist/src/agent/goals/post-turn.js.map +1 -1
- package/dist/src/agent/goals/state.d.ts +0 -5
- package/dist/src/agent/goals/state.js +1 -96
- package/dist/src/agent/goals/state.js.map +1 -1
- package/dist/src/agent/messaging/command-handler.d.ts +4 -0
- package/dist/src/agent/messaging/command-handler.js +6 -2
- package/dist/src/agent/messaging/command-handler.js.map +1 -1
- package/dist/src/agent/service/direct-turn-helpers.d.ts +2 -0
- package/dist/src/agent/service/direct-turn-helpers.js +3 -2
- package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +10 -1
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.js +20 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/service.types.d.ts +8 -0
- package/dist/src/agent/tools/cronjob-tool.d.ts +2 -0
- package/dist/src/agent/tools/cronjob-tool.js +27 -4
- package/dist/src/agent/tools/cronjob-tool.js.map +1 -1
- package/dist/src/agent/tools/edit.d.ts +2 -0
- package/dist/src/agent/tools/edit.js +10 -0
- package/dist/src/agent/tools/edit.js.map +1 -1
- package/dist/src/agent/tools/factory.js +16 -3
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/goal-evidence-recorder.d.ts +6 -0
- package/dist/src/agent/tools/goal-evidence-recorder.js +34 -0
- package/dist/src/agent/tools/goal-evidence-recorder.js.map +1 -0
- package/dist/src/agent/tools/goal-tool.d.ts +19 -0
- package/dist/src/agent/tools/goal-tool.js +101 -0
- package/dist/src/agent/tools/goal-tool.js.map +1 -0
- package/dist/src/agent/tools/index.d.ts +1 -0
- package/dist/src/agent/tools/index.js +2 -1
- package/dist/src/agent/tools/shell.d.ts +2 -0
- package/dist/src/agent/tools/shell.js +17 -2
- package/dist/src/agent/tools/shell.js.map +1 -1
- package/dist/src/agent/tools/workflow-tool.d.ts +1 -0
- package/dist/src/agent/tools/workflow-tool.js +10 -2
- package/dist/src/agent/tools/workflow-tool.js.map +1 -1
- package/dist/src/agent/tools/write.d.ts +2 -0
- package/dist/src/agent/tools/write.js +11 -0
- package/dist/src/agent/tools/write.js.map +1 -1
- package/dist/src/channels/attachments/inbound-persist.d.ts +7 -0
- package/dist/src/channels/attachments/inbound-persist.js +20 -2
- package/dist/src/channels/attachments/inbound-persist.js.map +1 -1
- package/dist/src/chat-commands/builtins/goal.d.ts +1 -1
- package/dist/src/chat-commands/builtins/goal.js +55 -97
- package/dist/src/chat-commands/builtins/goal.js.map +1 -1
- package/dist/src/chat-commands/builtins/subgoal.d.ts +1 -1
- package/dist/src/chat-commands/builtins/subgoal.js +49 -108
- package/dist/src/chat-commands/builtins/subgoal.js.map +1 -1
- package/dist/src/chat-commands/builtins/workflow.js +132 -8
- package/dist/src/chat-commands/builtins/workflow.js.map +1 -1
- package/dist/src/chat-commands/context.d.ts +3 -0
- package/dist/src/chat-commands/context.js +5 -0
- package/dist/src/chat-commands/context.js.map +1 -1
- package/dist/src/chat-commands/types.d.ts +5 -0
- package/dist/src/cli/command-catalog.js +43 -0
- package/dist/src/cli/command-catalog.js.map +1 -1
- package/dist/src/cli/command-loaders.js +1 -0
- package/dist/src/cli/command-loaders.js.map +1 -1
- package/dist/src/cli/command-manifest.js +4 -0
- package/dist/src/cli/command-manifest.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/goal.d.ts +4 -0
- package/dist/src/cli/commands/goal.js +213 -0
- package/dist/src/cli/commands/goal.js.map +1 -0
- package/dist/src/cli/commands/onboard.js +11 -5
- package/dist/src/cli/commands/onboard.js.map +1 -1
- package/dist/src/config/index.js +2 -2
- package/dist/src/config/schema.d.ts +115 -0
- package/dist/src/config/schema.js +63 -4
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/config/web-patch.d.ts +14 -0
- package/dist/src/config/web-patch.js +45 -6
- package/dist/src/config/web-patch.js.map +1 -1
- package/dist/src/cron/executor.d.ts +2 -0
- package/dist/src/cron/executor.js +25 -0
- package/dist/src/cron/executor.js.map +1 -1
- package/dist/src/cron/job-content.js +1 -0
- package/dist/src/cron/job-content.js.map +1 -1
- package/dist/src/cron/types.d.ts +19 -1
- package/dist/src/cron/validation.d.ts +20 -0
- package/dist/src/cron/validation.js +7 -0
- package/dist/src/cron/validation.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.d.ts +14 -0
- package/dist/src/gateway/hono/routes/goals.d.ts +1 -1
- package/dist/src/gateway/hono/routes/goals.js +670 -173
- package/dist/src/gateway/hono/routes/goals.js.map +1 -1
- package/dist/src/gateway/hono/routes/home.d.ts +5 -0
- package/dist/src/gateway/hono/routes/home.js +88 -3
- package/dist/src/gateway/hono/routes/home.js.map +1 -1
- package/dist/src/gateway/hono/routes/notes.js +20 -14
- package/dist/src/gateway/hono/routes/notes.js.map +1 -1
- package/dist/src/gateway/hono/routes/workflows.js +6 -2
- package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
- package/dist/src/gateway/hono/sse.js.map +1 -1
- package/dist/src/gateway/service/agent-runner.d.ts +2 -0
- package/dist/src/gateway/service/agent-runner.js +6 -2
- package/dist/src/gateway/service/agent-runner.js.map +1 -1
- package/dist/src/gateway/service/run-gateway-agent.d.ts +3 -0
- package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
- package/dist/src/gateway/service.d.ts +8 -0
- package/dist/src/gateway/service.js +69 -2
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/goals/goal-notifications.d.ts +29 -0
- package/dist/src/goals/goal-notifications.js +154 -0
- package/dist/src/goals/goal-notifications.js.map +1 -0
- package/dist/src/goals/goal-queue-store.d.ts +16 -0
- package/dist/src/goals/goal-queue-store.js +131 -0
- package/dist/src/goals/goal-queue-store.js.map +1 -0
- package/dist/src/goals/goal-queue-types.d.ts +31 -0
- package/dist/src/goals/goal-queue-types.js +1 -0
- package/dist/src/goals/goal-runner.d.ts +17 -0
- package/dist/src/goals/goal-runner.js +127 -0
- package/dist/src/goals/goal-runner.js.map +1 -0
- package/dist/src/goals/goal-service.d.ts +77 -0
- package/dist/src/goals/goal-service.js +261 -0
- package/dist/src/goals/goal-service.js.map +1 -0
- package/dist/src/goals/goal-store.d.ts +38 -0
- package/dist/src/goals/goal-store.js +404 -0
- package/dist/src/goals/goal-store.js.map +1 -0
- package/dist/src/goals/goal-workflow-judge.d.ts +13 -0
- package/dist/src/goals/goal-workflow-judge.js +147 -0
- package/dist/src/goals/goal-workflow-judge.js.map +1 -0
- package/dist/src/goals/index.d.ts +10 -0
- package/dist/src/goals/index.js +7 -0
- package/dist/src/goals/types.d.ts +119 -0
- package/dist/src/goals/types.js +1 -0
- package/dist/src/storage/sqlite/migrations/006_add_goals.sql +108 -0
- package/dist/src/storage/sqlite/migrations/007_goal_run_judge_schema.sql +4 -0
- package/dist/src/storage/sqlite/migrations/008_goal_queue_workflow_runs.sql +53 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/storage/sqlite/schema.sql +163 -0
- package/dist/src/utils/logger/stats.d.ts +1 -1
- package/dist/src/workflows/domain/run.d.ts +1 -0
- package/dist/src/workflows/domain/run.js.map +1 -1
- package/dist/src/workflows/service/workflow-run-service.d.ts +1 -0
- package/dist/src/workflows/service/workflow-run-service.js +5 -1
- package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
- package/dist/src/workflows/service/workflow-run-service.types.d.ts +1 -0
- package/dist/src/workflows/service/workflow-session-bridge.js +7 -0
- package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
- package/dist/src/workflows/store/run-index-store.d.ts +8 -0
- package/dist/src/workflows/store/run-index-store.js +58 -0
- package/dist/src/workflows/store/run-index-store.js.map +1 -0
- package/dist/src/workflows/store/run-store.d.ts +2 -0
- package/dist/src/workflows/store/run-store.js +14 -21
- package/dist/src/workflows/store/run-store.js.map +1 -1
- package/package.json +9 -3
- package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +0 -41
- package/dist/gateway/static/root/assets/agents-CFFioJCI.js +0 -78
- package/dist/gateway/static/root/assets/apps-page-BYDmkbzG.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-CsbDaMvd.js +0 -1
- package/dist/gateway/static/root/assets/circle-check-C23XjkUj.js +0 -1
- package/dist/gateway/static/root/assets/copy-Dv6d4Dvw.js +0 -1
- package/dist/gateway/static/root/assets/cron-page-Apk-7p4p.js +0 -1
- package/dist/gateway/static/root/assets/dist-DmGDgpy2.js +0 -1
- package/dist/gateway/static/root/assets/extension-debug-page-pxFC-tUa.js +0 -1
- package/dist/gateway/static/root/assets/extension-settings-page-COXSv6xP.js +0 -1
- package/dist/gateway/static/root/assets/fetch-CFdKBnE6.js +0 -1
- package/dist/gateway/static/root/assets/gateway-startup-retry-DdngdObI.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-config-api-DcmAMYmN.js +0 -1
- package/dist/gateway/static/root/assets/index-C_2yKRJX.js +0 -4845
- package/dist/gateway/static/root/assets/index-DI5_GsZt.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-DvTWUOzd.js +0 -2
- package/dist/gateway/static/root/assets/note-detail-page-CZbdugWZ.js +0 -180
- package/dist/gateway/static/root/assets/note-time-RdyBZxj3.js +0 -1
- package/dist/gateway/static/root/assets/notes-page-mEAwy3e1.js +0 -1
- package/dist/gateway/static/root/assets/schema-form-DEvdni_r.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-BsgffzV4.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-t6v8Dwb8.js +0 -2
- package/dist/gateway/static/root/assets/settings-form-section-BoYYZmJG.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-ClIthYF8.js +0 -3
- package/dist/gateway/static/root/assets/share-preview-page-Dd9et7KD.js +0 -2
- package/dist/gateway/static/root/assets/skills-page-DEXtxyfQ.js +0 -2
- package/dist/gateway/static/root/assets/theme-store-BZozwAeK.js +0 -1
- package/dist/gateway/static/root/assets/url-DQRqHuL-.js +0 -3
- package/dist/gateway/static/root/assets/vendor-codemirror-DYoKfS8f.js +0 -45
- package/dist/gateway/static/root/assets/voice-api-key-field-CNrKzQrf.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-EMZkzJNN.js +0 -18
- package/dist/src/agent/goals/checklist-user.d.ts +0 -24
- package/dist/src/agent/goals/checklist-user.js +0 -92
- package/dist/src/agent/goals/checklist-user.js.map +0 -1
- package/dist/src/agent/goals/goal-run-store.d.ts +0 -19
- package/dist/src/agent/goals/goal-run-store.js +0 -138
- package/dist/src/agent/goals/goal-run-store.js.map +0 -1
- package/dist/src/agent/goals/goal-run-types.d.ts +0 -28
- package/dist/src/agent/goals/goal-run-types.js +0 -6
- package/dist/src/agent/goals/goal-run-types.js.map +0 -1
- package/dist/src/agent/goals/patch-from-user-action.d.ts +0 -20
- package/dist/src/agent/goals/patch-from-user-action.js +0 -103
- package/dist/src/agent/goals/patch-from-user-action.js.map +0 -1
- /package/dist/gateway/static/root/assets/{note-detail-page-D4ZIVQbk.css → block-editor-D4ZIVQbk.css} +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { init_session_key, parseSessionKey } from "../routing/session-key.js";
|
|
2
|
+
import { createLogger } from "../utils/logger/index.js";
|
|
3
|
+
import { init_logger } from "../utils/logger.js";
|
|
4
|
+
import { GoalService } from "./goal-service.js";
|
|
5
|
+
//#region src/goals/goal-notifications.ts
|
|
6
|
+
init_session_key();
|
|
7
|
+
init_logger();
|
|
8
|
+
const log = createLogger("GoalNotifications");
|
|
9
|
+
function eventForQueueStatus(status) {
|
|
10
|
+
if (status === "failed") return "queue_failed";
|
|
11
|
+
if (status === "retry_waiting") return "queue_retry";
|
|
12
|
+
if (status === "succeeded") return "queue_succeeded";
|
|
13
|
+
if (status === "skipped") return "queue_skipped";
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function eventForGoalStatus(status) {
|
|
17
|
+
if (status === "done" || status === "blocked" || status === "needs_input") return status;
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function targetKey(target) {
|
|
21
|
+
return [
|
|
22
|
+
target.channel,
|
|
23
|
+
target.chatId,
|
|
24
|
+
target.accountId ?? "",
|
|
25
|
+
target.threadId ?? ""
|
|
26
|
+
].join("\0");
|
|
27
|
+
}
|
|
28
|
+
function formatGoalNotification(event, goal, opts) {
|
|
29
|
+
const lines = [`${{
|
|
30
|
+
done: "Goal completed",
|
|
31
|
+
blocked: "Goal blocked",
|
|
32
|
+
needs_input: "Goal needs input",
|
|
33
|
+
queue_failed: "Goal run failed",
|
|
34
|
+
queue_retry: "Goal run retry scheduled",
|
|
35
|
+
queue_succeeded: "Goal run finished",
|
|
36
|
+
queue_skipped: "Goal run skipped"
|
|
37
|
+
}[event]}: ${goal.title}`];
|
|
38
|
+
if (goal.blockedReason) lines.push(`Reason: ${goal.blockedReason}`);
|
|
39
|
+
if (goal.nextAction) lines.push(`Next: ${goal.nextAction}`);
|
|
40
|
+
if (goal.latestRun?.reason && goal.latestRun.reason !== goal.blockedReason) lines.push(`Run: ${goal.latestRun.reason}`);
|
|
41
|
+
if (opts?.queueItem?.lastError) lines.push(`Queue: ${opts.queueItem.lastError}`);
|
|
42
|
+
const progress = goal.checklist.length ? goal.checklist.filter((it) => it.status === "completed" || it.status === "impossible").length : 0;
|
|
43
|
+
if (goal.checklist.length) lines.push(`Checklist: ${progress}/${goal.checklist.length}`);
|
|
44
|
+
lines.push(`Turns: ${goal.turnsUsed}/${goal.maxTurns}`);
|
|
45
|
+
return lines.join("\n");
|
|
46
|
+
}
|
|
47
|
+
var GoalNotificationService = class {
|
|
48
|
+
goals = new GoalService();
|
|
49
|
+
sentKeys = /* @__PURE__ */ new Set();
|
|
50
|
+
constructor(opts) {
|
|
51
|
+
this.opts = opts;
|
|
52
|
+
}
|
|
53
|
+
handleGatewayEvent(type, payload) {
|
|
54
|
+
if (type === "goal.queue.updated") {
|
|
55
|
+
this.handleQueueUpdated(payload).catch((err) => {
|
|
56
|
+
log.warn({ err }, "Goal queue notification failed");
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (type === "goal.status.updated") this.handleStatusUpdated(payload).catch((err) => {
|
|
61
|
+
log.warn({ err }, "Goal status notification failed");
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async handleQueueUpdated(payload) {
|
|
65
|
+
const item = this.parseQueueItem(payload);
|
|
66
|
+
if (!item) return;
|
|
67
|
+
const event = eventForQueueStatus(item.status);
|
|
68
|
+
if (!event) return;
|
|
69
|
+
const goal = this.goals.get(item.goalId);
|
|
70
|
+
if (!goal) return;
|
|
71
|
+
await this.notify(event, goal, {
|
|
72
|
+
queueItem: item,
|
|
73
|
+
eventKey: `${item.id}:${item.status}:${item.attempts}`
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async handleStatusUpdated(payload) {
|
|
77
|
+
if (!payload || typeof payload !== "object") return;
|
|
78
|
+
const rec = payload;
|
|
79
|
+
const goal = this.isGoal(rec.goal) ? rec.goal : typeof rec.goalId === "string" ? this.goals.get(rec.goalId) : null;
|
|
80
|
+
if (!goal) return;
|
|
81
|
+
const status = typeof rec.status === "string" ? rec.status : goal.status;
|
|
82
|
+
const event = eventForGoalStatus(status);
|
|
83
|
+
if (!event) return;
|
|
84
|
+
if (rec.previousStatus === status) return;
|
|
85
|
+
await this.notify(event, goal, { eventKey: `${goal.id}:${event}:${goal.updatedAt}` });
|
|
86
|
+
}
|
|
87
|
+
async notify(event, goal, opts) {
|
|
88
|
+
const policy = this.opts.getConfig().goals?.notifications;
|
|
89
|
+
if (!policy?.enabled) return;
|
|
90
|
+
if (!policy.events.includes(event)) return;
|
|
91
|
+
const targets = this.resolveTargets(event, goal);
|
|
92
|
+
if (targets.length === 0) return;
|
|
93
|
+
const text = formatGoalNotification(event, goal, { queueItem: opts?.queueItem });
|
|
94
|
+
for (const target of targets) {
|
|
95
|
+
const dedupeKey = `${opts?.eventKey ?? `${goal.id}:${event}`}:${targetKey(target)}`;
|
|
96
|
+
if (this.sentKeys.has(dedupeKey)) continue;
|
|
97
|
+
this.sentKeys.add(dedupeKey);
|
|
98
|
+
await this.opts.send({
|
|
99
|
+
...target,
|
|
100
|
+
text
|
|
101
|
+
}).catch((err) => {
|
|
102
|
+
this.sentKeys.delete(dedupeKey);
|
|
103
|
+
throw err;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
resolveTargets(event, goal) {
|
|
108
|
+
const policy = this.opts.getConfig().goals?.notifications;
|
|
109
|
+
if (!policy?.enabled) return [];
|
|
110
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
111
|
+
if (policy.includeLinkedSessions !== false && goal.activeSessionKey) {
|
|
112
|
+
const parsed = parseSessionKey(goal.activeSessionKey);
|
|
113
|
+
if (parsed && policy.channels.includes(parsed.source)) {
|
|
114
|
+
const target = {
|
|
115
|
+
channel: parsed.source,
|
|
116
|
+
chatId: parsed.peerId,
|
|
117
|
+
accountId: parsed.accountId,
|
|
118
|
+
...parsed.threadId ? { threadId: parsed.threadId } : {}
|
|
119
|
+
};
|
|
120
|
+
byKey.set(targetKey(target), target);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const target of policy.targets ?? []) {
|
|
124
|
+
const events = target.events;
|
|
125
|
+
if (events && !events.includes(event)) continue;
|
|
126
|
+
const normalized = {
|
|
127
|
+
channel: target.channel,
|
|
128
|
+
chatId: target.chatId,
|
|
129
|
+
...target.accountId ? { accountId: target.accountId } : {},
|
|
130
|
+
...target.threadId != null ? { threadId: target.threadId } : {},
|
|
131
|
+
...target.silent != null ? { silent: target.silent } : {}
|
|
132
|
+
};
|
|
133
|
+
byKey.set(targetKey(normalized), normalized);
|
|
134
|
+
}
|
|
135
|
+
return [...byKey.values()];
|
|
136
|
+
}
|
|
137
|
+
parseQueueItem(payload) {
|
|
138
|
+
if (!payload || typeof payload !== "object") return null;
|
|
139
|
+
const item = payload.item;
|
|
140
|
+
if (!item || typeof item !== "object") return null;
|
|
141
|
+
const rec = item;
|
|
142
|
+
if (typeof rec.id !== "string" || typeof rec.goalId !== "string" || typeof rec.status !== "string") return null;
|
|
143
|
+
return rec;
|
|
144
|
+
}
|
|
145
|
+
isGoal(value) {
|
|
146
|
+
if (!value || typeof value !== "object") return false;
|
|
147
|
+
const rec = value;
|
|
148
|
+
return typeof rec.id === "string" && typeof rec.title === "string" && Array.isArray(rec.checklist);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
//#endregion
|
|
152
|
+
export { GoalNotificationService };
|
|
153
|
+
|
|
154
|
+
//# sourceMappingURL=goal-notifications.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goal-notifications.js","names":[],"sources":["../../../src/goals/goal-notifications.ts"],"sourcesContent":["import type { Config } from '../config/schema.js';\nimport { parseSessionKey } from '../routing/session-key.js';\nimport { createLogger } from '../utils/logger.js';\n\nimport { GoalService } from './goal-service.js';\nimport type { GoalWithDetails } from './types.js';\nimport type { GoalQueueItemSnapshot } from './goal-queue-types.js';\n\nconst log = createLogger('GoalNotifications');\n\nexport type GoalNotificationEvent =\n | 'done'\n | 'blocked'\n | 'needs_input'\n | 'queue_failed'\n | 'queue_retry'\n | 'queue_succeeded'\n | 'queue_skipped';\n\nexport interface GoalNotificationTarget {\n channel: string;\n chatId: string;\n accountId?: string;\n threadId?: string | number;\n silent?: boolean;\n}\n\nexport interface GoalNotificationSendInput extends GoalNotificationTarget {\n text: string;\n}\n\nexport interface GoalNotificationServiceOptions {\n getConfig: () => Config;\n send: (input: GoalNotificationSendInput) => Promise<void>;\n}\n\nfunction eventForQueueStatus(status: GoalQueueItemSnapshot['status']): GoalNotificationEvent | null {\n if (status === 'failed') return 'queue_failed';\n if (status === 'retry_waiting') return 'queue_retry';\n if (status === 'succeeded') return 'queue_succeeded';\n if (status === 'skipped') return 'queue_skipped';\n return null;\n}\n\nfunction eventForGoalStatus(status: GoalWithDetails['status']): GoalNotificationEvent | null {\n if (status === 'done' || status === 'blocked' || status === 'needs_input') return status;\n return null;\n}\n\nfunction targetKey(target: GoalNotificationTarget): string {\n return [\n target.channel,\n target.chatId,\n target.accountId ?? '',\n target.threadId ?? '',\n ].join('\\u0000');\n}\n\nfunction formatGoalNotification(event: GoalNotificationEvent, goal: GoalWithDetails, opts?: {\n queueItem?: GoalQueueItemSnapshot;\n}): string {\n const labels: Record<GoalNotificationEvent, string> = {\n done: 'Goal completed',\n blocked: 'Goal blocked',\n needs_input: 'Goal needs input',\n queue_failed: 'Goal run failed',\n queue_retry: 'Goal run retry scheduled',\n queue_succeeded: 'Goal run finished',\n queue_skipped: 'Goal run skipped',\n };\n const lines = [`${labels[event]}: ${goal.title}`];\n if (goal.blockedReason) lines.push(`Reason: ${goal.blockedReason}`);\n if (goal.nextAction) lines.push(`Next: ${goal.nextAction}`);\n if (goal.latestRun?.reason && goal.latestRun.reason !== goal.blockedReason) {\n lines.push(`Run: ${goal.latestRun.reason}`);\n }\n if (opts?.queueItem?.lastError) lines.push(`Queue: ${opts.queueItem.lastError}`);\n const progress = goal.checklist.length\n ? goal.checklist.filter((it) => it.status === 'completed' || it.status === 'impossible').length\n : 0;\n if (goal.checklist.length) lines.push(`Checklist: ${progress}/${goal.checklist.length}`);\n lines.push(`Turns: ${goal.turnsUsed}/${goal.maxTurns}`);\n return lines.join('\\n');\n}\n\nexport class GoalNotificationService {\n private readonly goals = new GoalService();\n private readonly sentKeys = new Set<string>();\n\n constructor(private readonly opts: GoalNotificationServiceOptions) {}\n\n handleGatewayEvent(type: string, payload: unknown): void {\n if (type === 'goal.queue.updated') {\n void this.handleQueueUpdated(payload).catch((err) => {\n log.warn({ err }, 'Goal queue notification failed');\n });\n return;\n }\n if (type === 'goal.status.updated') {\n void this.handleStatusUpdated(payload).catch((err) => {\n log.warn({ err }, 'Goal status notification failed');\n });\n }\n }\n\n private async handleQueueUpdated(payload: unknown): Promise<void> {\n const item = this.parseQueueItem(payload);\n if (!item) return;\n const event = eventForQueueStatus(item.status);\n if (!event) return;\n const goal = this.goals.get(item.goalId);\n if (!goal) return;\n await this.notify(event, goal, { queueItem: item, eventKey: `${item.id}:${item.status}:${item.attempts}` });\n }\n\n private async handleStatusUpdated(payload: unknown): Promise<void> {\n if (!payload || typeof payload !== 'object') return;\n const rec = payload as { goal?: unknown; status?: unknown; goalId?: unknown; previousStatus?: unknown };\n const goal =\n this.isGoal(rec.goal)\n ? rec.goal\n : typeof rec.goalId === 'string'\n ? this.goals.get(rec.goalId)\n : null;\n if (!goal) return;\n const status = typeof rec.status === 'string' ? rec.status : goal.status;\n const event = eventForGoalStatus(status as GoalWithDetails['status']);\n if (!event) return;\n if (rec.previousStatus === status) return;\n await this.notify(event, goal, { eventKey: `${goal.id}:${event}:${goal.updatedAt}` });\n }\n\n private async notify(\n event: GoalNotificationEvent,\n goal: GoalWithDetails,\n opts?: { queueItem?: GoalQueueItemSnapshot; eventKey?: string },\n ): Promise<void> {\n const policy = this.opts.getConfig().goals?.notifications;\n if (!policy?.enabled) return;\n if (!policy.events.includes(event)) return;\n\n const targets = this.resolveTargets(event, goal);\n if (targets.length === 0) return;\n\n const text = formatGoalNotification(event, goal, { queueItem: opts?.queueItem });\n for (const target of targets) {\n const dedupeKey = `${opts?.eventKey ?? `${goal.id}:${event}`}:${targetKey(target)}`;\n if (this.sentKeys.has(dedupeKey)) continue;\n this.sentKeys.add(dedupeKey);\n await this.opts.send({ ...target, text }).catch((err) => {\n this.sentKeys.delete(dedupeKey);\n throw err;\n });\n }\n }\n\n private resolveTargets(event: GoalNotificationEvent, goal: GoalWithDetails): GoalNotificationTarget[] {\n const policy = this.opts.getConfig().goals?.notifications;\n if (!policy?.enabled) return [];\n const byKey = new Map<string, GoalNotificationTarget>();\n\n if (policy.includeLinkedSessions !== false && goal.activeSessionKey) {\n const parsed = parseSessionKey(goal.activeSessionKey);\n const eligible = parsed && policy.channels.includes(parsed.source);\n if (eligible) {\n const target: GoalNotificationTarget = {\n channel: parsed.source,\n chatId: parsed.peerId,\n accountId: parsed.accountId,\n ...(parsed.threadId ? { threadId: parsed.threadId } : {}),\n };\n byKey.set(targetKey(target), target);\n }\n }\n\n for (const target of policy.targets ?? []) {\n const events = target.events;\n if (events && !events.includes(event)) continue;\n const normalized: GoalNotificationTarget = {\n channel: target.channel,\n chatId: target.chatId,\n ...(target.accountId ? { accountId: target.accountId } : {}),\n ...(target.threadId != null ? { threadId: target.threadId } : {}),\n ...(target.silent != null ? { silent: target.silent } : {}),\n };\n byKey.set(targetKey(normalized), normalized);\n }\n\n return [...byKey.values()];\n }\n\n private parseQueueItem(payload: unknown): GoalQueueItemSnapshot | null {\n if (!payload || typeof payload !== 'object') return null;\n const item = (payload as { item?: unknown }).item;\n if (!item || typeof item !== 'object') return null;\n const rec = item as Partial<GoalQueueItemSnapshot>;\n if (typeof rec.id !== 'string' || typeof rec.goalId !== 'string' || typeof rec.status !== 'string') return null;\n return rec as GoalQueueItemSnapshot;\n }\n\n private isGoal(value: unknown): value is GoalWithDetails {\n if (!value || typeof value !== 'object') return false;\n const rec = value as Partial<GoalWithDetails>;\n return typeof rec.id === 'string' && typeof rec.title === 'string' && Array.isArray(rec.checklist);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GoalQueueItemSnapshot, GoalQueueStatus } from './goal-queue-types.js';
|
|
2
|
+
export declare class GoalQueueStore {
|
|
3
|
+
enqueue(input: {
|
|
4
|
+
goalId: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
maxRetries: number;
|
|
7
|
+
source: GoalQueueItemSnapshot['source'];
|
|
8
|
+
}): GoalQueueItemSnapshot;
|
|
9
|
+
claimNext(now?: number): GoalQueueItemSnapshot | null;
|
|
10
|
+
markRetry(itemId: string, error: string, nextRunAt: number): GoalQueueItemSnapshot | null;
|
|
11
|
+
markFinished(itemId: string, status: GoalQueueStatus, error?: string): GoalQueueItemSnapshot | null;
|
|
12
|
+
setSessionKey(itemId: string, sessionKey: string): GoalQueueItemSnapshot | null;
|
|
13
|
+
list(limit?: number): GoalQueueItemSnapshot[];
|
|
14
|
+
resetRunningToRetry(reason?: string): number;
|
|
15
|
+
private update;
|
|
16
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
//#region src/goals/goal-queue-store.ts
|
|
4
|
+
function parsePayload(raw) {
|
|
5
|
+
try {
|
|
6
|
+
const parsed = JSON.parse(raw);
|
|
7
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
8
|
+
} catch {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function rowToSnapshot(row) {
|
|
13
|
+
const payload = parsePayload(row.payload_json);
|
|
14
|
+
return {
|
|
15
|
+
id: row.queue_id,
|
|
16
|
+
goalId: row.goal_id,
|
|
17
|
+
status: row.status,
|
|
18
|
+
attempts: row.attempts,
|
|
19
|
+
maxRetries: row.max_retries,
|
|
20
|
+
enqueuedAt: row.enqueued_at,
|
|
21
|
+
startedAt: row.started_at ?? void 0,
|
|
22
|
+
finishedAt: row.finished_at ?? void 0,
|
|
23
|
+
nextRunAt: row.next_run_at ?? void 0,
|
|
24
|
+
sessionKey: row.session_key ?? void 0,
|
|
25
|
+
message: payload.message,
|
|
26
|
+
lastError: row.last_error ?? void 0,
|
|
27
|
+
source: row.source
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
var GoalQueueStore = class {
|
|
31
|
+
enqueue(input) {
|
|
32
|
+
return runSqliteWriteTransaction((db) => {
|
|
33
|
+
const existing = db.prepare(`SELECT * FROM goal_queue
|
|
34
|
+
WHERE goal_id = ?
|
|
35
|
+
AND status IN ('queued', 'running', 'retry_waiting')
|
|
36
|
+
ORDER BY enqueued_at DESC
|
|
37
|
+
LIMIT 1`).get(input.goalId);
|
|
38
|
+
if (existing) return rowToSnapshot(existing);
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const row = {
|
|
41
|
+
queue_id: randomUUID(),
|
|
42
|
+
goal_id: input.goalId,
|
|
43
|
+
status: "queued",
|
|
44
|
+
payload_json: JSON.stringify({ message: input.message }),
|
|
45
|
+
attempts: 0,
|
|
46
|
+
max_retries: input.maxRetries,
|
|
47
|
+
enqueued_at: now,
|
|
48
|
+
started_at: null,
|
|
49
|
+
finished_at: null,
|
|
50
|
+
next_run_at: null,
|
|
51
|
+
session_key: null,
|
|
52
|
+
last_error: null,
|
|
53
|
+
source: input.source
|
|
54
|
+
};
|
|
55
|
+
db.prepare(`INSERT INTO goal_queue (
|
|
56
|
+
queue_id, goal_id, status, payload_json, attempts, max_retries,
|
|
57
|
+
enqueued_at, source
|
|
58
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(row.queue_id, row.goal_id, row.status, row.payload_json, row.attempts, row.max_retries, row.enqueued_at, row.source);
|
|
59
|
+
return rowToSnapshot(row);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
claimNext(now = Date.now()) {
|
|
63
|
+
return runSqliteWriteTransaction((db) => {
|
|
64
|
+
const row = db.prepare(`SELECT * FROM goal_queue
|
|
65
|
+
WHERE status = 'queued'
|
|
66
|
+
OR (status = 'retry_waiting' AND COALESCE(next_run_at, 0) <= ?)
|
|
67
|
+
ORDER BY enqueued_at ASC
|
|
68
|
+
LIMIT 1`).get(now);
|
|
69
|
+
if (!row) return null;
|
|
70
|
+
const startedAt = Date.now();
|
|
71
|
+
db.prepare(`UPDATE goal_queue
|
|
72
|
+
SET status = 'running',
|
|
73
|
+
started_at = ?,
|
|
74
|
+
attempts = attempts + 1,
|
|
75
|
+
last_error = NULL
|
|
76
|
+
WHERE queue_id = ?`).run(startedAt, row.queue_id);
|
|
77
|
+
return rowToSnapshot(db.prepare(`SELECT * FROM goal_queue WHERE queue_id = ?`).get(row.queue_id));
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
markRetry(itemId, error, nextRunAt) {
|
|
81
|
+
return this.update(itemId, {
|
|
82
|
+
status: "retry_waiting",
|
|
83
|
+
next_run_at: nextRunAt,
|
|
84
|
+
last_error: error,
|
|
85
|
+
finished_at: null
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
markFinished(itemId, status, error) {
|
|
89
|
+
return this.update(itemId, {
|
|
90
|
+
status,
|
|
91
|
+
finished_at: Date.now(),
|
|
92
|
+
last_error: error ?? null
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
setSessionKey(itemId, sessionKey) {
|
|
96
|
+
return this.update(itemId, { session_key: sessionKey });
|
|
97
|
+
}
|
|
98
|
+
list(limit = 200) {
|
|
99
|
+
const safeLimit = Math.min(500, Math.max(1, Math.floor(limit)));
|
|
100
|
+
return getSqliteDatabase().prepare(`SELECT * FROM goal_queue ORDER BY enqueued_at DESC LIMIT ?`).all(safeLimit).map(rowToSnapshot);
|
|
101
|
+
}
|
|
102
|
+
resetRunningToRetry(reason = "Gateway restarted during goal run") {
|
|
103
|
+
return runSqliteWriteTransaction((db) => {
|
|
104
|
+
const result = db.prepare(`UPDATE goal_queue
|
|
105
|
+
SET status = 'retry_waiting',
|
|
106
|
+
next_run_at = ?,
|
|
107
|
+
last_error = ?
|
|
108
|
+
WHERE status = 'running'`).run(Date.now(), reason);
|
|
109
|
+
return Number(result.changes ?? 0);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
update(itemId, patch) {
|
|
113
|
+
return runSqliteWriteTransaction((db) => {
|
|
114
|
+
const row = db.prepare(`SELECT * FROM goal_queue WHERE queue_id = ?`).get(itemId);
|
|
115
|
+
if (!row) return null;
|
|
116
|
+
const next = {
|
|
117
|
+
...row,
|
|
118
|
+
...patch
|
|
119
|
+
};
|
|
120
|
+
db.prepare(`UPDATE goal_queue SET
|
|
121
|
+
status = ?, attempts = ?, started_at = ?, finished_at = ?,
|
|
122
|
+
next_run_at = ?, session_key = ?, last_error = ?
|
|
123
|
+
WHERE queue_id = ?`).run(next.status, next.attempts, next.started_at, next.finished_at, next.next_run_at, next.session_key, next.last_error, itemId);
|
|
124
|
+
return rowToSnapshot(db.prepare(`SELECT * FROM goal_queue WHERE queue_id = ?`).get(itemId));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
//#endregion
|
|
129
|
+
export { GoalQueueStore };
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=goal-queue-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goal-queue-store.js","names":[],"sources":["../../../src/goals/goal-queue-store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { getSqliteDatabase, runSqliteWriteTransaction } from '../storage/sqlite/transaction.js';\n\nimport type { GoalQueueItemSnapshot, GoalQueueStatus } from './goal-queue-types.js';\n\ntype GoalQueueRow = {\n queue_id: string;\n goal_id: string;\n status: GoalQueueStatus;\n payload_json: string;\n attempts: number;\n max_retries: number;\n enqueued_at: number;\n started_at: number | null;\n finished_at: number | null;\n next_run_at: number | null;\n session_key: string | null;\n last_error: string | null;\n source: GoalQueueItemSnapshot['source'];\n};\n\ntype GoalQueuePayload = {\n message?: string;\n};\n\nfunction parsePayload(raw: string): GoalQueuePayload {\n try {\n const parsed = JSON.parse(raw) as GoalQueuePayload;\n return parsed && typeof parsed === 'object' ? parsed : {};\n } catch {\n return {};\n }\n}\n\nfunction rowToSnapshot(row: GoalQueueRow): GoalQueueItemSnapshot {\n const payload = parsePayload(row.payload_json);\n return {\n id: row.queue_id,\n goalId: row.goal_id,\n status: row.status,\n attempts: row.attempts,\n maxRetries: row.max_retries,\n enqueuedAt: row.enqueued_at,\n startedAt: row.started_at ?? undefined,\n finishedAt: row.finished_at ?? undefined,\n nextRunAt: row.next_run_at ?? undefined,\n sessionKey: row.session_key ?? undefined,\n message: payload.message,\n lastError: row.last_error ?? undefined,\n source: row.source,\n };\n}\n\nexport class GoalQueueStore {\n enqueue(input: {\n goalId: string;\n message?: string;\n maxRetries: number;\n source: GoalQueueItemSnapshot['source'];\n }): GoalQueueItemSnapshot {\n return runSqliteWriteTransaction((db) => {\n const existing = db\n .prepare(\n `SELECT * FROM goal_queue\n WHERE goal_id = ?\n AND status IN ('queued', 'running', 'retry_waiting')\n ORDER BY enqueued_at DESC\n LIMIT 1`,\n )\n .get(input.goalId) as GoalQueueRow | undefined;\n if (existing) return rowToSnapshot(existing);\n\n const now = Date.now();\n const row: GoalQueueRow = {\n queue_id: randomUUID(),\n goal_id: input.goalId,\n status: 'queued',\n payload_json: JSON.stringify({ message: input.message }),\n attempts: 0,\n max_retries: input.maxRetries,\n enqueued_at: now,\n started_at: null,\n finished_at: null,\n next_run_at: null,\n session_key: null,\n last_error: null,\n source: input.source,\n };\n db.prepare(\n `INSERT INTO goal_queue (\n queue_id, goal_id, status, payload_json, attempts, max_retries,\n enqueued_at, source\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n row.queue_id,\n row.goal_id,\n row.status,\n row.payload_json,\n row.attempts,\n row.max_retries,\n row.enqueued_at,\n row.source,\n );\n return rowToSnapshot(row);\n });\n }\n\n claimNext(now = Date.now()): GoalQueueItemSnapshot | null {\n return runSqliteWriteTransaction((db) => {\n const row = db\n .prepare(\n `SELECT * FROM goal_queue\n WHERE status = 'queued'\n OR (status = 'retry_waiting' AND COALESCE(next_run_at, 0) <= ?)\n ORDER BY enqueued_at ASC\n LIMIT 1`,\n )\n .get(now) as GoalQueueRow | undefined;\n if (!row) return null;\n\n const startedAt = Date.now();\n db.prepare(\n `UPDATE goal_queue\n SET status = 'running',\n started_at = ?,\n attempts = attempts + 1,\n last_error = NULL\n WHERE queue_id = ?`,\n ).run(startedAt, row.queue_id);\n\n const next = db\n .prepare(`SELECT * FROM goal_queue WHERE queue_id = ?`)\n .get(row.queue_id) as GoalQueueRow;\n return rowToSnapshot(next);\n });\n }\n\n markRetry(itemId: string, error: string, nextRunAt: number): GoalQueueItemSnapshot | null {\n return this.update(itemId, {\n status: 'retry_waiting',\n next_run_at: nextRunAt,\n last_error: error,\n finished_at: null,\n });\n }\n\n markFinished(itemId: string, status: GoalQueueStatus, error?: string): GoalQueueItemSnapshot | null {\n return this.update(itemId, {\n status,\n finished_at: Date.now(),\n last_error: error ?? null,\n });\n }\n\n setSessionKey(itemId: string, sessionKey: string): GoalQueueItemSnapshot | null {\n return this.update(itemId, { session_key: sessionKey });\n }\n\n list(limit = 200): GoalQueueItemSnapshot[] {\n const safeLimit = Math.min(500, Math.max(1, Math.floor(limit)));\n const rows = getSqliteDatabase()\n .prepare(`SELECT * FROM goal_queue ORDER BY enqueued_at DESC LIMIT ?`)\n .all(safeLimit) as GoalQueueRow[];\n return rows.map(rowToSnapshot);\n }\n\n resetRunningToRetry(reason = 'Gateway restarted during goal run'): number {\n return runSqliteWriteTransaction((db) => {\n const result = db\n .prepare(\n `UPDATE goal_queue\n SET status = 'retry_waiting',\n next_run_at = ?,\n last_error = ?\n WHERE status = 'running'`,\n )\n .run(Date.now(), reason);\n return Number(result.changes ?? 0);\n });\n }\n\n private update(itemId: string, patch: Partial<GoalQueueRow>): GoalQueueItemSnapshot | null {\n return runSqliteWriteTransaction((db) => {\n const row = db\n .prepare(`SELECT * FROM goal_queue WHERE queue_id = ?`)\n .get(itemId) as GoalQueueRow | undefined;\n if (!row) return null;\n const next = { ...row, ...patch };\n db.prepare(\n `UPDATE goal_queue SET\n status = ?, attempts = ?, started_at = ?, finished_at = ?,\n next_run_at = ?, session_key = ?, last_error = ?\n WHERE queue_id = ?`,\n ).run(\n next.status,\n next.attempts,\n next.started_at,\n next.finished_at,\n next.next_run_at,\n next.session_key,\n next.last_error,\n itemId,\n );\n const updated = db\n .prepare(`SELECT * FROM goal_queue WHERE queue_id = ?`)\n .get(itemId) as GoalQueueRow;\n return rowToSnapshot(updated);\n });\n }\n}\n\n"],"mappings":";;;AA0BA,SAAS,aAAa,KAA+B;AACnD,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,SAAO,UAAU,OAAO,WAAW,WAAW,SAAS,EAAE;SACnD;AACN,SAAO,EAAE;;;AAIb,SAAS,cAAc,KAA0C;CAC/D,MAAM,UAAU,aAAa,IAAI,aAAa;AAC9C,QAAO;EACL,IAAI,IAAI;EACR,QAAQ,IAAI;EACZ,QAAQ,IAAI;EACZ,UAAU,IAAI;EACd,YAAY,IAAI;EAChB,YAAY,IAAI;EAChB,WAAW,IAAI,cAAc,KAAA;EAC7B,YAAY,IAAI,eAAe,KAAA;EAC/B,WAAW,IAAI,eAAe,KAAA;EAC9B,YAAY,IAAI,eAAe,KAAA;EAC/B,SAAS,QAAQ;EACjB,WAAW,IAAI,cAAc,KAAA;EAC7B,QAAQ,IAAI;EACb;;AAGH,IAAa,iBAAb,MAA4B;CAC1B,QAAQ,OAKkB;AACxB,SAAO,2BAA2B,OAAO;GACvC,MAAM,WAAW,GACd,QACC;;;;oBAKD,CACA,IAAI,MAAM,OAAO;AACpB,OAAI,SAAU,QAAO,cAAc,SAAS;GAE5C,MAAM,MAAM,KAAK,KAAK;GACtB,MAAM,MAAoB;IACxB,UAAU,YAAY;IACtB,SAAS,MAAM;IACf,QAAQ;IACR,cAAc,KAAK,UAAU,EAAE,SAAS,MAAM,SAAS,CAAC;IACxD,UAAU;IACV,aAAa,MAAM;IACnB,aAAa;IACb,YAAY;IACZ,aAAa;IACb,aAAa;IACb,aAAa;IACb,YAAY;IACZ,QAAQ,MAAM;IACf;AACD,MAAG,QACD;;;2CAID,CAAC,IACA,IAAI,UACJ,IAAI,SACJ,IAAI,QACJ,IAAI,cACJ,IAAI,UACJ,IAAI,aACJ,IAAI,aACJ,IAAI,OACL;AACD,UAAO,cAAc,IAAI;IACzB;;CAGJ,UAAU,MAAM,KAAK,KAAK,EAAgC;AACxD,SAAO,2BAA2B,OAAO;GACvC,MAAM,MAAM,GACT,QACC;;;;oBAKD,CACA,IAAI,IAAI;AACX,OAAI,CAAC,IAAK,QAAO;GAEjB,MAAM,YAAY,KAAK,KAAK;AAC5B,MAAG,QACD;;;;;6BAMD,CAAC,IAAI,WAAW,IAAI,SAAS;AAK9B,UAAO,cAHM,GACV,QAAQ,8CAA8C,CACtD,IAAI,IAAI,SACc,CAAC;IAC1B;;CAGJ,UAAU,QAAgB,OAAe,WAAiD;AACxF,SAAO,KAAK,OAAO,QAAQ;GACzB,QAAQ;GACR,aAAa;GACb,YAAY;GACZ,aAAa;GACd,CAAC;;CAGJ,aAAa,QAAgB,QAAyB,OAA8C;AAClG,SAAO,KAAK,OAAO,QAAQ;GACzB;GACA,aAAa,KAAK,KAAK;GACvB,YAAY,SAAS;GACtB,CAAC;;CAGJ,cAAc,QAAgB,YAAkD;AAC9E,SAAO,KAAK,OAAO,QAAQ,EAAE,aAAa,YAAY,CAAC;;CAGzD,KAAK,QAAQ,KAA8B;EACzC,MAAM,YAAY,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,CAAC,CAAC;AAI/D,SAHa,mBAAmB,CAC7B,QAAQ,6DAA6D,CACrE,IAAI,UACI,CAAC,IAAI,cAAc;;CAGhC,oBAAoB,SAAS,qCAA6C;AACxE,SAAO,2BAA2B,OAAO;GACvC,MAAM,SAAS,GACZ,QACC;;;;qCAKD,CACA,IAAI,KAAK,KAAK,EAAE,OAAO;AAC1B,UAAO,OAAO,OAAO,WAAW,EAAE;IAClC;;CAGJ,OAAe,QAAgB,OAA4D;AACzF,SAAO,2BAA2B,OAAO;GACvC,MAAM,MAAM,GACT,QAAQ,8CAA8C,CACtD,IAAI,OAAO;AACd,OAAI,CAAC,IAAK,QAAO;GACjB,MAAM,OAAO;IAAE,GAAG;IAAK,GAAG;IAAO;AACjC,MAAG,QACD;;;6BAID,CAAC,IACA,KAAK,QACL,KAAK,UACL,KAAK,YACL,KAAK,aACL,KAAK,aACL,KAAK,aACL,KAAK,YACL,OACD;AAID,UAAO,cAHS,GACb,QAAQ,8CAA8C,CACtD,IAAI,OACqB,CAAC;IAC7B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { GoalWithDetails } from './types.js';
|
|
2
|
+
export type GoalQueueStatus = 'queued' | 'running' | 'retry_waiting' | 'succeeded' | 'failed' | 'skipped';
|
|
3
|
+
export interface GoalQueueItemSnapshot {
|
|
4
|
+
id: string;
|
|
5
|
+
goalId: string;
|
|
6
|
+
status: GoalQueueStatus;
|
|
7
|
+
attempts: number;
|
|
8
|
+
maxRetries: number;
|
|
9
|
+
enqueuedAt: number;
|
|
10
|
+
startedAt?: number;
|
|
11
|
+
finishedAt?: number;
|
|
12
|
+
nextRunAt?: number;
|
|
13
|
+
sessionKey?: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
lastError?: string;
|
|
16
|
+
source: 'api' | 'cron' | 'workflow' | 'system';
|
|
17
|
+
}
|
|
18
|
+
export interface GoalRunnerOptions {
|
|
19
|
+
maxConcurrent?: number;
|
|
20
|
+
defaultMaxRetries?: number;
|
|
21
|
+
retryBaseMs?: number;
|
|
22
|
+
ensureSession: (goal: GoalWithDetails) => Promise<string>;
|
|
23
|
+
hasActiveRun: (sessionKey: string) => boolean;
|
|
24
|
+
runContinuation: (sessionKey: string, message: string) => Promise<void>;
|
|
25
|
+
emit?: (type: string, payload: unknown) => void;
|
|
26
|
+
}
|
|
27
|
+
export interface EnqueueGoalRunOptions {
|
|
28
|
+
message?: string;
|
|
29
|
+
maxRetries?: number;
|
|
30
|
+
source?: GoalQueueItemSnapshot['source'];
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { EnqueueGoalRunOptions, GoalQueueItemSnapshot, GoalRunnerOptions } from './goal-queue-types.js';
|
|
2
|
+
export declare class GoalRunner {
|
|
3
|
+
private readonly opts;
|
|
4
|
+
private readonly goals;
|
|
5
|
+
private readonly store;
|
|
6
|
+
private active;
|
|
7
|
+
private pumpTimer;
|
|
8
|
+
constructor(opts: GoalRunnerOptions);
|
|
9
|
+
enqueue(goalId: string, options?: EnqueueGoalRunOptions): GoalQueueItemSnapshot;
|
|
10
|
+
snapshot(): GoalQueueItemSnapshot[];
|
|
11
|
+
private schedulePump;
|
|
12
|
+
private pump;
|
|
13
|
+
private runItem;
|
|
14
|
+
private retry;
|
|
15
|
+
private finish;
|
|
16
|
+
private emit;
|
|
17
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createLogger } from "../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../utils/logger.js";
|
|
3
|
+
import { GoalService } from "./goal-service.js";
|
|
4
|
+
import { GoalQueueStore } from "./goal-queue-store.js";
|
|
5
|
+
//#region src/goals/goal-runner.ts
|
|
6
|
+
init_logger();
|
|
7
|
+
const log = createLogger("GoalRunner");
|
|
8
|
+
function terminalGoalStatus(status) {
|
|
9
|
+
return status === "done" || status === "archived";
|
|
10
|
+
}
|
|
11
|
+
var GoalRunner = class {
|
|
12
|
+
goals = new GoalService();
|
|
13
|
+
store = new GoalQueueStore();
|
|
14
|
+
active = 0;
|
|
15
|
+
pumpTimer = null;
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
this.opts = opts;
|
|
18
|
+
this.store.resetRunningToRetry();
|
|
19
|
+
this.schedulePump(0);
|
|
20
|
+
}
|
|
21
|
+
enqueue(goalId, options = {}) {
|
|
22
|
+
const item = this.store.enqueue({
|
|
23
|
+
goalId,
|
|
24
|
+
maxRetries: Math.max(0, Math.floor(options.maxRetries ?? this.opts.defaultMaxRetries ?? 2)),
|
|
25
|
+
message: options.message?.trim() || void 0,
|
|
26
|
+
source: options.source ?? "api"
|
|
27
|
+
});
|
|
28
|
+
this.emit("goal.queue.updated", item);
|
|
29
|
+
this.schedulePump(0);
|
|
30
|
+
return item;
|
|
31
|
+
}
|
|
32
|
+
snapshot() {
|
|
33
|
+
return this.store.list();
|
|
34
|
+
}
|
|
35
|
+
schedulePump(delayMs) {
|
|
36
|
+
if (this.pumpTimer) return;
|
|
37
|
+
this.pumpTimer = setTimeout(() => {
|
|
38
|
+
this.pumpTimer = null;
|
|
39
|
+
this.pump();
|
|
40
|
+
}, delayMs);
|
|
41
|
+
}
|
|
42
|
+
async pump() {
|
|
43
|
+
const maxConcurrent = Math.max(1, Math.floor(this.opts.maxConcurrent ?? 1));
|
|
44
|
+
while (this.active < maxConcurrent) {
|
|
45
|
+
const item = this.store.claimNext();
|
|
46
|
+
if (!item) break;
|
|
47
|
+
this.active += 1;
|
|
48
|
+
this.runItem(item).finally(() => {
|
|
49
|
+
this.active -= 1;
|
|
50
|
+
this.schedulePump(0);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const nextRetry = this.store.list().filter((item) => item.status === "retry_waiting" && item.nextRunAt).sort((a, b) => (a.nextRunAt ?? 0) - (b.nextRunAt ?? 0))[0];
|
|
54
|
+
if (nextRetry) this.schedulePump(Math.max(0, (nextRetry.nextRunAt ?? Date.now()) - Date.now()));
|
|
55
|
+
}
|
|
56
|
+
async runItem(item) {
|
|
57
|
+
this.emit("goal.queue.updated", item);
|
|
58
|
+
try {
|
|
59
|
+
const goal = this.goals.get(item.goalId);
|
|
60
|
+
if (!goal) {
|
|
61
|
+
this.finish(item, "failed", "Goal not found");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (terminalGoalStatus(goal.status)) {
|
|
65
|
+
this.finish(item, "skipped", `Goal is ${goal.status}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const activeGoal = goal.status === "paused" || goal.status === "blocked" || goal.status === "needs_input" ? this.goals.resume(goal.id) : goal;
|
|
69
|
+
if (!activeGoal) {
|
|
70
|
+
this.finish(item, "failed", "Goal not found");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const sessionKey = activeGoal.activeSessionKey ?? await this.opts.ensureSession(activeGoal);
|
|
74
|
+
item = this.store.setSessionKey(item.id, sessionKey) ?? {
|
|
75
|
+
...item,
|
|
76
|
+
sessionKey
|
|
77
|
+
};
|
|
78
|
+
if (this.opts.hasActiveRun(sessionKey)) {
|
|
79
|
+
this.retry(item, "Goal session already has an active run");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const message = item.message || activeGoal.nextAction || activeGoal.title;
|
|
83
|
+
await this.opts.runContinuation(sessionKey, message);
|
|
84
|
+
this.finish(item, "succeeded");
|
|
85
|
+
} catch (err) {
|
|
86
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
87
|
+
if (item.attempts <= item.maxRetries) this.retry(item, message);
|
|
88
|
+
else this.finish(item, "failed", message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
retry(item, error) {
|
|
92
|
+
const base = Math.max(1e3, this.opts.retryBaseMs ?? 5e3);
|
|
93
|
+
item.status = "retry_waiting";
|
|
94
|
+
item.lastError = error;
|
|
95
|
+
item.nextRunAt = Date.now() + base * 2 ** Math.max(0, item.attempts - 1);
|
|
96
|
+
item = this.store.markRetry(item.id, error, item.nextRunAt) ?? item;
|
|
97
|
+
log.warn({
|
|
98
|
+
goalId: item.goalId,
|
|
99
|
+
attempts: item.attempts,
|
|
100
|
+
nextRunAt: item.nextRunAt,
|
|
101
|
+
errorMessage: error
|
|
102
|
+
}, `Goal run queued for retry: ${error}`);
|
|
103
|
+
this.emit("goal.queue.updated", item);
|
|
104
|
+
this.schedulePump(Math.max(0, item.nextRunAt - Date.now()));
|
|
105
|
+
}
|
|
106
|
+
finish(item, status, error) {
|
|
107
|
+
item = this.store.markFinished(item.id, status, error) ?? {
|
|
108
|
+
...item,
|
|
109
|
+
status,
|
|
110
|
+
finishedAt: Date.now(),
|
|
111
|
+
lastError: error
|
|
112
|
+
};
|
|
113
|
+
if (status === "failed") log.warn({
|
|
114
|
+
goalId: item.goalId,
|
|
115
|
+
attempts: item.attempts,
|
|
116
|
+
errorMessage: error
|
|
117
|
+
}, `Goal run failed: ${error}`);
|
|
118
|
+
this.emit("goal.queue.updated", item);
|
|
119
|
+
}
|
|
120
|
+
emit(type, item) {
|
|
121
|
+
this.opts.emit?.(type, { item: { ...item } });
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
//#endregion
|
|
125
|
+
export { GoalRunner };
|
|
126
|
+
|
|
127
|
+
//# sourceMappingURL=goal-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goal-runner.js","names":[],"sources":["../../../src/goals/goal-runner.ts"],"sourcesContent":["import { GoalQueueStore } from './goal-queue-store.js';\nimport { GoalService } from './goal-service.js';\nimport type { EnqueueGoalRunOptions, GoalQueueItemSnapshot, GoalQueueStatus, GoalRunnerOptions } from './goal-queue-types.js';\nimport type { GoalWithDetails } from './types.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst log = createLogger('GoalRunner');\n\nfunction terminalGoalStatus(status: GoalWithDetails['status']): boolean {\n return status === 'done' || status === 'archived';\n}\n\nexport class GoalRunner {\n private readonly goals = new GoalService();\n private readonly store = new GoalQueueStore();\n private active = 0;\n private pumpTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(private readonly opts: GoalRunnerOptions) {\n this.store.resetRunningToRetry();\n this.schedulePump(0);\n }\n\n enqueue(goalId: string, options: EnqueueGoalRunOptions = {}): GoalQueueItemSnapshot {\n const item = this.store.enqueue({\n goalId,\n maxRetries: Math.max(0, Math.floor(options.maxRetries ?? this.opts.defaultMaxRetries ?? 2)),\n message: options.message?.trim() || undefined,\n source: options.source ?? 'api',\n });\n this.emit('goal.queue.updated', item);\n this.schedulePump(0);\n return item;\n }\n\n snapshot(): GoalQueueItemSnapshot[] {\n return this.store.list();\n }\n\n private schedulePump(delayMs: number): void {\n if (this.pumpTimer) return;\n this.pumpTimer = setTimeout(() => {\n this.pumpTimer = null;\n void this.pump();\n }, delayMs);\n }\n\n private async pump(): Promise<void> {\n const maxConcurrent = Math.max(1, Math.floor(this.opts.maxConcurrent ?? 1));\n while (this.active < maxConcurrent) {\n const item = this.store.claimNext();\n if (!item) break;\n this.active += 1;\n void this.runItem(item).finally(() => {\n this.active -= 1;\n this.schedulePump(0);\n });\n }\n\n const nextRetry = this.store.list()\n .filter((item) => item.status === 'retry_waiting' && item.nextRunAt)\n .sort((a, b) => (a.nextRunAt ?? 0) - (b.nextRunAt ?? 0))[0];\n if (nextRetry) this.schedulePump(Math.max(0, (nextRetry.nextRunAt ?? Date.now()) - Date.now()));\n }\n\n private async runItem(item: GoalQueueItemSnapshot): Promise<void> {\n this.emit('goal.queue.updated', item);\n try {\n const goal = this.goals.get(item.goalId);\n if (!goal) {\n this.finish(item, 'failed', 'Goal not found');\n return;\n }\n if (terminalGoalStatus(goal.status)) {\n this.finish(item, 'skipped', `Goal is ${goal.status}`);\n return;\n }\n const activeGoal =\n goal.status === 'paused' || goal.status === 'blocked' || goal.status === 'needs_input'\n ? this.goals.resume(goal.id)\n : goal;\n if (!activeGoal) {\n this.finish(item, 'failed', 'Goal not found');\n return;\n }\n const sessionKey = activeGoal.activeSessionKey ?? (await this.opts.ensureSession(activeGoal));\n item = this.store.setSessionKey(item.id, sessionKey) ?? { ...item, sessionKey };\n if (this.opts.hasActiveRun(sessionKey)) {\n this.retry(item, 'Goal session already has an active run');\n return;\n }\n const message = item.message || activeGoal.nextAction || activeGoal.title;\n await this.opts.runContinuation(sessionKey, message);\n this.finish(item, 'succeeded');\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (item.attempts <= item.maxRetries) {\n this.retry(item, message);\n } else {\n this.finish(item, 'failed', message);\n }\n }\n }\n\n private retry(item: GoalQueueItemSnapshot, error: string): void {\n const base = Math.max(1_000, this.opts.retryBaseMs ?? 5_000);\n item.status = 'retry_waiting';\n item.lastError = error;\n item.nextRunAt = Date.now() + base * 2 ** Math.max(0, item.attempts - 1);\n item = this.store.markRetry(item.id, error, item.nextRunAt) ?? item;\n log.warn({ goalId: item.goalId, attempts: item.attempts, nextRunAt: item.nextRunAt, errorMessage: error }, `Goal run queued for retry: ${error}`);\n this.emit('goal.queue.updated', item);\n this.schedulePump(Math.max(0, item.nextRunAt - Date.now()));\n }\n\n private finish(item: GoalQueueItemSnapshot, status: GoalQueueStatus, error?: string): void {\n item = this.store.markFinished(item.id, status, error) ?? { ...item, status, finishedAt: Date.now(), lastError: error };\n if (status === 'failed') {\n log.warn({ goalId: item.goalId, attempts: item.attempts, errorMessage: error }, `Goal run failed: ${error}`);\n }\n this.emit('goal.queue.updated', item);\n }\n\n private emit(type: string, item: GoalQueueItemSnapshot): void {\n this.opts.emit?.(type, { item: { ...item } });\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Config } from '../config/schema.js';
|
|
2
|
+
import { GoalStore } from './goal-store.js';
|
|
3
|
+
import type { CreateGoalInput, Goal, GoalChecklistAddedBy, GoalChecklistStatus, GoalJudgeDecision, GoalListQuery, GoalSource, GoalStatus, GoalUiLocale, GoalWithDetails } from './types.js';
|
|
4
|
+
export declare class GoalService {
|
|
5
|
+
private readonly store;
|
|
6
|
+
constructor(store?: GoalStore);
|
|
7
|
+
create(input: Omit<CreateGoalInput, 'maxTurns' | 'agentId'> & {
|
|
8
|
+
maxTurns?: number;
|
|
9
|
+
agentId?: string;
|
|
10
|
+
config?: Config;
|
|
11
|
+
}): Goal;
|
|
12
|
+
get(goalId: string): GoalWithDetails | null;
|
|
13
|
+
getActiveForSession(sessionKey: string): GoalWithDetails | null;
|
|
14
|
+
list(query?: GoalListQuery): GoalWithDetails[];
|
|
15
|
+
update(goalId: string, patch: Partial<Pick<Goal, 'title' | 'description' | 'priority' | 'deadlineAt' | 'maxTurns' | 'judgeModelRef' | 'nextAction' | 'blockedReason' | 'activeSessionKey' | 'uiLocale'>>): GoalWithDetails | null;
|
|
16
|
+
setStatus(goalId: string, status: GoalStatus, opts?: {
|
|
17
|
+
reason?: string;
|
|
18
|
+
}): GoalWithDetails | null;
|
|
19
|
+
pause(goalId: string, reason?: string): GoalWithDetails | null;
|
|
20
|
+
resume(goalId: string): GoalWithDetails | null;
|
|
21
|
+
reopen(goalId: string): GoalWithDetails | null;
|
|
22
|
+
complete(goalId: string): GoalWithDetails | null;
|
|
23
|
+
archive(goalId: string): GoalWithDetails | null;
|
|
24
|
+
unarchive(goalId: string): GoalWithDetails | null;
|
|
25
|
+
attachSession(goalId: string, sessionKey: string): GoalWithDetails | null;
|
|
26
|
+
updateChecklist(goalId: string, op: {
|
|
27
|
+
type: 'add';
|
|
28
|
+
text: string;
|
|
29
|
+
addedBy?: GoalChecklistAddedBy;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'mark';
|
|
32
|
+
itemId: string;
|
|
33
|
+
status: GoalChecklistStatus;
|
|
34
|
+
evidenceSummary?: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: 'remove';
|
|
37
|
+
itemId: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'reset';
|
|
40
|
+
}): GoalWithDetails | null;
|
|
41
|
+
recordJudgeDecision(input: {
|
|
42
|
+
goalId: string;
|
|
43
|
+
sessionKey: string;
|
|
44
|
+
source: GoalSource;
|
|
45
|
+
decision: GoalJudgeDecision;
|
|
46
|
+
}): GoalWithDetails | null;
|
|
47
|
+
syncPostTurnState(input: {
|
|
48
|
+
goalId: string;
|
|
49
|
+
sessionKey: string;
|
|
50
|
+
source: GoalSource;
|
|
51
|
+
status: GoalStatus;
|
|
52
|
+
turnsUsed: number;
|
|
53
|
+
maxTurns: number;
|
|
54
|
+
reason?: string;
|
|
55
|
+
nextAction?: string;
|
|
56
|
+
assistantPreview?: string;
|
|
57
|
+
verdict: 'continue' | 'done' | 'blocked' | 'needs_input' | 'decompose';
|
|
58
|
+
confidence?: number;
|
|
59
|
+
missingEvidence?: string[];
|
|
60
|
+
userQuestion?: string;
|
|
61
|
+
completedChecklistItemIds?: string[];
|
|
62
|
+
checklist?: Array<{
|
|
63
|
+
text: string;
|
|
64
|
+
status: 'pending' | 'completed' | 'impossible';
|
|
65
|
+
addedBy: 'user' | 'judge';
|
|
66
|
+
addedAt?: number;
|
|
67
|
+
completedAt?: number;
|
|
68
|
+
evidenceSummary?: string;
|
|
69
|
+
}>;
|
|
70
|
+
}): GoalWithDetails | null;
|
|
71
|
+
listRuns(goalId: string, limit?: number): import("./types.js").GoalRun[];
|
|
72
|
+
listRunsForSession(sessionKey: string, limit?: number): import("./types.js").GoalRun[];
|
|
73
|
+
listEvents(goalId: string, limit?: number): import("./types.js").GoalEvent[];
|
|
74
|
+
addEvidence(input: Parameters<GoalStore['addEvidence']>[0]): import("./types.js").GoalEvidence;
|
|
75
|
+
listEvidence(goalId: string, limit?: number): import("./types.js").GoalEvidence[];
|
|
76
|
+
}
|
|
77
|
+
export declare function normalizeGoalUiLocale(raw: unknown): GoalUiLocale | undefined;
|