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
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { mapWithConcurrency } from './concurrency';
|
|
5
|
+
import {
|
|
6
|
+
getDefaultKiroDataDir,
|
|
7
|
+
type KiroSessionSummary,
|
|
8
|
+
type KiroSessionTranscript,
|
|
9
|
+
type KiroTranscriptEntry,
|
|
10
|
+
type KiroTranscriptPart,
|
|
11
|
+
type KiroWorkspaceGroup,
|
|
12
|
+
resolveKiroWorkspaceSessionsDir,
|
|
13
|
+
} from './kiro-exporter-types';
|
|
14
|
+
import {
|
|
15
|
+
asObject,
|
|
16
|
+
asString,
|
|
17
|
+
cleanExtractedText,
|
|
18
|
+
cleanInlineTitle,
|
|
19
|
+
getPortablePathBasename,
|
|
20
|
+
isWorkspacePathQuery,
|
|
21
|
+
type JsonValue,
|
|
22
|
+
workspacePathMatchesQuery,
|
|
23
|
+
} from './shared';
|
|
24
|
+
|
|
25
|
+
export { getDefaultKiroDataDir, resolveKiroWorkspaceSessionsDir };
|
|
26
|
+
|
|
27
|
+
const READ_CONCURRENCY = 8;
|
|
28
|
+
const WORKSPACE_KEY_PREFIX = 'workspace:';
|
|
29
|
+
|
|
30
|
+
type KiroSessionIndexEntry = {
|
|
31
|
+
createdAtMs: number | null;
|
|
32
|
+
sessionId: string;
|
|
33
|
+
title: string | null;
|
|
34
|
+
workspaceDirectory: string | null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type KiroSessionFile = {
|
|
38
|
+
directoryName: string;
|
|
39
|
+
filePath: string;
|
|
40
|
+
indexEntry: KiroSessionIndexEntry | null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type KiroExecutionFile = {
|
|
44
|
+
filePath: string;
|
|
45
|
+
raw: Record<string, JsonValue>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type ReadSessionFileOptions = {
|
|
49
|
+
includeExecutions?: boolean;
|
|
50
|
+
sessionsDir: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type SessionStats = {
|
|
54
|
+
assistantMessageCount: number;
|
|
55
|
+
imageCount: number;
|
|
56
|
+
messageCount: number;
|
|
57
|
+
promptLogCount: number;
|
|
58
|
+
renderablePartCount: number;
|
|
59
|
+
userMessageCount: number;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type SessionIdentity = {
|
|
63
|
+
autonomyMode: string | null;
|
|
64
|
+
defaultModelTitle: string | null;
|
|
65
|
+
firstUserText: string | null;
|
|
66
|
+
selectedModel: string | null;
|
|
67
|
+
selectedProfileId: string | null;
|
|
68
|
+
sessionId: string;
|
|
69
|
+
sessionType: string | null;
|
|
70
|
+
title: string | null;
|
|
71
|
+
workspaceDirectory: string | null;
|
|
72
|
+
workspacePath: string | null;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const pathExists = async (target: string): Promise<boolean> => {
|
|
76
|
+
return await stat(target)
|
|
77
|
+
.then(() => true)
|
|
78
|
+
.catch(() => false);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const toIso = (value: number | null): string | null => {
|
|
82
|
+
return value === null ? null : new Date(value).toISOString();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const parseTimestampMs = (value: JsonValue | undefined): number | null => {
|
|
86
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
87
|
+
return value > 0 && value < 10_000_000_000 ? value * 1000 : value;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof value === 'string') {
|
|
91
|
+
const numeric = Number(value);
|
|
92
|
+
if (Number.isFinite(numeric)) {
|
|
93
|
+
return numeric > 0 && numeric < 10_000_000_000 ? numeric * 1000 : numeric;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const parsed = Date.parse(value);
|
|
97
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return null;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const getWorkspaceKey = (directoryName: string): string => `${WORKSPACE_KEY_PREFIX}${directoryName}`;
|
|
104
|
+
|
|
105
|
+
const getDirectoryNameFromWorkspaceKey = (workspaceKey: string): string | null => {
|
|
106
|
+
return workspaceKey.startsWith(WORKSPACE_KEY_PREFIX) ? workspaceKey.slice(WORKSPACE_KEY_PREFIX.length) : null;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const isPlausibleWorkspacePath = (value: string): boolean => {
|
|
110
|
+
return Boolean(value.trim()) && !value.includes('\uFFFD') && !value.includes('\0');
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const decodeBase64WorkspacePath = (value: string, encoding: BufferEncoding): string | null => {
|
|
114
|
+
try {
|
|
115
|
+
const decoded = Buffer.from(value, encoding).toString('utf8');
|
|
116
|
+
return isPlausibleWorkspacePath(decoded) ? decoded : null;
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const decodeWorkspaceDirectoryName = (directoryName: string): string => {
|
|
123
|
+
// Kiro currently stores base64 with trailing "_" characters standing in for padding.
|
|
124
|
+
const base64WithPadding = directoryName.replace(/_+$/u, (match) => '='.repeat(match.length));
|
|
125
|
+
const decoded =
|
|
126
|
+
decodeBase64WorkspacePath(base64WithPadding, 'base64') ?? decodeBase64WorkspacePath(directoryName, 'base64url');
|
|
127
|
+
|
|
128
|
+
return decoded ?? directoryName;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const getWorkspaceLabel = (worktree: string): string => {
|
|
132
|
+
return getPortablePathBasename(worktree) || worktree;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const getWorkspaceUri = (worktree: string): string => {
|
|
136
|
+
return worktree.startsWith(path.sep) ? `file://${worktree}` : worktree;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const cleanLabel = (value: string | null | undefined): string | null => {
|
|
140
|
+
const cleaned = value?.replace(/\s+/g, ' ').trim();
|
|
141
|
+
return cleaned ? cleaned : null;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const readJsonObject = async (filePath: string): Promise<Record<string, JsonValue> | null> => {
|
|
145
|
+
const value = (await Bun.file(filePath)
|
|
146
|
+
.json()
|
|
147
|
+
.catch(() => null)) as JsonValue | null;
|
|
148
|
+
return asObject(value);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const readSessionIndex = async (workspaceDir: string): Promise<Map<string, KiroSessionIndexEntry>> => {
|
|
152
|
+
const indexPath = path.join(workspaceDir, 'sessions.json');
|
|
153
|
+
const value = (await Bun.file(indexPath)
|
|
154
|
+
.json()
|
|
155
|
+
.catch(() => null)) as JsonValue | null;
|
|
156
|
+
const items = Array.isArray(value) ? value : [];
|
|
157
|
+
const entries = new Map<string, KiroSessionIndexEntry>();
|
|
158
|
+
|
|
159
|
+
for (const item of items) {
|
|
160
|
+
const object = asObject(item);
|
|
161
|
+
const sessionId = asString(object?.sessionId ?? null);
|
|
162
|
+
if (!object || !sessionId) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
entries.set(sessionId, {
|
|
167
|
+
createdAtMs: parseTimestampMs(object.dateCreated),
|
|
168
|
+
sessionId,
|
|
169
|
+
title: cleanLabel(asString(object.title ?? null)),
|
|
170
|
+
workspaceDirectory: asString(object.workspaceDirectory ?? null),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return entries;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const parseTextPart = (raw: Record<string, JsonValue>, text: string): KiroTranscriptPart => ({
|
|
178
|
+
raw,
|
|
179
|
+
text,
|
|
180
|
+
type: 'text',
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const parseImagePart = (raw: Record<string, JsonValue>): KiroTranscriptPart => {
|
|
184
|
+
const imageUrl = asObject(raw.imageUrl ?? null);
|
|
185
|
+
return {
|
|
186
|
+
imageUrl: asString(imageUrl?.url ?? null) ?? asString(raw.url ?? null),
|
|
187
|
+
raw,
|
|
188
|
+
text: 'Image attachment',
|
|
189
|
+
type: 'image',
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const parseObjectContentPart = (raw: Record<string, JsonValue>): KiroTranscriptPart => {
|
|
194
|
+
const type = asString(raw.type ?? null);
|
|
195
|
+
if (type === 'imageUrl') {
|
|
196
|
+
return parseImagePart(raw);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const text = asString(raw.text ?? null);
|
|
200
|
+
if (text !== null) {
|
|
201
|
+
return parseTextPart(raw, text);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
raw,
|
|
206
|
+
type: 'unknown',
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const parseMessageContentPart = (item: JsonValue): KiroTranscriptPart | null => {
|
|
211
|
+
if (typeof item === 'string') {
|
|
212
|
+
return parseTextPart({ text: item }, item);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const object = asObject(item);
|
|
216
|
+
return object ? parseObjectContentPart(object) : null;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const getTextJoiner = (left: string, right: string): string => {
|
|
220
|
+
const leftTrimmed = left.trim();
|
|
221
|
+
const rightTrimmed = right.trim();
|
|
222
|
+
return leftTrimmed.startsWith('|') && rightTrimmed.startsWith('|') ? '\n' : '\n\n';
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const mergeTextParts = (left: KiroTranscriptPart, right: KiroTranscriptPart): KiroTranscriptPart => {
|
|
226
|
+
const text = `${left.text ?? ''}${getTextJoiner(left.text ?? '', right.text ?? '')}${right.text ?? ''}`;
|
|
227
|
+
return {
|
|
228
|
+
raw: {
|
|
229
|
+
sourceParts: [left.raw, right.raw],
|
|
230
|
+
text,
|
|
231
|
+
type: 'text',
|
|
232
|
+
},
|
|
233
|
+
text,
|
|
234
|
+
type: 'text',
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const mergeAdjacentTextParts = (parts: KiroTranscriptPart[]): KiroTranscriptPart[] => {
|
|
239
|
+
const merged: KiroTranscriptPart[] = [];
|
|
240
|
+
|
|
241
|
+
for (const part of parts) {
|
|
242
|
+
const previous = merged.at(-1);
|
|
243
|
+
if (previous?.type === 'text' && part.type === 'text') {
|
|
244
|
+
merged[merged.length - 1] = mergeTextParts(previous, part);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
merged.push(part);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return merged;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const parseMessageParts = (message: Record<string, JsonValue>): KiroTranscriptPart[] => {
|
|
255
|
+
const content = message.content;
|
|
256
|
+
const items = Array.isArray(content) ? content : [content];
|
|
257
|
+
const parts = items
|
|
258
|
+
.map(parseMessageContentPart)
|
|
259
|
+
.filter((part): part is KiroTranscriptPart => part !== null && part.type !== 'unknown');
|
|
260
|
+
return mergeAdjacentTextParts(parts);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const getPromptLogCount = (raw: Record<string, JsonValue>): number => {
|
|
264
|
+
return Array.isArray(raw.promptLogs) ? raw.promptLogs.length : 0;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const parseHistoryEntry = (raw: Record<string, JsonValue>, index: number): KiroTranscriptEntry | null => {
|
|
268
|
+
const message = asObject(raw.message ?? null);
|
|
269
|
+
if (!message) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const parts = parseMessageParts(message);
|
|
274
|
+
if (parts.length === 0) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
entryId: asString(message.id ?? null) ?? `entry:${index}`,
|
|
280
|
+
entryType: 'message',
|
|
281
|
+
executionId: asString(raw.executionId ?? null),
|
|
282
|
+
parts,
|
|
283
|
+
promptLogCount: getPromptLogCount(raw),
|
|
284
|
+
raw,
|
|
285
|
+
role: asString(message.role ?? null) ?? 'message',
|
|
286
|
+
timestamp: null,
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const createEmptyStats = (): SessionStats => ({
|
|
291
|
+
assistantMessageCount: 0,
|
|
292
|
+
imageCount: 0,
|
|
293
|
+
messageCount: 0,
|
|
294
|
+
promptLogCount: 0,
|
|
295
|
+
renderablePartCount: 0,
|
|
296
|
+
userMessageCount: 0,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const updateStatsFromEntry = (stats: SessionStats, entry: KiroTranscriptEntry) => {
|
|
300
|
+
if (entry.role === 'assistant' || entry.role === 'user') {
|
|
301
|
+
stats.messageCount += 1;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (entry.role === 'assistant') {
|
|
305
|
+
stats.assistantMessageCount += 1;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (entry.role === 'user') {
|
|
309
|
+
stats.userMessageCount += 1;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
stats.imageCount += entry.parts.filter((part) => part.type === 'image').length;
|
|
313
|
+
stats.promptLogCount += entry.promptLogCount;
|
|
314
|
+
stats.renderablePartCount += entry.parts.filter(isRenderablePart).length;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const updateIdentityFromRaw = (identity: SessionIdentity, raw: Record<string, JsonValue>) => {
|
|
318
|
+
const rawString = (key: string): string | null => asString(raw[key] ?? null);
|
|
319
|
+
const rawValues = {
|
|
320
|
+
autonomyMode: rawString('autonomyMode'),
|
|
321
|
+
defaultModelTitle: rawString('defaultModelTitle'),
|
|
322
|
+
selectedModel: rawString('selectedModel'),
|
|
323
|
+
selectedProfileId: rawString('selectedProfileId'),
|
|
324
|
+
sessionId: rawString('sessionId'),
|
|
325
|
+
sessionType: rawString('sessionType'),
|
|
326
|
+
title: cleanLabel(rawString('title')),
|
|
327
|
+
workspaceDirectory: rawString('workspaceDirectory'),
|
|
328
|
+
workspacePath: rawString('workspacePath'),
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
identity.autonomyMode = rawValues.autonomyMode ?? identity.autonomyMode;
|
|
332
|
+
identity.defaultModelTitle = rawValues.defaultModelTitle ?? identity.defaultModelTitle;
|
|
333
|
+
identity.selectedModel = rawValues.selectedModel ?? identity.selectedModel;
|
|
334
|
+
identity.selectedProfileId = rawValues.selectedProfileId ?? identity.selectedProfileId;
|
|
335
|
+
identity.sessionId = rawValues.sessionId ?? identity.sessionId;
|
|
336
|
+
identity.sessionType = rawValues.sessionType ?? identity.sessionType;
|
|
337
|
+
identity.title = rawValues.title ?? identity.title;
|
|
338
|
+
identity.workspaceDirectory = rawValues.workspaceDirectory ?? identity.workspaceDirectory;
|
|
339
|
+
identity.workspacePath = rawValues.workspacePath ?? identity.workspacePath;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const updateIdentityFromEntry = (identity: SessionIdentity, entry: KiroTranscriptEntry) => {
|
|
343
|
+
if (entry.role !== 'user' || identity.firstUserText) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const textPart = entry.parts.find((part) => part.type === 'text' && part.text?.trim());
|
|
348
|
+
identity.firstUserText = cleanExtractedText(textPart?.text ?? '').trim() || null;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const getEntryText = (entry: KiroTranscriptEntry): string => {
|
|
352
|
+
return entry.parts
|
|
353
|
+
.filter((part) => part.type === 'text')
|
|
354
|
+
.map((part) => cleanExtractedText(part.text ?? '').trim())
|
|
355
|
+
.filter(Boolean)
|
|
356
|
+
.join('\n\n')
|
|
357
|
+
.trim();
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const KIRO_ASSISTANT_PLACEHOLDER_PATTERN = /^on it[.!]?$/iu;
|
|
361
|
+
|
|
362
|
+
const isAssistantPlaceholderEntry = (entry: KiroTranscriptEntry): boolean => {
|
|
363
|
+
return entry.role === 'assistant' && KIRO_ASSISTANT_PLACEHOLDER_PATTERN.test(getEntryText(entry));
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const isRenderablePart = (part: KiroTranscriptPart): boolean => {
|
|
367
|
+
if (part.type === 'image') {
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (part.type === 'text') {
|
|
372
|
+
return Boolean(part.text?.trim());
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return false;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const getTitle = (identity: SessionIdentity, indexEntry: KiroSessionIndexEntry | null): string => {
|
|
379
|
+
return cleanInlineTitle(identity.title ?? indexEntry?.title ?? identity.firstUserText ?? identity.sessionId);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const getCreatedAtMs = (
|
|
383
|
+
indexEntry: KiroSessionIndexEntry | null,
|
|
384
|
+
fileStats: { birthtimeMs: number; mtimeMs: number } | null,
|
|
385
|
+
): number | null => {
|
|
386
|
+
return indexEntry?.createdAtMs ?? fileStats?.birthtimeMs ?? fileStats?.mtimeMs ?? null;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const getLastActiveAtMs = (
|
|
390
|
+
indexEntry: KiroSessionIndexEntry | null,
|
|
391
|
+
fileStats: { birthtimeMs: number; mtimeMs: number } | null,
|
|
392
|
+
): number | null => {
|
|
393
|
+
return fileStats?.mtimeMs ?? indexEntry?.createdAtMs ?? null;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const toSessionSummary = (
|
|
397
|
+
file: KiroSessionFile,
|
|
398
|
+
identity: SessionIdentity,
|
|
399
|
+
stats: SessionStats,
|
|
400
|
+
fileStats: { birthtimeMs: number; mtimeMs: number } | null,
|
|
401
|
+
): KiroSessionSummary => {
|
|
402
|
+
const worktree =
|
|
403
|
+
identity.workspaceDirectory ??
|
|
404
|
+
identity.workspacePath ??
|
|
405
|
+
file.indexEntry?.workspaceDirectory ??
|
|
406
|
+
decodeWorkspaceDirectoryName(file.directoryName);
|
|
407
|
+
const workspaceLabel = getWorkspaceLabel(worktree);
|
|
408
|
+
const createdAtMs = getCreatedAtMs(file.indexEntry, fileStats);
|
|
409
|
+
const lastActiveAtMs = getLastActiveAtMs(file.indexEntry, fileStats);
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
...stats,
|
|
413
|
+
autonomyMode: identity.autonomyMode,
|
|
414
|
+
createdAtIso: toIso(createdAtMs),
|
|
415
|
+
createdAtMs,
|
|
416
|
+
defaultModelTitle: identity.defaultModelTitle,
|
|
417
|
+
filePath: file.filePath,
|
|
418
|
+
lastActiveAtIso: toIso(lastActiveAtMs),
|
|
419
|
+
lastActiveAtMs,
|
|
420
|
+
selectedModel: identity.selectedModel,
|
|
421
|
+
selectedProfileId: identity.selectedProfileId,
|
|
422
|
+
sessionId: identity.sessionId,
|
|
423
|
+
sessionType: identity.sessionType,
|
|
424
|
+
title: getTitle(identity, file.indexEntry),
|
|
425
|
+
workspaceDirectory: identity.workspaceDirectory ?? file.indexEntry?.workspaceDirectory ?? null,
|
|
426
|
+
workspaceKey: getWorkspaceKey(file.directoryName),
|
|
427
|
+
workspaceLabel,
|
|
428
|
+
workspacePath: identity.workspacePath,
|
|
429
|
+
worktree,
|
|
430
|
+
};
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const getKiroDataDirFromSessionsDir = (sessionsDir: string): string => {
|
|
434
|
+
return path.basename(sessionsDir) === 'workspace-sessions' ? path.dirname(sessionsDir) : sessionsDir;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const getKiroWorkspaceHash = (workspacePath: string): string => {
|
|
438
|
+
return createHash('sha256').update(workspacePath).digest('hex').slice(0, 32);
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const listFilesRecursively = async (root: string, maxDepth = 3): Promise<string[]> => {
|
|
442
|
+
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
443
|
+
const files: string[] = [];
|
|
444
|
+
|
|
445
|
+
for (const entry of entries) {
|
|
446
|
+
const entryPath = path.join(root, entry.name);
|
|
447
|
+
if (entry.isFile()) {
|
|
448
|
+
files.push(entryPath);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (entry.isDirectory() && maxDepth > 0) {
|
|
453
|
+
files.push(...(await listFilesRecursively(entryPath, maxDepth - 1)));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return files;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const listExecutionFilesForSession = async (
|
|
461
|
+
dataDir: string,
|
|
462
|
+
sessionId: string,
|
|
463
|
+
worktree: string,
|
|
464
|
+
): Promise<KiroExecutionFile[]> => {
|
|
465
|
+
const workspaceExecutionRoot = path.join(dataDir, getKiroWorkspaceHash(worktree));
|
|
466
|
+
if (!(await pathExists(workspaceExecutionRoot))) {
|
|
467
|
+
return [];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const files = await listFilesRecursively(workspaceExecutionRoot);
|
|
471
|
+
const executions = await mapWithConcurrency(files, READ_CONCURRENCY, async (filePath) => {
|
|
472
|
+
const raw = await readJsonObject(filePath);
|
|
473
|
+
return asString(raw?.chatSessionId ?? null) === sessionId && raw ? { filePath, raw } : null;
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
return executions.flatMap((execution) => (execution ? [execution] : []));
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const getActionTimestamp = (action: Record<string, JsonValue>, execution: Record<string, JsonValue>): string | null => {
|
|
480
|
+
return toIso(
|
|
481
|
+
parseTimestampMs(action.emittedAt) ?? parseTimestampMs(action.endTime) ?? parseTimestampMs(execution.endTime),
|
|
482
|
+
);
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const formatHumanLine = (value: JsonValue | undefined): number | null => {
|
|
486
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0 || value >= Number.MAX_SAFE_INTEGER) {
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return value + 1;
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const formatFileRange = (file: Record<string, JsonValue>): string => {
|
|
494
|
+
const range = asObject(file.range ?? null);
|
|
495
|
+
const startLine = formatHumanLine(range?.startLine);
|
|
496
|
+
const endLine = formatHumanLine(range?.endLine);
|
|
497
|
+
if (startLine !== null && endLine !== null) {
|
|
498
|
+
return `:${startLine}-${endLine}`;
|
|
499
|
+
}
|
|
500
|
+
if (startLine !== null) {
|
|
501
|
+
return `:${startLine}`;
|
|
502
|
+
}
|
|
503
|
+
return '';
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const formatToolItems = (singularPrefix: string, pluralPrefix: string, items: string[]): string => {
|
|
507
|
+
if (items.length === 1) {
|
|
508
|
+
return `${singularPrefix}: ${items[0]}`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return `${pluralPrefix}:\n${items.map((item) => `- ${item}`).join('\n')}`;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const getToolCallSummary = (action: Record<string, JsonValue>): { command: string; toolName: string } | null => {
|
|
515
|
+
const input = asObject(action.input ?? null);
|
|
516
|
+
if (!input) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const files = Array.isArray(input.files)
|
|
521
|
+
? input.files.flatMap((item) => {
|
|
522
|
+
const file = asObject(item);
|
|
523
|
+
const filePath = asString(file?.path ?? null);
|
|
524
|
+
return file && filePath ? [`${filePath}${formatFileRange(file)}`] : [];
|
|
525
|
+
})
|
|
526
|
+
: [];
|
|
527
|
+
if (files.length > 0) {
|
|
528
|
+
return {
|
|
529
|
+
command: formatToolItems('Read file', 'Read files', files),
|
|
530
|
+
toolName: 'read_file',
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const documents = Array.isArray(input.documents)
|
|
535
|
+
? input.documents.flatMap((item) => {
|
|
536
|
+
const document = typeof item === 'string' ? item : asString(asObject(item)?.uri ?? null);
|
|
537
|
+
return document ? [document] : [];
|
|
538
|
+
})
|
|
539
|
+
: [];
|
|
540
|
+
if (documents.length > 0) {
|
|
541
|
+
return {
|
|
542
|
+
command: formatToolItems('Read document', 'Read documents', documents),
|
|
543
|
+
toolName: 'read_file',
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const query = asString(input.query ?? null);
|
|
548
|
+
const why = asString(input.why ?? null);
|
|
549
|
+
if (query || why) {
|
|
550
|
+
return {
|
|
551
|
+
command: `Search: ${why ?? query}${why && query ? `\nQuery: ${query}` : ''}`,
|
|
552
|
+
toolName: 'search',
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return null;
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
const parseExecutionActionMessageEntry = (
|
|
560
|
+
execution: KiroExecutionFile,
|
|
561
|
+
action: Record<string, JsonValue>,
|
|
562
|
+
index: number,
|
|
563
|
+
): KiroTranscriptEntry | null => {
|
|
564
|
+
const output = asObject(action.output ?? null);
|
|
565
|
+
const text = cleanExtractedText(asString(output?.message ?? null) ?? '').trim();
|
|
566
|
+
if (!text) {
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const executionId = asString(execution.raw.executionId ?? null);
|
|
571
|
+
const actionId = asString(action.actionId ?? null) ?? `action:${index}`;
|
|
572
|
+
|
|
573
|
+
return {
|
|
574
|
+
entryId: `${executionId ?? path.basename(execution.filePath)}:${actionId}`,
|
|
575
|
+
entryType: 'message',
|
|
576
|
+
executionId,
|
|
577
|
+
parts: [
|
|
578
|
+
parseTextPart(
|
|
579
|
+
{
|
|
580
|
+
actionId,
|
|
581
|
+
executionFilePath: execution.filePath,
|
|
582
|
+
message: text,
|
|
583
|
+
type: 'assistantMessage',
|
|
584
|
+
},
|
|
585
|
+
text,
|
|
586
|
+
),
|
|
587
|
+
],
|
|
588
|
+
promptLogCount: 0,
|
|
589
|
+
raw: {
|
|
590
|
+
...action,
|
|
591
|
+
executionFilePath: execution.filePath,
|
|
592
|
+
},
|
|
593
|
+
role: 'assistant',
|
|
594
|
+
timestamp: getActionTimestamp(action, execution.raw),
|
|
595
|
+
};
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
const parseExecutionActionToolEntry = (
|
|
599
|
+
execution: KiroExecutionFile,
|
|
600
|
+
action: Record<string, JsonValue>,
|
|
601
|
+
index: number,
|
|
602
|
+
): KiroTranscriptEntry | null => {
|
|
603
|
+
const summary = getToolCallSummary(action);
|
|
604
|
+
if (!summary) {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const executionId = asString(execution.raw.executionId ?? null);
|
|
609
|
+
const actionId = asString(action.actionId ?? null) ?? `action:${index}`;
|
|
610
|
+
|
|
611
|
+
return {
|
|
612
|
+
entryId: `${executionId ?? path.basename(execution.filePath)}:${actionId}`,
|
|
613
|
+
entryType: 'tool_call',
|
|
614
|
+
executionId,
|
|
615
|
+
parts: [
|
|
616
|
+
parseTextPart(
|
|
617
|
+
{
|
|
618
|
+
actionId,
|
|
619
|
+
command: summary.command,
|
|
620
|
+
executionFilePath: execution.filePath,
|
|
621
|
+
toolName: summary.toolName,
|
|
622
|
+
type: 'toolCall',
|
|
623
|
+
},
|
|
624
|
+
summary.command,
|
|
625
|
+
),
|
|
626
|
+
],
|
|
627
|
+
promptLogCount: 0,
|
|
628
|
+
raw: {
|
|
629
|
+
...action,
|
|
630
|
+
executionFilePath: execution.filePath,
|
|
631
|
+
},
|
|
632
|
+
role: 'tool',
|
|
633
|
+
timestamp: getActionTimestamp(action, execution.raw),
|
|
634
|
+
};
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
const parseExecutionActionEntries = (
|
|
638
|
+
execution: KiroExecutionFile,
|
|
639
|
+
action: Record<string, JsonValue>,
|
|
640
|
+
index: number,
|
|
641
|
+
): KiroTranscriptEntry[] => {
|
|
642
|
+
const entries = [
|
|
643
|
+
parseExecutionActionToolEntry(execution, action, index),
|
|
644
|
+
parseExecutionActionMessageEntry(execution, action, index),
|
|
645
|
+
];
|
|
646
|
+
return entries.filter((entry): entry is KiroTranscriptEntry => entry !== null);
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
const parseExecutionEntries = (execution: KiroExecutionFile): KiroTranscriptEntry[] => {
|
|
650
|
+
const actions = Array.isArray(execution.raw.actions) ? execution.raw.actions : [];
|
|
651
|
+
return actions.flatMap((item, index) => {
|
|
652
|
+
const action = asObject(item);
|
|
653
|
+
return action ? parseExecutionActionEntries(execution, action, index) : [];
|
|
654
|
+
});
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
const compareExecutionFiles = (left: KiroExecutionFile, right: KiroExecutionFile): number => {
|
|
658
|
+
return (
|
|
659
|
+
(parseTimestampMs(left.raw.startTime) ?? 0) - (parseTimestampMs(right.raw.startTime) ?? 0) ||
|
|
660
|
+
left.filePath.localeCompare(right.filePath)
|
|
661
|
+
);
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
const readExecutionEntries = async (
|
|
665
|
+
sessionsDir: string,
|
|
666
|
+
session: KiroSessionSummary,
|
|
667
|
+
): Promise<KiroTranscriptEntry[]> => {
|
|
668
|
+
const executions = await listExecutionFilesForSession(
|
|
669
|
+
getKiroDataDirFromSessionsDir(sessionsDir),
|
|
670
|
+
session.sessionId,
|
|
671
|
+
session.worktree,
|
|
672
|
+
);
|
|
673
|
+
return executions.sort(compareExecutionFiles).flatMap(parseExecutionEntries);
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const getVisibleEntries = (
|
|
677
|
+
historyEntries: KiroTranscriptEntry[],
|
|
678
|
+
executionEntries: KiroTranscriptEntry[],
|
|
679
|
+
): KiroTranscriptEntry[] => {
|
|
680
|
+
if (executionEntries.length === 0) {
|
|
681
|
+
return historyEntries;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const executionEntriesById = new Map<string, KiroTranscriptEntry[]>();
|
|
685
|
+
for (const entry of executionEntries) {
|
|
686
|
+
if (!entry.executionId) {
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const entries = executionEntriesById.get(entry.executionId) ?? [];
|
|
691
|
+
entries.push(entry);
|
|
692
|
+
executionEntriesById.set(entry.executionId, entries);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const usedExecutionIds = new Set<string>();
|
|
696
|
+
const visibleEntries: KiroTranscriptEntry[] = [];
|
|
697
|
+
for (const entry of historyEntries) {
|
|
698
|
+
const matchingExecutionEntries = entry.executionId ? executionEntriesById.get(entry.executionId) : undefined;
|
|
699
|
+
if (isAssistantPlaceholderEntry(entry)) {
|
|
700
|
+
if (matchingExecutionEntries) {
|
|
701
|
+
visibleEntries.push(...matchingExecutionEntries);
|
|
702
|
+
usedExecutionIds.add(entry.executionId!);
|
|
703
|
+
}
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
visibleEntries.push(entry);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
visibleEntries.push(
|
|
711
|
+
...executionEntries.filter((entry) => !entry.executionId || !usedExecutionIds.has(entry.executionId)),
|
|
712
|
+
);
|
|
713
|
+
return visibleEntries;
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const createStatsFromEntries = (entries: KiroTranscriptEntry[]): SessionStats => {
|
|
717
|
+
const stats = createEmptyStats();
|
|
718
|
+
for (const entry of entries) {
|
|
719
|
+
updateStatsFromEntry(stats, entry);
|
|
720
|
+
}
|
|
721
|
+
return stats;
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const readSessionFile = async (
|
|
725
|
+
file: KiroSessionFile,
|
|
726
|
+
options: ReadSessionFileOptions,
|
|
727
|
+
): Promise<KiroSessionTranscript | null> => {
|
|
728
|
+
const rawSession = await readJsonObject(file.filePath);
|
|
729
|
+
if (!rawSession) {
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const fileStats = await stat(file.filePath)
|
|
734
|
+
.then((stats) => ({ birthtimeMs: stats.birthtimeMs, mtimeMs: stats.mtimeMs }))
|
|
735
|
+
.catch(() => null);
|
|
736
|
+
const identity: SessionIdentity = {
|
|
737
|
+
autonomyMode: null,
|
|
738
|
+
defaultModelTitle: null,
|
|
739
|
+
firstUserText: null,
|
|
740
|
+
selectedModel: null,
|
|
741
|
+
selectedProfileId: null,
|
|
742
|
+
sessionId: path.basename(file.filePath, '.json'),
|
|
743
|
+
sessionType: null,
|
|
744
|
+
title: null,
|
|
745
|
+
workspaceDirectory: file.indexEntry?.workspaceDirectory ?? null,
|
|
746
|
+
workspacePath: null,
|
|
747
|
+
};
|
|
748
|
+
const historyEntries: KiroTranscriptEntry[] = [];
|
|
749
|
+
const history = Array.isArray(rawSession.history) ? rawSession.history : [];
|
|
750
|
+
|
|
751
|
+
updateIdentityFromRaw(identity, rawSession);
|
|
752
|
+
|
|
753
|
+
history.forEach((item, index) => {
|
|
754
|
+
const raw = asObject(item);
|
|
755
|
+
if (!raw) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const entry = parseHistoryEntry(raw, index);
|
|
760
|
+
if (!entry) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
historyEntries.push(entry);
|
|
765
|
+
updateIdentityFromEntry(identity, entry);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
const baseSummary = toSessionSummary(file, identity, createEmptyStats(), fileStats);
|
|
769
|
+
const executionEntries = options.includeExecutions
|
|
770
|
+
? await readExecutionEntries(options.sessionsDir, baseSummary)
|
|
771
|
+
: [];
|
|
772
|
+
const entries = getVisibleEntries(historyEntries, executionEntries);
|
|
773
|
+
const stats = createStatsFromEntries(entries);
|
|
774
|
+
return {
|
|
775
|
+
entries,
|
|
776
|
+
rawSession,
|
|
777
|
+
renderablePartCount: stats.renderablePartCount,
|
|
778
|
+
session: toSessionSummary(file, identity, stats, fileStats),
|
|
779
|
+
};
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
const listSessionFilesForWorkspace = async (sessionsDir: string, directoryName: string): Promise<KiroSessionFile[]> => {
|
|
783
|
+
const workspaceDir = path.join(sessionsDir, directoryName);
|
|
784
|
+
const index = await readSessionIndex(workspaceDir);
|
|
785
|
+
const entries = await readdir(workspaceDir, { withFileTypes: true }).catch(() => []);
|
|
786
|
+
|
|
787
|
+
return entries
|
|
788
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.json') && entry.name !== 'sessions.json')
|
|
789
|
+
.map((entry) => {
|
|
790
|
+
const sessionId = path.basename(entry.name, '.json');
|
|
791
|
+
return {
|
|
792
|
+
directoryName,
|
|
793
|
+
filePath: path.join(workspaceDir, entry.name),
|
|
794
|
+
indexEntry: index.get(sessionId) ?? null,
|
|
795
|
+
};
|
|
796
|
+
});
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
const listSessionFiles = async (sessionsDir: string): Promise<KiroSessionFile[]> => {
|
|
800
|
+
if (!(await pathExists(sessionsDir))) {
|
|
801
|
+
return [];
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const workspaceDirs = (await readdir(sessionsDir, { withFileTypes: true }))
|
|
805
|
+
.filter((entry) => entry.isDirectory())
|
|
806
|
+
.map((entry) => entry.name)
|
|
807
|
+
.sort();
|
|
808
|
+
const groupedFiles = await mapWithConcurrency(workspaceDirs, READ_CONCURRENCY, (directoryName) =>
|
|
809
|
+
listSessionFilesForWorkspace(sessionsDir, directoryName),
|
|
810
|
+
);
|
|
811
|
+
return groupedFiles.flat();
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
const readSessionFiles = async (files: KiroSessionFile[]): Promise<KiroSessionTranscript[]> => {
|
|
815
|
+
const transcripts = await mapWithConcurrency(files, READ_CONCURRENCY, (file) =>
|
|
816
|
+
readSessionFile(file, { includeExecutions: false, sessionsDir: path.dirname(path.dirname(file.filePath)) }),
|
|
817
|
+
);
|
|
818
|
+
return transcripts.flatMap((transcript) => (transcript ? [transcript] : []));
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
const compareNullableMsDesc = (left: number | null, right: number | null): number => {
|
|
822
|
+
return (right ?? 0) - (left ?? 0);
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
const sumSessions = (sessions: KiroSessionSummary[], key: keyof SessionStats): number => {
|
|
826
|
+
return sessions.reduce((total, session) => total + session[key], 0);
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
const toWorkspaceGroup = (directoryName: string, sessions: KiroSessionSummary[]): KiroWorkspaceGroup => {
|
|
830
|
+
const worktree = sessions[0]?.worktree ?? decodeWorkspaceDirectoryName(directoryName);
|
|
831
|
+
const lastActiveAtMs = sessions.reduce<number | null>((latest, session) => {
|
|
832
|
+
if (session.lastActiveAtMs === null) {
|
|
833
|
+
return latest;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
return latest === null ? session.lastActiveAtMs : Math.max(latest, session.lastActiveAtMs);
|
|
837
|
+
}, null);
|
|
838
|
+
|
|
839
|
+
return {
|
|
840
|
+
assistantMessageCount: sumSessions(sessions, 'assistantMessageCount'),
|
|
841
|
+
directoryName,
|
|
842
|
+
imageCount: sumSessions(sessions, 'imageCount'),
|
|
843
|
+
key: getWorkspaceKey(directoryName),
|
|
844
|
+
label: getWorkspaceLabel(worktree),
|
|
845
|
+
lastActiveAtIso: toIso(lastActiveAtMs),
|
|
846
|
+
lastActiveAtMs,
|
|
847
|
+
messageCount: sumSessions(sessions, 'messageCount'),
|
|
848
|
+
promptLogCount: sumSessions(sessions, 'promptLogCount'),
|
|
849
|
+
sessionCount: sessions.length,
|
|
850
|
+
uri: getWorkspaceUri(worktree),
|
|
851
|
+
userMessageCount: sumSessions(sessions, 'userMessageCount'),
|
|
852
|
+
worktree,
|
|
853
|
+
};
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
export const listKiroWorkspaceGroups = async (
|
|
857
|
+
sessionsDir = resolveKiroWorkspaceSessionsDir(),
|
|
858
|
+
): Promise<KiroWorkspaceGroup[]> => {
|
|
859
|
+
const files = await listSessionFiles(sessionsDir);
|
|
860
|
+
const transcripts = await readSessionFiles(files);
|
|
861
|
+
const sessionsByDirectory = new Map<string, KiroSessionSummary[]>();
|
|
862
|
+
|
|
863
|
+
for (const transcript of transcripts) {
|
|
864
|
+
const directoryName = getDirectoryNameFromWorkspaceKey(transcript.session.workspaceKey);
|
|
865
|
+
if (!directoryName) {
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const sessions = sessionsByDirectory.get(directoryName) ?? [];
|
|
870
|
+
sessions.push(transcript.session);
|
|
871
|
+
sessionsByDirectory.set(directoryName, sessions);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return [...sessionsByDirectory.entries()]
|
|
875
|
+
.map(([directoryName, sessions]) => toWorkspaceGroup(directoryName, sessions))
|
|
876
|
+
.sort(
|
|
877
|
+
(left, right) =>
|
|
878
|
+
compareNullableMsDesc(left.lastActiveAtMs, right.lastActiveAtMs) ||
|
|
879
|
+
left.worktree.localeCompare(right.worktree),
|
|
880
|
+
);
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
const kiroWorkspaceMatchesQuery = (workspace: KiroWorkspaceGroup, query: string): boolean => {
|
|
884
|
+
const raw = query.trim();
|
|
885
|
+
if (!raw) {
|
|
886
|
+
return true;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const lowered = raw.toLowerCase();
|
|
890
|
+
if (
|
|
891
|
+
workspace.key.toLowerCase() === lowered ||
|
|
892
|
+
workspace.directoryName.toLowerCase() === lowered ||
|
|
893
|
+
workspace.label.toLowerCase() === lowered
|
|
894
|
+
) {
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (isWorkspacePathQuery(raw)) {
|
|
899
|
+
return workspacePathMatchesQuery(workspace.worktree, raw);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
return getPortablePathBasename(workspace.worktree).toLowerCase() === lowered;
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
export const findKiroWorkspaceGroups = (groups: KiroWorkspaceGroup[], query: string): KiroWorkspaceGroup[] => {
|
|
906
|
+
return groups.filter((group) => kiroWorkspaceMatchesQuery(group, query));
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
const sortSessions = (sessions: KiroSessionSummary[]): KiroSessionSummary[] => {
|
|
910
|
+
return [...sessions].sort(
|
|
911
|
+
(left, right) =>
|
|
912
|
+
compareNullableMsDesc(left.lastActiveAtMs, right.lastActiveAtMs) || left.title.localeCompare(right.title),
|
|
913
|
+
);
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
export const listKiroSessionsForGroup = async (
|
|
917
|
+
workspaceKey: string,
|
|
918
|
+
sessionsDir = resolveKiroWorkspaceSessionsDir(),
|
|
919
|
+
): Promise<KiroSessionSummary[]> => {
|
|
920
|
+
const directoryName = getDirectoryNameFromWorkspaceKey(workspaceKey);
|
|
921
|
+
if (!directoryName || !(await pathExists(sessionsDir))) {
|
|
922
|
+
return [];
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const files = await listSessionFilesForWorkspace(sessionsDir, directoryName);
|
|
926
|
+
const transcripts = await readSessionFiles(files);
|
|
927
|
+
return sortSessions(transcripts.map((transcript) => transcript.session));
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
const locateSessionFile = async (sessionsDir: string, sessionId: string): Promise<KiroSessionFile | null> => {
|
|
931
|
+
const files = await listSessionFiles(sessionsDir);
|
|
932
|
+
return files.find((file) => path.basename(file.filePath, '.json') === sessionId) ?? null;
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
export const readKiroSessionTranscript = async (
|
|
936
|
+
sessionsDir: string,
|
|
937
|
+
sessionId: string,
|
|
938
|
+
): Promise<KiroSessionTranscript | null> => {
|
|
939
|
+
if (!(await pathExists(sessionsDir))) {
|
|
940
|
+
return null;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
const file = await locateSessionFile(sessionsDir, sessionId);
|
|
944
|
+
if (!file) {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
return readSessionFile(file, { includeExecutions: true, sessionsDir });
|
|
949
|
+
};
|