@cloudcli-ai/cloudcli 1.37.2 → 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 +269 -0
- package/dist/assets/{abnfDiagram-N423BO3Z-C4UAoHJb.js → abnfDiagram-N423BO3Z-CJntskwA.js} +1 -1
- package/dist/assets/{arc-B8jF-ja9.js → arc-CIfH4uMN.js} +1 -1
- package/dist/assets/{architectureDiagram-T3A2C74G-C_Wur_CU.js → architectureDiagram-T3A2C74G-_IpEkukg.js} +1 -1
- package/dist/assets/{blockDiagram-VBNYF7ZC-Cq4P1i8t.js → blockDiagram-VBNYF7ZC-BttxrpgZ.js} +1 -1
- package/dist/assets/{c4Diagram-5PPSVZJV-C1wG4eUj.js → c4Diagram-5PPSVZJV-g51dSRr1.js} +1 -1
- package/dist/assets/channel-C5xU9jMw.js +1 -0
- package/dist/assets/{chunk-2GRJ4B5K-B8fnBG6E.js → chunk-2GRJ4B5K-D_-LbQUa.js} +1 -1
- package/dist/assets/{chunk-2Q5K7J3B-WdfPooPJ.js → chunk-2Q5K7J3B-BtlHzwaO.js} +1 -1
- package/dist/assets/{chunk-5RXB4S5H-DhZJuG2E.js → chunk-5RXB4S5H-UzkpvpAT.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-B0rjslal.js → chunk-5VM5RSS4-BKwx5Gag.js} +1 -1
- package/dist/assets/{chunk-6Q2QTUOP-CG2Oj4jL.js → chunk-6Q2QTUOP-D_KEBH5f.js} +1 -1
- package/dist/assets/{chunk-GF5L2VYU-rrl2lRxY.js → chunk-GF5L2VYU-rK2DGyto.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-CjVbrVMA.js → chunk-JWPE2WC7-CeBl_N9i.js} +1 -1
- package/dist/assets/{chunk-KBJHAD2P-CRzKgU8S.js → chunk-KBJHAD2P-BiQLWVIr.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-DBz4GTZF.js → chunk-RYQCIY6F-C1knVGV5.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-wHxalsXk.js → chunk-XXDRQBXY-CtKbJkvF.js} +1 -1
- 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-s1hygY8e.js → cose-bilkent-JH36ORCC-DjX8y40Z.js} +1 -1
- package/dist/assets/{cynefin-VYW2F7L2-C4LGV15i.js → cynefin-VYW2F7L2-DM6YeRba.js} +1 -1
- package/dist/assets/{cynefinDiagram-MW4NZA55-C5uhu7B3.js → cynefinDiagram-MW4NZA55-wZJ-vy5B.js} +1 -1
- package/dist/assets/{dagre-VZM6K2ZE-B-OBTewm.js → dagre-VZM6K2ZE-BzP0ejkH.js} +1 -1
- package/dist/assets/{diagram-7IWD3JNH-BjGKQxih.js → diagram-7IWD3JNH-BTL_j23J.js} +1 -1
- package/dist/assets/{diagram-B4RE2ZJO-BMKz1X2L.js → diagram-B4RE2ZJO-Cf1Norgu.js} +1 -1
- package/dist/assets/{diagram-LBJQPF4R-DNh-GhKC.js → diagram-LBJQPF4R-DPiUEKr-.js} +1 -1
- package/dist/assets/{diagram-Q27KOJAE-BaTG9jPT.js → diagram-Q27KOJAE-CTcTpiz5.js} +1 -1
- package/dist/assets/{diagram-UB23O5K3-BUwo2OvJ.js → diagram-UB23O5K3-DPMSdYm0.js} +1 -1
- package/dist/assets/{ebnfDiagram-BXEA7PRR-DAZgfzX2.js → ebnfDiagram-BXEA7PRR-7yvD6uiF.js} +1 -1
- package/dist/assets/{erDiagram-JOGREHBK-1w1CcGEb.js → erDiagram-JOGREHBK-CfgeDoV6.js} +1 -1
- package/dist/assets/{flowDiagram-UKHOOZJN-D4EII8Jj.js → flowDiagram-UKHOOZJN-B27ng5zu.js} +1 -1
- package/dist/assets/{ganttDiagram-PKOTCBZU-ChvJZAgB.js → ganttDiagram-PKOTCBZU-CcC6b_7t.js} +1 -1
- package/dist/assets/{gitGraphDiagram-DS77QQ5N-DtOn41S8.js → gitGraphDiagram-DS77QQ5N-BX5kPGrO.js} +1 -1
- package/dist/assets/index-BTDHQ6Id.css +32 -0
- package/dist/assets/index-sFfz8iBR.js +4931 -0
- package/dist/assets/{infoDiagram-6WML65LV-DN_PVEJZ.js → infoDiagram-6WML65LV-CUP-SlN4.js} +1 -1
- package/dist/assets/{ishikawaDiagram-WSZJBQD7-DbkF1kXb.js → ishikawaDiagram-WSZJBQD7-BTTDhOpX.js} +1 -1
- package/dist/assets/{journeyDiagram-NVQOT4AX-pJwmhddE.js → journeyDiagram-NVQOT4AX-ARXcc61m.js} +1 -1
- package/dist/assets/{kanban-definition-27J2QSJJ-PPAPgNp5.js → kanban-definition-27J2QSJJ-BieMpqMA.js} +1 -1
- package/dist/assets/{linear-iPR-J9f7.js → linear-61vxjRnP.js} +1 -1
- package/dist/assets/{mermaid.core-BEUL86Z6.js → mermaid.core-nhnwkfQx.js} +5 -5
- package/dist/assets/{mindmap-definition-FAOFIHXS-BvZoBib7.js → mindmap-definition-FAOFIHXS-Cm6x5vT1.js} +1 -1
- package/dist/assets/{pegDiagram-VL7TDLO6-DhYreaUd.js → pegDiagram-VL7TDLO6-CcYBq_9H.js} +1 -1
- package/dist/assets/{pieDiagram-7S7Q4E2Y-CrOHlyD0.js → pieDiagram-7S7Q4E2Y-CfvIjfzo.js} +1 -1
- package/dist/assets/{quadrantDiagram-CIZ2JOQS-D4g5yH3t.js → quadrantDiagram-CIZ2JOQS-SgYoWPNb.js} +1 -1
- package/dist/assets/{railroadDiagram-AXF67PYL-Dj39lHlf.js → railroadDiagram-AXF67PYL-D2Mu5E_f.js} +1 -1
- package/dist/assets/{requirementDiagram-LRYGKXZP-1AS4Zgm_.js → requirementDiagram-LRYGKXZP-Azwe_k4Q.js} +1 -1
- package/dist/assets/{sankeyDiagram-W5VNT64P-CKlMn19P.js → sankeyDiagram-W5VNT64P-DN1Ju3PA.js} +1 -1
- package/dist/assets/{sequenceDiagram-SI44F4Z6-B_j3qyvo.js → sequenceDiagram-SI44F4Z6-Bj4qC34S.js} +1 -1
- package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
- package/dist/assets/{sizeCapture-X5ZJPWSS-Bp2Op16v.js → sizeCapture-X5ZJPWSS-Co4PdklG.js} +1 -1
- package/dist/assets/{stateDiagram-OKZ733FA-C0cN6DoY.js → stateDiagram-OKZ733FA-nOvL9dRC.js} +1 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DNv8ppw7.js +1 -0
- package/dist/assets/{swimlanes-SLNWSIFB-BEvXOV9b.js → swimlanes-SLNWSIFB-CCK3wMid.js} +2 -2
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +8 -0
- package/dist/assets/{timeline-definition-Z64GVDOM-zdJQinvZ.js → timeline-definition-Z64GVDOM-BC29RNFm.js} +1 -1
- package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
- package/dist/assets/{vennDiagram-T6HMQDX7-DuF1uZMY.js → vennDiagram-T6HMQDX7-pZlZCtEo.js} +1 -1
- package/dist/assets/{wardleyDiagram-T6FBY63Y-B9AlLQup.js → wardleyDiagram-T6FBY63Y-De8k2-zM.js} +1 -1
- package/dist/assets/{xychartDiagram-ELKLHX3M-DTJX48Qg.js → xychartDiagram-ELKLHX3M-Bk8ykH0w.js} +1 -1
- package/dist/index.html +3 -3
- package/dist-server/server/index.js +7 -3
- package/dist-server/server/index.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/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/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/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 +168 -38
- 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 +24 -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-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/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 +34 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +19 -16
- package/server/index.ts +11 -4
- package/server/modules/agent/agent.routes.ts +2 -2
- package/server/modules/agent/tests/agent.routes.test.ts +33 -1
- package/server/modules/browser-use/browser-use.routes.ts +96 -33
- 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/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 +188 -39
- 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 +31 -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-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/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 -2
- package/server/shared/utils.ts +40 -0
- package/dist/assets/channel-_falYbU1.js +0 -1
- package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +0 -1
- package/dist/assets/index-CzPLBvXT.css +0 -32
- package/dist/assets/index-DFkBBuSA.js +0 -1549
- package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +0 -1
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +0 -8
|
@@ -4,17 +4,41 @@ import path from 'node:path';
|
|
|
4
4
|
import readline from 'node:readline';
|
|
5
5
|
|
|
6
6
|
import type { IProviderSessions } from '@/shared/interfaces.js';
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
AnyRecord,
|
|
9
|
+
FetchHistoryOptions,
|
|
10
|
+
FetchHistoryResult,
|
|
11
|
+
NormalizedMessage,
|
|
12
|
+
SubagentActivity,
|
|
13
|
+
SubagentInfo,
|
|
14
|
+
} from '@/shared/types.js';
|
|
8
15
|
import { parseFilesInputTag } from '@/shared/image-attachments.js';
|
|
9
|
-
import {
|
|
16
|
+
import { prepareTranscriptMessages } from '@/shared/message-unification.js';
|
|
17
|
+
import {
|
|
18
|
+
createNormalizedMessage,
|
|
19
|
+
generateMessageId,
|
|
20
|
+
readObjectRecord,
|
|
21
|
+
sliceTailPage,
|
|
22
|
+
truncateSubagentActivity,
|
|
23
|
+
} from '@/shared/utils.js';
|
|
10
24
|
import { sessionsDb } from '@/modules/database/index.js';
|
|
25
|
+
import { summarizeClaudeTokenUsage } from '@/modules/providers/services/provider-token-usage.service.js';
|
|
11
26
|
|
|
12
27
|
const PROVIDER = 'claude';
|
|
13
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Upper bound on how much of a subagent's timeline is sent to the client. A
|
|
31
|
+
* long-running agent can record hundreds of tool calls, and the transcript only
|
|
32
|
+
* ever shows them behind a collapsed header, so shipping the whole history on
|
|
33
|
+
* every load costs far more than it shows.
|
|
34
|
+
*/
|
|
35
|
+
const MAX_TRANSMITTED_SUBAGENT_ACTIVITIES = 200;
|
|
36
|
+
|
|
14
37
|
type ClaudeToolResult = {
|
|
15
38
|
content: unknown;
|
|
16
39
|
isError: boolean;
|
|
17
|
-
subagentTools?:
|
|
40
|
+
subagentTools?: SubagentActivity[];
|
|
41
|
+
subagent?: SubagentInfo;
|
|
18
42
|
toolUseResult?: unknown;
|
|
19
43
|
};
|
|
20
44
|
|
|
@@ -36,8 +60,27 @@ type ClaudeHistoryMessagesResult =
|
|
|
36
60
|
limit?: number | null;
|
|
37
61
|
};
|
|
38
62
|
|
|
39
|
-
|
|
40
|
-
|
|
63
|
+
type ClaudeSubagentTranscript = {
|
|
64
|
+
activity: SubagentActivity[];
|
|
65
|
+
model?: string;
|
|
66
|
+
/**
|
|
67
|
+
* True when the transcript's last tool call never received a result, which
|
|
68
|
+
* is the only evidence in the file that the agent stopped mid-flight.
|
|
69
|
+
*/
|
|
70
|
+
endedMidToolCall: boolean;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Flattens one subagent transcript into the shared activity timeline.
|
|
75
|
+
*
|
|
76
|
+
* Assistant prose and reasoning are kept alongside the tool calls so the
|
|
77
|
+
* transcript can replay what the agent actually did rather than listing tool
|
|
78
|
+
* names with no narrative.
|
|
79
|
+
*/
|
|
80
|
+
async function readClaudeSubagentTranscript(filePath: string): Promise<ClaudeSubagentTranscript> {
|
|
81
|
+
const activity: SubagentActivity[] = [];
|
|
82
|
+
const transcript: ClaudeSubagentTranscript = { activity, endedMidToolCall: false };
|
|
83
|
+
const toolsById = new Map<string, SubagentActivity>();
|
|
41
84
|
|
|
42
85
|
try {
|
|
43
86
|
const fileStream = fs.createReadStream(filePath);
|
|
@@ -53,16 +96,34 @@ async function parseAgentTools(filePath: string): Promise<AnyRecord[]> {
|
|
|
53
96
|
|
|
54
97
|
try {
|
|
55
98
|
const entry = JSON.parse(line) as AnyRecord;
|
|
99
|
+
const timestamp = typeof entry.timestamp === 'string' ? entry.timestamp : undefined;
|
|
56
100
|
|
|
57
101
|
if (entry.message?.role === 'assistant' && Array.isArray(entry.message?.content)) {
|
|
102
|
+
if (typeof entry.message.model === 'string') {
|
|
103
|
+
transcript.model = entry.message.model;
|
|
104
|
+
}
|
|
105
|
+
|
|
58
106
|
for (const part of entry.message.content as AnyRecord[]) {
|
|
59
107
|
if (part.type === 'tool_use') {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
108
|
+
const tool: SubagentActivity = {
|
|
109
|
+
kind: 'tool',
|
|
110
|
+
toolId: String(part.id ?? ''),
|
|
111
|
+
toolName: String(part.name ?? 'Tool'),
|
|
63
112
|
toolInput: part.input,
|
|
64
|
-
timestamp
|
|
65
|
-
}
|
|
113
|
+
timestamp,
|
|
114
|
+
};
|
|
115
|
+
activity.push(tool);
|
|
116
|
+
if (tool.toolId) {
|
|
117
|
+
toolsById.set(tool.toolId, tool);
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (part.type === 'text' && typeof part.text === 'string' && part.text.trim()) {
|
|
122
|
+
activity.push({ kind: 'text', content: part.text, timestamp });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (part.type === 'thinking' && typeof part.thinking === 'string' && part.thinking.trim()) {
|
|
126
|
+
activity.push({ kind: 'thinking', content: part.thinking, timestamp });
|
|
66
127
|
}
|
|
67
128
|
}
|
|
68
129
|
}
|
|
@@ -73,7 +134,7 @@ async function parseAgentTools(filePath: string): Promise<AnyRecord[]> {
|
|
|
73
134
|
continue;
|
|
74
135
|
}
|
|
75
136
|
|
|
76
|
-
const tool =
|
|
137
|
+
const tool = toolsById.get(String(part.tool_use_id ?? ''));
|
|
77
138
|
if (!tool) {
|
|
78
139
|
continue;
|
|
79
140
|
}
|
|
@@ -99,7 +160,249 @@ async function parseAgentTools(filePath: string): Promise<AnyRecord[]> {
|
|
|
99
160
|
console.warn(`Error parsing agent file ${filePath}:`, message);
|
|
100
161
|
}
|
|
101
162
|
|
|
102
|
-
|
|
163
|
+
const lastActivity = activity[activity.length - 1];
|
|
164
|
+
transcript.endedMidToolCall = lastActivity?.kind === 'tool' && !lastActivity.toolResult;
|
|
165
|
+
|
|
166
|
+
return transcript;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
type ClaudeSubagentMeta = {
|
|
170
|
+
agentType?: string;
|
|
171
|
+
description?: string;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/** Reads the sidecar `.meta.json` Claude writes next to a subagent transcript. */
|
|
175
|
+
async function readClaudeSubagentMeta(metaPath: string): Promise<ClaudeSubagentMeta> {
|
|
176
|
+
try {
|
|
177
|
+
const parsed = JSON.parse(await fsp.readFile(metaPath, 'utf8')) as AnyRecord;
|
|
178
|
+
return {
|
|
179
|
+
agentType: typeof parsed.agentType === 'string' ? parsed.agentType : undefined,
|
|
180
|
+
description: typeof parsed.description === 'string' ? parsed.description : undefined,
|
|
181
|
+
};
|
|
182
|
+
} catch {
|
|
183
|
+
return {};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Resolves where a subagent's transcript lives.
|
|
189
|
+
*
|
|
190
|
+
* Current Claude versions write it to
|
|
191
|
+
* `<projectDir>/<providerSessionId>/subagents/agent-<agentId>.jsonl`; older
|
|
192
|
+
* ones dropped it next to the parent transcript. Both are checked, newest
|
|
193
|
+
* layout first, because a project directory usually holds sessions from
|
|
194
|
+
* several CLI versions.
|
|
195
|
+
*/
|
|
196
|
+
async function findClaudeSubagentTranscript(
|
|
197
|
+
projectDirectory: string,
|
|
198
|
+
providerSessionId: string,
|
|
199
|
+
agentId: string,
|
|
200
|
+
): Promise<{ transcriptPath: string; metaPath: string } | null> {
|
|
201
|
+
const candidates = [
|
|
202
|
+
path.join(projectDirectory, providerSessionId, 'subagents', `agent-${agentId}.jsonl`),
|
|
203
|
+
path.join(projectDirectory, `agent-${agentId}.jsonl`),
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
for (const transcriptPath of candidates) {
|
|
207
|
+
try {
|
|
208
|
+
await fsp.access(transcriptPath);
|
|
209
|
+
return { transcriptPath, metaPath: transcriptPath.replace(/\.jsonl$/, '.meta.json') };
|
|
210
|
+
} catch {
|
|
211
|
+
// Try the next layout.
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
type ClaudeTaskNotification = {
|
|
219
|
+
/** `uuid` of the transcript row the notification came from, so it can be dropped. */
|
|
220
|
+
sourceUuid: string;
|
|
221
|
+
toolUseId: string;
|
|
222
|
+
status: string;
|
|
223
|
+
summary: string;
|
|
224
|
+
result: string;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
function readTaggedValue(content: string, tagName: string): string {
|
|
228
|
+
const match = new RegExp(`<${tagName}>([\\s\\S]*?)<\\/${tagName}>`).exec(content);
|
|
229
|
+
return match ? match[1].trim() : '';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Collects every `<task-notification>` turn keyed by the tool call that
|
|
234
|
+
* spawned the agent it reports on.
|
|
235
|
+
*
|
|
236
|
+
* Only notifications that name a tool-use id are collected: without one there
|
|
237
|
+
* is no card to fold them into, and they must keep rendering on their own.
|
|
238
|
+
*/
|
|
239
|
+
function collectTaskNotifications(messages: AnyRecord[]): Map<string, ClaudeTaskNotification> {
|
|
240
|
+
const notifications = new Map<string, ClaudeTaskNotification>();
|
|
241
|
+
|
|
242
|
+
for (const message of messages) {
|
|
243
|
+
if (message.message?.role !== 'user') {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const content = message.message.content;
|
|
248
|
+
const texts: string[] = typeof content === 'string'
|
|
249
|
+
? [content]
|
|
250
|
+
: Array.isArray(content)
|
|
251
|
+
? content.filter((part: AnyRecord) => part?.type === 'text').map((part: AnyRecord) => String(part.text ?? ''))
|
|
252
|
+
: [];
|
|
253
|
+
|
|
254
|
+
for (const text of texts) {
|
|
255
|
+
if (!text.trimStart().startsWith('<task-notification>')) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const toolUseId = readTaggedValue(text, 'tool-use-id');
|
|
260
|
+
if (!toolUseId) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// A resumed agent notifies more than once; the last word wins.
|
|
265
|
+
notifications.set(toolUseId, {
|
|
266
|
+
sourceUuid: String(message.uuid ?? ''),
|
|
267
|
+
toolUseId,
|
|
268
|
+
status: readTaggedValue(text, 'status') || 'completed',
|
|
269
|
+
summary: readTaggedValue(text, 'summary'),
|
|
270
|
+
result: readTaggedValue(text, 'result'),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return notifications;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Reads the `tool_use_id` off the tool-result row that launched an agent. */
|
|
279
|
+
function readAgentToolUseId(message: AnyRecord): string | null {
|
|
280
|
+
const content = message.message?.content;
|
|
281
|
+
if (!Array.isArray(content)) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
for (const part of content) {
|
|
286
|
+
if (part?.type === 'tool_result' && typeof part.tool_use_id === 'string') {
|
|
287
|
+
return part.tool_use_id;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Swaps an agent launch acknowledgement for the agent's actual answer. */
|
|
295
|
+
function replaceAgentToolResultContent(message: AnyRecord, replacement: string): void {
|
|
296
|
+
const content = message.message?.content;
|
|
297
|
+
if (!Array.isArray(content)) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
for (const part of content) {
|
|
302
|
+
if (part?.type === 'tool_result') {
|
|
303
|
+
part.content = replacement;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Reads a Claude transcript's rows as a graph keyed by `uuid`.
|
|
310
|
+
*
|
|
311
|
+
* The file is append-only and every row names its predecessor in `parentUuid`,
|
|
312
|
+
* so a conversation is a path through it rather than the whole file. Editing a
|
|
313
|
+
* sent message makes a second path appear alongside the first.
|
|
314
|
+
*/
|
|
315
|
+
async function readTranscriptRows(jsonlPath: string, providerSessionId: string): Promise<AnyRecord[]> {
|
|
316
|
+
const rows: AnyRecord[] = [];
|
|
317
|
+
const fileStream = fs.createReadStream(jsonlPath);
|
|
318
|
+
const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
319
|
+
|
|
320
|
+
for await (const line of rl) {
|
|
321
|
+
if (!line.trim()) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const entry = JSON.parse(line) as AnyRecord;
|
|
326
|
+
if (entry.sessionId === providerSessionId) {
|
|
327
|
+
rows.push(entry);
|
|
328
|
+
}
|
|
329
|
+
} catch {
|
|
330
|
+
// A row can be half-written while the CLI is streaming into the file.
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return rows;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** True for a row the user typed, as opposed to a tool result or an injected note. */
|
|
338
|
+
function isUserPromptRow(row: AnyRecord): boolean {
|
|
339
|
+
if (row.type !== 'user' || row.isMeta === true || row.isCompactSummary === true) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const content = row.message?.content;
|
|
344
|
+
if (Array.isArray(content)) {
|
|
345
|
+
return content.some((part: AnyRecord) => part?.type === 'text' || part?.type === 'image');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return typeof content === 'string' && content.length > 0;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Drops the rows belonging to prompts that were replaced by an edit.
|
|
353
|
+
*
|
|
354
|
+
* When a message is edited, Claude resumes the conversation partway and appends
|
|
355
|
+
* the replacement, so two prompts end up sharing one parent and the file holds
|
|
356
|
+
* both the abandoned attempt and the live one. A flat read would show them
|
|
357
|
+
* stacked, which reads as the app having sent the message twice.
|
|
358
|
+
*
|
|
359
|
+
* Only sibling *prompts* are treated as a fork. Branch points made by parallel
|
|
360
|
+
* tool calls are extremely common — one assistant turn writes several chained
|
|
361
|
+
* rows and each tool result parents onto its own — and pruning those would
|
|
362
|
+
* delete tool output from every transcript in the app.
|
|
363
|
+
*/
|
|
364
|
+
function dropSupersededPromptBranches(rows: AnyRecord[]): AnyRecord[] {
|
|
365
|
+
const promptSiblings = new Map<string, AnyRecord[]>();
|
|
366
|
+
for (const row of rows) {
|
|
367
|
+
if (typeof row.parentUuid !== 'string' || !isUserPromptRow(row)) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const siblings = promptSiblings.get(row.parentUuid);
|
|
371
|
+
if (siblings) {
|
|
372
|
+
siblings.push(row);
|
|
373
|
+
} else {
|
|
374
|
+
promptSiblings.set(row.parentUuid, [row]);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const supersededRoots = new Set<string>();
|
|
379
|
+
for (const siblings of promptSiblings.values()) {
|
|
380
|
+
if (siblings.length < 2) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
// The transcript is append-only, so the last prompt written under a parent
|
|
384
|
+
// is the one that replaced the others.
|
|
385
|
+
for (const row of siblings.slice(0, -1)) {
|
|
386
|
+
if (typeof row.uuid === 'string') {
|
|
387
|
+
supersededRoots.add(row.uuid);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (supersededRoots.size === 0) {
|
|
393
|
+
return rows;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const abandoned = new Set(supersededRoots);
|
|
397
|
+
// Rows are appended in order, so one forward pass propagates each superseded
|
|
398
|
+
// root to its whole subtree.
|
|
399
|
+
for (const row of rows) {
|
|
400
|
+
if (typeof row.parentUuid === 'string' && abandoned.has(row.parentUuid) && typeof row.uuid === 'string') {
|
|
401
|
+
abandoned.add(row.uuid);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return rows.filter((row) => typeof row.uuid !== 'string' || !abandoned.has(row.uuid));
|
|
103
406
|
}
|
|
104
407
|
|
|
105
408
|
async function getSessionMessages(
|
|
@@ -118,32 +421,10 @@ async function getSessionMessages(
|
|
|
118
421
|
}
|
|
119
422
|
|
|
120
423
|
const projectDir = path.dirname(jsonLPath);
|
|
121
|
-
const files = await fsp.readdir(projectDir);
|
|
122
|
-
const agentFiles = files.filter((file) => file.endsWith('.jsonl') && file.startsWith('agent-'));
|
|
123
|
-
|
|
124
|
-
const messages: AnyRecord[] = [];
|
|
125
|
-
const agentToolsCache = new Map<string, AnyRecord[]>();
|
|
126
|
-
|
|
127
|
-
const fileStream = fs.createReadStream(jsonLPath);
|
|
128
|
-
const rl = readline.createInterface({
|
|
129
|
-
input: fileStream,
|
|
130
|
-
crlfDelay: Infinity,
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
for await (const line of rl) {
|
|
134
|
-
if (!line.trim()) {
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
424
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
messages.push(entry);
|
|
142
|
-
}
|
|
143
|
-
} catch {
|
|
144
|
-
// Skip malformed JSONL lines that can happen during concurrent writes.
|
|
145
|
-
}
|
|
146
|
-
}
|
|
425
|
+
const messages = dropSupersededPromptBranches(
|
|
426
|
+
await readTranscriptRows(jsonLPath, providerSessionId),
|
|
427
|
+
);
|
|
147
428
|
|
|
148
429
|
const agentIds = new Set<string>();
|
|
149
430
|
for (const message of messages) {
|
|
@@ -153,30 +434,97 @@ async function getSessionMessages(
|
|
|
153
434
|
}
|
|
154
435
|
}
|
|
155
436
|
|
|
437
|
+
// Read each spawned agent's own transcript once, then hang it off every
|
|
438
|
+
// row that references it.
|
|
439
|
+
const subagentsById = new Map<string, {
|
|
440
|
+
activity: SubagentActivity[];
|
|
441
|
+
info: SubagentInfo;
|
|
442
|
+
endedMidToolCall: boolean;
|
|
443
|
+
}>();
|
|
156
444
|
for (const agentId of agentIds) {
|
|
157
|
-
const
|
|
158
|
-
if (!
|
|
445
|
+
const located = await findClaudeSubagentTranscript(projectDir, providerSessionId, agentId);
|
|
446
|
+
if (!located) {
|
|
159
447
|
continue;
|
|
160
448
|
}
|
|
161
449
|
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
450
|
+
const [transcript, meta] = await Promise.all([
|
|
451
|
+
readClaudeSubagentTranscript(located.transcriptPath),
|
|
452
|
+
readClaudeSubagentMeta(located.metaPath),
|
|
453
|
+
]);
|
|
454
|
+
|
|
455
|
+
subagentsById.set(agentId, {
|
|
456
|
+
endedMidToolCall: transcript.endedMidToolCall,
|
|
457
|
+
activity: transcript.activity
|
|
458
|
+
.slice(0, MAX_TRANSMITTED_SUBAGENT_ACTIVITIES)
|
|
459
|
+
.map(truncateSubagentActivity),
|
|
460
|
+
info: {
|
|
461
|
+
id: agentId,
|
|
462
|
+
name: meta.agentType,
|
|
463
|
+
type: meta.agentType,
|
|
464
|
+
description: meta.description,
|
|
465
|
+
model: transcript.model,
|
|
466
|
+
status: 'completed',
|
|
467
|
+
activityCount: transcript.activity.length,
|
|
468
|
+
},
|
|
469
|
+
});
|
|
165
470
|
}
|
|
166
471
|
|
|
472
|
+
// An async agent's launch result is internal bookkeeping ("Async agent
|
|
473
|
+
// launched successfully…"); its real answer arrives later as a separate
|
|
474
|
+
// `<task-notification>` turn. Folding the notification back onto the tool
|
|
475
|
+
// call that started the agent keeps one card per agent instead of a card,
|
|
476
|
+
// an unrelated status line, and a stray markdown reply.
|
|
477
|
+
const notificationsByToolUseId = collectTaskNotifications(messages);
|
|
478
|
+
const foldedNotificationUuids = new Set<string>();
|
|
479
|
+
|
|
167
480
|
for (const message of messages) {
|
|
168
481
|
const agentId = message.toolUseResult?.agentId;
|
|
169
482
|
if (!agentId) {
|
|
170
483
|
continue;
|
|
171
484
|
}
|
|
172
485
|
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
486
|
+
const subagent = subagentsById.get(String(agentId));
|
|
487
|
+
const toolUseId = readAgentToolUseId(message);
|
|
488
|
+
const notification = toolUseId ? notificationsByToolUseId.get(toolUseId) : undefined;
|
|
489
|
+
// An async agent's launch row never tells you it finished — only the
|
|
490
|
+
// later notification does. When that notification is missing (a live run,
|
|
491
|
+
// or one compacted out of the transcript), the agent's own transcript is
|
|
492
|
+
// the evidence: a timeline that does not stop mid-tool-call is done.
|
|
493
|
+
const isAwaitingAsyncAgent = message.toolUseResult?.isAsync === true
|
|
494
|
+
&& !notification
|
|
495
|
+
&& (!subagent || subagent.endedMidToolCall);
|
|
496
|
+
|
|
497
|
+
if (subagent) {
|
|
498
|
+
if (subagent.activity.length > 0) {
|
|
499
|
+
message.subagentTools = subagent.activity;
|
|
500
|
+
}
|
|
501
|
+
message.subagent = {
|
|
502
|
+
...subagent.info,
|
|
503
|
+
description: subagent.info.description
|
|
504
|
+
?? (typeof message.toolUseResult?.description === 'string' ? message.toolUseResult.description : undefined),
|
|
505
|
+
model: subagent.info.model
|
|
506
|
+
?? (typeof message.toolUseResult?.resolvedModel === 'string' ? message.toolUseResult.resolvedModel : undefined),
|
|
507
|
+
status: isAwaitingAsyncAgent
|
|
508
|
+
? 'running'
|
|
509
|
+
: notification && notification.status !== 'completed'
|
|
510
|
+
? 'failed'
|
|
511
|
+
: 'completed',
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (notification) {
|
|
516
|
+
replaceAgentToolResultContent(message, notification.result || notification.summary);
|
|
517
|
+
foldedNotificationUuids.add(notification.sourceUuid);
|
|
518
|
+
} else if (message.toolUseResult?.isAsync === true) {
|
|
519
|
+
// Without a notification there is no answer to show, and the launch
|
|
520
|
+
// acknowledgement is internal bookkeeping the user must never read.
|
|
521
|
+
replaceAgentToolResultContent(message, '');
|
|
176
522
|
}
|
|
177
523
|
}
|
|
178
524
|
|
|
179
|
-
const sortedMessages = messages
|
|
525
|
+
const sortedMessages = messages
|
|
526
|
+
.filter((message) => !foldedNotificationUuids.has(String(message.uuid ?? '')))
|
|
527
|
+
.sort(
|
|
180
528
|
(a, b) => new Date(a.timestamp || 0).getTime() - new Date(b.timestamp || 0).getTime(),
|
|
181
529
|
);
|
|
182
530
|
const total = sortedMessages.length;
|
|
@@ -302,8 +650,31 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
302
650
|
/**
|
|
303
651
|
* Normalizes one Claude JSONL entry or live SDK stream event into the shared
|
|
304
652
|
* message shape consumed by REST and WebSocket clients.
|
|
653
|
+
*
|
|
654
|
+
* Every user turn it produces is stamped with the transcript row's `uuid`,
|
|
655
|
+
* which is the anchor "edit this message" and "fork from here" address. It is
|
|
656
|
+
* applied here rather than at each `createNormalizedMessage` call because the
|
|
657
|
+
* row-shape branches below have several exits, and an anchor missing from one
|
|
658
|
+
* of them would show up as a message the user silently cannot edit.
|
|
305
659
|
*/
|
|
306
660
|
normalizeMessage(rawMessage: unknown, sessionId: string | null): NormalizedMessage[] {
|
|
661
|
+
const messages = this.normalizeMessageRows(rawMessage, sessionId);
|
|
662
|
+
// A synthesized id is useless as an anchor — it changes on every read — so
|
|
663
|
+
// a row without its own uuid produces messages with no anchor at all.
|
|
664
|
+
const raw = readObjectRecord(rawMessage);
|
|
665
|
+
const anchorId = typeof raw?.uuid === 'string' && raw.uuid ? raw.uuid : null;
|
|
666
|
+
if (anchorId) {
|
|
667
|
+
for (const message of messages) {
|
|
668
|
+
if (message.role === 'user') {
|
|
669
|
+
message.transcriptAnchorId = anchorId;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return messages;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
private normalizeMessageRows(rawMessage: unknown, sessionId: string | null): NormalizedMessage[] {
|
|
307
678
|
const raw = readObjectRecord(rawMessage);
|
|
308
679
|
if (!raw) {
|
|
309
680
|
return [];
|
|
@@ -347,7 +718,6 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
347
718
|
toolId: part.tool_use_id,
|
|
348
719
|
content: typeof part.content === 'string' ? part.content : JSON.stringify(part.content),
|
|
349
720
|
isError: Boolean(part.is_error),
|
|
350
|
-
subagentTools: raw.subagentTools,
|
|
351
721
|
toolUseResult: raw.toolUseResult,
|
|
352
722
|
}));
|
|
353
723
|
} else if (part.type === 'text') {
|
|
@@ -600,6 +970,56 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
600
970
|
return messages;
|
|
601
971
|
}
|
|
602
972
|
|
|
973
|
+
/**
|
|
974
|
+
* Finds the row to resume *through* so that `anchorId`'s turn is replaced.
|
|
975
|
+
*
|
|
976
|
+
* Walks up the `parentUuid` chain to the nearest assistant row, because the
|
|
977
|
+
* SDK's `resumeSessionAt` is documented against assistant message ids — the
|
|
978
|
+
* user row's immediate parent can be an attachment or an injected note.
|
|
979
|
+
* Returns `null` when nothing precedes the edited prompt, which means the
|
|
980
|
+
* conversation should start over rather than resume.
|
|
981
|
+
*/
|
|
982
|
+
async resolveEditAnchor(
|
|
983
|
+
sessionId: string,
|
|
984
|
+
anchorId: string,
|
|
985
|
+
): Promise<{ found: boolean; resumeThroughId: string | null }> {
|
|
986
|
+
const session = sessionsDb.getSessionById(sessionId);
|
|
987
|
+
const jsonlPath = session?.jsonl_path;
|
|
988
|
+
const providerSessionId = session?.provider_session_id;
|
|
989
|
+
if (!jsonlPath || !providerSessionId) {
|
|
990
|
+
return { found: false, resumeThroughId: null };
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const rows = await readTranscriptRows(jsonlPath, providerSessionId);
|
|
994
|
+
const byUuid = new Map<string, AnyRecord>();
|
|
995
|
+
for (const row of rows) {
|
|
996
|
+
if (typeof row.uuid === 'string') {
|
|
997
|
+
byUuid.set(row.uuid, row);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const target = byUuid.get(anchorId);
|
|
1002
|
+
if (!target) {
|
|
1003
|
+
return { found: false, resumeThroughId: null };
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const visited = new Set<string>([anchorId]);
|
|
1007
|
+
let parentUuid: unknown = target.parentUuid;
|
|
1008
|
+
while (typeof parentUuid === 'string' && !visited.has(parentUuid)) {
|
|
1009
|
+
visited.add(parentUuid);
|
|
1010
|
+
const parent = byUuid.get(parentUuid);
|
|
1011
|
+
if (!parent) {
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
if (parent.type === 'assistant') {
|
|
1015
|
+
return { found: true, resumeThroughId: parentUuid };
|
|
1016
|
+
}
|
|
1017
|
+
parentUuid = parent.parentUuid;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
return { found: true, resumeThroughId: null };
|
|
1021
|
+
}
|
|
1022
|
+
|
|
603
1023
|
/**
|
|
604
1024
|
* Loads Claude JSONL history for a project/session and returns normalized
|
|
605
1025
|
* messages, preserving the existing pagination behavior from projects.js.
|
|
@@ -633,6 +1053,7 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
633
1053
|
content: part.content,
|
|
634
1054
|
isError: Boolean(part.is_error),
|
|
635
1055
|
subagentTools: raw.subagentTools,
|
|
1056
|
+
subagent: raw.subagent,
|
|
636
1057
|
toolUseResult: raw.toolUseResult,
|
|
637
1058
|
});
|
|
638
1059
|
}
|
|
@@ -660,18 +1081,17 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
660
1081
|
toolUseResult: toolResult.toolUseResult,
|
|
661
1082
|
};
|
|
662
1083
|
msg.subagentTools = toolResult.subagentTools;
|
|
1084
|
+
msg.subagent = toolResult.subagent;
|
|
663
1085
|
}
|
|
664
1086
|
}
|
|
665
1087
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
}
|
|
671
|
-
}
|
|
1088
|
+
// Everything the transcript draws, and nothing else — so a page of N rows
|
|
1089
|
+
// is N rows the user sees, and `total` counts the same thing.
|
|
1090
|
+
const transcript = prepareTranscriptMessages(normalized);
|
|
1091
|
+
const total = transcript.length;
|
|
672
1092
|
const normalizedOffset = Math.max(0, offset);
|
|
673
1093
|
const normalizedLimit = limit === null ? null : Math.max(0, limit);
|
|
674
|
-
const { page, hasMore } = sliceTailPage(
|
|
1094
|
+
const { page, hasMore } = sliceTailPage(transcript, normalizedLimit, normalizedOffset);
|
|
675
1095
|
|
|
676
1096
|
return {
|
|
677
1097
|
messages: page,
|
|
@@ -679,6 +1099,10 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
679
1099
|
hasMore,
|
|
680
1100
|
offset: normalizedOffset,
|
|
681
1101
|
limit: normalizedLimit,
|
|
1102
|
+
// Carried on every page, like the Codex and OpenCode readers do, so the
|
|
1103
|
+
// composer's counter tracks the conversation instead of being frozen at
|
|
1104
|
+
// whatever it was when the session was opened.
|
|
1105
|
+
tokenUsage: summarizeClaudeTokenUsage(rawMessages),
|
|
682
1106
|
};
|
|
683
1107
|
}
|
|
684
1108
|
}
|
|
@@ -158,12 +158,15 @@ export class ClaudeSkillsProvider extends SkillsProvider {
|
|
|
158
158
|
continue;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
// A plugin may ship commands, skills, or both, and the CLI offers
|
|
162
|
+
// both halves. Reading only the first folder found hid every skill
|
|
163
|
+
// that sits beside a commands folder -- and hid the whole plugin when
|
|
164
|
+
// its commands are in a format this reader does not take.
|
|
161
165
|
const commandsPath = path.join(pluginFolder, 'commands');
|
|
162
166
|
if (await pathExistsAsDirectory(commandsPath)) {
|
|
163
167
|
skills.push(
|
|
164
168
|
...(await this.listPluginCommandSkills(commandsPath, pluginId, pluginName)),
|
|
165
169
|
);
|
|
166
|
-
continue;
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
const skillsPath = path.join(pluginFolder, 'skills');
|
|
@@ -2,12 +2,14 @@ import { AbstractProvider } from '@/modules/providers/shared/base/abstract.provi
|
|
|
2
2
|
import { ClaudeProviderAuth } from '@/modules/providers/list/claude/claude-auth.provider.js';
|
|
3
3
|
import { ClaudeProviderModels } from '@/modules/providers/list/claude/claude-models.provider.js';
|
|
4
4
|
import { claudeRuntime } from '@/modules/providers/list/claude/claude-runtime.provider.js';
|
|
5
|
+
import { ClaudeForkProvider } from '@/modules/providers/list/claude/claude-fork.provider.js';
|
|
5
6
|
import { ClaudeMcpProvider } from '@/modules/providers/list/claude/claude-mcp.provider.js';
|
|
6
7
|
import { ClaudeSessionSynchronizer } from '@/modules/providers/list/claude/claude-session-synchronizer.provider.js';
|
|
7
8
|
import { ClaudeSessionsProvider } from '@/modules/providers/list/claude/claude-sessions.provider.js';
|
|
8
9
|
import { ClaudeSkillsProvider } from '@/modules/providers/list/claude/claude-skills.provider.js';
|
|
9
10
|
import type {
|
|
10
11
|
IProviderAuth,
|
|
12
|
+
IProviderFork,
|
|
11
13
|
IProviderModels,
|
|
12
14
|
IProviderRuntime,
|
|
13
15
|
IProviderSessionSynchronizer,
|
|
@@ -23,6 +25,7 @@ export class ClaudeProvider extends AbstractProvider {
|
|
|
23
25
|
readonly skills: IProviderSkills = new ClaudeSkillsProvider();
|
|
24
26
|
readonly sessions: IProviderSessions = new ClaudeSessionsProvider();
|
|
25
27
|
readonly sessionSynchronizer: IProviderSessionSynchronizer = new ClaudeSessionSynchronizer();
|
|
28
|
+
readonly fork: IProviderFork = new ClaudeForkProvider();
|
|
26
29
|
|
|
27
30
|
constructor() {
|
|
28
31
|
super('claude');
|