@cloudcli-ai/cloudcli 1.37.1 → 1.37.3
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 +1 -1
- package/dist/assets/abnfDiagram-N423BO3Z-CJntskwA.js +1 -0
- package/dist/assets/arc-CIfH4uMN.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-_IpEkukg.js +36 -0
- package/dist/assets/blockDiagram-VBNYF7ZC-BttxrpgZ.js +132 -0
- package/dist/assets/c4Diagram-5PPSVZJV-g51dSRr1.js +10 -0
- package/dist/assets/channel-C5xU9jMw.js +1 -0
- package/dist/assets/chunk-2GRJ4B5K-D_-LbQUa.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-BtlHzwaO.js +1 -0
- package/dist/assets/chunk-5RXB4S5H-UzkpvpAT.js +231 -0
- package/dist/assets/chunk-5VM5RSS4-BKwx5Gag.js +15 -0
- package/dist/assets/chunk-6Q2QTUOP-D_KEBH5f.js +88 -0
- package/dist/assets/chunk-GF5L2VYU-rK2DGyto.js +206 -0
- package/dist/assets/chunk-JWPE2WC7-CeBl_N9i.js +1 -0
- package/dist/assets/chunk-KBJHAD2P-BiQLWVIr.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-C1knVGV5.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-CtKbJkvF.js +1 -0
- package/dist/assets/classDiagram-JCYQIIEL-CRrL3vp7.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-CRrL3vp7.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-DjX8y40Z.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-DM6YeRba.js +178 -0
- package/dist/assets/cynefinDiagram-MW4NZA55-wZJ-vy5B.js +62 -0
- package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
- package/dist/assets/dagre-VZM6K2ZE-BzP0ejkH.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-7IWD3JNH-BTL_j23J.js +30 -0
- package/dist/assets/diagram-B4RE2ZJO-Cf1Norgu.js +3 -0
- package/dist/assets/diagram-LBJQPF4R-DPiUEKr-.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-CTcTpiz5.js +24 -0
- package/dist/assets/diagram-UB23O5K3-DPMSdYm0.js +41 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-7yvD6uiF.js +1 -0
- package/dist/assets/erDiagram-JOGREHBK-CfgeDoV6.js +85 -0
- package/dist/assets/flowDiagram-UKHOOZJN-B27ng5zu.js +156 -0
- package/dist/assets/ganttDiagram-PKOTCBZU-CcC6b_7t.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-BX5kPGrO.js +106 -0
- package/dist/assets/graph-Cx-EqisW.js +1 -0
- package/dist/assets/index-BTDHQ6Id.css +32 -0
- package/dist/assets/index-sFfz8iBR.js +4931 -0
- package/dist/assets/infoDiagram-6WML65LV-CUP-SlN4.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-BTTDhOpX.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-ARXcc61m.js +139 -0
- package/dist/assets/kanban-definition-27J2QSJJ-BieMpqMA.js +89 -0
- package/dist/assets/layout-D6BstSzF.js +1 -0
- package/dist/assets/linear-61vxjRnP.js +1 -0
- package/dist/assets/map-CV9uif0g.js +1 -0
- package/dist/assets/mermaid.core-nhnwkfQx.js +312 -0
- package/dist/assets/mindmap-definition-FAOFIHXS-Cm6x5vT1.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-VL7TDLO6-CcYBq_9H.js +1 -0
- package/dist/assets/pieDiagram-7S7Q4E2Y-CfvIjfzo.js +39 -0
- package/dist/assets/quadrantDiagram-CIZ2JOQS-SgYoWPNb.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-D2Mu5E_f.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-Azwe_k4Q.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-DN1Ju3PA.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-Bj4qC34S.js +162 -0
- package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-Co4PdklG.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-nOvL9dRC.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DNv8ppw7.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-CCK3wMid.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-BC29RNFm.js +120 -0
- package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
- package/dist/assets/vennDiagram-T6HMQDX7-pZlZCtEo.js +34 -0
- package/dist/assets/wardleyDiagram-T6FBY63Y-De8k2-zM.js +78 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-Bk8ykH0w.js +7 -0
- package/dist/index.html +3 -3
- package/dist-server/server/index.js +8 -5
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/agent/agent.module.js +2 -1
- package/dist-server/server/modules/agent/agent.module.js.map +1 -1
- package/dist-server/server/modules/agent/agent.routes.js +2 -2
- package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
- package/dist-server/server/modules/agent/tests/agent.routes.test.js +30 -1
- package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
- package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
- package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use.service.js +3 -6
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
- package/dist-server/server/modules/browser-use/index.js +2 -0
- package/dist-server/server/modules/browser-use/index.js.map +1 -1
- package/dist-server/server/modules/cli/cli.js +9 -3
- package/dist-server/server/modules/cli/cli.js.map +1 -1
- package/dist-server/server/modules/cli/cli.module.js +0 -1
- package/dist-server/server/modules/cli/cli.module.js.map +1 -1
- package/dist-server/server/modules/cli/cli.service.js +0 -18
- package/dist-server/server/modules/cli/cli.service.js.map +1 -1
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js +0 -3
- package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -1
- package/dist-server/server/modules/database/connection.js +1 -1
- package/dist-server/server/modules/database/connection.js.map +1 -1
- package/dist-server/server/modules/database/index.js +5 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +32 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +137 -5
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
- package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
- package/dist-server/server/modules/database/schema.js +92 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.service.js +18 -10
- package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +62 -11
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
- package/dist-server/server/modules/git/git-branch.service.js +15 -0
- package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
- package/dist-server/server/modules/git/git.routes.js +15 -7
- package/dist-server/server/modules/git/git.routes.js.map +1 -1
- package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
- package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js +84 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -1
- package/dist-server/server/modules/notifications/index.js +3 -1
- package/dist-server/server/modules/notifications/index.js.map +1 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +22 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
- package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +3 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +20 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +35 -6
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +422 -86
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +388 -50
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +16 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +60 -49
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1565 -432
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +258 -6
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +29 -0
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +14 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +63 -7
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-history-cache.service.js +87 -0
- package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +179 -9
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-auth.test.js +52 -0
- package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
- package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js +465 -39
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-runtime.test.js +51 -0
- package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +304 -15
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +117 -4
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +35 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
- package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +45 -1
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/scheduled-messages/index.js +5 -0
- package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
- package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
- package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
- package/dist-server/server/modules/user/user.module.js +10 -1
- package/dist-server/server/modules/user/user.module.js.map +1 -1
- package/dist-server/server/modules/user/user.routes.js +44 -0
- package/dist-server/server/modules/user/user.routes.js.map +1 -1
- package/dist-server/server/modules/user/user.service.js +75 -0
- package/dist-server/server/modules/user/user.service.js.map +1 -1
- package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
- package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
- package/dist-server/server/modules/websocket/index.js +7 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +10 -44
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +166 -9
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -3
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +80 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
- package/dist-server/server/shared/claude-cli-path.js +22 -5
- package/dist-server/server/shared/claude-cli-path.js.map +1 -1
- package/dist-server/server/shared/index.js +3 -0
- package/dist-server/server/shared/index.js.map +1 -0
- package/dist-server/server/shared/message-unification.js +376 -0
- package/dist-server/server/shared/message-unification.js.map +1 -0
- package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
- package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
- package/dist-server/server/shared/tests/message-unification.test.js +174 -0
- package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +42 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +23 -16
- package/scripts/promote-dist-server.mjs +50 -0
- package/server/index.ts +12 -6
- package/server/modules/agent/agent.module.ts +2 -1
- package/server/modules/agent/agent.routes.ts +2 -2
- package/server/modules/agent/tests/agent.routes.test.ts +33 -1
- package/server/modules/auth/auth.middleware.ts +2 -2
- package/server/modules/browser-use/browser-use-runtime.ts +10 -0
- package/server/modules/browser-use/browser-use.routes.ts +96 -33
- package/server/modules/browser-use/browser-use.service.ts +5 -8
- package/server/modules/browser-use/index.ts +3 -0
- package/server/modules/cli/cli.module.ts +0 -1
- package/server/modules/cli/cli.service.ts +0 -20
- package/server/modules/cli/cli.ts +9 -3
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
- package/server/modules/cli/tests/cli.service.test.ts +0 -3
- package/server/modules/database/connection.ts +2 -1
- package/server/modules/database/index.ts +10 -0
- package/server/modules/database/migrations.ts +37 -0
- package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
- package/server/modules/database/repositories/session-drafts.db.ts +171 -0
- package/server/modules/database/repositories/sessions.db.ts +196 -3
- package/server/modules/database/repositories/user-preferences.db.ts +80 -0
- package/server/modules/database/schema.ts +96 -0
- package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
- package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
- package/server/modules/file-tree/file-tree.service.ts +21 -10
- package/server/modules/file-tree/tests/file-tree.service.test.ts +65 -11
- package/server/modules/git/git-branch.service.ts +43 -0
- package/server/modules/git/git.routes.ts +16 -9
- package/server/modules/git/tests/git-branch.service.test.ts +60 -0
- package/server/modules/git/tests/git-init.routes.test.ts +83 -0
- package/server/modules/notifications/index.ts +2 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +24 -1
- package/server/modules/projects/services/project-delete.service.ts +13 -1
- package/server/modules/projects/services/project-star.service.ts +2 -2
- package/server/modules/providers/index.ts +4 -0
- package/server/modules/providers/list/claude/claude-auth.provider.ts +21 -1
- package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
- package/server/modules/providers/list/claude/claude-models.provider.ts +38 -6
- package/server/modules/providers/list/claude/claude-runtime.provider.js +463 -93
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +478 -54
- package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
- package/server/modules/providers/list/claude/claude.provider.ts +3 -0
- package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
- package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
- package/server/modules/providers/list/codex/codex-models.provider.ts +16 -0
- package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +93 -66
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +1806 -477
- package/server/modules/providers/list/codex/codex.provider.ts +3 -0
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +279 -6
- package/server/modules/providers/provider.routes.ts +36 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +23 -0
- package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
- package/server/modules/providers/services/session-conversations-search.service.ts +94 -7
- package/server/modules/providers/services/session-history-cache.service.ts +139 -0
- package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
- package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
- package/server/modules/providers/services/sessions.service.ts +201 -9
- package/server/modules/providers/tests/claude-auth.test.ts +58 -0
- package/server/modules/providers/tests/claude-models.test.ts +87 -0
- package/server/modules/providers/tests/claude-sessions.test.ts +552 -40
- package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
- package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
- package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
- package/server/modules/providers/tests/codex-runtime.test.ts +58 -0
- package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
- package/server/modules/providers/tests/codex-sessions.test.ts +337 -15
- package/server/modules/providers/tests/opencode-models.test.ts +166 -8
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
- package/server/modules/providers/tests/provider.routes.test.ts +61 -0
- package/server/modules/providers/tests/session-fork.test.ts +173 -0
- package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
- package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
- package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
- package/server/modules/providers/tests/sessions.service.test.ts +67 -1
- package/server/modules/providers/tests/skills.test.ts +21 -5
- package/server/modules/scheduled-messages/index.ts +8 -0
- package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
- package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
- package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
- package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
- package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
- package/server/modules/user/tests/user.service.test.ts +90 -0
- package/server/modules/user/user.module.ts +10 -1
- package/server/modules/user/user.routes.ts +44 -0
- package/server/modules/user/user.service.ts +105 -0
- package/server/modules/voice/tests/voice.service.test.ts +3 -2
- package/server/modules/websocket/index.ts +8 -0
- package/server/modules/websocket/services/chat-run-registry.service.ts +17 -50
- package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
- package/server/modules/websocket/services/chat-websocket.service.ts +244 -15
- package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
- package/server/modules/websocket/services/shell-websocket.service.ts +10 -3
- package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
- package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +108 -0
- package/server/shared/claude-cli-path.ts +24 -6
- package/server/shared/index.ts +3 -0
- package/server/shared/interfaces.ts +62 -0
- package/server/shared/message-unification.ts +449 -0
- package/server/shared/tests/claude-cli-path.test.ts +58 -1
- package/server/shared/tests/message-unification.test.ts +218 -0
- package/server/shared/types.ts +123 -3
- package/server/shared/utils.ts +50 -0
- package/server/tsconfig.json +6 -2
- package/README.de.md +0 -257
- package/README.ja.md +0 -249
- package/README.ko.md +0 -249
- package/README.ru.md +0 -257
- package/README.tr.md +0 -258
- package/README.zh-CN.md +0 -249
- package/README.zh-TW.md +0 -249
- package/dist/assets/index-D3jfzuKN.css +0 -32
- package/dist/assets/index-DTMWjJj8.js +0 -1550
- package/dist-server/server/constants/config.js +0 -6
- package/dist-server/server/constants/config.js.map +0 -1
- package/server/constants/config.ts +0 -5
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { scheduledMessagesDb, sessionsDb } from '@/modules/database/index.js';
|
|
2
|
+
import type { ScheduledMessageRow } from '@/modules/database/index.js';
|
|
3
|
+
import { AppError } from '@/shared/utils.js';
|
|
4
|
+
|
|
5
|
+
/** How far ahead a message may be scheduled. Beyond this it is almost certainly a mistake. */
|
|
6
|
+
const MAX_SCHEDULE_AHEAD_MS = 365 * 24 * 60 * 60 * 1000;
|
|
7
|
+
const MAX_CONTENT_LENGTH = 100_000;
|
|
8
|
+
|
|
9
|
+
export type ScheduledMessage = {
|
|
10
|
+
id: string;
|
|
11
|
+
sessionId: string;
|
|
12
|
+
content: string;
|
|
13
|
+
options: Record<string, unknown>;
|
|
14
|
+
scheduledFor: string;
|
|
15
|
+
status: ScheduledMessageRow['status'];
|
|
16
|
+
failureReason: string | null;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function readOptions(raw: string): Record<string, unknown> {
|
|
21
|
+
try {
|
|
22
|
+
const parsed: unknown = JSON.parse(raw);
|
|
23
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
24
|
+
? parsed as Record<string, unknown>
|
|
25
|
+
: {};
|
|
26
|
+
} catch {
|
|
27
|
+
// A corrupt options blob must not stop the message from being sent.
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function toScheduledMessage(row: ScheduledMessageRow): ScheduledMessage {
|
|
33
|
+
return {
|
|
34
|
+
id: row.id,
|
|
35
|
+
sessionId: row.session_id,
|
|
36
|
+
content: row.content,
|
|
37
|
+
options: readOptions(row.options),
|
|
38
|
+
scheduledFor: row.scheduled_for,
|
|
39
|
+
status: row.status,
|
|
40
|
+
failureReason: row.failure_reason,
|
|
41
|
+
createdAt: row.created_at,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const scheduledMessagesService = {
|
|
46
|
+
/**
|
|
47
|
+
* Records a message to send to a session later.
|
|
48
|
+
*
|
|
49
|
+
* The time is taken as an absolute instant, so the schedule does not move if
|
|
50
|
+
* the user changes time zone — or schedules from a phone and is on a laptop
|
|
51
|
+
* when it fires.
|
|
52
|
+
*/
|
|
53
|
+
schedule(input: {
|
|
54
|
+
userId: number;
|
|
55
|
+
sessionId: string;
|
|
56
|
+
content: string;
|
|
57
|
+
options?: unknown;
|
|
58
|
+
scheduledFor: string;
|
|
59
|
+
}): ScheduledMessage {
|
|
60
|
+
const content = input.content.trim();
|
|
61
|
+
if (!content) {
|
|
62
|
+
throw new AppError('A scheduled message needs some text.', {
|
|
63
|
+
code: 'CONTENT_REQUIRED',
|
|
64
|
+
statusCode: 400,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (content.length > MAX_CONTENT_LENGTH) {
|
|
68
|
+
throw new AppError('That message is too long to schedule.', {
|
|
69
|
+
code: 'CONTENT_TOO_LONG',
|
|
70
|
+
statusCode: 400,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const scheduledFor = new Date(input.scheduledFor);
|
|
75
|
+
if (Number.isNaN(scheduledFor.getTime())) {
|
|
76
|
+
throw new AppError('scheduledFor must be an ISO timestamp.', {
|
|
77
|
+
code: 'INVALID_SCHEDULE_TIME',
|
|
78
|
+
statusCode: 400,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (scheduledFor.getTime() - Date.now() > MAX_SCHEDULE_AHEAD_MS) {
|
|
82
|
+
throw new AppError('That is too far in the future to schedule.', {
|
|
83
|
+
code: 'SCHEDULE_TOO_FAR_AHEAD',
|
|
84
|
+
statusCode: 400,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!sessionsDb.getSessionById(input.sessionId)) {
|
|
89
|
+
throw new AppError(`Session "${input.sessionId}" was not found.`, {
|
|
90
|
+
code: 'SESSION_NOT_FOUND',
|
|
91
|
+
statusCode: 404,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return toScheduledMessage(scheduledMessagesDb.create({
|
|
96
|
+
userId: input.userId,
|
|
97
|
+
sessionId: input.sessionId,
|
|
98
|
+
content,
|
|
99
|
+
options: input.options ?? {},
|
|
100
|
+
scheduledFor,
|
|
101
|
+
}));
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
listForSession(userId: number, sessionId: string): ScheduledMessage[] {
|
|
105
|
+
return scheduledMessagesDb.listForSession(userId, sessionId).map(toScheduledMessage);
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
listPending(userId: number): ScheduledMessage[] {
|
|
109
|
+
return scheduledMessagesDb.listPendingForUser(userId).map(toScheduledMessage);
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
/** Cancels a pending message, or dismisses a failed one. */
|
|
113
|
+
cancel(userId: number, id: string): void {
|
|
114
|
+
if (!scheduledMessagesDb.cancel(userId, id)) {
|
|
115
|
+
throw new AppError('That message has already been sent or cancelled.', {
|
|
116
|
+
code: 'SCHEDULED_MESSAGE_NOT_PENDING',
|
|
117
|
+
statusCode: 409,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import { closeConnection, initializeDatabase, scheduledMessagesDb, sessionDraftsDb, sessionsDb, userDb } from '@/modules/database/index.js';
|
|
8
|
+
import { dispatchDueScheduledMessages, dispatchQueuedMessages } from '@/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js';
|
|
9
|
+
import { scheduledMessagesService } from '@/modules/scheduled-messages/services/scheduled-messages.service.js';
|
|
10
|
+
import { chatRunRegistry } from '@/modules/websocket/index.js';
|
|
11
|
+
|
|
12
|
+
const SESSION_ID = 'scheduled-session';
|
|
13
|
+
|
|
14
|
+
async function withIsolatedDatabase(runTest: (userId: number) => void | Promise<void>): Promise<void> {
|
|
15
|
+
const previousDatabasePath = process.env.DATABASE_PATH;
|
|
16
|
+
const tempDirectory = await mkdtemp(path.join(tmpdir(), 'scheduled-messages-'));
|
|
17
|
+
|
|
18
|
+
closeConnection();
|
|
19
|
+
process.env.DATABASE_PATH = path.join(tempDirectory, 'auth.db');
|
|
20
|
+
await initializeDatabase();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const user = userDb.createUser('scheduler', 'hash');
|
|
24
|
+
sessionsDb.createAppSession(SESSION_ID, 'claude', tempDirectory, 'Scheduled session');
|
|
25
|
+
await runTest(Number(user.id));
|
|
26
|
+
} finally {
|
|
27
|
+
chatRunRegistry.clearAll();
|
|
28
|
+
closeConnection();
|
|
29
|
+
if (previousDatabasePath === undefined) {
|
|
30
|
+
delete process.env.DATABASE_PATH;
|
|
31
|
+
} else {
|
|
32
|
+
process.env.DATABASE_PATH = previousDatabasePath;
|
|
33
|
+
}
|
|
34
|
+
await rm(tempDirectory, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type RunCall = { provider: string; command: string; options: Record<string, unknown> };
|
|
39
|
+
|
|
40
|
+
function createRuntime(runs: RunCall[], behaviour: 'ok' | 'throw' = 'ok', aborts: string[] = []) {
|
|
41
|
+
return {
|
|
42
|
+
hasRuntime: () => true,
|
|
43
|
+
run: async (provider: string, command: string, options: Record<string, unknown>) => {
|
|
44
|
+
if (behaviour === 'throw') {
|
|
45
|
+
throw new Error('provider exploded');
|
|
46
|
+
}
|
|
47
|
+
runs.push({ provider, command, options });
|
|
48
|
+
},
|
|
49
|
+
abort: async (_provider: string, sessionId: string) => {
|
|
50
|
+
aborts.push(sessionId);
|
|
51
|
+
return true;
|
|
52
|
+
},
|
|
53
|
+
} as never;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test('a message due in the past is sent on the next pass, not skipped', async () => {
|
|
57
|
+
await withIsolatedDatabase(async (userId) => {
|
|
58
|
+
// The server was down when this came due.
|
|
59
|
+
scheduledMessagesService.schedule({
|
|
60
|
+
userId,
|
|
61
|
+
sessionId: SESSION_ID,
|
|
62
|
+
content: 'run the nightly checks',
|
|
63
|
+
scheduledFor: new Date(Date.now() - 60_000).toISOString(),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const runs: RunCall[] = [];
|
|
67
|
+
const sent = await dispatchDueScheduledMessages(createRuntime(runs));
|
|
68
|
+
|
|
69
|
+
assert.equal(sent, 1);
|
|
70
|
+
assert.equal(runs.length, 1);
|
|
71
|
+
assert.equal(runs[0].command, 'run the nightly checks');
|
|
72
|
+
assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'sent');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('a queued message is sent by the server without a browser connection', async () => {
|
|
77
|
+
await withIsolatedDatabase(async (userId) => {
|
|
78
|
+
sessionDraftsDb.saveDraft(userId, SESSION_ID, {
|
|
79
|
+
text: '',
|
|
80
|
+
queuedMessage: {
|
|
81
|
+
content: 'continue on the VPS',
|
|
82
|
+
options: { model: 'claude-opus-5' },
|
|
83
|
+
attachments: [{ path: '/tmp/upload.png' }],
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const runs: RunCall[] = [];
|
|
88
|
+
assert.equal(await dispatchQueuedMessages(createRuntime(runs)), 1);
|
|
89
|
+
assert.equal(runs.length, 1);
|
|
90
|
+
assert.equal(runs[0].command, 'continue on the VPS');
|
|
91
|
+
assert.equal(runs[0].options.model, 'claude-opus-5');
|
|
92
|
+
assert.deepEqual(runs[0].options.attachments, []);
|
|
93
|
+
assert.equal(sessionDraftsDb.getDrafts(userId).length, 0);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('a queued message stays pending while its session is busy', async () => {
|
|
98
|
+
await withIsolatedDatabase(async (userId) => {
|
|
99
|
+
sessionDraftsDb.saveDraft(userId, SESSION_ID, {
|
|
100
|
+
text: '',
|
|
101
|
+
queuedMessage: { content: 'send after this run' },
|
|
102
|
+
});
|
|
103
|
+
chatRunRegistry.startRun({
|
|
104
|
+
appSessionId: SESSION_ID,
|
|
105
|
+
provider: 'claude',
|
|
106
|
+
providerSessionId: null,
|
|
107
|
+
connection: null,
|
|
108
|
+
userId,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const runs: RunCall[] = [];
|
|
112
|
+
assert.equal(await dispatchQueuedMessages(createRuntime(runs)), 0);
|
|
113
|
+
assert.equal(runs.length, 0);
|
|
114
|
+
assert.deepEqual(sessionDraftsDb.getDrafts(userId)[0]?.queuedMessage, {
|
|
115
|
+
content: 'send after this run',
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('a due message interrupts a run in progress instead of failing', async () => {
|
|
121
|
+
await withIsolatedDatabase(async (userId) => {
|
|
122
|
+
scheduledMessagesService.schedule({
|
|
123
|
+
userId,
|
|
124
|
+
sessionId: SESSION_ID,
|
|
125
|
+
content: 'the schedule wins',
|
|
126
|
+
scheduledFor: new Date(Date.now() - 1_000).toISOString(),
|
|
127
|
+
});
|
|
128
|
+
chatRunRegistry.startRun({
|
|
129
|
+
appSessionId: SESSION_ID,
|
|
130
|
+
provider: 'claude',
|
|
131
|
+
providerSessionId: null,
|
|
132
|
+
connection: null,
|
|
133
|
+
userId,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const runs: RunCall[] = [];
|
|
137
|
+
const aborts: string[] = [];
|
|
138
|
+
assert.equal(await dispatchDueScheduledMessages(createRuntime(runs, 'ok', aborts)), 1);
|
|
139
|
+
|
|
140
|
+
assert.deepEqual(aborts, [SESSION_ID]);
|
|
141
|
+
assert.equal(runs.length, 1);
|
|
142
|
+
assert.equal(runs[0].command, 'the schedule wins');
|
|
143
|
+
assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'sent');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('a message that is not due yet is left alone', async () => {
|
|
148
|
+
await withIsolatedDatabase(async (userId) => {
|
|
149
|
+
scheduledMessagesService.schedule({
|
|
150
|
+
userId,
|
|
151
|
+
sessionId: SESSION_ID,
|
|
152
|
+
content: 'later',
|
|
153
|
+
scheduledFor: new Date(Date.now() + 3_600_000).toISOString(),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const runs: RunCall[] = [];
|
|
157
|
+
assert.equal(await dispatchDueScheduledMessages(createRuntime(runs)), 0);
|
|
158
|
+
assert.equal(runs.length, 0);
|
|
159
|
+
assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'pending');
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('a due message is claimed once, so overlapping passes cannot double-send it', async () => {
|
|
164
|
+
await withIsolatedDatabase(async (userId) => {
|
|
165
|
+
scheduledMessagesService.schedule({
|
|
166
|
+
userId,
|
|
167
|
+
sessionId: SESSION_ID,
|
|
168
|
+
content: 'only once',
|
|
169
|
+
scheduledFor: new Date(Date.now() - 1_000).toISOString(),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const runs: RunCall[] = [];
|
|
173
|
+
const runtime = createRuntime(runs);
|
|
174
|
+
await Promise.all([
|
|
175
|
+
dispatchDueScheduledMessages(runtime),
|
|
176
|
+
dispatchDueScheduledMessages(runtime),
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
assert.equal(runs.length, 1);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('the composer settings it was scheduled with travel with it', async () => {
|
|
184
|
+
await withIsolatedDatabase(async (userId) => {
|
|
185
|
+
scheduledMessagesService.schedule({
|
|
186
|
+
userId,
|
|
187
|
+
sessionId: SESSION_ID,
|
|
188
|
+
content: 'with options',
|
|
189
|
+
options: { model: 'claude-opus-5', permissionMode: 'plan' },
|
|
190
|
+
scheduledFor: new Date(Date.now() - 1_000).toISOString(),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const runs: RunCall[] = [];
|
|
194
|
+
await dispatchDueScheduledMessages(createRuntime(runs));
|
|
195
|
+
|
|
196
|
+
assert.equal(runs[0].options.model, 'claude-opus-5');
|
|
197
|
+
assert.equal(runs[0].options.permissionMode, 'plan');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('a provider failure is recorded on the message instead of vanishing', async () => {
|
|
202
|
+
await withIsolatedDatabase(async (userId) => {
|
|
203
|
+
scheduledMessagesService.schedule({
|
|
204
|
+
userId,
|
|
205
|
+
sessionId: SESSION_ID,
|
|
206
|
+
content: 'will fail',
|
|
207
|
+
scheduledFor: new Date(Date.now() - 1_000).toISOString(),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
await dispatchDueScheduledMessages(createRuntime([], 'throw'));
|
|
211
|
+
|
|
212
|
+
const row = scheduledMessagesDb.listForSession(userId, SESSION_ID)[0];
|
|
213
|
+
assert.equal(row.status, 'failed');
|
|
214
|
+
assert.match(row.failure_reason ?? '', /provider exploded/);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('a cancelled message never fires', async () => {
|
|
219
|
+
await withIsolatedDatabase(async (userId) => {
|
|
220
|
+
const scheduled = scheduledMessagesService.schedule({
|
|
221
|
+
userId,
|
|
222
|
+
sessionId: SESSION_ID,
|
|
223
|
+
content: 'never mind',
|
|
224
|
+
scheduledFor: new Date(Date.now() - 1_000).toISOString(),
|
|
225
|
+
});
|
|
226
|
+
scheduledMessagesService.cancel(userId, scheduled.id);
|
|
227
|
+
|
|
228
|
+
const runs: RunCall[] = [];
|
|
229
|
+
assert.equal(await dispatchDueScheduledMessages(createRuntime(runs)), 0);
|
|
230
|
+
assert.equal(runs.length, 0);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('a failed message can be dismissed, and stays dismissed', async () => {
|
|
235
|
+
await withIsolatedDatabase(async (userId) => {
|
|
236
|
+
const scheduled = scheduledMessagesService.schedule({
|
|
237
|
+
userId,
|
|
238
|
+
sessionId: SESSION_ID,
|
|
239
|
+
content: 'will fail',
|
|
240
|
+
scheduledFor: new Date(Date.now() - 1_000).toISOString(),
|
|
241
|
+
});
|
|
242
|
+
await dispatchDueScheduledMessages(createRuntime([], 'throw'));
|
|
243
|
+
assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'failed');
|
|
244
|
+
|
|
245
|
+
scheduledMessagesService.cancel(userId, scheduled.id);
|
|
246
|
+
|
|
247
|
+
assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'cancelled');
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('cancelling something that already fired is refused', async () => {
|
|
252
|
+
await withIsolatedDatabase(async (userId) => {
|
|
253
|
+
const scheduled = scheduledMessagesService.schedule({
|
|
254
|
+
userId,
|
|
255
|
+
sessionId: SESSION_ID,
|
|
256
|
+
content: 'gone',
|
|
257
|
+
scheduledFor: new Date(Date.now() - 1_000).toISOString(),
|
|
258
|
+
});
|
|
259
|
+
await dispatchDueScheduledMessages(createRuntime([]));
|
|
260
|
+
|
|
261
|
+
assert.throws(
|
|
262
|
+
() => scheduledMessagesService.cancel(userId, scheduled.id),
|
|
263
|
+
(error: Error & { code?: string }) => error.code === 'SCHEDULED_MESSAGE_NOT_PENDING',
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('one user cannot cancel another user\'s scheduled message', async () => {
|
|
269
|
+
await withIsolatedDatabase(async (userId) => {
|
|
270
|
+
const scheduled = scheduledMessagesService.schedule({
|
|
271
|
+
userId,
|
|
272
|
+
sessionId: SESSION_ID,
|
|
273
|
+
content: 'mine',
|
|
274
|
+
scheduledFor: new Date(Date.now() + 3_600_000).toISOString(),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
assert.throws(
|
|
278
|
+
() => scheduledMessagesService.cancel(userId + 1, scheduled.id),
|
|
279
|
+
(error: Error & { code?: string }) => error.code === 'SCHEDULED_MESSAGE_NOT_PENDING',
|
|
280
|
+
);
|
|
281
|
+
assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'pending');
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test('scheduling validates its input', async () => {
|
|
286
|
+
await withIsolatedDatabase(async (userId) => {
|
|
287
|
+
const base = { userId, sessionId: SESSION_ID, scheduledFor: new Date(Date.now() + 1000).toISOString() };
|
|
288
|
+
|
|
289
|
+
assert.throws(
|
|
290
|
+
() => scheduledMessagesService.schedule({ ...base, content: ' ' }),
|
|
291
|
+
(error: Error & { code?: string }) => error.code === 'CONTENT_REQUIRED',
|
|
292
|
+
);
|
|
293
|
+
assert.throws(
|
|
294
|
+
() => scheduledMessagesService.schedule({ ...base, content: 'hi', scheduledFor: 'not a date' }),
|
|
295
|
+
(error: Error & { code?: string }) => error.code === 'INVALID_SCHEDULE_TIME',
|
|
296
|
+
);
|
|
297
|
+
assert.throws(
|
|
298
|
+
() => scheduledMessagesService.schedule({
|
|
299
|
+
...base,
|
|
300
|
+
content: 'hi',
|
|
301
|
+
scheduledFor: new Date(Date.now() + 400 * 24 * 3600 * 1000).toISOString(),
|
|
302
|
+
}),
|
|
303
|
+
(error: Error & { code?: string }) => error.code === 'SCHEDULE_TOO_FAR_AHEAD',
|
|
304
|
+
);
|
|
305
|
+
assert.throws(
|
|
306
|
+
() => scheduledMessagesService.schedule({ ...base, sessionId: 'nope', content: 'hi' }),
|
|
307
|
+
(error: Error & { code?: string }) => error.code === 'SESSION_NOT_FOUND',
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
@@ -13,6 +13,8 @@ import path from 'path';
|
|
|
13
13
|
|
|
14
14
|
import express from 'express';
|
|
15
15
|
|
|
16
|
+
import { WS_OPEN_STATE, connectedClients } from '@/modules/websocket/index.js';
|
|
17
|
+
|
|
16
18
|
import type { createTaskmasterService } from './taskmaster.service.js';
|
|
17
19
|
// cross-spawn: drop-in spawn with Windows .cmd/PATHEXT resolution — required
|
|
18
20
|
// here since task-master/npx are .cmd shims on Windows.
|
|
@@ -25,22 +27,30 @@ type TaskmasterRouterDependencies = {
|
|
|
25
27
|
taskmasterService: ReturnType<typeof createTaskmasterService>;
|
|
26
28
|
};
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
/**
|
|
31
|
+
* These frames are `type`-keyed and only TaskMasterContext, which lives on the
|
|
32
|
+
* `/ws` chat socket, knows what to do with them. Broadcasting over the raw
|
|
33
|
+
* `wss.clients` set instead delivered them to every `/shell`, `/plugin-ws` and
|
|
34
|
+
* `/desktop-notifications` socket as well, where they were parsed and dropped —
|
|
35
|
+
* and, on `/plugin-ws`, handed to third-party plugin frontends that have no
|
|
36
|
+
* business seeing them. `connectedClients` is the `/ws`-only set every other
|
|
37
|
+
* broadcast in the server already uses.
|
|
38
|
+
*/
|
|
39
|
+
function broadcastTaskMasterUpdate(type, projectId, payloadKey, payload) {
|
|
40
|
+
if (!projectId) return;
|
|
30
41
|
const message = JSON.stringify({
|
|
31
42
|
type,
|
|
32
43
|
projectId,
|
|
33
44
|
...(payload === undefined ? {} : { [payloadKey]: payload }),
|
|
34
45
|
timestamp: new Date().toISOString(),
|
|
35
46
|
});
|
|
36
|
-
|
|
37
|
-
if (client.readyState ===
|
|
47
|
+
connectedClients.forEach((client) => {
|
|
48
|
+
if (client.readyState === WS_OPEN_STATE) client.send(message);
|
|
38
49
|
});
|
|
39
50
|
}
|
|
40
51
|
|
|
41
|
-
function broadcastTaskMasterProjectUpdate(
|
|
52
|
+
function broadcastTaskMasterProjectUpdate(projectId, taskMasterData) {
|
|
42
53
|
broadcastTaskMasterUpdate(
|
|
43
|
-
wss,
|
|
44
54
|
'taskmaster-project-updated',
|
|
45
55
|
projectId,
|
|
46
56
|
'taskMasterData',
|
|
@@ -48,8 +58,8 @@ function broadcastTaskMasterProjectUpdate(wss, projectId, taskMasterData) {
|
|
|
48
58
|
);
|
|
49
59
|
}
|
|
50
60
|
|
|
51
|
-
function broadcastTaskMasterTasksUpdate(
|
|
52
|
-
broadcastTaskMasterUpdate(
|
|
61
|
+
function broadcastTaskMasterTasksUpdate(projectId, tasksData) {
|
|
62
|
+
broadcastTaskMasterUpdate('taskmaster-tasks-updated', projectId, 'tasksData', tasksData);
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
/**
|
|
@@ -596,13 +606,7 @@ export function createTaskmasterRouter(dependencies: TaskmasterRouterDependencie
|
|
|
596
606
|
// Broadcast TaskMaster project update via WebSocket. The
|
|
597
607
|
// WebSocket payload keeps using `projectId` so the frontend
|
|
598
608
|
// can match notifications against the current selection.
|
|
599
|
-
|
|
600
|
-
broadcastTaskMasterProjectUpdate(
|
|
601
|
-
req.app.locals.wss,
|
|
602
|
-
projectId,
|
|
603
|
-
{ hasTaskmaster: true, status: 'initialized' }
|
|
604
|
-
);
|
|
605
|
-
}
|
|
609
|
+
broadcastTaskMasterProjectUpdate(projectId, { hasTaskmaster: true, status: 'initialized' });
|
|
606
610
|
|
|
607
611
|
res.json({
|
|
608
612
|
projectId,
|
|
@@ -688,12 +692,7 @@ export function createTaskmasterRouter(dependencies: TaskmasterRouterDependencie
|
|
|
688
692
|
if (code === 0) {
|
|
689
693
|
// Broadcast task update via WebSocket using the projectId so
|
|
690
694
|
// clients subscribed to this project get notified immediately.
|
|
691
|
-
|
|
692
|
-
broadcastTaskMasterTasksUpdate(
|
|
693
|
-
req.app.locals.wss,
|
|
694
|
-
projectId
|
|
695
|
-
);
|
|
696
|
-
}
|
|
695
|
+
broadcastTaskMasterTasksUpdate(projectId);
|
|
697
696
|
|
|
698
697
|
res.json({
|
|
699
698
|
projectId,
|
|
@@ -748,9 +747,7 @@ export function createTaskmasterRouter(dependencies: TaskmasterRouterDependencie
|
|
|
748
747
|
}, ({ code, error, stdout, stderr }) => {
|
|
749
748
|
if (code === 0) {
|
|
750
749
|
// Broadcast task update via WebSocket
|
|
751
|
-
|
|
752
|
-
broadcastTaskMasterTasksUpdate(req.app.locals.wss, projectId);
|
|
753
|
-
}
|
|
750
|
+
broadcastTaskMasterTasksUpdate(projectId);
|
|
754
751
|
|
|
755
752
|
res.json({
|
|
756
753
|
projectId,
|
|
@@ -787,9 +784,7 @@ export function createTaskmasterRouter(dependencies: TaskmasterRouterDependencie
|
|
|
787
784
|
}, ({ code, error, stdout, stderr }) => {
|
|
788
785
|
if (code === 0) {
|
|
789
786
|
// Broadcast task update via WebSocket
|
|
790
|
-
|
|
791
|
-
broadcastTaskMasterTasksUpdate(req.app.locals.wss, projectId);
|
|
792
|
-
}
|
|
787
|
+
broadcastTaskMasterTasksUpdate(projectId);
|
|
793
788
|
|
|
794
789
|
res.json({
|
|
795
790
|
projectId,
|
|
@@ -870,12 +865,7 @@ export function createTaskmasterRouter(dependencies: TaskmasterRouterDependencie
|
|
|
870
865
|
}, ({ code, error, stdout, stderr }) => {
|
|
871
866
|
if (code === 0) {
|
|
872
867
|
// Broadcast task update via WebSocket
|
|
873
|
-
|
|
874
|
-
broadcastTaskMasterTasksUpdate(
|
|
875
|
-
req.app.locals.wss,
|
|
876
|
-
projectId
|
|
877
|
-
);
|
|
878
|
-
}
|
|
868
|
+
broadcastTaskMasterTasksUpdate(projectId);
|
|
879
869
|
|
|
880
870
|
res.json({
|
|
881
871
|
projectId,
|
|
@@ -6,6 +6,8 @@ import test from 'node:test';
|
|
|
6
6
|
|
|
7
7
|
import express from 'express';
|
|
8
8
|
|
|
9
|
+
import { connectedClients } from '@/modules/websocket/index.js';
|
|
10
|
+
|
|
9
11
|
import { createTaskmasterRouter } from '../taskmaster.routes.js';
|
|
10
12
|
|
|
11
13
|
test('tasks route resolves project ids through the injected project adapter', async () => {
|
|
@@ -142,3 +144,58 @@ test('TaskMaster process errors use the endpoint failure response and settle onc
|
|
|
142
144
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
143
145
|
}
|
|
144
146
|
});
|
|
147
|
+
|
|
148
|
+
test('TaskMaster broadcasts reach only the tracked chat clients', async () => {
|
|
149
|
+
const child = new EventEmitter() as EventEmitter & {
|
|
150
|
+
stdin: PassThrough;
|
|
151
|
+
stdout: PassThrough;
|
|
152
|
+
stderr: PassThrough;
|
|
153
|
+
};
|
|
154
|
+
child.stdin = new PassThrough();
|
|
155
|
+
child.stdout = new PassThrough();
|
|
156
|
+
child.stderr = new PassThrough();
|
|
157
|
+
|
|
158
|
+
const chatClient = { readyState: 1, frames: [] as string[], send(data: string) { this.frames.push(data); } };
|
|
159
|
+
const closingClient = { readyState: 3, frames: [] as string[], send(data: string) { this.frames.push(data); } };
|
|
160
|
+
|
|
161
|
+
const router = createTaskmasterRouter({
|
|
162
|
+
fileSystem: { constants: { F_OK: 0 } } as typeof import('node:fs'),
|
|
163
|
+
fileSystemPromises: {
|
|
164
|
+
access: async () => { throw new Error('not initialized'); },
|
|
165
|
+
} as unknown as typeof import('node:fs/promises'),
|
|
166
|
+
spawnProcess: (() => {
|
|
167
|
+
process.nextTick(() => child.emit('close', 0));
|
|
168
|
+
return child;
|
|
169
|
+
}) as unknown as Parameters<typeof createTaskmasterRouter>[0]['spawnProcess'],
|
|
170
|
+
resolveProjectPathById: () => '/workspace/project',
|
|
171
|
+
taskmasterService: {
|
|
172
|
+
detectMcpServer: async () => ({
|
|
173
|
+
hasMCPServer: false,
|
|
174
|
+
reason: 'Not configured',
|
|
175
|
+
hasConfig: false,
|
|
176
|
+
}),
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
const app = express().use('/api/taskmaster', router);
|
|
180
|
+
const server = app.listen(0, '127.0.0.1');
|
|
181
|
+
await once(server, 'listening');
|
|
182
|
+
|
|
183
|
+
connectedClients.add(chatClient as never);
|
|
184
|
+
connectedClients.add(closingClient as never);
|
|
185
|
+
try {
|
|
186
|
+
const address = server.address() as AddressInfo;
|
|
187
|
+
const response = await fetch(`http://127.0.0.1:${address.port}/api/taskmaster/init/project-1`, {
|
|
188
|
+
method: 'POST',
|
|
189
|
+
});
|
|
190
|
+
assert.equal(response.status, 200);
|
|
191
|
+
|
|
192
|
+
assert.equal(chatClient.frames.length, 1);
|
|
193
|
+
const frame = JSON.parse(chatClient.frames[0]) as Record<string, unknown>;
|
|
194
|
+
assert.equal(frame.type, 'taskmaster-project-updated');
|
|
195
|
+
assert.equal(frame.projectId, 'project-1');
|
|
196
|
+
assert.equal(closingClient.frames.length, 0);
|
|
197
|
+
} finally {
|
|
198
|
+
connectedClients.clear();
|
|
199
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
200
|
+
}
|
|
201
|
+
});
|