spiracha 1.3.3 → 1.5.0
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/AGENTS.md +55 -6
- package/README.md +46 -5
- package/apps/ui/AGENTS.md +31 -4
- package/apps/ui/README.md +59 -5
- package/apps/ui/dist/client/assets/analytics-DOy1b6Sh.js +1 -0
- package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DqySVO2K.js +7 -0
- package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-KAcXtNxI.js +1 -0
- package/apps/ui/dist/client/assets/antigravity-keychain-panel-B77qJz6f.js +1 -0
- package/apps/ui/dist/client/assets/antigravity._workspaceKey-B3NM6gRH.js +1 -0
- package/apps/ui/dist/client/assets/antigravity._workspaceKey-ByitGWki.js +1 -0
- package/apps/ui/dist/client/assets/antigravity.index-DP9JC2P9.js +1 -0
- package/apps/ui/dist/client/assets/antigravity.index-DgW25FEV.js +1 -0
- package/apps/ui/dist/client/assets/badge-C3i4OKs8.js +1 -0
- package/apps/ui/dist/client/assets/checkbox-CdHitXWQ.js +1 -0
- package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-8pQ55ci2.js +1 -0
- package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-DVz5UmtU.js +1 -0
- package/apps/ui/dist/client/assets/claude-code._workspaceKey-BBKskSum.js +1 -0
- package/apps/ui/dist/client/assets/claude-code._workspaceKey-OkikMUFs.js +1 -0
- package/apps/ui/dist/client/assets/claude-code.index-Cwu76dbb.js +1 -0
- package/apps/ui/dist/client/assets/claude-code.index-D6eMd08t.js +1 -0
- package/apps/ui/dist/client/assets/createServerFn-B-KkwmHU.js +3 -0
- package/apps/ui/dist/client/assets/cursor-threads._composerId-CmBFclK8.js +1 -0
- package/apps/ui/dist/client/assets/cursor-threads._composerId-hBWlP1D-.js +1 -0
- package/apps/ui/dist/client/assets/cursor._workspaceKey-BFPkIRi-.js +1 -0
- package/apps/ui/dist/client/assets/cursor._workspaceKey-CaAZjGmm.js +1 -0
- package/apps/ui/dist/client/assets/cursor.index-8y4o20kb.js +2 -0
- package/apps/ui/dist/client/assets/cursor.index-CrnMwDCi.js +1 -0
- package/apps/ui/dist/client/assets/{data-table-DeQq8xdt.js → data-table-Br78_dk3.js} +2 -2
- package/apps/ui/dist/client/assets/delete-confirm-dialog-Bxs07u-j.js +7 -0
- package/apps/ui/dist/client/assets/dist-B9pyYCVU.js +1 -0
- package/apps/ui/dist/client/assets/dist-BrJhN2A0.js +5 -0
- package/apps/ui/dist/client/assets/dist-Cmd3AIfD.js +1 -0
- package/apps/ui/dist/client/assets/dist-DvMS2965.js +1 -0
- package/apps/ui/dist/client/assets/download-Bf0QJKD3.js +1 -0
- package/apps/ui/dist/client/assets/dropdown-menu-D5cvPaZt.js +1 -0
- package/apps/ui/dist/client/assets/es2015-ad6040dw.js +41 -0
- package/apps/ui/dist/client/assets/export-dialog-Dr8d2MqD.js +1 -0
- package/apps/ui/dist/client/assets/formatters-BtqyrX_c.js +1 -0
- package/apps/ui/dist/client/assets/index-C-STgIH4.js +166 -0
- package/apps/ui/dist/client/assets/jsx-runtime-DGeXAQPT.js +1 -0
- package/apps/ui/dist/client/assets/kiro-sessions._sessionId-CKgfJ7GR.js +1 -0
- package/apps/ui/dist/client/assets/kiro-sessions._sessionId-zvIJHtQF.js +1 -0
- package/apps/ui/dist/client/assets/kiro._workspaceKey-DhVuJuZB.js +1 -0
- package/apps/ui/dist/client/assets/kiro._workspaceKey-QArsjkTY.js +1 -0
- package/apps/ui/dist/client/assets/kiro.index-C-fr4m7i.js +1 -0
- package/apps/ui/dist/client/assets/kiro.index-OYgAdtZd.js +1 -0
- package/apps/ui/dist/client/assets/metric-card-Dplm0ZiM.js +1 -0
- package/apps/ui/dist/client/assets/opencode-sessions._sessionId-DLjiTst-.js +1 -0
- package/apps/ui/dist/client/assets/opencode-sessions._sessionId-biBwrRbt.js +8 -0
- package/apps/ui/dist/client/assets/opencode._workspaceKey-C7OIzMOJ.js +1 -0
- package/apps/ui/dist/client/assets/opencode._workspaceKey-sBakX0oI.js +1 -0
- package/apps/ui/dist/client/assets/opencode.index-Bl_kNs9N.js +1 -0
- package/apps/ui/dist/client/assets/opencode.index-CN53bVmb.js +1 -0
- package/apps/ui/dist/client/assets/page-header-Cwa3p6Tl.js +1 -0
- package/apps/ui/dist/client/assets/preload-helper-B8fFrdxC.js +1 -0
- package/apps/ui/dist/client/assets/projects._project-D8WloxQN.js +1 -0
- package/apps/ui/dist/client/assets/projects._project-DSDwH4NF.js +1 -0
- package/apps/ui/dist/client/assets/projects.index-5xRtuJdX.js +3 -0
- package/apps/ui/dist/client/assets/projects.index-DEfhWqa8.js +1 -0
- package/apps/ui/dist/client/assets/react-dom-DL96Jor4.js +1 -0
- package/apps/ui/dist/client/assets/refresh-ccw-BgKjOiA6.js +1 -0
- package/apps/ui/dist/client/assets/reload-error-panel-AHqu0akK.js +1 -0
- package/apps/ui/dist/client/assets/routes-DnzF_9CX.js +1 -0
- package/apps/ui/dist/client/assets/scroll-text-fM0sMOB3.js +1 -0
- package/apps/ui/dist/client/assets/select-CYF6pF37.js +1 -0
- package/apps/ui/dist/client/assets/settings-Dx70jgP_.js +1 -0
- package/apps/ui/dist/client/assets/sqlite-error-B6uZjUe8.js +1 -0
- package/apps/ui/dist/client/assets/styles-B7Zi9EFA.css +1 -0
- package/apps/ui/dist/client/assets/tabs-CmuhkT-c.js +7 -0
- package/apps/ui/dist/client/assets/text-filter-DVAZySUS.js +2 -0
- package/apps/ui/dist/client/assets/thread-transcript-stats-BUNz6u7F.js +1 -0
- package/apps/ui/dist/client/assets/threads._threadId-BB9tTsV4.js +1 -0
- package/apps/ui/dist/client/assets/threads._threadId-BMRSFow3.js +1 -0
- package/apps/ui/dist/client/assets/useMutation-BSFult2i.js +1 -0
- package/apps/ui/dist/client/assets/useQuery-CjINKZf1.js +1 -0
- package/apps/ui/dist/client/icon.svg +2 -1
- package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CQ8Jjs3B.js +387 -0
- package/apps/ui/dist/server/assets/analytics-CYP_LT5Y.js +16 -0
- package/apps/ui/dist/server/assets/{analytics-Dc_QDZZC.js → analytics-CtpOMVMO.js} +13 -6
- package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BKOyL2T6.js +613 -0
- package/apps/ui/dist/server/assets/{antigravity-conversations._conversationId-C9z4AeN-.js → antigravity-conversations._conversationId-eS87Dx7g.js} +2 -2
- package/apps/ui/dist/server/assets/{antigravity-db-D9gW1D8G.js → antigravity-db-Bh8_U9uw.js} +11 -7
- package/apps/ui/dist/server/assets/{antigravity-keychain-panel-bpPoxhyp.js → antigravity-keychain-panel-CC7FLmAK.js} +1 -1
- package/apps/ui/dist/server/assets/{antigravity-queries-DVIKyOXi.js → antigravity-queries-CGrJO9Vr.js} +5 -5
- package/apps/ui/dist/server/assets/{antigravity-server-Cp1lc8fp.js → antigravity-server-CHRjVFqN.js} +11 -11
- package/apps/ui/dist/server/assets/{antigravity._workspaceKey-Dl8o1ptg.js → antigravity._workspaceKey-B0QeYUOh.js} +2 -2
- package/apps/ui/dist/server/assets/{antigravity._workspaceKey-BAoa4izU.js → antigravity._workspaceKey-C3kP-qyN.js} +4 -4
- package/apps/ui/dist/server/assets/{antigravity.index-BOlrJasZ.js → antigravity.index-CPCYkqCi.js} +2 -2
- package/apps/ui/dist/server/assets/claude-code-db-Ik3VStKZ.js +361 -0
- package/apps/ui/dist/server/assets/claude-code-queries-Cgzi4K-e.js +37 -0
- package/apps/ui/dist/server/assets/claude-code-server-PfraClXS.js +72 -0
- package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-C55oe6zj.js +18 -0
- package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-D1RYYi3j.js +473 -0
- package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-DSvPLEUL.js +11 -0
- package/apps/ui/dist/server/assets/claude-code-transcript-C-bxh3cS.js +139 -0
- package/apps/ui/dist/server/assets/claude-code-transcript-phase-DmUMc8VU.js +10 -0
- package/apps/ui/dist/server/assets/claude-code._workspaceKey-B5Xg_-tE.js +28 -0
- package/apps/ui/dist/server/assets/claude-code._workspaceKey-BPlzyXPa.js +11 -0
- package/apps/ui/dist/server/assets/claude-code._workspaceKey-DqDve2wl.js +191 -0
- package/apps/ui/dist/server/assets/claude-code.index-B7yYR7dD.js +92 -0
- package/apps/ui/dist/server/assets/claude-code.index-BUHThG1W.js +11 -0
- package/apps/ui/dist/server/assets/{codex-queries-ev_mRUzr.js → codex-queries-CtgeZ7VL.js} +15 -13
- package/apps/ui/dist/server/assets/{codex-server-DIYFLozB.js → codex-server-CGXU4H8-.js} +421 -146
- package/apps/ui/dist/server/assets/concurrency-VXtYvlGh.js +18 -0
- package/apps/ui/dist/server/assets/{createServerRpc-Cg-Zr1rq.js → createServerRpc-CF_DEwnm.js} +1 -1
- package/apps/ui/dist/server/assets/{createSsrRpc-DVnVQhGx.js → createSsrRpc-C754NPki.js} +1 -1
- package/apps/ui/dist/server/assets/{cursor-db-B7agkAvM.js → cursor-db-CZnYy7aT.js} +196 -9
- package/apps/ui/dist/server/assets/{cursor-queries-f7DjBKSD.js → cursor-queries-NCIM0Nat.js} +12 -10
- package/apps/ui/dist/server/assets/{cursor-recovery-9bJLs7vG.js → cursor-recovery-CXpO8KcD.js} +2 -2
- package/apps/ui/dist/server/assets/{cursor-server-nPzojHN3.js → cursor-server-DNu52A3K.js} +71 -42
- package/apps/ui/dist/server/assets/cursor-threads._composerId-Ckn6ItHI.js +582 -0
- package/apps/ui/dist/server/assets/{cursor-threads._composerId-B3qcsh9C.js → cursor-threads._composerId-p9-ARAzc.js} +2 -2
- package/apps/ui/dist/server/assets/{cursor-transcript-2iL3KFSK.js → cursor-transcript-B7tGnqtn.js} +1 -1
- package/apps/ui/dist/server/assets/{cursor._workspaceKey-BL_CQyHQ.js → cursor._workspaceKey-CZuohIUL.js} +2 -2
- package/apps/ui/dist/server/assets/{cursor._workspaceKey-BhQeepXB.js → cursor._workspaceKey-DPp5JSjy.js} +11 -9
- package/apps/ui/dist/server/assets/{cursor.index-CsrdckZD.js → cursor.index-DkWT-mxw.js} +2 -2
- package/apps/ui/dist/server/assets/{delete-confirm-dialog-PCD7S0_M.js → delete-confirm-dialog-BcBNCxWB.js} +5 -1
- package/apps/ui/dist/server/assets/{empty-plugin-adapters-D3qLMPfC.js → empty-plugin-adapters-B3lHh1La.js} +1 -1
- package/apps/ui/dist/server/assets/{export-dialog-DaPlOGFT.js → export-dialog-D88ze9Gy.js} +54 -12
- package/apps/ui/dist/server/assets/kiro-db--fXrED_-.js +552 -0
- package/apps/ui/dist/server/assets/kiro-queries-CzNlM2ok.js +37 -0
- package/apps/ui/dist/server/assets/kiro-server-9WRxg2Au.js +72 -0
- package/apps/ui/dist/server/assets/kiro-sessions._sessionId-CRY2c7YO.js +11 -0
- package/apps/ui/dist/server/assets/kiro-sessions._sessionId-F1UlwxmR.js +449 -0
- package/apps/ui/dist/server/assets/kiro-sessions._sessionId-bvTulT7C.js +18 -0
- package/apps/ui/dist/server/assets/kiro-transcript-DoekQBaH.js +112 -0
- package/apps/ui/dist/server/assets/kiro-transcript-phase-CJG40nQg.js +27 -0
- package/apps/ui/dist/server/assets/kiro._workspaceKey-BDe9sQwo.js +11 -0
- package/apps/ui/dist/server/assets/kiro._workspaceKey-CJsjgNwO.js +28 -0
- package/apps/ui/dist/server/assets/kiro._workspaceKey-D_-4Qb7y.js +193 -0
- package/apps/ui/dist/server/assets/kiro.index-C__-UROk.js +11 -0
- package/apps/ui/dist/server/assets/kiro.index-CmTbqeYj.js +93 -0
- package/apps/ui/dist/server/assets/opencode-db-BM7KjOzc.js +397 -0
- package/apps/ui/dist/server/assets/opencode-queries-BeNYXnQv.js +50 -0
- package/apps/ui/dist/server/assets/opencode-server-BpQ_kRze.js +72 -0
- package/apps/ui/dist/server/assets/opencode-sessions._sessionId-CGQ-t0vD.js +518 -0
- package/apps/ui/dist/server/assets/opencode-sessions._sessionId-D8Q7in_Z.js +18 -0
- package/apps/ui/dist/server/assets/opencode-sessions._sessionId-DKOvi0lV.js +11 -0
- package/apps/ui/dist/server/assets/opencode-think-tags-CAoD-EcZ.js +87 -0
- package/apps/ui/dist/server/assets/opencode-transcript-BNTcGNym.js +141 -0
- package/apps/ui/dist/server/assets/opencode-transcript-phase-OqLkiQD0.js +32 -0
- package/apps/ui/dist/server/assets/opencode._workspaceKey-BdqcWHw1.js +11 -0
- package/apps/ui/dist/server/assets/opencode._workspaceKey-CfH96HTT.js +28 -0
- package/apps/ui/dist/server/assets/opencode._workspaceKey-DdbHPDrO.js +207 -0
- package/apps/ui/dist/server/assets/opencode.index-BbVzkJXP.js +11 -0
- package/apps/ui/dist/server/assets/opencode.index-Vh_Gq_1L.js +93 -0
- package/apps/ui/dist/server/assets/{projects._project-CqqUFr4l.js → projects._project-4io5LO0E.js} +5 -3
- package/apps/ui/dist/server/assets/{projects._project-DahGqNYS.js → projects._project-D6ZoGhJ8.js} +42 -12
- package/apps/ui/dist/server/assets/{projects.index-CkTeFWNU.js → projects.index-BkLiF2FF.js} +21 -7
- package/apps/ui/dist/server/assets/projects.index-DesYXwfi.js +14 -0
- package/apps/ui/dist/server/assets/route-search-ts4W9MJ9.js +38 -0
- package/apps/ui/dist/server/assets/router-Sac2DGk6.js +518 -0
- package/apps/ui/dist/server/assets/{routes-Xix3ZeSo.js → routes-Bsact1uB.js} +2 -2
- package/apps/ui/dist/server/assets/{routes-0x1U0IHq.js → routes-BzkJgq4W.js} +2 -2
- package/apps/ui/dist/server/assets/{shared-CPRNYIql.js → shared-DyhChtHf.js} +4 -1
- package/apps/ui/dist/server/assets/source-session-export-server-DNutCawb.js +38 -0
- package/apps/ui/dist/server/assets/start-B_HFwkkk.js +4 -0
- package/apps/ui/dist/server/assets/tabs-DkaChNVg.js +501 -0
- package/apps/ui/dist/server/assets/thread-transcript-stats-Dh34mt2u.js +45 -0
- package/apps/ui/dist/server/assets/threads._threadId-BeXEc4Lo.js +648 -0
- package/apps/ui/dist/server/assets/{threads._threadId-BLCkGW--.js → threads._threadId-Ci0S4mKU.js} +2 -2
- package/apps/ui/dist/server/assets/ui-export-files-BHLX9bhN.js +83 -0
- package/apps/ui/dist/server/server.js +141 -83
- package/package.json +28 -11
- package/src/lib/antigravity-db.ts +20 -13
- package/src/lib/claude-code-db.ts +655 -0
- package/src/lib/claude-code-exporter-types.ts +110 -0
- package/src/lib/claude-code-transcript-phase.ts +25 -0
- package/src/lib/claude-code-transcript.ts +180 -0
- package/src/lib/codex-analytics.ts +118 -18
- package/src/lib/codex-browser-db.ts +491 -56
- package/src/lib/codex-browser-export.ts +27 -71
- package/src/lib/codex-thread-cache.ts +17 -1
- package/src/lib/concurrency.ts +25 -0
- package/src/lib/cursor-db.ts +318 -7
- package/src/lib/cursor-exporter.ts +2 -2
- package/src/lib/kiro-db.ts +949 -0
- package/src/lib/kiro-exporter-types.ts +100 -0
- package/src/lib/kiro-transcript-phase.ts +41 -0
- package/src/lib/kiro-transcript.ts +115 -0
- package/src/lib/opencode-db.ts +610 -0
- package/src/lib/opencode-exporter-types.ts +129 -0
- package/src/lib/opencode-think-tags.ts +126 -0
- package/src/lib/opencode-transcript-phase.ts +50 -0
- package/src/lib/opencode-transcript.ts +194 -0
- package/src/lib/shared.ts +25 -0
- package/src/lib/ui-cache.ts +14 -1
- package/src/lib/ui-export-archive.ts +61 -0
- package/apps/ui/dist/client/assets/analytics-C83LcWNb.js +0 -1
- package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DkY8EEpK.js +0 -1
- package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-OraHuz8j.js +0 -1
- package/apps/ui/dist/client/assets/antigravity-keychain-panel-DnDcq1ge.js +0 -1
- package/apps/ui/dist/client/assets/antigravity._workspaceKey-CQaZscA5.js +0 -1
- package/apps/ui/dist/client/assets/antigravity._workspaceKey-DTs99obU.js +0 -1
- package/apps/ui/dist/client/assets/antigravity.index-DbJa715k.js +0 -1
- package/apps/ui/dist/client/assets/antigravity.index-P-rs6w5X.js +0 -1
- package/apps/ui/dist/client/assets/badge-wgTVp-aq.js +0 -1
- package/apps/ui/dist/client/assets/checkbox-BUx0Gkuf.js +0 -1
- package/apps/ui/dist/client/assets/cursor-threads._composerId-D0DFXt8t.js +0 -1
- package/apps/ui/dist/client/assets/cursor-threads._composerId-tFa6Cf6A.js +0 -1
- package/apps/ui/dist/client/assets/cursor._workspaceKey-Df4PfA2k.js +0 -1
- package/apps/ui/dist/client/assets/cursor._workspaceKey-DwrzEtnV.js +0 -1
- package/apps/ui/dist/client/assets/cursor.index-CNBf4ZiR.js +0 -1
- package/apps/ui/dist/client/assets/cursor.index-DP9Hy8X6.js +0 -2
- package/apps/ui/dist/client/assets/delete-confirm-dialog-CPlNsLhM.js +0 -11
- package/apps/ui/dist/client/assets/dist-Ds3kS91l.js +0 -1
- package/apps/ui/dist/client/assets/download-DLuPnxpV.js +0 -1
- package/apps/ui/dist/client/assets/dropdown-menu-lFk6zjoL.js +0 -1
- package/apps/ui/dist/client/assets/es2015-D-H_iZF6.js +0 -41
- package/apps/ui/dist/client/assets/export-dialog-CyqyvwiP.js +0 -1
- package/apps/ui/dist/client/assets/formatters-DbCKP02D.js +0 -1
- package/apps/ui/dist/client/assets/index-C-ZPpKJ0.js +0 -22
- package/apps/ui/dist/client/assets/json-panel-BsngvucB.js +0 -1
- package/apps/ui/dist/client/assets/metadata-section-Dtl8dNag.js +0 -1
- package/apps/ui/dist/client/assets/metric-card-DNzR1Rne.js +0 -1
- package/apps/ui/dist/client/assets/page-header-dha_O7ZL.js +0 -1
- package/apps/ui/dist/client/assets/projects._project-B7HBhbXv.js +0 -1
- package/apps/ui/dist/client/assets/projects._project-DHVvuT7A.js +0 -1
- package/apps/ui/dist/client/assets/projects.index-BUUtx2AZ.js +0 -1
- package/apps/ui/dist/client/assets/projects.index-BojAe1Y4.js +0 -3
- package/apps/ui/dist/client/assets/refresh-ccw-B-ASKHtx.js +0 -1
- package/apps/ui/dist/client/assets/reload-error-panel-DD8M1BIY.js +0 -1
- package/apps/ui/dist/client/assets/routes-q7bzFp35.js +0 -1
- package/apps/ui/dist/client/assets/scroll-text-TvHyiJ_N.js +0 -1
- package/apps/ui/dist/client/assets/select-BFv1HMiT.js +0 -1
- package/apps/ui/dist/client/assets/settings-CvegTo4F.js +0 -1
- package/apps/ui/dist/client/assets/styles-Ch0r3kMZ.css +0 -1
- package/apps/ui/dist/client/assets/text-document-panel-D7Rws41o.js +0 -1
- package/apps/ui/dist/client/assets/text-filter-2q5pEoZh.js +0 -2
- package/apps/ui/dist/client/assets/threads._threadId-LpZVUlPq.js +0 -7
- package/apps/ui/dist/client/assets/threads._threadId-MSmNIBkq.js +0 -1
- package/apps/ui/dist/client/assets/useMutation-BOPJU9e0.js +0 -1
- package/apps/ui/dist/client/assets/useQuery-CbxZiVGF.js +0 -1
- package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-zvGzK-Yx.js +0 -229
- package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-JEhx-f2R.js +0 -212
- package/apps/ui/dist/server/assets/cursor-threads._composerId-BK3iW-3e.js +0 -218
- package/apps/ui/dist/server/assets/json-panel-RYsxWFae.js +0 -16
- package/apps/ui/dist/server/assets/metadata-section-D6Lbc7D6.js +0 -54
- package/apps/ui/dist/server/assets/router-K0m7dK4T.js +0 -394
- package/apps/ui/dist/server/assets/start-bX7bXjzw.js +0 -4
- package/apps/ui/dist/server/assets/text-document-panel-D8JbQWAn.js +0 -23
- package/apps/ui/dist/server/assets/threads._threadId-LjgsGUh-.js +0 -1052
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { Database } from 'bun:sqlite';
|
|
1
|
+
import { constants, Database } from 'bun:sqlite';
|
|
2
|
+
import { closeSync, openSync, readdirSync, readSync, statSync } from 'node:fs';
|
|
2
3
|
import { rm } from 'node:fs/promises';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
6
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
7
|
+
import { pathToFileURL } from 'node:url';
|
|
5
8
|
import type {
|
|
6
9
|
DashboardSummary,
|
|
7
10
|
DeleteProjectResult,
|
|
@@ -14,6 +17,7 @@ import type {
|
|
|
14
17
|
import type { ThreadRelations, ThreadRow } from './codex-exporter-types';
|
|
15
18
|
import { DEFAULT_CODEX_DIR, DEFAULT_DB_PATH } from './codex-exporter-types';
|
|
16
19
|
import { getCachedParsedCodexTranscript, getThreadRolloutLoadState } from './codex-thread-cache';
|
|
20
|
+
import { mapWithConcurrency } from './concurrency';
|
|
17
21
|
import { cleanInlineTitle, getPortablePathBasename } from './shared';
|
|
18
22
|
import { runWithSqliteRetry } from './sqlite-retry';
|
|
19
23
|
import { invalidateCacheByPrefix } from './ui-cache';
|
|
@@ -29,6 +33,44 @@ type DeleteProjectOptions = {
|
|
|
29
33
|
const SQLITE_DELETE_BATCH_SIZE = 400;
|
|
30
34
|
const SESSION_FILE_DELETE_CONCURRENCY = 16;
|
|
31
35
|
const THREAD_LIST_IO_CONCURRENCY = 8;
|
|
36
|
+
const CODEX_READONLY_DB_OPEN_FLAGS = constants.SQLITE_OPEN_READONLY | constants.SQLITE_OPEN_URI;
|
|
37
|
+
const JSONL_READ_CHUNK_BYTES = 64 * 1024;
|
|
38
|
+
const SESSION_META_READ_LIMIT_BYTES = 4 * 1024 * 1024;
|
|
39
|
+
const THREAD_ID_PATTERN = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/iu;
|
|
40
|
+
|
|
41
|
+
type SessionIndexEntry = {
|
|
42
|
+
id: string;
|
|
43
|
+
thread_name?: string;
|
|
44
|
+
updated_at?: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type FallbackSessionMeta = {
|
|
48
|
+
agent_nickname?: string;
|
|
49
|
+
agent_path?: string;
|
|
50
|
+
agent_role?: string;
|
|
51
|
+
cli_version?: string;
|
|
52
|
+
cwd?: string;
|
|
53
|
+
forked_from_id?: string;
|
|
54
|
+
id?: string;
|
|
55
|
+
model_provider?: string;
|
|
56
|
+
parent_thread_id?: string;
|
|
57
|
+
source?: unknown;
|
|
58
|
+
thread_source?: string;
|
|
59
|
+
timestamp?: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type ReadFallbackThreadRowsOptions = {
|
|
63
|
+
includeSubagents?: boolean;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type FallbackRolloutStats = {
|
|
67
|
+
model: string | null;
|
|
68
|
+
tokensUsed: number;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const isSqliteCantOpenError = (error: unknown) => {
|
|
72
|
+
return (error as { code?: unknown }).code === 'SQLITE_CANTOPEN';
|
|
73
|
+
};
|
|
32
74
|
|
|
33
75
|
const chunkValues = <T>(values: T[], chunkSize: number) => {
|
|
34
76
|
const chunks: T[][] = [];
|
|
@@ -48,40 +90,20 @@ const isPromiseLike = (value: unknown): value is PromiseLike<unknown> => {
|
|
|
48
90
|
return 'then' in value && typeof value.then === 'function';
|
|
49
91
|
};
|
|
50
92
|
|
|
51
|
-
const
|
|
52
|
-
values: T[],
|
|
53
|
-
limit: number,
|
|
54
|
-
mapper: (value: T, index: number) => Promise<TResult>,
|
|
55
|
-
) => {
|
|
56
|
-
const results = new Array<TResult>(values.length);
|
|
57
|
-
let nextIndex = 0;
|
|
58
|
-
|
|
59
|
-
const worker = async () => {
|
|
60
|
-
while (true) {
|
|
61
|
-
const currentIndex = nextIndex;
|
|
62
|
-
nextIndex += 1;
|
|
63
|
-
|
|
64
|
-
if (currentIndex >= values.length) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
results[currentIndex] = await mapper(values[currentIndex]!, currentIndex);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
await Promise.all(Array.from({ length: Math.min(limit, values.length) }, () => worker()));
|
|
73
|
-
return results;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const openReadonlyDb = (dbPath: string, busyTimeoutMs: number) => {
|
|
93
|
+
const openReadonlyDb = (dbPath: string) => {
|
|
77
94
|
const db = new Database(dbPath, { readonly: true });
|
|
78
95
|
try {
|
|
79
|
-
db.
|
|
96
|
+
db.query('SELECT name FROM sqlite_master LIMIT 1').get();
|
|
80
97
|
return db;
|
|
81
98
|
} catch (error) {
|
|
82
99
|
db.close();
|
|
83
|
-
|
|
100
|
+
if (!isSqliteCantOpenError(error)) {
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
84
103
|
}
|
|
104
|
+
|
|
105
|
+
// Codex uses WAL mode; immutable URI reads keep Bun usable after clean shutdown removes sidecar files.
|
|
106
|
+
return new Database(`${pathToFileURL(dbPath).href}?immutable=1`, CODEX_READONLY_DB_OPEN_FLAGS);
|
|
85
107
|
};
|
|
86
108
|
|
|
87
109
|
const openWritableDb = (dbPath: string, busyTimeoutMs: number) => {
|
|
@@ -126,7 +148,7 @@ const parseJsonSafely = (value: string | null) => {
|
|
|
126
148
|
export const withReadonlyDb = <T>(dbPath: string, callback: (db: Database) => T): T => {
|
|
127
149
|
return runWithSqliteRetry({
|
|
128
150
|
action: () => {
|
|
129
|
-
const db = openReadonlyDb(dbPath
|
|
151
|
+
const db = openReadonlyDb(dbPath);
|
|
130
152
|
try {
|
|
131
153
|
const result = callback(db);
|
|
132
154
|
if (isPromiseLike(result)) {
|
|
@@ -173,12 +195,10 @@ export const resolveCodexThreadDbPath = () => {
|
|
|
173
195
|
|
|
174
196
|
for (const candidate of candidates) {
|
|
175
197
|
try {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
});
|
|
181
|
-
db.close();
|
|
198
|
+
// Avoid opening candidates as a probe: Bun can make later read-only opens fail on Codex WAL databases.
|
|
199
|
+
if (!statSync(candidate).isFile()) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
182
202
|
return candidate;
|
|
183
203
|
} catch {}
|
|
184
204
|
}
|
|
@@ -194,6 +214,399 @@ const readAllThreads = (dbPath: string): ThreadRow[] => {
|
|
|
194
214
|
});
|
|
195
215
|
};
|
|
196
216
|
|
|
217
|
+
const resolveCodexDirFromDbPath = (dbPath: string) => {
|
|
218
|
+
const dbDir = path.dirname(dbPath);
|
|
219
|
+
return path.basename(dbDir) === 'sqlite' ? path.dirname(dbDir) : dbDir;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const parseJsonlObject = <T>(line: string): T | null => {
|
|
223
|
+
try {
|
|
224
|
+
return JSON.parse(line) as T;
|
|
225
|
+
} catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const emitJsonlLine = <T>(line: string, onRecord: (record: T) => void) => {
|
|
231
|
+
const trimmed = line.trim();
|
|
232
|
+
const parsed = trimmed ? parseJsonlObject<T>(trimmed) : null;
|
|
233
|
+
if (parsed) {
|
|
234
|
+
onRecord(parsed);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const emitCompleteJsonlLines = <T>(text: string, onRecord: (record: T) => void): string => {
|
|
239
|
+
const lines = text.split(/\r?\n/u);
|
|
240
|
+
const pending = lines.pop() ?? '';
|
|
241
|
+
for (const line of lines) {
|
|
242
|
+
emitJsonlLine(line, onRecord);
|
|
243
|
+
}
|
|
244
|
+
return pending;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const readJsonlObjects = <T>(filePath: string, onRecord: (record: T) => void) => {
|
|
248
|
+
let descriptor: number | null = null;
|
|
249
|
+
try {
|
|
250
|
+
const stats = statSync(filePath);
|
|
251
|
+
if (!stats.isFile()) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
descriptor = openSync(filePath, 'r');
|
|
256
|
+
const buffer = Buffer.alloc(JSONL_READ_CHUNK_BYTES);
|
|
257
|
+
const decoder = new StringDecoder('utf8');
|
|
258
|
+
let position = 0;
|
|
259
|
+
let pending = '';
|
|
260
|
+
|
|
261
|
+
while (true) {
|
|
262
|
+
const bytesRead = readSync(descriptor, buffer, 0, buffer.length, position);
|
|
263
|
+
if (bytesRead === 0) {
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
position += bytesRead;
|
|
268
|
+
pending += decoder.write(buffer.subarray(0, bytesRead));
|
|
269
|
+
pending = emitCompleteJsonlLines(pending, onRecord);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
emitJsonlLine(pending + decoder.end(), onRecord);
|
|
273
|
+
} catch {
|
|
274
|
+
return;
|
|
275
|
+
} finally {
|
|
276
|
+
if (descriptor !== null) {
|
|
277
|
+
closeSync(descriptor);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const collectJsonlObjects = <T>(filePath: string): T[] => {
|
|
283
|
+
const records: T[] = [];
|
|
284
|
+
readJsonlObjects<T>(filePath, (record) => {
|
|
285
|
+
records.push(record);
|
|
286
|
+
});
|
|
287
|
+
return records;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const readSessionIndexEntries = (codexDir: string): SessionIndexEntry[] => {
|
|
291
|
+
return collectJsonlObjects<SessionIndexEntry>(path.join(codexDir, 'session_index.jsonl')).filter(
|
|
292
|
+
(entry) => typeof entry.id === 'string' && entry.id.length > 0,
|
|
293
|
+
);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const collectSessionFilesByThreadId = (sessionsDir: string): Map<string, string> => {
|
|
297
|
+
const sessionFiles = new Map<string, string>();
|
|
298
|
+
const visit = (directory: string) => {
|
|
299
|
+
const entries = (() => {
|
|
300
|
+
try {
|
|
301
|
+
return readdirSync(directory, { withFileTypes: true });
|
|
302
|
+
} catch {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
})();
|
|
306
|
+
if (!entries) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for (const entry of entries) {
|
|
311
|
+
const entryPath = path.join(directory, entry.name);
|
|
312
|
+
if (entry.isDirectory()) {
|
|
313
|
+
visit(entryPath);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!entry.isFile()) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const threadId = THREAD_ID_PATTERN.exec(entry.name)?.[1];
|
|
322
|
+
if (threadId && !sessionFiles.has(threadId)) {
|
|
323
|
+
sessionFiles.set(threadId, entryPath);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
visit(sessionsDir);
|
|
329
|
+
return sessionFiles;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const readSessionMetaLine = (sessionFile: string): string | null => {
|
|
333
|
+
let descriptor: number | null = null;
|
|
334
|
+
try {
|
|
335
|
+
descriptor = openSync(sessionFile, 'r');
|
|
336
|
+
const chunks: Buffer[] = [];
|
|
337
|
+
let totalBytes = 0;
|
|
338
|
+
|
|
339
|
+
while (totalBytes < SESSION_META_READ_LIMIT_BYTES) {
|
|
340
|
+
const buffer = Buffer.alloc(Math.min(64 * 1024, SESSION_META_READ_LIMIT_BYTES - totalBytes));
|
|
341
|
+
const bytesRead = readSync(descriptor, buffer, 0, buffer.length, totalBytes);
|
|
342
|
+
if (bytesRead === 0) {
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
chunks.push(buffer.subarray(0, bytesRead));
|
|
347
|
+
totalBytes += bytesRead;
|
|
348
|
+
if (buffer.subarray(0, bytesRead).includes(10)) {
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const firstLine = Buffer.concat(chunks).toString('utf8').split(/\r?\n/u)[0]?.trim();
|
|
354
|
+
return firstLine || null;
|
|
355
|
+
} catch {
|
|
356
|
+
return null;
|
|
357
|
+
} finally {
|
|
358
|
+
if (descriptor !== null) {
|
|
359
|
+
closeSync(descriptor);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const readFallbackSessionMeta = (sessionFile: string): FallbackSessionMeta | null => {
|
|
365
|
+
const line = readSessionMetaLine(sessionFile);
|
|
366
|
+
if (!line) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
const record = JSON.parse(line) as { payload?: FallbackSessionMeta; type?: string };
|
|
372
|
+
return record.type === 'session_meta' && record.payload ? record.payload : null;
|
|
373
|
+
} catch {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const parseIsoMs = (value: string | undefined, fallback: number) => {
|
|
379
|
+
if (!value) {
|
|
380
|
+
return fallback;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const parsed = Date.parse(value);
|
|
384
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const stringOrNull = (value: unknown) => (typeof value === 'string' && value.length > 0 ? value : null);
|
|
388
|
+
|
|
389
|
+
const numberOrNull = (value: unknown) => (typeof value === 'number' && Number.isFinite(value) ? value : null);
|
|
390
|
+
|
|
391
|
+
const objectOrNull = (value: unknown) => {
|
|
392
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
393
|
+
? (value as Record<string, unknown>)
|
|
394
|
+
: null;
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const isFallbackSubagent = (sessionMeta: FallbackSessionMeta) => {
|
|
398
|
+
return Boolean(
|
|
399
|
+
sessionMeta.thread_source === 'subagent' ||
|
|
400
|
+
stringOrNull(sessionMeta.parent_thread_id) ||
|
|
401
|
+
stringOrNull(sessionMeta.forked_from_id),
|
|
402
|
+
);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const readFallbackRolloutStats = (sessionFile: string): FallbackRolloutStats => {
|
|
406
|
+
let model: string | null = null;
|
|
407
|
+
let tokensUsed = 0;
|
|
408
|
+
|
|
409
|
+
readJsonlObjects<Record<string, unknown>>(sessionFile, (record) => {
|
|
410
|
+
const payload = objectOrNull(record.payload);
|
|
411
|
+
if (!payload) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (record.type === 'turn_context') {
|
|
416
|
+
model = stringOrNull(payload.model) ?? model;
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const payloadType = stringOrNull(payload.type);
|
|
421
|
+
if (payloadType === 'message' || payloadType === 'agent_message') {
|
|
422
|
+
model = stringOrNull(payload.model) ?? model;
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (payloadType !== 'token_count') {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const info = objectOrNull(payload.info);
|
|
431
|
+
const totalTokenUsage = objectOrNull(info?.total_token_usage);
|
|
432
|
+
tokensUsed = numberOrNull(totalTokenUsage?.total_tokens) ?? tokensUsed;
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
model,
|
|
437
|
+
tokensUsed,
|
|
438
|
+
};
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const buildFallbackThreadRow = (
|
|
442
|
+
entry: SessionIndexEntry,
|
|
443
|
+
sessionFile: string,
|
|
444
|
+
sessionMeta: FallbackSessionMeta,
|
|
445
|
+
rolloutStats: FallbackRolloutStats,
|
|
446
|
+
): ThreadRow | null => {
|
|
447
|
+
const cwd = stringOrNull(sessionMeta.cwd);
|
|
448
|
+
if (!cwd) {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
let mtimeMs = Date.now();
|
|
453
|
+
try {
|
|
454
|
+
mtimeMs = statSync(sessionFile).mtimeMs;
|
|
455
|
+
} catch {}
|
|
456
|
+
|
|
457
|
+
const updatedAtMs = parseIsoMs(entry.updated_at, mtimeMs);
|
|
458
|
+
const createdAtMs = parseIsoMs(sessionMeta.timestamp, updatedAtMs);
|
|
459
|
+
const title = entry.thread_name?.trim() || path.basename(sessionFile, '.jsonl');
|
|
460
|
+
const source = stringOrNull(sessionMeta.source) ?? 'session_file';
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
agent_nickname: sessionMeta.agent_nickname ?? null,
|
|
464
|
+
agent_path: sessionMeta.agent_path ?? null,
|
|
465
|
+
agent_role: sessionMeta.agent_role ?? null,
|
|
466
|
+
approval_mode: 'unknown',
|
|
467
|
+
archived: 0,
|
|
468
|
+
archived_at: null,
|
|
469
|
+
cli_version: sessionMeta.cli_version ?? '',
|
|
470
|
+
created_at: Math.floor(createdAtMs / 1000),
|
|
471
|
+
created_at_ms: Math.floor(createdAtMs),
|
|
472
|
+
cwd,
|
|
473
|
+
first_user_message: title,
|
|
474
|
+
git_branch: null,
|
|
475
|
+
git_origin_url: null,
|
|
476
|
+
git_sha: null,
|
|
477
|
+
has_user_event: sessionMeta.thread_source === 'user' ? 1 : 0,
|
|
478
|
+
id: entry.id,
|
|
479
|
+
memory_mode: 'enabled',
|
|
480
|
+
model: rolloutStats.model,
|
|
481
|
+
model_provider: sessionMeta.model_provider ?? 'unknown',
|
|
482
|
+
preview: title,
|
|
483
|
+
reasoning_effort: null,
|
|
484
|
+
rollout_path: sessionFile,
|
|
485
|
+
sandbox_policy: '{}',
|
|
486
|
+
source,
|
|
487
|
+
thread_source: sessionMeta.thread_source ?? null,
|
|
488
|
+
title,
|
|
489
|
+
tokens_used: rolloutStats.tokensUsed,
|
|
490
|
+
updated_at: Math.floor(updatedAtMs / 1000),
|
|
491
|
+
updated_at_ms: Math.floor(updatedAtMs),
|
|
492
|
+
};
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const readFallbackThreadRows = (
|
|
496
|
+
dbPath: string,
|
|
497
|
+
existingThreadIds: Set<string>,
|
|
498
|
+
projectName: string | null = null,
|
|
499
|
+
options: ReadFallbackThreadRowsOptions = {},
|
|
500
|
+
): ThreadRow[] => {
|
|
501
|
+
const codexDir = resolveCodexDirFromDbPath(dbPath);
|
|
502
|
+
const sessionFilesByThreadId = collectSessionFilesByThreadId(path.join(codexDir, 'sessions'));
|
|
503
|
+
const fallbackThreads: ThreadRow[] = [];
|
|
504
|
+
|
|
505
|
+
for (const entry of readSessionIndexEntries(codexDir)) {
|
|
506
|
+
if (existingThreadIds.has(entry.id)) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const sessionFile = sessionFilesByThreadId.get(entry.id);
|
|
511
|
+
if (!sessionFile) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const sessionMeta = readFallbackSessionMeta(sessionFile);
|
|
516
|
+
if (!sessionMeta) {
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (!options.includeSubagents && isFallbackSubagent(sessionMeta)) {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const fallbackThread = buildFallbackThreadRow(
|
|
525
|
+
entry,
|
|
526
|
+
sessionFile,
|
|
527
|
+
sessionMeta,
|
|
528
|
+
readFallbackRolloutStats(sessionFile),
|
|
529
|
+
);
|
|
530
|
+
if (!fallbackThread || (projectName && getPortablePathBasename(fallbackThread.cwd) !== projectName)) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
fallbackThreads.push(fallbackThread);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return fallbackThreads;
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
const mergeFallbackThreadRows = (dbPath: string, threads: ThreadRow[], projectName: string | null = null) => {
|
|
541
|
+
const threadIds = new Set(threads.map((thread) => thread.id));
|
|
542
|
+
return [...threads, ...readFallbackThreadRows(dbPath, threadIds, projectName)].sort((left, right) => {
|
|
543
|
+
const updatedDifference = toTimestampMs(right) - toTimestampMs(left);
|
|
544
|
+
if (updatedDifference !== 0) {
|
|
545
|
+
return updatedDifference;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return right.id.localeCompare(left.id);
|
|
549
|
+
});
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const applyRolloutActivityTimestamps = (threads: ThreadRow[]) => {
|
|
553
|
+
return threads
|
|
554
|
+
.map((thread) => {
|
|
555
|
+
let rolloutUpdatedAtMs = toTimestampMs(thread);
|
|
556
|
+
try {
|
|
557
|
+
rolloutUpdatedAtMs = Math.max(rolloutUpdatedAtMs, statSync(thread.rollout_path).mtimeMs);
|
|
558
|
+
} catch {}
|
|
559
|
+
|
|
560
|
+
if (rolloutUpdatedAtMs <= toTimestampMs(thread)) {
|
|
561
|
+
return thread;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
...thread,
|
|
566
|
+
updated_at: Math.floor(rolloutUpdatedAtMs / 1000),
|
|
567
|
+
updated_at_ms: Math.floor(rolloutUpdatedAtMs),
|
|
568
|
+
};
|
|
569
|
+
})
|
|
570
|
+
.sort((left, right) => {
|
|
571
|
+
const updatedDifference = toTimestampMs(right) - toTimestampMs(left);
|
|
572
|
+
if (updatedDifference !== 0) {
|
|
573
|
+
return updatedDifference;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return right.id.localeCompare(left.id);
|
|
577
|
+
});
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const buildDashboardRecentThreads = (threads: ThreadRow[]) => {
|
|
581
|
+
const bestThreadByProject = new Map<string, ThreadRow>();
|
|
582
|
+
for (const thread of threads) {
|
|
583
|
+
const project = getPortablePathBasename(thread.cwd);
|
|
584
|
+
if (!project) {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const current = bestThreadByProject.get(project);
|
|
589
|
+
if (!current || toTimestampMs(thread) > toTimestampMs(current)) {
|
|
590
|
+
bestThreadByProject.set(project, thread);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return [...bestThreadByProject.values()]
|
|
595
|
+
.sort((left, right) => {
|
|
596
|
+
const updatedDifference = toTimestampMs(right) - toTimestampMs(left);
|
|
597
|
+
if (updatedDifference !== 0) {
|
|
598
|
+
return updatedDifference;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return right.id.localeCompare(left.id);
|
|
602
|
+
})
|
|
603
|
+
.slice(0, 5)
|
|
604
|
+
.map((thread) => ({
|
|
605
|
+
project: getPortablePathBasename(thread.cwd),
|
|
606
|
+
thread: compactThreadListRow(thread),
|
|
607
|
+
}));
|
|
608
|
+
};
|
|
609
|
+
|
|
197
610
|
const filterThreadsByProject = (threads: ThreadRow[], projectName: string | null) => {
|
|
198
611
|
if (!projectName) {
|
|
199
612
|
return threads;
|
|
@@ -391,7 +804,9 @@ const deleteThreadSessionFiles = async (sessionFiles: string[]) => {
|
|
|
391
804
|
};
|
|
392
805
|
|
|
393
806
|
export const listCodexProjects = (dbPath: string): ProjectSummary[] => {
|
|
394
|
-
return mapProjectSummaries(
|
|
807
|
+
return mapProjectSummaries(
|
|
808
|
+
buildProjectSummaryMap(applyRolloutActivityTimestamps(mergeFallbackThreadRows(dbPath, readAllThreads(dbPath)))),
|
|
809
|
+
);
|
|
395
810
|
};
|
|
396
811
|
|
|
397
812
|
type ListProjectThreadsOptions = {
|
|
@@ -411,10 +826,29 @@ export const listProjectThreads = async (
|
|
|
411
826
|
projectName: string,
|
|
412
827
|
options: ListProjectThreadsOptions = {},
|
|
413
828
|
): Promise<ThreadListEntry[]> => {
|
|
414
|
-
const threads =
|
|
415
|
-
|
|
829
|
+
const threads = mergeFallbackThreadRows(
|
|
830
|
+
dbPath,
|
|
831
|
+
filterThreadsByProject(readAllThreads(dbPath), projectName),
|
|
832
|
+
projectName,
|
|
833
|
+
);
|
|
834
|
+
const activeThreads = applyRolloutActivityTimestamps(threads);
|
|
835
|
+
const entries = await mapWithConcurrency(activeThreads, THREAD_LIST_IO_CONCURRENCY, async (thread) => {
|
|
416
836
|
const rollout = await getThreadRolloutLoadState(thread.rollout_path, options.largeTranscriptThresholdBytes);
|
|
417
837
|
|
|
838
|
+
if (rollout.fileSizeBytes === null) {
|
|
839
|
+
return {
|
|
840
|
+
project: projectName,
|
|
841
|
+
rolloutSizeBytes: null,
|
|
842
|
+
stats: {
|
|
843
|
+
deferred: false,
|
|
844
|
+
execCommandCount: 0,
|
|
845
|
+
toolCallCount: 0,
|
|
846
|
+
webSearchEventCount: 0,
|
|
847
|
+
},
|
|
848
|
+
thread: compactThreadListRow(thread),
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
418
852
|
if (rollout.shouldDeferTranscriptLoad) {
|
|
419
853
|
return {
|
|
420
854
|
project: projectName,
|
|
@@ -450,18 +884,25 @@ export const listProjectThreads = async (
|
|
|
450
884
|
export const getThreadBrowseData = (dbPath: string, threadId: string): ThreadBrowseData => {
|
|
451
885
|
return withReadonlyDb(dbPath, (db) => {
|
|
452
886
|
const existingTableNames = getExistingTableNames(db);
|
|
453
|
-
const
|
|
887
|
+
const dbThread = db.query('SELECT * FROM threads WHERE id = ? LIMIT 1').get(threadId) as ThreadRow | null;
|
|
888
|
+
const thread =
|
|
889
|
+
dbThread ??
|
|
890
|
+
readFallbackThreadRows(dbPath, new Set(), null, { includeSubagents: true }).find(
|
|
891
|
+
(fallbackThread) => fallbackThread.id === threadId,
|
|
892
|
+
) ??
|
|
893
|
+
null;
|
|
454
894
|
if (!thread) {
|
|
455
895
|
throw new Error(`Thread not found: ${threadId}`);
|
|
456
896
|
}
|
|
457
897
|
|
|
458
|
-
const dynamicTools =
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
898
|
+
const dynamicTools =
|
|
899
|
+
dbThread && existingTableNames.has('thread_dynamic_tools')
|
|
900
|
+
? (db
|
|
901
|
+
.query(
|
|
902
|
+
'SELECT thread_id, position, name, description, input_schema, defer_loading, namespace FROM thread_dynamic_tools WHERE thread_id = ? ORDER BY position ASC',
|
|
903
|
+
)
|
|
904
|
+
.all(threadId) as Array<Record<string, number | string | null>>)
|
|
905
|
+
: [];
|
|
465
906
|
|
|
466
907
|
return {
|
|
467
908
|
dynamicTools: dynamicTools.map((row) => parseDynamicToolRow(row)),
|
|
@@ -473,7 +914,7 @@ export const getThreadBrowseData = (dbPath: string, threadId: string): ThreadBro
|
|
|
473
914
|
};
|
|
474
915
|
|
|
475
916
|
export const getCodexDashboardSummary = (dbPath: string): DashboardSummary => {
|
|
476
|
-
const threads = readAllThreads(dbPath);
|
|
917
|
+
const threads = applyRolloutActivityTimestamps(mergeFallbackThreadRows(dbPath, readAllThreads(dbPath)));
|
|
477
918
|
const projects = mapProjectSummaries(buildProjectSummaryMap(threads));
|
|
478
919
|
const threadsWithRelations = withReadonlyDb(dbPath, (db) => {
|
|
479
920
|
if (!getExistingTableNames(db).has('thread_spawn_edges')) {
|
|
@@ -491,13 +932,7 @@ export const getCodexDashboardSummary = (dbPath: string): DashboardSummary => {
|
|
|
491
932
|
return {
|
|
492
933
|
activeThreads: threads.filter((thread) => !thread.archived).length,
|
|
493
934
|
archivedThreads: threads.filter((thread) => Boolean(thread.archived)).length,
|
|
494
|
-
recentThreads: threads
|
|
495
|
-
.slice(0, 5)
|
|
496
|
-
.filter((thread) => Boolean(getPortablePathBasename(thread.cwd)))
|
|
497
|
-
.map((thread) => ({
|
|
498
|
-
project: getPortablePathBasename(thread.cwd),
|
|
499
|
-
thread: compactThreadListRow(thread),
|
|
500
|
-
})),
|
|
935
|
+
recentThreads: buildDashboardRecentThreads(threads),
|
|
501
936
|
threadsWithRelations,
|
|
502
937
|
topProjectsByThreadCount: [...projects]
|
|
503
938
|
.sort((left, right) => {
|
|
@@ -603,7 +1038,7 @@ export const deleteCodexProject = async (
|
|
|
603
1038
|
};
|
|
604
1039
|
|
|
605
1040
|
export const listScopedThreads = (dbPath: string, projectName: string | null): ThreadRow[] => {
|
|
606
|
-
return filterThreadsByProject(readAllThreads(dbPath), projectName);
|
|
1041
|
+
return mergeFallbackThreadRows(dbPath, filterThreadsByProject(readAllThreads(dbPath), projectName), projectName);
|
|
607
1042
|
};
|
|
608
1043
|
|
|
609
1044
|
export const invalidateCodexUiCaches = async () => {
|