@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,104 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
extractCumulativeTokenBudget,
|
|
6
|
+
extractTokenBudget,
|
|
7
|
+
} from '@/modules/providers/list/claude/claude-runtime.provider.js';
|
|
8
|
+
|
|
9
|
+
test('assistant usage produces a cumulative budget', () => {
|
|
10
|
+
const budget = extractTokenBudget({
|
|
11
|
+
type: 'assistant',
|
|
12
|
+
message: {
|
|
13
|
+
usage: {
|
|
14
|
+
input_tokens: 12,
|
|
15
|
+
cache_read_input_tokens: 40_000,
|
|
16
|
+
cache_creation_input_tokens: 2_000,
|
|
17
|
+
output_tokens: 500,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
assert.ok(budget);
|
|
23
|
+
assert.equal(budget.inputTokens, 42_012);
|
|
24
|
+
assert.equal(budget.outputTokens, 500);
|
|
25
|
+
assert.equal(budget.used, 42_512);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('system task events with tool-usage shaped usage emit no budget', () => {
|
|
29
|
+
// task_progress/task_notification carry usage {total_tokens, tool_uses,
|
|
30
|
+
// duration_ms}; reading Anthropic keys off it produced a used: 0 budget
|
|
31
|
+
// that flashed "0" in the composer mid-generation.
|
|
32
|
+
const budget = extractTokenBudget({
|
|
33
|
+
type: 'system',
|
|
34
|
+
subtype: 'task_progress',
|
|
35
|
+
task_id: 't-1',
|
|
36
|
+
usage: { total_tokens: 5_000, tool_uses: 3, duration_ms: 1_200 },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
assert.equal(budget, null);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('subagent messages emit no budget for the parent session', () => {
|
|
43
|
+
// A subagent's usage is its own context window; surfacing it made the
|
|
44
|
+
// session counter drop to the subagent's number and bounce back.
|
|
45
|
+
const budget = extractTokenBudget({
|
|
46
|
+
type: 'assistant',
|
|
47
|
+
parent_tool_use_id: 'toolu_123',
|
|
48
|
+
message: { usage: { input_tokens: 900, output_tokens: 10 } },
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
assert.equal(budget, null);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('a turn-ending result emits no budget', () => {
|
|
55
|
+
// `result.usage` is the turn's bill: every request it made, summed, each
|
|
56
|
+
// subagent's included. A four-request turn therefore reports roughly four
|
|
57
|
+
// times the context the conversation holds, so publishing it made the
|
|
58
|
+
// counter leap when the turn ended and fall back on the next turn's first
|
|
59
|
+
// assistant message.
|
|
60
|
+
const budget = extractTokenBudget({
|
|
61
|
+
type: 'result',
|
|
62
|
+
usage: {
|
|
63
|
+
input_tokens: 18,
|
|
64
|
+
cache_creation_input_tokens: 8_138,
|
|
65
|
+
cache_read_input_tokens: 40_460,
|
|
66
|
+
output_tokens: 166,
|
|
67
|
+
},
|
|
68
|
+
modelUsage: {
|
|
69
|
+
'claude-sonnet-5': { inputTokens: 929, outputTokens: 177 },
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
assert.equal(budget, null);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('the cumulative reader stays available for SDK builds with no assistant usage', () => {
|
|
77
|
+
const fromUsage = extractCumulativeTokenBudget({
|
|
78
|
+
type: 'result',
|
|
79
|
+
usage: { input_tokens: 18, cache_read_input_tokens: 40_460, output_tokens: 166 },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
assert.ok(fromUsage);
|
|
83
|
+
assert.equal(fromUsage.used, 40_644);
|
|
84
|
+
|
|
85
|
+
const fromModelUsage = extractCumulativeTokenBudget({
|
|
86
|
+
type: 'result',
|
|
87
|
+
modelUsage: {
|
|
88
|
+
'claude-sonnet-5': { cumulativeInputTokens: 1_000, cumulativeOutputTokens: 200 },
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
assert.ok(fromModelUsage);
|
|
93
|
+
assert.equal(fromModelUsage.used, 1_200);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('the cumulative reader ignores anything that is not a result', () => {
|
|
97
|
+
assert.equal(
|
|
98
|
+
extractCumulativeTokenBudget({
|
|
99
|
+
type: 'assistant',
|
|
100
|
+
message: { usage: { input_tokens: 10, output_tokens: 2 } },
|
|
101
|
+
}),
|
|
102
|
+
null,
|
|
103
|
+
);
|
|
104
|
+
});
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import test from 'node:test';
|
|
8
|
+
|
|
9
|
+
import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js';
|
|
10
|
+
import { codexAppServer } from '@/modules/providers/list/codex/codex-app-server.client.js';
|
|
11
|
+
import { CodexSessionSynchronizer } from '@/modules/providers/list/codex/codex-session-synchronizer.provider.js';
|
|
12
|
+
import { CodexSessionsProvider } from '@/modules/providers/list/codex/codex-sessions.provider.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Codex rows carry no id of their own, so an edit addresses the enclosing
|
|
16
|
+
* *turn*. These cover the two halves of that: that the reader can name the
|
|
17
|
+
* turn a prompt belongs to, and that a turn the thread has retired is never
|
|
18
|
+
* offered as one.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const patchHomeDir = (nextHomeDir: string) => {
|
|
22
|
+
const original = os.homedir;
|
|
23
|
+
(os as any).homedir = () => nextHomeDir;
|
|
24
|
+
return () => {
|
|
25
|
+
(os as any).homedir = original;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
async function withIsolatedDatabase(runTest: () => void | Promise<void>): Promise<void> {
|
|
30
|
+
const previousDatabasePath = process.env.DATABASE_PATH;
|
|
31
|
+
const tempDirectory = await mkdtemp(path.join(os.tmpdir(), 'codex-edit-db-'));
|
|
32
|
+
|
|
33
|
+
closeConnection();
|
|
34
|
+
process.env.DATABASE_PATH = path.join(tempDirectory, 'auth.db');
|
|
35
|
+
await initializeDatabase();
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await runTest();
|
|
39
|
+
} finally {
|
|
40
|
+
closeConnection();
|
|
41
|
+
if (previousDatabasePath === undefined) {
|
|
42
|
+
delete process.env.DATABASE_PATH;
|
|
43
|
+
} else {
|
|
44
|
+
process.env.DATABASE_PATH = previousDatabasePath;
|
|
45
|
+
}
|
|
46
|
+
await rm(tempDirectory, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type TranscriptRow = Record<string, unknown>;
|
|
51
|
+
|
|
52
|
+
/** One completed turn: the bracket rows that name it, plus its prompt(s). */
|
|
53
|
+
function turn(turnId: string, prompts: string[]): TranscriptRow[] {
|
|
54
|
+
return [
|
|
55
|
+
{ type: 'event_msg', payload: { type: 'task_started', turn_id: turnId } },
|
|
56
|
+
{ type: 'turn_context', payload: { turn_id: turnId, cwd: '/tmp' } },
|
|
57
|
+
...prompts.map((message) => ({ type: 'event_msg', payload: { type: 'user_message', message } })),
|
|
58
|
+
{ type: 'event_msg', payload: { type: 'agent_message', message: `answer to ${prompts[0]}` } },
|
|
59
|
+
{ type: 'event_msg', payload: { type: 'task_complete', turn_id: turnId } },
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function writeRollout(
|
|
64
|
+
homeDir: string,
|
|
65
|
+
threadId: string,
|
|
66
|
+
workspacePath: string,
|
|
67
|
+
rows: TranscriptRow[],
|
|
68
|
+
): Promise<string> {
|
|
69
|
+
const sessionsDir = path.join(homeDir, '.codex', 'sessions', '2026', '07', '07');
|
|
70
|
+
await mkdir(sessionsDir, { recursive: true });
|
|
71
|
+
const lines = [
|
|
72
|
+
JSON.stringify({ type: 'session_meta', payload: { id: threadId, cwd: workspacePath } }),
|
|
73
|
+
...rows.map((row) => JSON.stringify(row)),
|
|
74
|
+
];
|
|
75
|
+
const filePath = path.join(sessionsDir, `rollout-${threadId}.jsonl`);
|
|
76
|
+
await writeFile(filePath, `${lines.join('\n')}\n`, 'utf8');
|
|
77
|
+
return filePath;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Stands up an indexed Codex session from a set of transcript rows. */
|
|
81
|
+
async function withIndexedSession(
|
|
82
|
+
rows: TranscriptRow[],
|
|
83
|
+
runTest: (context: { sessionId: string; workspacePath: string; homeDir: string }) => Promise<void>,
|
|
84
|
+
): Promise<void> {
|
|
85
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-edit-'));
|
|
86
|
+
const workspacePath = path.join(tempRoot, 'workspace');
|
|
87
|
+
await mkdir(workspacePath, { recursive: true });
|
|
88
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await writeRollout(tempRoot, 'thread-1', workspacePath, rows);
|
|
92
|
+
await withIsolatedDatabase(async () => {
|
|
93
|
+
await new CodexSessionSynchronizer().synchronize();
|
|
94
|
+
await runTest({ sessionId: 'thread-1', workspacePath, homeDir: tempRoot });
|
|
95
|
+
});
|
|
96
|
+
} finally {
|
|
97
|
+
restoreHomeDir();
|
|
98
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const THREE_TURNS = [
|
|
103
|
+
...turn('turn-a', ['first prompt']),
|
|
104
|
+
...turn('turn-b', ['second prompt']),
|
|
105
|
+
...turn('turn-c', ['third prompt']),
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
test('every Codex prompt is anchored to the turn that contains it', { concurrency: false }, async () => {
|
|
109
|
+
await withIndexedSession(THREE_TURNS, async ({ sessionId }) => {
|
|
110
|
+
const history = await new CodexSessionsProvider().fetchHistory(sessionId);
|
|
111
|
+
const prompts = history.messages.filter((message) => message.role === 'user');
|
|
112
|
+
|
|
113
|
+
assert.deepEqual(
|
|
114
|
+
prompts.map((message) => [message.content, message.transcriptAnchorId]),
|
|
115
|
+
[
|
|
116
|
+
['first prompt', 'turn-a'],
|
|
117
|
+
['second prompt', 'turn-b'],
|
|
118
|
+
['third prompt', 'turn-c'],
|
|
119
|
+
],
|
|
120
|
+
);
|
|
121
|
+
// An anchor on an answer would put a pencil on a message that cannot be
|
|
122
|
+
// edited, so only prompts carry one.
|
|
123
|
+
assert.equal(
|
|
124
|
+
history.messages.some((message) => message.role !== 'user' && message.transcriptAnchorId),
|
|
125
|
+
false,
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('only the first prompt of a turn is anchored', { concurrency: false }, async () => {
|
|
131
|
+
// A follow-up typed while a turn is running is recorded inside that same
|
|
132
|
+
// turn. The cut is per turn, so anchoring the follow-up would silently take
|
|
133
|
+
// the prompt before it as well.
|
|
134
|
+
await withIndexedSession(turn('turn-a', ['first prompt', 'queued follow-up']), async ({ sessionId }) => {
|
|
135
|
+
const history = await new CodexSessionsProvider().fetchHistory(sessionId);
|
|
136
|
+
const prompts = history.messages.filter((message) => message.role === 'user');
|
|
137
|
+
|
|
138
|
+
assert.deepEqual(
|
|
139
|
+
prompts.map((message) => [message.content, message.transcriptAnchorId]),
|
|
140
|
+
[
|
|
141
|
+
['first prompt', 'turn-a'],
|
|
142
|
+
['queued follow-up', undefined],
|
|
143
|
+
],
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('a turn the thread rolled back keeps its rows but loses its anchor', { concurrency: false }, async () => {
|
|
149
|
+
const rows = [
|
|
150
|
+
...turn('turn-a', ['first prompt']),
|
|
151
|
+
...turn('turn-b', ['abandoned prompt']),
|
|
152
|
+
{ type: 'event_msg', payload: { type: 'thread_rolled_back', num_turns: 1 } },
|
|
153
|
+
...turn('turn-c', ['replacement prompt']),
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
await withIndexedSession(rows, async ({ sessionId }) => {
|
|
157
|
+
const provider = new CodexSessionsProvider();
|
|
158
|
+
const history = await provider.fetchHistory(sessionId);
|
|
159
|
+
const prompts = history.messages.filter((message) => message.role === 'user');
|
|
160
|
+
|
|
161
|
+
assert.deepEqual(
|
|
162
|
+
prompts.map((message) => [message.content, message.transcriptAnchorId]),
|
|
163
|
+
[
|
|
164
|
+
['first prompt', 'turn-a'],
|
|
165
|
+
// Still rendered — it is what the conversation looked like — but the
|
|
166
|
+
// turn is gone from the thread and the fork endpoint would reject it.
|
|
167
|
+
['abandoned prompt', undefined],
|
|
168
|
+
['replacement prompt', 'turn-c'],
|
|
169
|
+
],
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
assert.deepEqual(
|
|
173
|
+
await provider.resolveEditAnchor(sessionId, 'turn-b'),
|
|
174
|
+
{ found: false, resumeThroughId: null },
|
|
175
|
+
);
|
|
176
|
+
// turn-b is out of the sequence, so turn-c resumes through turn-a.
|
|
177
|
+
assert.deepEqual(
|
|
178
|
+
await provider.resolveEditAnchor(sessionId, 'turn-c'),
|
|
179
|
+
{ found: true, resumeThroughId: 'turn-a' },
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('the edit anchor resolves to the turn before the one being replaced', { concurrency: false }, async () => {
|
|
185
|
+
await withIndexedSession(THREE_TURNS, async ({ sessionId }) => {
|
|
186
|
+
const provider = new CodexSessionsProvider();
|
|
187
|
+
|
|
188
|
+
assert.deepEqual(
|
|
189
|
+
await provider.resolveEditAnchor(sessionId, 'turn-c'),
|
|
190
|
+
{ found: true, resumeThroughId: 'turn-b' },
|
|
191
|
+
);
|
|
192
|
+
// Nothing precedes the first prompt, so the conversation starts over.
|
|
193
|
+
assert.deepEqual(
|
|
194
|
+
await provider.resolveEditAnchor(sessionId, 'turn-a'),
|
|
195
|
+
{ found: true, resumeThroughId: null },
|
|
196
|
+
);
|
|
197
|
+
assert.deepEqual(
|
|
198
|
+
await provider.resolveEditAnchor(sessionId, 'turn-nope'),
|
|
199
|
+
{ found: false, resumeThroughId: null },
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('rewinding a Codex session moves it onto the branch and retires the old thread', { concurrency: false }, async () => {
|
|
205
|
+
await withIndexedSession(THREE_TURNS, async ({ sessionId, homeDir, workspacePath }) => {
|
|
206
|
+
const forkPath = path.join(homeDir, '.codex', 'sessions', '2026', '07', '08', 'rollout-thread-2.jsonl');
|
|
207
|
+
await mkdir(path.dirname(forkPath), { recursive: true });
|
|
208
|
+
await writeFile(forkPath, `${JSON.stringify({ type: 'session_meta', payload: { id: 'thread-2', cwd: workspacePath } })}\n`, 'utf8');
|
|
209
|
+
|
|
210
|
+
const realForkThread = codexAppServer.forkThread;
|
|
211
|
+
const forkCalls: unknown[] = [];
|
|
212
|
+
codexAppServer.forkThread = async (input) => {
|
|
213
|
+
forkCalls.push(input);
|
|
214
|
+
return { threadId: 'thread-2', path: forkPath };
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
await new CodexSessionsProvider().rewindSession(sessionId, 'turn-b');
|
|
219
|
+
} finally {
|
|
220
|
+
codexAppServer.forkThread = realForkThread;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
assert.deepEqual(forkCalls, [{ threadId: 'thread-1', lastTurnId: 'turn-b', cwd: workspacePath }]);
|
|
224
|
+
|
|
225
|
+
const session = sessionsDb.getSessionById(sessionId);
|
|
226
|
+
assert.equal(session?.provider_session_id, 'thread-2');
|
|
227
|
+
// Not `COALESCE`d onto the old path: the session reads its transcript from
|
|
228
|
+
// here, and leaving it on the pre-edit file would serve the conversation
|
|
229
|
+
// the user just edited away from, for good.
|
|
230
|
+
assert.equal(session?.jsonl_path, forkPath);
|
|
231
|
+
assert.equal(sessionsDb.isProviderSessionSuperseded('thread-1', 'codex'), true);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('editing the first prompt starts the conversation over', { concurrency: false }, async () => {
|
|
236
|
+
await withIndexedSession(THREE_TURNS, async ({ sessionId }) => {
|
|
237
|
+
const realForkThread = codexAppServer.forkThread;
|
|
238
|
+
let forked = false;
|
|
239
|
+
codexAppServer.forkThread = async () => {
|
|
240
|
+
forked = true;
|
|
241
|
+
throw new Error('a fork of no turns should never be attempted');
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
await new CodexSessionsProvider().rewindSession(sessionId, null);
|
|
246
|
+
} finally {
|
|
247
|
+
codexAppServer.forkThread = realForkThread;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
assert.equal(forked, false);
|
|
251
|
+
const session = sessionsDb.getSessionById(sessionId);
|
|
252
|
+
assert.equal(session?.provider_session_id, null);
|
|
253
|
+
assert.equal(session?.jsonl_path, null);
|
|
254
|
+
assert.equal(sessionsDb.isProviderSessionSuperseded('thread-1', 'codex'), true);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test('the indexer does not hand back a thread a session was edited off', { concurrency: false }, async () => {
|
|
259
|
+
await withIndexedSession(THREE_TURNS, async ({ sessionId, homeDir, workspacePath }) => {
|
|
260
|
+
const forkPath = path.join(homeDir, '.codex', 'sessions', '2026', '07', '08', 'rollout-thread-2.jsonl');
|
|
261
|
+
await mkdir(path.dirname(forkPath), { recursive: true });
|
|
262
|
+
await writeFile(forkPath, `${JSON.stringify({ type: 'session_meta', payload: { id: 'thread-2', cwd: workspacePath } })}\n`, 'utf8');
|
|
263
|
+
|
|
264
|
+
const realForkThread = codexAppServer.forkThread;
|
|
265
|
+
codexAppServer.forkThread = async () => ({ threadId: 'thread-2', path: forkPath });
|
|
266
|
+
try {
|
|
267
|
+
await new CodexSessionsProvider().rewindSession(sessionId, 'turn-b');
|
|
268
|
+
} finally {
|
|
269
|
+
codexAppServer.forkThread = realForkThread;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// The pre-edit transcript is still on disk on purpose. A full rescan finds
|
|
273
|
+
// it again, and for a session discovered from disk — whose app id is its
|
|
274
|
+
// original thread id — reindexing it would point the row straight back at
|
|
275
|
+
// the conversation the edit replaced.
|
|
276
|
+
await new CodexSessionSynchronizer().synchronize();
|
|
277
|
+
|
|
278
|
+
const session = sessionsDb.getSessionById(sessionId);
|
|
279
|
+
assert.equal(session?.provider_session_id, 'thread-2');
|
|
280
|
+
assert.equal(session?.jsonl_path, forkPath);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('deleting an edited session removes every transcript it lived in', { concurrency: false }, async () => {
|
|
285
|
+
await withIndexedSession(THREE_TURNS, async ({ sessionId, homeDir, workspacePath }) => {
|
|
286
|
+
const { sessionsService } = await import('@/modules/providers/services/sessions.service.js');
|
|
287
|
+
const preEditPath = sessionsDb.getSessionById(sessionId)?.jsonl_path as string;
|
|
288
|
+
const forkPath = path.join(homeDir, '.codex', 'sessions', '2026', '07', '08', 'rollout-thread-2.jsonl');
|
|
289
|
+
await mkdir(path.dirname(forkPath), { recursive: true });
|
|
290
|
+
await writeFile(forkPath, `${JSON.stringify({ type: 'session_meta', payload: { id: 'thread-2', cwd: workspacePath } })}\n`, 'utf8');
|
|
291
|
+
|
|
292
|
+
const realForkThread = codexAppServer.forkThread;
|
|
293
|
+
codexAppServer.forkThread = async () => ({ threadId: 'thread-2', path: forkPath });
|
|
294
|
+
try {
|
|
295
|
+
await new CodexSessionsProvider().rewindSession(sessionId, 'turn-b');
|
|
296
|
+
} finally {
|
|
297
|
+
codexAppServer.forkThread = realForkThread;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
await sessionsService.deleteOrArchiveSessionById(sessionId, { force: true, deletedFromDisk: true });
|
|
301
|
+
|
|
302
|
+
// The row only ever points at the newest transcript, so deleting from that
|
|
303
|
+
// alone would leave the replaced turns on disk — and unreachable, because
|
|
304
|
+
// the indexer refuses a superseded thread.
|
|
305
|
+
assert.equal(existsSync(forkPath), false);
|
|
306
|
+
assert.equal(existsSync(preEditPath), false);
|
|
307
|
+
assert.equal(sessionsDb.isProviderSessionSuperseded('thread-1', 'codex'), false);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('a session detached by an edit reports no token usage of its own', { concurrency: false }, async () => {
|
|
312
|
+
const spentTurns = [
|
|
313
|
+
...THREE_TURNS,
|
|
314
|
+
{
|
|
315
|
+
type: 'event_msg',
|
|
316
|
+
payload: {
|
|
317
|
+
type: 'token_count',
|
|
318
|
+
info: {
|
|
319
|
+
total_token_usage: { input_tokens: 100_000, output_tokens: 25_000, total_tokens: 125_000 },
|
|
320
|
+
model_context_window: 200_000,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
];
|
|
325
|
+
|
|
326
|
+
await withIndexedSession(spentTurns, async ({ sessionId }) => {
|
|
327
|
+
const { createProviderTokenUsageService } = await import('@/modules/providers/services/provider-token-usage.service.js');
|
|
328
|
+
const service = createProviderTokenUsageService();
|
|
329
|
+
|
|
330
|
+
assert.equal((await service.getSessionTokenUsage(sessionId)).used, 125_000);
|
|
331
|
+
|
|
332
|
+
await new CodexSessionsProvider().rewindSession(sessionId, null);
|
|
333
|
+
|
|
334
|
+
// A session discovered from disk is keyed by its own thread id, so the
|
|
335
|
+
// "no provider id was ever recorded" fallback resolves the very thread the
|
|
336
|
+
// edit discarded — by filename, which still matches — and an empty
|
|
337
|
+
// conversation reports the spent context of the one it replaced.
|
|
338
|
+
const usage = await service.getSessionTokenUsage(sessionId);
|
|
339
|
+
assert.equal(usage.used, 0);
|
|
340
|
+
assert.equal(usage.inputTokens, 0);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Everything above stubs the app-server. This one runs the real thing, because
|
|
346
|
+
* the whole feature rests on a protocol this repo does not own: that
|
|
347
|
+
* `thread/fork` exists without asking for experimental capabilities, that
|
|
348
|
+
* `lastTurnId` is inclusive, and that the copy is a rollout of its own rather
|
|
349
|
+
* than a pointer at the original.
|
|
350
|
+
*/
|
|
351
|
+
test('codex app-server forks a real thread at a turn', { concurrency: false }, async (t) => {
|
|
352
|
+
const require_ = createRequire(import.meta.url);
|
|
353
|
+
try {
|
|
354
|
+
require_.resolve('@openai/codex/bin/codex.js');
|
|
355
|
+
} catch {
|
|
356
|
+
t.skip('the Codex CLI package is not installed');
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const fixture = path.join(
|
|
361
|
+
process.cwd(),
|
|
362
|
+
'temp-jsonls/codex/ask and todolist',
|
|
363
|
+
'rollout-2026-08-22T20-24-44-01a02a80-dfb9-7882-8013-00e41955e656.jsonl',
|
|
364
|
+
);
|
|
365
|
+
const { readFile } = await import('node:fs/promises');
|
|
366
|
+
let transcript: string;
|
|
367
|
+
try {
|
|
368
|
+
transcript = await readFile(fixture, 'utf8');
|
|
369
|
+
} catch {
|
|
370
|
+
t.skip('the committed Codex rollout fixture is not available');
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-appserver-'));
|
|
375
|
+
const previousCodexHome = process.env.CODEX_HOME;
|
|
376
|
+
process.env.CODEX_HOME = tempRoot;
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const sessionsDir = path.join(tempRoot, 'sessions', '2026', '08', '22');
|
|
380
|
+
await mkdir(sessionsDir, { recursive: true });
|
|
381
|
+
await writeFile(path.join(sessionsDir, path.basename(fixture)), transcript, 'utf8');
|
|
382
|
+
|
|
383
|
+
const workspacePath = path.join(tempRoot, 'workspace');
|
|
384
|
+
await mkdir(workspacePath, { recursive: true });
|
|
385
|
+
|
|
386
|
+
const fork = await codexAppServer.forkThread({
|
|
387
|
+
threadId: '01a02a80-dfb9-7882-8013-00e41955e656',
|
|
388
|
+
// The fixture's second turn; the third must not survive the cut.
|
|
389
|
+
lastTurnId: '01a02a81-2427-7853-b5e2-6eee36690146',
|
|
390
|
+
cwd: workspacePath,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
assert.notEqual(fork.threadId, '01a02a80-dfb9-7882-8013-00e41955e656');
|
|
394
|
+
|
|
395
|
+
const copy = await readFile(fork.path, 'utf8');
|
|
396
|
+
const prompts = copy
|
|
397
|
+
.split('\n')
|
|
398
|
+
.filter(Boolean)
|
|
399
|
+
.map((line) => JSON.parse(line) as { type?: string; payload?: Record<string, unknown> })
|
|
400
|
+
.filter((row) => row.type === 'event_msg' && row.payload?.type === 'user_message')
|
|
401
|
+
.map((row) => String(row.payload?.message ?? '').trim());
|
|
402
|
+
|
|
403
|
+
assert.deepEqual(prompts, [
|
|
404
|
+
'ask me a question using ur ask tool...wanted to check somehting',
|
|
405
|
+
'anytning...just wanted to test',
|
|
406
|
+
]);
|
|
407
|
+
|
|
408
|
+
// The source is a copy source, not a move source.
|
|
409
|
+
const source = await readFile(path.join(sessionsDir, path.basename(fixture)), 'utf8');
|
|
410
|
+
assert.equal(source, transcript);
|
|
411
|
+
} finally {
|
|
412
|
+
if (previousCodexHome === undefined) {
|
|
413
|
+
delete process.env.CODEX_HOME;
|
|
414
|
+
} else {
|
|
415
|
+
process.env.CODEX_HOME = previousCodexHome;
|
|
416
|
+
}
|
|
417
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
418
|
+
}
|
|
419
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { Codex } from '@openai/codex-sdk';
|
|
5
|
+
import type { Thread, ThreadOptions } from '@openai/codex-sdk';
|
|
6
|
+
|
|
7
|
+
import { codexRuntime } from '@/modules/providers/list/codex/codex-runtime.provider.js';
|
|
8
|
+
import type { ProviderRuntimeContext } from '@/shared/index.js';
|
|
9
|
+
|
|
10
|
+
for (const resumed of [false, true]) {
|
|
11
|
+
for (const permissionMode of [undefined, 'default', 'unknown', 'acceptEdits', 'bypassPermissions']) {
|
|
12
|
+
test(`Codex ${resumed ? 'resumes' : 'starts'} with supported permissions (${permissionMode ?? 'omitted'})`, async (t) => {
|
|
13
|
+
let capturedOptions: ThreadOptions | undefined;
|
|
14
|
+
let capturedPrompt: unknown;
|
|
15
|
+
const messages: unknown[] = [];
|
|
16
|
+
const thread = {
|
|
17
|
+
id: 'native-thread',
|
|
18
|
+
async runStreamed(prompt: unknown) {
|
|
19
|
+
capturedPrompt = prompt;
|
|
20
|
+
return { events: (async function* () {
|
|
21
|
+
yield { type: 'thread.started', thread_id: 'native-thread' };
|
|
22
|
+
})() };
|
|
23
|
+
},
|
|
24
|
+
} as unknown as Thread;
|
|
25
|
+
|
|
26
|
+
const start = t.mock.method(Codex.prototype, 'startThread', (options?: ThreadOptions) => {
|
|
27
|
+
capturedOptions = options;
|
|
28
|
+
return thread;
|
|
29
|
+
});
|
|
30
|
+
const resume = t.mock.method(Codex.prototype, 'resumeThread', (id: string, options?: ThreadOptions) => {
|
|
31
|
+
assert.equal(id, 'native-thread');
|
|
32
|
+
capturedOptions = options;
|
|
33
|
+
return thread;
|
|
34
|
+
});
|
|
35
|
+
const context: ProviderRuntimeContext = {
|
|
36
|
+
resolveProviderSessionId: () => resumed ? 'native-thread' : null,
|
|
37
|
+
resolveResumeModel: async () => 'test-model',
|
|
38
|
+
getProviderModels: async () => ({ OPTIONS: [], DEFAULT: 'test-model' }),
|
|
39
|
+
normalizeMessage: () => [],
|
|
40
|
+
isProviderInstalled: async () => true,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
await codexRuntime.run('hey there', {
|
|
44
|
+
sessionId: resumed ? 'app-session' : undefined,
|
|
45
|
+
permissionMode,
|
|
46
|
+
cwd: process.cwd(),
|
|
47
|
+
}, { isWebSocketWriter: true, send: (message) => messages.push(message) }, context);
|
|
48
|
+
|
|
49
|
+
assert.equal(start.mock.callCount(), resumed ? 0 : 1);
|
|
50
|
+
assert.equal(resume.mock.callCount(), resumed ? 1 : 0);
|
|
51
|
+
assert.equal(capturedPrompt, 'hey there');
|
|
52
|
+
assert.equal(capturedOptions?.sandboxMode, permissionMode === 'bypassPermissions' ? 'danger-full-access' : 'workspace-write');
|
|
53
|
+
assert.equal(capturedOptions?.approvalPolicy, permissionMode === 'acceptEdits' || permissionMode === 'bypassPermissions' ? 'never' : 'on-request');
|
|
54
|
+
assert.ok(messages.some((message: any) => message.kind === 'complete' && message.exitCode === 0));
|
|
55
|
+
assert.ok(!messages.some((message: any) => message.kind === 'error'));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { codexAppServer } from '@/modules/providers/list/codex/codex-app-server.client.js';
|
|
5
|
+
import { CodexForkProvider } from '@/modules/providers/list/codex/codex-fork.provider.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The fork endpoint itself is exercised against the real `codex app-server` in
|
|
9
|
+
* codex-message-editing.test.ts, which is where the rewind that depends on it
|
|
10
|
+
* lives. What is left for the fork provider is the mapping either side of it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
test('a Codex fork asks for the whole thread when no anchor is given', { concurrency: false }, async () => {
|
|
14
|
+
const realForkThread = codexAppServer.forkThread;
|
|
15
|
+
const forkCalls: unknown[] = [];
|
|
16
|
+
codexAppServer.forkThread = async (input) => {
|
|
17
|
+
forkCalls.push(input);
|
|
18
|
+
return { threadId: 'thread-2', path: '/tmp/rollout-thread-2.jsonl' };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const forked = await new CodexForkProvider().forkSession({
|
|
23
|
+
providerSessionId: 'thread-1',
|
|
24
|
+
jsonlPath: '/tmp/rollout-thread-1.jsonl',
|
|
25
|
+
projectPath: '/tmp/workspace',
|
|
26
|
+
title: 'ignored by codex',
|
|
27
|
+
});
|
|
28
|
+
assert.deepEqual(forked, { providerSessionId: 'thread-2', jsonlPath: '/tmp/rollout-thread-2.jsonl' });
|
|
29
|
+
} finally {
|
|
30
|
+
codexAppServer.forkThread = realForkThread;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
assert.deepEqual(forkCalls, [{ threadId: 'thread-1', lastTurnId: undefined, cwd: '/tmp/workspace' }]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('forking from a message cuts through that message\'s turn', async () => {
|
|
37
|
+
const realForkThread = codexAppServer.forkThread;
|
|
38
|
+
const forkCalls: unknown[] = [];
|
|
39
|
+
codexAppServer.forkThread = async (input) => {
|
|
40
|
+
forkCalls.push(input);
|
|
41
|
+
return { threadId: 'thread-2', path: '/tmp/rollout-thread-2.jsonl' };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await new CodexForkProvider().forkSession({
|
|
46
|
+
providerSessionId: 'thread-1',
|
|
47
|
+
jsonlPath: '/tmp/rollout-thread-1.jsonl',
|
|
48
|
+
projectPath: '/tmp/workspace',
|
|
49
|
+
upToAnchorId: 'turn-b',
|
|
50
|
+
});
|
|
51
|
+
} finally {
|
|
52
|
+
codexAppServer.forkThread = realForkThread;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Inclusive of the turn it names, so the branch keeps the message that was
|
|
56
|
+
// forked from *and* the answer it got. A turn is written as one thing; there
|
|
57
|
+
// is no cut that stops between them.
|
|
58
|
+
assert.deepEqual(forkCalls, [{ threadId: 'thread-1', lastTurnId: 'turn-b', cwd: '/tmp/workspace' }]);
|
|
59
|
+
});
|