spiracha 1.4.0 → 2.0.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 +116 -164
- package/README.md +92 -237
- package/apps/ui/AGENTS.md +36 -7
- package/apps/ui/README.md +79 -6
- package/apps/ui/components.json +21 -0
- package/apps/ui/package.json +65 -0
- package/apps/ui/{dist/client → public}/icon.svg +2 -1
- package/apps/ui/src/components/antigravity-conversations-table.tsx +206 -0
- package/apps/ui/src/components/antigravity-keychain-panel.tsx +76 -0
- package/apps/ui/src/components/antigravity-workspaces-table.tsx +63 -0
- package/apps/ui/src/components/app-shell.tsx +124 -0
- package/apps/ui/src/components/breadcrumbs.tsx +53 -0
- package/apps/ui/src/components/claude-code-sessions-table.tsx +114 -0
- package/apps/ui/src/components/claude-code-workspaces-table.tsx +55 -0
- package/apps/ui/src/components/cursor-threads-table.tsx +189 -0
- package/apps/ui/src/components/cursor-workspaces-table.tsx +140 -0
- package/apps/ui/src/components/data-table.tsx +241 -0
- package/apps/ui/src/components/delete-confirm-dialog.tsx +88 -0
- package/apps/ui/src/components/export-dialog.tsx +184 -0
- package/apps/ui/src/components/json-panel.tsx +17 -0
- package/apps/ui/src/components/kiro-sessions-table.tsx +112 -0
- package/apps/ui/src/components/kiro-workspaces-table.tsx +53 -0
- package/apps/ui/src/components/list-search-input.tsx +18 -0
- package/apps/ui/src/components/loading-panel.tsx +23 -0
- package/apps/ui/src/components/metadata-section.tsx +31 -0
- package/apps/ui/src/components/metric-card.tsx +19 -0
- package/apps/ui/src/components/opencode-sessions-table.tsx +128 -0
- package/apps/ui/src/components/opencode-workspaces-table.tsx +57 -0
- package/apps/ui/src/components/page-header.tsx +33 -0
- package/apps/ui/src/components/projects-loading-state.tsx +74 -0
- package/apps/ui/src/components/projects-table.tsx +108 -0
- package/apps/ui/src/components/qoder-sessions-table.tsx +112 -0
- package/apps/ui/src/components/qoder-workspaces-table.tsx +53 -0
- package/apps/ui/src/components/recent-threads-list.tsx +52 -0
- package/apps/ui/src/components/reload-error-panel.tsx +20 -0
- package/apps/ui/src/components/text-document-panel.tsx +19 -0
- package/apps/ui/src/components/theme-toggle.tsx +48 -0
- package/apps/ui/src/components/threads-table.tsx +202 -0
- package/apps/ui/src/components/transcript-view.tsx +552 -0
- package/apps/ui/src/components/ui/alert-dialog.tsx +160 -0
- package/apps/ui/src/components/ui/badge.tsx +45 -0
- package/apps/ui/src/components/ui/button.tsx +62 -0
- package/apps/ui/src/components/ui/checkbox.tsx +29 -0
- package/apps/ui/src/components/ui/dialog.tsx +137 -0
- package/apps/ui/src/components/ui/dropdown-menu.tsx +217 -0
- package/apps/ui/src/components/ui/input.tsx +21 -0
- package/apps/ui/src/components/ui/scroll-area.tsx +46 -0
- package/apps/ui/src/components/ui/select.tsx +163 -0
- package/apps/ui/src/components/ui/separator.tsx +28 -0
- package/apps/ui/src/components/ui/sheet.tsx +107 -0
- package/apps/ui/src/components/ui/skeleton.tsx +7 -0
- package/apps/ui/src/components/ui/table.tsx +76 -0
- package/apps/ui/src/components/ui/tabs.tsx +71 -0
- package/apps/ui/src/components/ui/tooltip.tsx +44 -0
- package/apps/ui/src/integrations/tanstack-query/devtools.tsx +6 -0
- package/apps/ui/src/integrations/tanstack-query/root-provider.tsx +10 -0
- package/apps/ui/src/lib/antigravity-conversation-state.ts +28 -0
- package/apps/ui/src/lib/antigravity-queries.ts +33 -0
- package/apps/ui/src/lib/antigravity-server.ts +128 -0
- package/apps/ui/src/lib/antigravity-transcript-events.ts +335 -0
- package/apps/ui/src/lib/claude-code-queries.ts +26 -0
- package/apps/ui/src/lib/claude-code-server.ts +75 -0
- package/apps/ui/src/lib/claude-code-transcript-events.ts +154 -0
- package/apps/ui/src/lib/codex-queries.ts +66 -0
- package/apps/ui/src/lib/codex-server.ts +209 -0
- package/apps/ui/src/lib/cursor-queries.ts +37 -0
- package/apps/ui/src/lib/cursor-server.ts +272 -0
- package/apps/ui/src/lib/cursor-transcript-events.ts +229 -0
- package/apps/ui/src/lib/download.ts +166 -0
- package/apps/ui/src/lib/formatters.ts +149 -0
- package/apps/ui/src/lib/kiro-queries.ts +22 -0
- package/apps/ui/src/lib/kiro-server.ts +73 -0
- package/apps/ui/src/lib/kiro-transcript-events.ts +113 -0
- package/apps/ui/src/lib/opencode-queries.ts +37 -0
- package/apps/ui/src/lib/opencode-server.ts +73 -0
- package/apps/ui/src/lib/opencode-transcript-events.ts +221 -0
- package/apps/ui/src/lib/package-metadata.ts +16 -0
- package/apps/ui/src/lib/path-utils.ts +13 -0
- package/apps/ui/src/lib/qoder-queries.ts +22 -0
- package/apps/ui/src/lib/qoder-server.ts +79 -0
- package/apps/ui/src/lib/qoder-transcript-events.ts +139 -0
- package/apps/ui/src/lib/route-search.ts +68 -0
- package/apps/ui/src/lib/settings-store.tsx +59 -0
- package/apps/ui/src/lib/source-session-export-server.ts +56 -0
- package/apps/ui/src/lib/text-filter.ts +22 -0
- package/apps/ui/src/lib/thread-id.ts +3 -0
- package/apps/ui/src/lib/thread-transcript-stats.ts +57 -0
- package/apps/ui/src/lib/utils.ts +7 -0
- package/apps/ui/src/routeTree.gen.ts +730 -0
- package/apps/ui/src/router.tsx +20 -0
- package/apps/ui/src/routes/$threadId.tsx +22 -0
- package/apps/ui/src/routes/__root.tsx +127 -0
- package/apps/ui/src/routes/analytics.tsx +143 -0
- package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +438 -0
- package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +127 -0
- package/apps/ui/src/routes/antigravity.index.tsx +51 -0
- package/apps/ui/src/routes/api.v1.conversation-query.ts +12 -0
- package/apps/ui/src/routes/api.v1.conversations.$source.$id.export.ts +12 -0
- package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +12 -0
- package/apps/ui/src/routes/api.v1.conversations.ts +12 -0
- package/apps/ui/src/routes/api.v1.resolve.ts +12 -0
- package/apps/ui/src/routes/api.v1.sources.ts +12 -0
- package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +342 -0
- package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +135 -0
- package/apps/ui/src/routes/claude-code.index.tsx +48 -0
- package/apps/ui/src/routes/codex.$project.tsx +390 -0
- package/apps/ui/src/routes/codex.index.tsx +116 -0
- package/apps/ui/src/routes/cursor-threads.$composerId.tsx +435 -0
- package/apps/ui/src/routes/cursor.$workspaceKey.tsx +416 -0
- package/apps/ui/src/routes/cursor.index.tsx +120 -0
- package/apps/ui/src/routes/index.tsx +124 -0
- package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +345 -0
- package/apps/ui/src/routes/kiro.$workspaceKey.tsx +136 -0
- package/apps/ui/src/routes/kiro.index.tsx +48 -0
- package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +334 -0
- package/apps/ui/src/routes/opencode.$workspaceKey.tsx +135 -0
- package/apps/ui/src/routes/opencode.index.tsx +48 -0
- package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +341 -0
- package/apps/ui/src/routes/qoder.$workspaceKey.tsx +139 -0
- package/apps/ui/src/routes/qoder.index.tsx +48 -0
- package/apps/ui/src/routes/settings.tsx +91 -0
- package/apps/ui/src/routes/threads.$threadId.tsx +619 -0
- package/apps/ui/src/styles.css +122 -0
- package/apps/ui/tsconfig.json +29 -0
- package/apps/ui/vite.config.ts +84 -0
- package/bin/spiracha.ts +32 -0
- package/package.json +50 -60
- package/src/client.ts +290 -0
- package/src/lib/antigravity-db.ts +213 -19
- package/src/lib/antigravity-exporter-types.ts +1 -0
- 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 +1 -1
- package/src/lib/codex-browser-db.ts +856 -61
- package/src/lib/codex-browser-export.ts +24 -16
- package/src/lib/codex-browser-types.ts +1 -1
- package/src/lib/codex-thread-cache.ts +17 -1
- package/src/lib/{codex-exporter-types.ts → codex-thread-types.ts} +2 -30
- package/src/lib/{codex-exporter-transcript.ts → codex-transcript-renderer.ts} +64 -28
- package/src/lib/concurrency.ts +25 -0
- package/src/lib/conversation-api.ts +792 -0
- package/src/lib/conversation-data/adapter-helpers.ts +110 -0
- package/src/lib/conversation-data/antigravity-adapter.ts +148 -0
- package/src/lib/conversation-data/claude-code-adapter.ts +175 -0
- package/src/lib/conversation-data/codex-adapter.ts +245 -0
- package/src/lib/conversation-data/cursor-adapter.ts +171 -0
- package/src/lib/conversation-data/index.ts +311 -0
- package/src/lib/conversation-data/kiro-adapter.ts +140 -0
- package/src/lib/conversation-data/message-selector.ts +37 -0
- package/src/lib/conversation-data/opencode-adapter.ts +180 -0
- package/src/lib/conversation-data/path-match.ts +79 -0
- package/src/lib/conversation-data/qoder-adapter.ts +230 -0
- package/src/lib/conversation-data/types.ts +116 -0
- package/src/lib/cursor-db.ts +67 -21
- package/src/lib/cursor-exporter-types.ts +0 -19
- 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/qoder-acp-client.ts +268 -0
- package/src/lib/qoder-db.ts +1601 -0
- package/src/lib/qoder-exporter-types.ts +114 -0
- package/src/lib/qoder-transcript-phase.ts +41 -0
- package/src/lib/qoder-transcript.ts +112 -0
- package/src/lib/shared.ts +25 -0
- package/src/lib/ui-export-archive.ts +61 -0
- package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +0 -1
- package/apps/ui/dist/client/assets/analytics-DK2jdvmI.js +0 -1
- package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DmD0sN8X.js +0 -7
- package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-KAcXtNxI.js +0 -1
- package/apps/ui/dist/client/assets/antigravity-keychain-panel-BRHVjFAM.js +0 -1
- package/apps/ui/dist/client/assets/antigravity._workspaceKey-ByitGWki.js +0 -1
- package/apps/ui/dist/client/assets/antigravity._workspaceKey-CGV7amas.js +0 -1
- package/apps/ui/dist/client/assets/antigravity.index-DEEkjlZa.js +0 -1
- package/apps/ui/dist/client/assets/antigravity.index-DgW25FEV.js +0 -1
- package/apps/ui/dist/client/assets/badge-DfWvPfKF.js +0 -1
- package/apps/ui/dist/client/assets/checkbox-CdHitXWQ.js +0 -1
- package/apps/ui/dist/client/assets/createServerFn-CgRRVpBH.js +0 -3
- package/apps/ui/dist/client/assets/cursor-threads._composerId-CmBFclK8.js +0 -1
- package/apps/ui/dist/client/assets/cursor-threads._composerId-Ti2gTm63.js +0 -1
- package/apps/ui/dist/client/assets/cursor._workspaceKey-CaAZjGmm.js +0 -1
- package/apps/ui/dist/client/assets/cursor._workspaceKey-DzD_c6j9.js +0 -1
- package/apps/ui/dist/client/assets/cursor.index-CrnMwDCi.js +0 -1
- package/apps/ui/dist/client/assets/cursor.index-PkCFVcl4.js +0 -2
- package/apps/ui/dist/client/assets/data-table-Br78_dk3.js +0 -4
- package/apps/ui/dist/client/assets/delete-confirm-dialog-BHRmyLHr.js +0 -11
- package/apps/ui/dist/client/assets/dist-B9pyYCVU.js +0 -1
- package/apps/ui/dist/client/assets/dist-BhZOxAPP.js +0 -1
- package/apps/ui/dist/client/assets/dist-DvMS2965.js +0 -1
- package/apps/ui/dist/client/assets/download-Bf0QJKD3.js +0 -1
- package/apps/ui/dist/client/assets/dropdown-menu-Dy5cLzTy.js +0 -1
- package/apps/ui/dist/client/assets/es2015-BkCWttyM.js +0 -41
- package/apps/ui/dist/client/assets/export-dialog-C86SYHJn.js +0 -1
- package/apps/ui/dist/client/assets/formatters-BM9kB7ed.js +0 -1
- package/apps/ui/dist/client/assets/index-CPvAP-jk.js +0 -149
- package/apps/ui/dist/client/assets/jsx-runtime-DGeXAQPT.js +0 -1
- package/apps/ui/dist/client/assets/metric-card-Dplm0ZiM.js +0 -1
- package/apps/ui/dist/client/assets/page-header-Cwa3p6Tl.js +0 -1
- package/apps/ui/dist/client/assets/projects._project-Ckf6muWZ.js +0 -1
- package/apps/ui/dist/client/assets/projects._project-DSDwH4NF.js +0 -1
- package/apps/ui/dist/client/assets/projects.index-DEfhWqa8.js +0 -1
- package/apps/ui/dist/client/assets/projects.index-V5ibNZII.js +0 -3
- package/apps/ui/dist/client/assets/react-dom-DL96Jor4.js +0 -1
- package/apps/ui/dist/client/assets/refresh-ccw-BgKjOiA6.js +0 -1
- package/apps/ui/dist/client/assets/reload-error-panel-AHqu0akK.js +0 -1
- package/apps/ui/dist/client/assets/routes-Df2tgpyd.js +0 -1
- package/apps/ui/dist/client/assets/scroll-text-fM0sMOB3.js +0 -1
- package/apps/ui/dist/client/assets/select-Bf2GwfPR.js +0 -1
- package/apps/ui/dist/client/assets/settings-5VQIzME0.js +0 -1
- package/apps/ui/dist/client/assets/sqlite-error-B6uZjUe8.js +0 -1
- package/apps/ui/dist/client/assets/styles-BhRkXgwB.css +0 -1
- package/apps/ui/dist/client/assets/tabs-OO4VU5KR.js +0 -7
- package/apps/ui/dist/client/assets/text-filter-DVAZySUS.js +0 -2
- package/apps/ui/dist/client/assets/threads._threadId-BB9tTsV4.js +0 -1
- package/apps/ui/dist/client/assets/threads._threadId-BgDhFj2I.js +0 -1
- package/apps/ui/dist/client/assets/useMutation-CC_B7uy5.js +0 -1
- package/apps/ui/dist/client/assets/useQuery-C5By6WKU.js +0 -1
- package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CRW2kqj0.js +0 -232
- package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +0 -6
- package/apps/ui/dist/server/assets/analytics-CYP_LT5Y.js +0 -16
- package/apps/ui/dist/server/assets/analytics-CtpOMVMO.js +0 -146
- package/apps/ui/dist/server/assets/antigravity-conversation-state-HgzS302O.js +0 -16
- package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BtexWY-K.js +0 -613
- package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-D426O-64.js +0 -11
- package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-zMrR6v6R.js +0 -20
- package/apps/ui/dist/server/assets/antigravity-db-Bh8_U9uw.js +0 -580
- package/apps/ui/dist/server/assets/antigravity-keychain-DOiuHDwK.js +0 -126
- package/apps/ui/dist/server/assets/antigravity-keychain-panel-CC7FLmAK.js +0 -55
- package/apps/ui/dist/server/assets/antigravity-queries-CGrJO9Vr.js +0 -37
- package/apps/ui/dist/server/assets/antigravity-server-CHRjVFqN.js +0 -114
- package/apps/ui/dist/server/assets/antigravity._workspaceKey-3m_MzNFA.js +0 -11
- package/apps/ui/dist/server/assets/antigravity._workspaceKey-B0QeYUOh.js +0 -28
- package/apps/ui/dist/server/assets/antigravity._workspaceKey-C3kP-qyN.js +0 -210
- package/apps/ui/dist/server/assets/antigravity.index-CPCYkqCi.js +0 -104
- package/apps/ui/dist/server/assets/antigravity.index-DudTB3Tq.js +0 -11
- package/apps/ui/dist/server/assets/badge-EvdhKK_Z.js +0 -26
- package/apps/ui/dist/server/assets/button-CmTDnzOn.js +0 -46
- package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +0 -19
- package/apps/ui/dist/server/assets/codex-queries-CtgeZ7VL.js +0 -99
- package/apps/ui/dist/server/assets/codex-server-CNXSJuc2.js +0 -2004
- package/apps/ui/dist/server/assets/concurrency-VXtYvlGh.js +0 -18
- package/apps/ui/dist/server/assets/createServerRpc-CF_DEwnm.js +0 -12
- package/apps/ui/dist/server/assets/createSsrRpc-C754NPki.js +0 -16
- package/apps/ui/dist/server/assets/cursor-db-cYZEU3WQ.js +0 -830
- package/apps/ui/dist/server/assets/cursor-exporter-types-CI3goo-c.js +0 -34
- package/apps/ui/dist/server/assets/cursor-queries-NCIM0Nat.js +0 -67
- package/apps/ui/dist/server/assets/cursor-recovery-nq-kR62j.js +0 -361
- package/apps/ui/dist/server/assets/cursor-server-C3q7hrp-.js +0 -213
- package/apps/ui/dist/server/assets/cursor-threads._composerId-BB0Y_Mao.js +0 -11
- package/apps/ui/dist/server/assets/cursor-threads._composerId-BPJFWfJj.js +0 -18
- package/apps/ui/dist/server/assets/cursor-threads._composerId-BkMMTQ3v.js +0 -582
- package/apps/ui/dist/server/assets/cursor-transcript-2iL3KFSK.js +0 -125
- package/apps/ui/dist/server/assets/cursor._workspaceKey-C5PdTaKB.js +0 -28
- package/apps/ui/dist/server/assets/cursor._workspaceKey-Ckj7apKI.js +0 -401
- package/apps/ui/dist/server/assets/cursor._workspaceKey-nmg3YIOQ.js +0 -11
- package/apps/ui/dist/server/assets/cursor.index-CcsX7DG0.js +0 -11
- package/apps/ui/dist/server/assets/cursor.index-DkWT-mxw.js +0 -189
- package/apps/ui/dist/server/assets/data-table-Cdct823O.js +0 -189
- package/apps/ui/dist/server/assets/delete-confirm-dialog-BcBNCxWB.js +0 -144
- package/apps/ui/dist/server/assets/download-DMmiy1xf.js +0 -92
- package/apps/ui/dist/server/assets/dropdown-menu-Dy_9t6TN.js +0 -36
- package/apps/ui/dist/server/assets/empty-plugin-adapters-B3lHh1La.js +0 -5
- package/apps/ui/dist/server/assets/export-dialog-BItjWgkZ.js +0 -240
- package/apps/ui/dist/server/assets/formatters-FJaGZgJk.js +0 -91
- package/apps/ui/dist/server/assets/loading-panel-BGFnWseS.js +0 -27
- package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +0 -23
- package/apps/ui/dist/server/assets/model-label-B1NWGc65.js +0 -13
- package/apps/ui/dist/server/assets/page-header-VNSaM3xd.js +0 -29
- package/apps/ui/dist/server/assets/path-transforms-DL2IwtYd.js +0 -31
- package/apps/ui/dist/server/assets/projects._project-Bpbc3C-L.js +0 -20
- package/apps/ui/dist/server/assets/projects._project-Bshqk7JA.js +0 -12
- package/apps/ui/dist/server/assets/projects._project-CvZho6EQ.js +0 -395
- package/apps/ui/dist/server/assets/projects.index-BLXOx5eL.js +0 -12
- package/apps/ui/dist/server/assets/projects.index-BkLiF2FF.js +0 -182
- package/apps/ui/dist/server/assets/projects.index-DesYXwfi.js +0 -14
- package/apps/ui/dist/server/assets/reload-error-panel-BJMxY3U1.js +0 -25
- package/apps/ui/dist/server/assets/route-search-ts4W9MJ9.js +0 -38
- package/apps/ui/dist/server/assets/router-6m-ihwqA.js +0 -410
- package/apps/ui/dist/server/assets/routes-Bsact1uB.js +0 -184
- package/apps/ui/dist/server/assets/routes-BzkJgq4W.js +0 -34
- package/apps/ui/dist/server/assets/select-GW76p-ld.js +0 -76
- package/apps/ui/dist/server/assets/settings-OayxIYQQ.js +0 -100
- package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +0 -52
- package/apps/ui/dist/server/assets/shared-CPRNYIql.js +0 -134
- package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +0 -13
- package/apps/ui/dist/server/assets/start-CI_0bSiY.js +0 -4
- package/apps/ui/dist/server/assets/tabs-CGA13IZM.js +0 -502
- package/apps/ui/dist/server/assets/text-filter-CGKxMCKt.js +0 -36
- package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +0 -26
- package/apps/ui/dist/server/assets/threads._threadId-Bs0yuqO9.js +0 -18
- package/apps/ui/dist/server/assets/threads._threadId-D2GYkVn6.js +0 -639
- package/apps/ui/dist/server/assets/ui-export-files-BHLX9bhN.js +0 -83
- package/apps/ui/dist/server/assets/utils-C_uf36nf.js +0 -8
- package/apps/ui/dist/server/server.js +0 -5847
- package/bin/codex-chats-claude.js +0 -5
- package/bin/codex-chats.js +0 -5
- package/bin/spiracha.js +0 -5
- package/src/export-chats.ts +0 -120
- package/src/export-claude.ts +0 -36
- package/src/export-cursor.ts +0 -244
- package/src/lib/claude-exporter.ts +0 -864
- package/src/lib/codex-exporter-cli.ts +0 -277
- package/src/lib/codex-exporter-db.ts +0 -319
- package/src/lib/codex-exporter.ts +0 -115
- package/src/lib/cursor-exporter.ts +0 -266
- package/src/lib/interactive-cli.ts +0 -433
- package/src/lib/native-open.ts +0 -54
- package/src/mcp-server.ts +0 -137
- package/src/spiracha.ts +0 -116
- package/src/ui-cli.ts +0 -310
- /package/apps/ui/{dist/client → public}/manifest.json +0 -0
- /package/apps/ui/{dist/client → public}/robots.txt +0 -0
package/src/client.ts
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getConversation as getLocalConversation,
|
|
3
|
+
listConversationSources as listLocalConversationSources,
|
|
4
|
+
listConversationsForPath as listLocalConversationsForPath,
|
|
5
|
+
renderConversationMarkdown as renderLocalConversationMarkdown,
|
|
6
|
+
resolveConversationRef as resolveLocalConversationRef,
|
|
7
|
+
} from './lib/conversation-data';
|
|
8
|
+
import type {
|
|
9
|
+
ConversationDataLocations,
|
|
10
|
+
ConversationDetail,
|
|
11
|
+
ConversationMessageSelector,
|
|
12
|
+
ConversationPage,
|
|
13
|
+
ConversationSourceInfo,
|
|
14
|
+
GetConversationOptions,
|
|
15
|
+
ListConversationsForPathOptions,
|
|
16
|
+
ResolvedConversationRef,
|
|
17
|
+
} from './lib/conversation-data/types';
|
|
18
|
+
|
|
19
|
+
export type {
|
|
20
|
+
ConversationDataLocations,
|
|
21
|
+
ConversationDeepLinks,
|
|
22
|
+
ConversationDetail,
|
|
23
|
+
ConversationMessage,
|
|
24
|
+
ConversationMessagePhase,
|
|
25
|
+
ConversationMessageRole,
|
|
26
|
+
ConversationMessageSelector,
|
|
27
|
+
ConversationPage,
|
|
28
|
+
ConversationPathMatch,
|
|
29
|
+
ConversationSource,
|
|
30
|
+
ConversationSourceInfo,
|
|
31
|
+
GetConversationOptions,
|
|
32
|
+
ListConversationsForPathOptions,
|
|
33
|
+
ResolvedConversationRef,
|
|
34
|
+
} from './lib/conversation-data/types';
|
|
35
|
+
|
|
36
|
+
type HttpEnvelope<T> = {
|
|
37
|
+
data?: T;
|
|
38
|
+
error?: {
|
|
39
|
+
message?: string | null;
|
|
40
|
+
} | null;
|
|
41
|
+
meta?: {
|
|
42
|
+
hasNext?: boolean | null;
|
|
43
|
+
nextCursor?: string | null;
|
|
44
|
+
next_cursor?: string | null;
|
|
45
|
+
} | null;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export class SpirachaClientError extends Error {
|
|
49
|
+
readonly status: number | null;
|
|
50
|
+
|
|
51
|
+
constructor(message: string, status: number | null = null) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.name = 'SpirachaClientError';
|
|
54
|
+
this.status = status;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type LocalConversationClientOptions = {
|
|
59
|
+
locations?: ConversationDataLocations;
|
|
60
|
+
mode?: 'local';
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type HttpConversationClientOptions = {
|
|
64
|
+
baseUrl: string;
|
|
65
|
+
mode: 'http';
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type CreateConversationClientOptions = HttpConversationClientOptions | LocalConversationClientOptions;
|
|
69
|
+
|
|
70
|
+
export type ExportConversationMarkdownOptions = GetConversationOptions;
|
|
71
|
+
|
|
72
|
+
export type ConversationClient = {
|
|
73
|
+
exportConversationMarkdown: (options: ExportConversationMarkdownOptions) => Promise<string | null>;
|
|
74
|
+
getConversation: (options: GetConversationOptions) => Promise<ConversationDetail | null>;
|
|
75
|
+
listConversations: (options: ListConversationsForPathOptions) => Promise<ConversationPage>;
|
|
76
|
+
listSources: () => Promise<ConversationSourceInfo[]>;
|
|
77
|
+
resolveConversationRef: (ref: string) => Promise<ResolvedConversationRef | null>;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const withDefaultLocations = <T extends { locations?: ConversationDataLocations }>(
|
|
81
|
+
options: T,
|
|
82
|
+
locations: ConversationDataLocations | undefined,
|
|
83
|
+
): T => {
|
|
84
|
+
return locations && !options.locations ? { ...options, locations } : options;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const normalizeBaseUrl = (value: string): URL => {
|
|
88
|
+
try {
|
|
89
|
+
const url = new URL(value);
|
|
90
|
+
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
|
91
|
+
return url;
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// Fall through to a consistent client-facing error.
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
throw new SpirachaClientError(`Invalid Spiracha base URL "${value}". Use an http:// or https:// URL.`);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const appendOptionalNumber = (url: URL, key: string, value: number | undefined): void => {
|
|
101
|
+
if (value !== undefined) {
|
|
102
|
+
url.searchParams.set(key, String(value));
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const appendListOptions = (url: URL, options: ListConversationsForPathOptions): void => {
|
|
107
|
+
url.searchParams.set('cwd', options.cwd);
|
|
108
|
+
if (options.cursor) {
|
|
109
|
+
url.searchParams.set('cursor', options.cursor);
|
|
110
|
+
}
|
|
111
|
+
if (options.includeMessages !== undefined) {
|
|
112
|
+
url.searchParams.set('include_messages', String(options.includeMessages));
|
|
113
|
+
}
|
|
114
|
+
if (options.limit !== undefined) {
|
|
115
|
+
url.searchParams.set('limit', String(options.limit));
|
|
116
|
+
}
|
|
117
|
+
if (options.messageSelector) {
|
|
118
|
+
url.searchParams.set('message_selector', options.messageSelector);
|
|
119
|
+
}
|
|
120
|
+
if (options.sources && options.sources !== 'all') {
|
|
121
|
+
url.searchParams.set('source', options.sources.join(','));
|
|
122
|
+
}
|
|
123
|
+
appendOptionalNumber(url, 'updated_after_ms', options.updatedAfterMs);
|
|
124
|
+
appendOptionalNumber(url, 'updated_before_ms', options.updatedBeforeMs);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const appendMessageSelector = (url: URL, messageSelector: ConversationMessageSelector | undefined): void => {
|
|
128
|
+
if (messageSelector) {
|
|
129
|
+
url.searchParams.set('message_selector', messageSelector);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const httpErrorMessage = async (response: Response): Promise<string> => {
|
|
134
|
+
const text = await response.text();
|
|
135
|
+
if (!text.trim()) {
|
|
136
|
+
return response.statusText || `HTTP ${response.status}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const parsed = JSON.parse(text) as HttpEnvelope<unknown>;
|
|
141
|
+
return parsed.error?.message || text.trim();
|
|
142
|
+
} catch {
|
|
143
|
+
return text.trim();
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const fetchResponse = async (url: URL, init?: RequestInit): Promise<Response> => {
|
|
148
|
+
try {
|
|
149
|
+
return await fetch(url, init);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
152
|
+
throw new SpirachaClientError(`Unable to reach Spiracha at ${url.origin}: ${message}`);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const assertOkResponse = async (response: Response): Promise<void> => {
|
|
157
|
+
if (response.ok) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
throw new SpirachaClientError(
|
|
162
|
+
`Spiracha API request failed (${response.status}): ${await httpErrorMessage(response)}`,
|
|
163
|
+
response.status,
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const readJsonEnvelope = async <T>(response: Response): Promise<HttpEnvelope<T>> => {
|
|
168
|
+
try {
|
|
169
|
+
return (await response.json()) as HttpEnvelope<T>;
|
|
170
|
+
} catch {
|
|
171
|
+
throw new SpirachaClientError('Spiracha API returned invalid JSON.', response.status);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const fetchJson = async <T>(url: URL, init?: RequestInit): Promise<HttpEnvelope<T>> => {
|
|
176
|
+
const response = await fetchResponse(url, init);
|
|
177
|
+
await assertOkResponse(response);
|
|
178
|
+
return readJsonEnvelope(response);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const fetchJsonOrNull = async <T>(url: URL, init?: RequestInit): Promise<HttpEnvelope<T> | null> => {
|
|
182
|
+
const response = await fetchResponse(url, init);
|
|
183
|
+
if (response.status === 404) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await assertOkResponse(response);
|
|
188
|
+
return readJsonEnvelope(response);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const fetchTextOrNull = async (url: URL, init?: RequestInit): Promise<string | null> => {
|
|
192
|
+
const response = await fetchResponse(url, init);
|
|
193
|
+
if (response.status === 404) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
await assertOkResponse(response);
|
|
198
|
+
return response.text();
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const requireData = <T>(envelope: HttpEnvelope<T>, label: string): T => {
|
|
202
|
+
if (envelope.data === undefined) {
|
|
203
|
+
throw new SpirachaClientError(`Spiracha API response did not include ${label}.`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return envelope.data;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const normalizePage = (envelope: HttpEnvelope<ConversationDetail[]>): ConversationPage => {
|
|
210
|
+
const data = requireData(envelope, 'a data field');
|
|
211
|
+
if (!Array.isArray(data)) {
|
|
212
|
+
throw new SpirachaClientError('Spiracha API response did not include a data array.');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
data,
|
|
217
|
+
meta: {
|
|
218
|
+
hasNext: envelope.meta?.hasNext === true,
|
|
219
|
+
nextCursor: envelope.meta?.nextCursor ?? envelope.meta?.next_cursor ?? null,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const makeHttpUrl = (baseUrl: URL, pathname: string): URL => new URL(pathname, baseUrl);
|
|
225
|
+
|
|
226
|
+
const rejectHttpLocations = (locations: ConversationDataLocations | undefined): void => {
|
|
227
|
+
if (locations) {
|
|
228
|
+
throw new SpirachaClientError('`locations` is only supported by local Spiracha clients.');
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const makeLocalClient = (options: LocalConversationClientOptions): ConversationClient => ({
|
|
233
|
+
exportConversationMarkdown: async (getOptions) => {
|
|
234
|
+
const conversation = await getLocalConversation(withDefaultLocations(getOptions, options.locations));
|
|
235
|
+
return conversation ? renderLocalConversationMarkdown(conversation) : null;
|
|
236
|
+
},
|
|
237
|
+
getConversation: (getOptions) => getLocalConversation(withDefaultLocations(getOptions, options.locations)),
|
|
238
|
+
listConversations: (listOptions) =>
|
|
239
|
+
listLocalConversationsForPath(withDefaultLocations(listOptions, options.locations)),
|
|
240
|
+
listSources: () => listLocalConversationSources(),
|
|
241
|
+
resolveConversationRef: (ref) => resolveLocalConversationRef(ref),
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const makeHttpClient = (options: HttpConversationClientOptions): ConversationClient => {
|
|
245
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
exportConversationMarkdown: async (getOptions) => {
|
|
249
|
+
rejectHttpLocations(getOptions.locations);
|
|
250
|
+
const { id, messageSelector, source } = getOptions;
|
|
251
|
+
const url = makeHttpUrl(baseUrl, `/api/v1/conversations/${source}/${encodeURIComponent(id)}/export`);
|
|
252
|
+
appendMessageSelector(url, messageSelector);
|
|
253
|
+
return fetchTextOrNull(url);
|
|
254
|
+
},
|
|
255
|
+
getConversation: async (getOptions) => {
|
|
256
|
+
rejectHttpLocations(getOptions.locations);
|
|
257
|
+
const { id, messageSelector, source } = getOptions;
|
|
258
|
+
const url = makeHttpUrl(baseUrl, `/api/v1/conversations/${source}/${encodeURIComponent(id)}`);
|
|
259
|
+
appendMessageSelector(url, messageSelector);
|
|
260
|
+
const envelope = await fetchJsonOrNull<ConversationDetail>(url);
|
|
261
|
+
if (!envelope) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
return requireData(envelope, 'a conversation');
|
|
265
|
+
},
|
|
266
|
+
listConversations: async (listOptions) => {
|
|
267
|
+
rejectHttpLocations(listOptions.locations);
|
|
268
|
+
const url = makeHttpUrl(baseUrl, '/api/v1/conversations');
|
|
269
|
+
appendListOptions(url, listOptions);
|
|
270
|
+
return normalizePage(await fetchJson<ConversationDetail[]>(url));
|
|
271
|
+
},
|
|
272
|
+
listSources: async () => {
|
|
273
|
+
const envelope = await fetchJson<ConversationSourceInfo[]>(makeHttpUrl(baseUrl, '/api/v1/sources'));
|
|
274
|
+
return requireData(envelope, 'a source list');
|
|
275
|
+
},
|
|
276
|
+
resolveConversationRef: async (ref) => {
|
|
277
|
+
const url = makeHttpUrl(baseUrl, '/api/v1/resolve');
|
|
278
|
+
url.searchParams.set('ref', ref);
|
|
279
|
+
const envelope = await fetchJsonOrNull<ResolvedConversationRef>(url);
|
|
280
|
+
if (!envelope) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
return requireData(envelope, 'a resolved ref');
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export const createConversationClient = (options: CreateConversationClientOptions = {}): ConversationClient => {
|
|
289
|
+
return options.mode === 'http' ? makeHttpClient(options) : makeLocalClient(options);
|
|
290
|
+
};
|
|
@@ -48,12 +48,23 @@ const isSafeConversationId = (value: string) => SAFE_CONVERSATION_ID_PATTERN.tes
|
|
|
48
48
|
type TranscriptFile = {
|
|
49
49
|
bytes: number;
|
|
50
50
|
entryCount: number;
|
|
51
|
+
fullPath: string | null;
|
|
51
52
|
mtimeMs: number;
|
|
53
|
+
model: string | null;
|
|
52
54
|
path: string;
|
|
53
55
|
root: string;
|
|
54
56
|
source: Exclude<AntigravityTranscriptSource, 'safe-storage'>;
|
|
55
57
|
};
|
|
56
58
|
|
|
59
|
+
export type AntigravityConversationMessage = {
|
|
60
|
+
createdAtMs: number | null;
|
|
61
|
+
metadata: Record<string, unknown>;
|
|
62
|
+
order: number;
|
|
63
|
+
phase: 'final_answer' | 'reasoning' | 'tool_call' | 'tool_output' | 'unknown';
|
|
64
|
+
role: 'assistant' | 'system' | 'tool' | 'unknown' | 'user';
|
|
65
|
+
text: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
57
68
|
type WorkspaceInfo = Pick<SummaryEntry, 'workspaceFolder' | 'workspaceKey' | 'workspaceLabel' | 'workspaceUri'>;
|
|
58
69
|
|
|
59
70
|
const UNKNOWN_WORKSPACE: WorkspaceInfo = {
|
|
@@ -489,6 +500,44 @@ const countJsonlEntries = async (filePath: string): Promise<number> => {
|
|
|
489
500
|
}
|
|
490
501
|
};
|
|
491
502
|
|
|
503
|
+
const stripModelQualifier = (value: string): string => value.replace(/\s*\([^)]*\)\s*$/u, '').trim();
|
|
504
|
+
|
|
505
|
+
const extractModelSelection = (content: string): string | null => {
|
|
506
|
+
const marker = '`Model Selection`';
|
|
507
|
+
const markerIndex = content.indexOf(marker);
|
|
508
|
+
if (markerIndex < 0) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const toIndex = content.indexOf(' to ', markerIndex + marker.length);
|
|
513
|
+
if (toIndex < 0) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const candidate = content.slice(toIndex + ' to '.length);
|
|
518
|
+
const endMarkers = ['. No need', '. If reporting', '</USER_SETTINGS_CHANGE>', '\n'];
|
|
519
|
+
const endIndex = endMarkers.reduce<number | null>((earliest, endMarker) => {
|
|
520
|
+
const index = candidate.indexOf(endMarker);
|
|
521
|
+
return index < 0 || (earliest !== null && index >= earliest) ? earliest : index;
|
|
522
|
+
}, null);
|
|
523
|
+
const rawModel = (endIndex === null ? candidate : candidate.slice(0, endIndex)).trim();
|
|
524
|
+
return rawModel ? stripModelQualifier(rawModel) : null;
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const extractTranscriptModel = async (filePath: string): Promise<string | null> => {
|
|
528
|
+
const text = await Bun.file(filePath)
|
|
529
|
+
.text()
|
|
530
|
+
.catch(() => '');
|
|
531
|
+
for (const entry of parseLogEntries(text)) {
|
|
532
|
+
const model = extractModelSelection(getString(entry.content) ?? '');
|
|
533
|
+
if (model) {
|
|
534
|
+
return model;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return null;
|
|
539
|
+
};
|
|
540
|
+
|
|
492
541
|
const preferTranscriptFile = (current: TranscriptFile | undefined, candidate: TranscriptFile): TranscriptFile => {
|
|
493
542
|
if (!current) {
|
|
494
543
|
return candidate;
|
|
@@ -505,6 +554,36 @@ const preferTranscriptFile = (current: TranscriptFile | undefined, candidate: Tr
|
|
|
505
554
|
return candidate.entryCount > current.entryCount ? candidate : current;
|
|
506
555
|
};
|
|
507
556
|
|
|
557
|
+
const readTranscriptFileCandidate = async (
|
|
558
|
+
root: string,
|
|
559
|
+
logsDir: string,
|
|
560
|
+
candidate: { name: string; source: TranscriptFile['source'] },
|
|
561
|
+
): Promise<TranscriptFile | null> => {
|
|
562
|
+
const transcriptPath = path.join(logsDir, candidate.name);
|
|
563
|
+
try {
|
|
564
|
+
const info = await stat(transcriptPath);
|
|
565
|
+
if (!info.isFile()) {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const fullPath = path.join(logsDir, 'transcript_full.jsonl');
|
|
570
|
+
const hasFullTranscript = await pathExists(fullPath);
|
|
571
|
+
const modelSourcePath = hasFullTranscript ? fullPath : transcriptPath;
|
|
572
|
+
return {
|
|
573
|
+
bytes: info.size,
|
|
574
|
+
entryCount: await countJsonlEntries(modelSourcePath),
|
|
575
|
+
fullPath: hasFullTranscript ? fullPath : null,
|
|
576
|
+
model: await extractTranscriptModel(modelSourcePath),
|
|
577
|
+
mtimeMs: info.mtimeMs,
|
|
578
|
+
path: transcriptPath,
|
|
579
|
+
root,
|
|
580
|
+
source: candidate.source,
|
|
581
|
+
};
|
|
582
|
+
} catch {
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
|
|
508
587
|
const readTranscriptFilesForRoot = async (root: string): Promise<Map<string, TranscriptFile>> => {
|
|
509
588
|
const brainDir = getAntigravityBrainDir(root);
|
|
510
589
|
let entries: Array<{ isDirectory: () => boolean; name: string }> = [];
|
|
@@ -525,25 +604,10 @@ const readTranscriptFilesForRoot = async (root: string): Promise<Map<string, Tra
|
|
|
525
604
|
{ name: 'overview.txt', source: 'overview' as const },
|
|
526
605
|
{ name: 'transcript.jsonl', source: 'transcript' as const },
|
|
527
606
|
]) {
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
continue;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
transcripts.set(
|
|
536
|
-
entry.name,
|
|
537
|
-
preferTranscriptFile(transcripts.get(entry.name), {
|
|
538
|
-
bytes: info.size,
|
|
539
|
-
entryCount: await countJsonlEntries(transcriptPath),
|
|
540
|
-
mtimeMs: info.mtimeMs,
|
|
541
|
-
path: transcriptPath,
|
|
542
|
-
root,
|
|
543
|
-
source: candidate.source,
|
|
544
|
-
}),
|
|
545
|
-
);
|
|
546
|
-
} catch {}
|
|
607
|
+
const transcript = await readTranscriptFileCandidate(root, logsDir, candidate);
|
|
608
|
+
if (transcript) {
|
|
609
|
+
transcripts.set(entry.name, preferTranscriptFile(transcripts.get(entry.name), transcript));
|
|
610
|
+
}
|
|
547
611
|
}
|
|
548
612
|
}
|
|
549
613
|
|
|
@@ -640,6 +704,7 @@ const toConversation = (
|
|
|
640
704
|
createdAtMs: summary?.createdAtMs ?? null,
|
|
641
705
|
indexedItemCount: summary?.indexedItemCount ?? null,
|
|
642
706
|
lastUpdatedAtMs,
|
|
707
|
+
model: transcript?.model ?? null,
|
|
643
708
|
sourceRoot,
|
|
644
709
|
summaryPath: summary?.summaryPath ?? null,
|
|
645
710
|
title: summary?.title ?? cleanTitle(fallbackTitle, conversationId),
|
|
@@ -872,6 +937,135 @@ const renderLogEntry = (entry: AntigravityLogEntry): string[] => {
|
|
|
872
937
|
return parts;
|
|
873
938
|
};
|
|
874
939
|
|
|
940
|
+
const logEntryCreatedAtMs = (entry: AntigravityLogEntry): number | null => {
|
|
941
|
+
const timestamp = getString(entry.created_at);
|
|
942
|
+
if (!timestamp) {
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const parsed = Date.parse(timestamp);
|
|
947
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
const logEntryOrder = (entry: AntigravityLogEntry, fallback: number): number => {
|
|
951
|
+
return typeof entry.step_index === 'number' && Number.isFinite(entry.step_index) ? entry.step_index : fallback;
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
const isAssistantLogEntry = (entry: AntigravityLogEntry): boolean => {
|
|
955
|
+
return getString(entry.source) === 'MODEL' && getString(entry.type) === 'PLANNER_RESPONSE';
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
const logEntryRole = (entry: AntigravityLogEntry): AntigravityConversationMessage['role'] => {
|
|
959
|
+
const source = getString(entry.source);
|
|
960
|
+
if (source?.startsWith('USER')) {
|
|
961
|
+
return 'user';
|
|
962
|
+
}
|
|
963
|
+
if (isAssistantLogEntry(entry)) {
|
|
964
|
+
return 'assistant';
|
|
965
|
+
}
|
|
966
|
+
if (source === 'SYSTEM') {
|
|
967
|
+
return 'system';
|
|
968
|
+
}
|
|
969
|
+
if (source === 'MODEL') {
|
|
970
|
+
return 'tool';
|
|
971
|
+
}
|
|
972
|
+
return 'unknown';
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
const logEntryPhase = (entry: AntigravityLogEntry): AntigravityConversationMessage['phase'] => {
|
|
976
|
+
const role = logEntryRole(entry);
|
|
977
|
+
if (role === 'assistant') {
|
|
978
|
+
return 'final_answer';
|
|
979
|
+
}
|
|
980
|
+
if (role === 'tool') {
|
|
981
|
+
return 'tool_output';
|
|
982
|
+
}
|
|
983
|
+
return 'unknown';
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
const logEntryMetadata = (entry: AntigravityLogEntry): Record<string, unknown> => ({
|
|
987
|
+
source: getString(entry.source),
|
|
988
|
+
status: getString(entry.status),
|
|
989
|
+
type: getString(entry.type),
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
const toolCallsText = (toolCalls: unknown): string => {
|
|
993
|
+
if (!Array.isArray(toolCalls)) {
|
|
994
|
+
return '';
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
return toolCalls
|
|
998
|
+
.flatMap((call) => {
|
|
999
|
+
if (!call || typeof call !== 'object') {
|
|
1000
|
+
return [];
|
|
1001
|
+
}
|
|
1002
|
+
const { args, name } = call as { args?: unknown; name?: unknown };
|
|
1003
|
+
return [JSON.stringify({ args, name: typeof name === 'string' ? name : 'unknown' })];
|
|
1004
|
+
})
|
|
1005
|
+
.join('\n');
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
const logEntryToMessages = (entry: AntigravityLogEntry, index: number): AntigravityConversationMessage[] => {
|
|
1009
|
+
const order = logEntryOrder(entry, index);
|
|
1010
|
+
const createdAtMs = logEntryCreatedAtMs(entry);
|
|
1011
|
+
const role = logEntryRole(entry);
|
|
1012
|
+
const phase = logEntryPhase(entry);
|
|
1013
|
+
const metadata = logEntryMetadata(entry);
|
|
1014
|
+
const messages: AntigravityConversationMessage[] = [];
|
|
1015
|
+
const thinking = getString(entry.thinking)?.trim();
|
|
1016
|
+
if (thinking && role === 'assistant') {
|
|
1017
|
+
messages.push({
|
|
1018
|
+
createdAtMs,
|
|
1019
|
+
metadata,
|
|
1020
|
+
order,
|
|
1021
|
+
phase: 'reasoning',
|
|
1022
|
+
role: 'assistant',
|
|
1023
|
+
text: thinking,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const content = cleanLogContent(entry);
|
|
1028
|
+
if (content) {
|
|
1029
|
+
messages.push({
|
|
1030
|
+
createdAtMs,
|
|
1031
|
+
metadata,
|
|
1032
|
+
order,
|
|
1033
|
+
phase,
|
|
1034
|
+
role,
|
|
1035
|
+
text: content,
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const calls = toolCallsText(entry.tool_calls);
|
|
1040
|
+
if (calls) {
|
|
1041
|
+
messages.push({
|
|
1042
|
+
createdAtMs,
|
|
1043
|
+
metadata,
|
|
1044
|
+
order,
|
|
1045
|
+
phase: 'tool_call',
|
|
1046
|
+
role: 'tool',
|
|
1047
|
+
text: calls,
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
return messages;
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
export const readAntigravityConversationMessages = async (
|
|
1055
|
+
conversation: AntigravityConversation,
|
|
1056
|
+
): Promise<AntigravityConversationMessage[]> => {
|
|
1057
|
+
if (!conversation.transcriptPath || !conversation.transcriptSource) {
|
|
1058
|
+
return [];
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
try {
|
|
1062
|
+
const entries = parseLogEntries(await Bun.file(conversation.transcriptPath).text());
|
|
1063
|
+
return entries.flatMap(logEntryToMessages);
|
|
1064
|
+
} catch {
|
|
1065
|
+
return [];
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
|
|
875
1069
|
const renderAntigravityTranscriptMarkdown = async (conversation: AntigravityConversation): Promise<string | null> => {
|
|
876
1070
|
if (!conversation.transcriptPath || !conversation.transcriptSource) {
|
|
877
1071
|
return null;
|