mixdog 1.4.0 → 1.4.2

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.
Files changed (493) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  4. package/scripts/gate-local.mjs +183 -63
  5. package/scripts/latest-guard.mjs +21 -9
  6. package/scripts/lib/gate-scheduler.mjs +138 -32
  7. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  8. package/scripts/lib/isolated-test-env.mjs +10 -5
  9. package/scripts/lib/run-node-tests.mjs +91 -66
  10. package/scripts/lib/scoped-temp.d.mts +16 -0
  11. package/scripts/lib/scoped-temp.mjs +124 -0
  12. package/scripts/lib/stale-temp.mjs +44 -0
  13. package/scripts/lib/test-failure-records.mjs +14 -1
  14. package/scripts/lib/test-failure-summary.mjs +16 -4
  15. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  16. package/scripts/post-release-check.mjs +1 -6
  17. package/scripts/pptx-kit-index.mjs +17 -5
  18. package/scripts/suite-health.mjs +115 -24
  19. package/scripts/test-data-dir.mjs +10 -7
  20. package/scripts/test.mjs +9 -14
  21. package/src/agents/advisor/AGENT.md +4 -4
  22. package/src/agents/advisor/agent.json +1 -1
  23. package/src/agents/explore/AGENT.md +22 -0
  24. package/src/agents/explore/agent.json +6 -0
  25. package/src/app.mjs +4 -0
  26. package/src/cli.mjs +7 -0
  27. package/src/defaults/agents.json +7 -0
  28. package/src/defaults/shipped-history.json +23 -2
  29. package/src/defaults/skills/browser-use/SKILL.md +10 -5
  30. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  31. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  32. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  33. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  34. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  35. package/src/defaults/skills/computer-use/SKILL.md +16 -4
  36. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  37. package/src/defaults/skills/setup/references/actions.md +12 -6
  38. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  39. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  40. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  41. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  42. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  43. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  44. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  45. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  46. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  47. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  48. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  49. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  50. package/src/defaults/skills/visualize/SKILL.md +6 -2
  51. package/src/lib/rules-builder.cjs +35 -42
  52. package/src/output-styles/common.md +2 -2
  53. package/src/output-styles/simple.md +1 -1
  54. package/src/rules/agent/AGENT.md +5 -4
  55. package/src/rules/lead/LEAD.md +12 -7
  56. package/src/rules/shared/10-tool-workflow.md +3 -1
  57. package/src/rules/shared/20-research.md +3 -6
  58. package/src/rules/shared/40-editing.md +1 -0
  59. package/src/rules/shared/45-destructive.md +0 -2
  60. package/src/rules/shared/50-execution.md +2 -0
  61. package/src/rules/shared/60-verification.md +10 -9
  62. package/src/rules/shared/70-delivery.md +8 -9
  63. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  64. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  65. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
  66. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  67. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  68. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  69. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  70. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
  71. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  72. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  74. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  75. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  76. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  77. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  78. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  79. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
  80. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  81. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
  82. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  83. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
  84. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  85. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  86. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  87. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  89. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  90. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
  91. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
  92. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  93. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  94. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  95. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  97. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
  99. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  100. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  101. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  102. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  103. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  104. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  105. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  106. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
  108. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  109. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  110. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  113. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  114. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  115. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  117. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  119. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  125. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  127. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  129. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  130. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  131. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  132. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  133. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
  135. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  136. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
  137. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  139. package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
  140. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  141. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  142. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  143. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
  145. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
  147. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
  148. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
  150. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  151. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  152. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  153. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  154. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  155. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  156. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
  157. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  158. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  159. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  160. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  161. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
  162. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  163. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
  164. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
  165. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  166. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  167. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
  168. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  169. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  170. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
  171. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
  172. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  173. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  174. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
  175. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  179. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
  180. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  181. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  184. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
  185. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  186. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
  187. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
  188. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
  189. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  190. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
  192. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
  193. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  194. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  195. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
  196. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  197. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
  198. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
  199. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  200. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  201. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
  202. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
  203. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  204. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  206. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  207. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
  208. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
  209. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  210. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  211. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  212. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
  213. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  214. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  215. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  216. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
  217. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
  218. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
  219. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  220. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
  221. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  223. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  224. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  225. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  226. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  227. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  228. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  229. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  230. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  231. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  232. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  233. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  234. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  235. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  236. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  237. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  238. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  239. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  240. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  241. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  242. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  243. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  244. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
  245. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  246. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  248. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  249. package/src/runtime/channels/lib/whisper-port.mjs +29 -11
  250. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  251. package/src/runtime/computer-bridge/action-schema.mjs +2 -2
  252. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  253. package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
  254. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  255. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  256. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  257. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  258. package/src/runtime/explore/explorer.mjs +93 -0
  259. package/src/runtime/explore/tool-defs.mjs +35 -0
  260. package/src/runtime/explore/tool.mjs +123 -0
  261. package/src/runtime/local-provider/server-process.mjs +2 -1
  262. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  263. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  264. package/src/runtime/memory/index.mjs +4 -0
  265. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  266. package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
  267. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  268. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  269. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  270. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  271. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  272. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  273. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  274. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  275. package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
  276. package/src/runtime/office/quality/design-review.mjs +1 -5
  277. package/src/runtime/office/quality/document-brief.mjs +1 -5
  278. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  279. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  280. package/src/runtime/shared/agent-route-config.mjs +17 -2
  281. package/src/runtime/shared/atomic-file.mjs +103 -2
  282. package/src/runtime/shared/background-tasks.mjs +15 -6
  283. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  284. package/src/runtime/shared/connector-accounts.mjs +5 -5
  285. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  286. package/src/runtime/shared/connector-catalog.mjs +60 -52
  287. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  288. package/src/runtime/shared/defaults-separation.mjs +1 -4
  289. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  290. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  291. package/src/runtime/shared/file-has-data.mjs +26 -2
  292. package/src/runtime/shared/file-lock.mjs +13 -15
  293. package/src/runtime/shared/git-launch.d.mts +9 -0
  294. package/src/runtime/shared/git-launch.mjs +213 -0
  295. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  296. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  297. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  298. package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
  299. package/src/runtime/shared/llm/cost.mjs +11 -2
  300. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  301. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  302. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  303. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  304. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  305. package/src/runtime/shared/loopback-listener.mjs +25 -2
  306. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  307. package/src/runtime/shared/model-route-chain.mjs +22 -4
  308. package/src/runtime/shared/open-url.mjs +10 -0
  309. package/src/runtime/shared/provider-accounts.mjs +52 -2
  310. package/src/runtime/shared/session-activity-signal.mjs +14 -0
  311. package/src/runtime/shared/session-scratch.mjs +37 -0
  312. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  313. package/src/runtime/shared/staged-update.mjs +47 -2
  314. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  315. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  316. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  317. package/src/runtime/shared/tool-surface.mjs +113 -36
  318. package/src/runtime/shared/tool-work-units.mjs +6 -0
  319. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  320. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  321. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  322. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  323. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  324. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  325. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  326. package/src/runtime/shared/worktrees/git.mjs +72 -0
  327. package/src/runtime/shared/worktrees/manager.mjs +285 -168
  328. package/src/runtime/shared/worktrees/test-support.mjs +6 -10
  329. package/src/runtime/tidy/install.mjs +95 -15
  330. package/src/runtime/tidy/languages.mjs +17 -15
  331. package/src/runtime/tidy/process.mjs +7 -4
  332. package/src/runtime/tidy/resolve.mjs +19 -16
  333. package/src/runtime/tidy/runners/biome.mjs +3 -3
  334. package/src/runtime/tidy/tool.mjs +1 -1
  335. package/src/runtime/tidy/worktree.mjs +10 -6
  336. package/src/runtime/visualize-bridge/client.mjs +6 -1
  337. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  338. package/src/runtime/web-search/index.mjs +1 -1
  339. package/src/session-runtime/agent-home-delivery.mjs +41 -0
  340. package/src/session-runtime/agent-task-sessions.mjs +458 -312
  341. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  342. package/src/session-runtime/boot/tools.mjs +7 -1
  343. package/src/session-runtime/bot-status.mjs +107 -101
  344. package/src/session-runtime/bot-target.mjs +38 -0
  345. package/src/session-runtime/channel-config-api.mjs +6 -1
  346. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  347. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  348. package/src/session-runtime/conversation-import/discovery.mjs +401 -36
  349. package/src/session-runtime/conversation-import/service.mjs +35 -8
  350. package/src/session-runtime/handoff-attachments.mjs +82 -31
  351. package/src/session-runtime/held-requests.mjs +3 -1
  352. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
  353. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
  354. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  355. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  356. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  357. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
  358. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  359. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
  360. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  361. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  362. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  363. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  364. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  365. package/src/session-runtime/model-route/set-route.mjs +11 -2
  366. package/src/session-runtime/provider-auth/login.mjs +2 -2
  367. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  368. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  369. package/src/session-runtime/provider-models.mjs +4 -2
  370. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  371. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  372. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  373. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  374. package/src/session-runtime/resource-recall-api.mjs +6 -1
  375. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  376. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  377. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  378. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  379. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  380. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  381. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  382. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  383. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
  384. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  385. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  386. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  387. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  388. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  389. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  390. package/src/session-runtime/services/provider-admin.mjs +283 -11
  391. package/src/session-runtime/session-turn-api.mjs +2 -0
  392. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  393. package/src/session-runtime/settings-system-api.mjs +1 -1
  394. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  395. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  396. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  397. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  398. package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
  399. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  400. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  401. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  402. package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
  403. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  404. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  405. package/src/session-runtime/workspace/session-view.mjs +149 -0
  406. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  407. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  408. package/src/session-runtime/workspace/transitions.mjs +108 -1041
  409. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  410. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  411. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  412. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  413. package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
  414. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  415. package/src/standalone/daemon-session-control.mjs +169 -138
  416. package/src/standalone/daemon.mjs +7 -0
  417. package/src/standalone/desktop-service-registry.mjs +9 -0
  418. package/src/standalone/local-session-runtime.mjs +5 -1
  419. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  420. package/src/standalone/session-client.mjs +5 -1
  421. package/src/standalone/session-frame-stream.mjs +83 -11
  422. package/src/standalone/session-protocol.mjs +4 -0
  423. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  424. package/src/standalone/session-service/agent-tree.mjs +45 -9
  425. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  426. package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
  427. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  428. package/src/standalone/session-service/stored-reader.mjs +127 -40
  429. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  430. package/src/tui/App.jsx +154 -164
  431. package/src/tui/app/app-view/floating-panel.jsx +10 -4
  432. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  433. package/src/tui/app/clipboard.mjs +21 -10
  434. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  435. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  436. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  437. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  438. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  439. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  440. package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
  441. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  442. package/src/tui/app/provider-setup-picker.mjs +29 -18
  443. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  444. package/src/tui/app/slash-commands.mjs +1 -0
  445. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  446. package/src/tui/app/slash-dispatch.mjs +1 -1
  447. package/src/tui/app/use-btw-panel.mjs +29 -0
  448. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  449. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  450. package/src/tui/app/use-mouse-input.mjs +11 -0
  451. package/src/tui/app/use-onboarding-start.mjs +8 -3
  452. package/src/tui/app/use-panel-transition.mjs +10 -1
  453. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  454. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  455. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  456. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  457. package/src/tui/app/use-selection-focus.mjs +11 -2
  458. package/src/tui/app/use-selection-geometry.mjs +18 -9
  459. package/src/tui/app/use-selection-paint.mjs +4 -4
  460. package/src/tui/app/use-selection-stitch.mjs +3 -3
  461. package/src/tui/app/use-slash-palette.mjs +62 -0
  462. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  463. package/src/tui/app/use-transcript-activity.mjs +47 -32
  464. package/src/tui/app/use-transcript-follow.mjs +12 -4
  465. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  466. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  467. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  468. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  469. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  470. package/src/tui/app/use-transcript-window.mjs +21 -11
  471. package/src/tui/app/use-tui-ready.mjs +18 -0
  472. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  473. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  474. package/src/tui/components/Picker.jsx +27 -3
  475. package/src/tui/components/QuestionPanel.jsx +13 -4
  476. package/src/tui/components/SignInUrl.jsx +68 -0
  477. package/src/tui/components/TextEntryPanel.jsx +76 -46
  478. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  479. package/src/tui/dist/index.mjs +1963 -1361
  480. package/src/tui/session/goal-turn-state.mjs +6 -2
  481. package/src/tui/session/session-api/controls.mjs +4 -0
  482. package/src/tui/session/session-api/extensions.mjs +8 -0
  483. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  484. package/src/tui/session/session-api/intake.mjs +2 -0
  485. package/src/tui/session/session-api/integrations.mjs +2 -2
  486. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  487. package/src/tui/session/session-api/settings.mjs +2 -2
  488. package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
  489. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  490. package/src/tui/session/transcript-spill.mjs +92 -4
  491. package/src/tui/session/transcript-store.mjs +2 -0
  492. package/src/ui/statusline-agents.mjs +1 -3
  493. package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Discovery of importable foreign conversations on disk (read-only).
3
3
  *
4
- * codex $CODEX_HOME/sessions/**\/rollout-*.jsonl (default ~/.codex)
4
+ * codex $CODEX_HOME/sessions/**\/rollout-*.jsonl[.zst] (default ~/.codex)
5
5
  * claude $CLAUDE_CONFIG_DIR/projects/*\/*.jsonl (default ~/.claude)
6
6
  *
7
7
  * Enumeration is stat-only; per-file metadata (foreign id, cwd, title, and
@@ -13,7 +13,9 @@
13
13
  import { createHash, randomUUID } from 'node:crypto';
14
14
  import { open, readdir, realpath, stat } from 'node:fs/promises';
15
15
  import { homedir } from 'node:os';
16
- import { basename, extname, isAbsolute, join, relative, resolve } from 'node:path';
16
+ import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
17
+ import { pipeline } from 'node:stream';
18
+ import zlib from 'node:zlib';
17
19
  import {
18
20
  IMPORT_MAX_FILE_BYTES,
19
21
  IMPORT_MAX_LINE_BYTES,
@@ -24,8 +26,15 @@ import {
24
26
  isImportSource,
25
27
  } from '../../runtime/agent/orchestrator/session/conversation-import/convert.mjs';
26
28
  import { codexMetaFromTruncatedLine } from '../../runtime/agent/orchestrator/session/conversation-import/codex.mjs';
27
- import { IMPORT_WARNING_CWD_MISSING } from '../../runtime/agent/orchestrator/session/conversation-import/turns.mjs';
29
+ import {
30
+ IMPORT_ERROR_NEEDS_NEWER_NODE,
31
+ IMPORT_WARNING_CWD_MISSING,
32
+ IMPORT_WARNING_HISTORY_PARTIAL,
33
+ IMPORT_WARNING_HISTORY_SIZE_LIMIT,
34
+ IMPORT_WARNING_HISTORY_UNAVAILABLE,
35
+ } from '../../runtime/agent/orchestrator/session/conversation-import/turns.mjs';
28
36
  import { importOriginKey } from '../../runtime/agent/orchestrator/session/conversation-import/imported-session.mjs';
37
+ import { loadCodexThreadTitles } from './codex-titles.mjs';
29
38
 
30
39
  export const PREFIX_CHUNK_BYTES = 64 * 1024;
31
40
  export const PREFIX_MAX_BYTES = 512 * 1024;
@@ -49,10 +58,12 @@ const state = {
49
58
  queue: [],
50
59
  active: 0,
51
60
  views: new Map(), // viewId → view
61
+ scanMaxBytes: IMPORT_MAX_FILE_BYTES, // per-file budget of a sidechain-only scan
52
62
  };
53
63
 
54
- /** Test seam: forget every cached candidate, metadata entry and view. */
55
- export function resetConversationDiscovery() {
64
+ /** Test seam: forget every cached candidate, metadata entry and view; `scanMaxBytes` overrides the scan budget. */
65
+ export function resetConversationDiscovery({ scanMaxBytes = IMPORT_MAX_FILE_BYTES } = {}) {
66
+ state.scanMaxBytes = scanMaxBytes;
56
67
  state.candidates.clear();
57
68
  state.meta.clear();
58
69
  state.pendingMeta.clear();
@@ -74,6 +85,16 @@ export function requireSource(source) {
74
85
  return source;
75
86
  }
76
87
 
88
+ /** Titles the source app stores per foreign session id (Codex only), read fresh; else null. */
89
+ export function loadStoredTitles(source, env = process.env) {
90
+ return source === 'codex' ? loadCodexThreadTitles(dirname(sourceRoot(source, env))) : null;
91
+ }
92
+
93
+ /** The app's stored title of a conversation, else the one derived from its messages. */
94
+ export function displayTitle(meta, titles) {
95
+ return (meta?.foreignSessionId && titles?.get(meta.foreignSessionId)) || meta?.title || null;
96
+ }
97
+
77
98
  const caseFold = (value) => (process.platform === 'win32' ? value.toLowerCase() : value);
78
99
 
79
100
  function isInside(root, path) {
@@ -93,6 +114,51 @@ export function isNetworkOrDevicePath(path) {
93
114
 
94
115
  const fileIdentity = (info) => `${info.dev}:${info.ino}`;
95
116
 
117
+ // Rollout file-name rules, as the source app applies them: a compressed rollout is a
118
+ // name ending `.jsonl.zst`; a rollout name is, after dropping a `.zst` suffix,
119
+ // `rollout-` … `.jsonl`. Both are case-sensitive.
120
+ const COMPRESSED_ROLLOUT_SUFFIX = '.jsonl.zst';
121
+ const isCompressed = (path) => basename(path).endsWith(COMPRESSED_ROLLOUT_SUFFIX);
122
+ const isRolloutName = (name) => {
123
+ const plain = name.endsWith(COMPRESSED_ROLLOUT_SUFFIX) ? name.slice(0, -'.zst'.length) : name;
124
+ return plain.startsWith('rollout-') && plain.endsWith('.jsonl');
125
+ };
126
+ const zstdSupported = () => typeof zlib.createZstdDecompress === 'function';
127
+
128
+ /**
129
+ * Sequential `read(buffer, offset, length, position)` / `close()` over the
130
+ * decompressed bytes of an open compressed file, shaped like a file handle so
131
+ * the line reader needs no other change. Positions must run on from the last
132
+ * read; closing stops the decompression.
133
+ */
134
+ function zstdReader(handle) {
135
+ if (!zstdSupported()) throw importError(IMPORT_ERROR_NEEDS_NEWER_NODE);
136
+ const decoder = zlib.createZstdDecompress();
137
+ pipeline(handle.createReadStream({ autoClose: false }), decoder, () => {});
138
+ const chunks = decoder[Symbol.asyncIterator]();
139
+ let rest = Buffer.alloc(0);
140
+ let position = 0;
141
+ return {
142
+ async read(buffer, offset, length, from) {
143
+ if (from !== position) throw importError('unreadable');
144
+ if (!rest.length) {
145
+ const next = await chunks.next();
146
+ if (next.done) return { bytesRead: 0, buffer };
147
+ rest = next.value;
148
+ }
149
+ const bytesRead = Math.min(length, rest.length);
150
+ rest.copy(buffer, offset, 0, bytesRead);
151
+ rest = rest.subarray(bytesRead);
152
+ position += bytesRead;
153
+ return { bytesRead, buffer };
154
+ },
155
+ async close() {
156
+ decoder.destroy();
157
+ await handle.close();
158
+ },
159
+ };
160
+ }
161
+
96
162
  export function candidateId(source, canonicalPath) {
97
163
  return createHash('sha256')
98
164
  .update(`${source}\0${caseFold(canonicalPath)}`)
@@ -131,7 +197,10 @@ async function walkCodex(rootReal, dir, files, seenDirs) {
131
197
  continue;
132
198
  }
133
199
  }
134
- if (/^rollout-.*\.jsonl$/i.test(entry.name)) files.push(path);
200
+ if (!isRolloutName(entry.name)) continue;
201
+ // A compressed rollout is skipped while its plain sibling exists.
202
+ if (isCompressed(entry.name) && (await stat(path.slice(0, -'.zst'.length)).catch(() => null))) continue;
203
+ files.push(path);
135
204
  }
136
205
  }
137
206
 
@@ -227,6 +296,11 @@ function metaFromConversion(conversion, whole) {
227
296
  foreignSessionId: conversion.foreignSessionId,
228
297
  cwd: conversion.cwd,
229
298
  title: conversion.title,
299
+ // A Codex marker sits in the first line; a Claude transcript is sidechain-only only once read to the end.
300
+ internalKind: whole || conversion.source === 'codex' ? (conversion.internalKind ?? null) : null,
301
+ // Rollout id of the history base this file continues, else null.
302
+ historyBaseId:
303
+ typeof conversion.historyBase?.thread_id === 'string' ? conversion.historyBase.thread_id.toLowerCase() : null,
230
304
  messageCount: whole && !conversion.errorCode ? conversion.messageCount : null,
231
305
  warnings: whole ? conversion.warnings : conversion.warnings.filter((code) => code !== 'malformed'),
232
306
  errorCode: whole ? conversion.errorCode : null,
@@ -257,7 +331,7 @@ async function openCandidate(candidate) {
257
331
  ) {
258
332
  throw importError('unreadable');
259
333
  }
260
- return handle;
334
+ return isCompressed(candidate.path) ? zstdReader(handle) : handle;
261
335
  } catch (error) {
262
336
  await handle.close();
263
337
  throw error;
@@ -324,42 +398,242 @@ async function streamJsonlLines(
324
398
  return outcome({ eof: true });
325
399
  }
326
400
 
401
+ // Complete lines of the last prefix-chunk of a file (the cut-off first line dropped).
402
+ async function readTailLines(handle, size) {
403
+ const length = Math.min(size, PREFIX_CHUNK_BYTES);
404
+ const buffer = Buffer.allocUnsafe(length);
405
+ const { bytesRead } = await handle.read(buffer, 0, length, size - length);
406
+ const text = buffer.subarray(0, bytesRead).toString('utf8');
407
+ return (size > length ? text.slice(text.indexOf('\n') + 1) : text).split('\n');
408
+ }
409
+
410
+ // Whether the rollout a listed file continues is absent (a stat-only search): its own records are then all there is to name and count.
411
+ const baseIsMissing = async (candidate, meta) =>
412
+ !!meta.historyBaseId && !(await findRolloutById(dirname(candidate.root ?? sourceRoot('codex')), meta.historyBaseId));
413
+
327
414
  async function readPrefixMeta(candidate) {
328
415
  const converter = createConversationConverter(candidate.source);
329
416
  const handle = await openCandidate(candidate);
417
+ // Only the end of a Claude transcript proves it is sidechain-only, so while
418
+ // nothing but sidechain messages has been seen the scan streams on, line by
419
+ // line, to EOF or to the import file-size limit. A file over that limit stays
420
+ // unclassified (listed); importing it fails as tooLarge.
421
+ const claude = candidate.source === 'claude';
330
422
  let read;
331
423
  try {
332
424
  read = await streamJsonlLines(handle, (line) => converter.pushLine(line), {
333
- maxLineBytes: PREFIX_MAX_BYTES,
425
+ maxLineBytes: claude ? IMPORT_MAX_LINE_BYTES : PREFIX_MAX_BYTES,
334
426
  chunkBytes: PREFIX_CHUNK_BYTES,
335
- maxBytes: PREFIX_MAX_BYTES,
427
+ maxBytes: claude ? state.scanMaxBytes : PREFIX_MAX_BYTES,
336
428
  onChunk: (position) => {
337
429
  // A file read to its known size keeps going to EOF (whole-file metadata).
338
430
  if (position >= candidate.size) return true;
431
+ if (claude) {
432
+ if (converter.sidechainOnly) return true;
433
+ if (position >= PREFIX_MAX_BYTES) return false;
434
+ }
339
435
  const partial = converter.result();
340
436
  return !(partial.foreignSessionId && partial.cwd && partial.title);
341
437
  },
342
438
  });
439
+ // The stored title records sit near the end of a transcript.
440
+ if (claude && !read.eof) {
441
+ for (const line of await readTailLines(handle, candidate.size)) converter.pushTitleLine(line);
442
+ }
343
443
  } finally {
344
444
  await handle.close();
345
445
  }
346
446
  const conversion = converter.result();
347
- if (read.eof) return metaFromConversion(conversion, true);
348
- const meta = metaFromConversion(conversion, false);
447
+ // A thread continuing a history base is only counted, named and judged empty once the base is followed (on import).
448
+ const listing = async (meta) =>
449
+ conversion.historyBase && !(await baseIsMissing(candidate, meta))
450
+ ? { ...meta, title: null, messageCount: null, errorCode: meta.errorCode === 'empty' ? null : meta.errorCode }
451
+ : conversion.historyBase
452
+ ? { ...meta, warnings: [...meta.warnings, IMPORT_WARNING_HISTORY_UNAVAILABLE] }
453
+ : meta;
454
+ if (read.eof) return listing(metaFromConversion(conversion, true));
455
+ const meta = await listing(metaFromConversion(conversion, false));
349
456
  if (candidate.source === 'codex' && (!meta.foreignSessionId || !meta.cwd) && read.firstPartial) {
350
457
  // A session_meta line longer than the prefix still names its id/cwd.
351
458
  const fromLine = codexMetaFromTruncatedLine(read.firstPartial.replace(/^\uFEFF/, ''));
352
459
  meta.foreignSessionId ||= fromLine?.foreignSessionId || null;
353
460
  meta.cwd ||= fromLine?.cwd || null;
461
+ meta.internalKind ||= fromLine?.internalKind || null;
354
462
  }
355
463
  return meta;
356
464
  }
357
465
 
466
+ // ── Codex history base chain ─────────────────────────────────────────────────
467
+ const HISTORY_CHAIN_MAX_DEPTH = 32;
468
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
469
+ const ROLLOUT_ID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl(?:\.zst)?$/i;
470
+
471
+ /** Rollout id a rollout file name ends with (`…-<thread>.jsonl[.zst]` or `…-<thread>_<rollout>.jsonl[.zst]`). */
472
+ const rolloutIdOf = (path) => {
473
+ const name = basename(path);
474
+ return isRolloutName(name) ? (name.match(ROLLOUT_ID_RE)?.[1].toLowerCase() ?? null) : null;
475
+ };
476
+
477
+ const META_CHUNK_BYTES = 8 * 1024;
478
+
479
+ /**
480
+ * First JSON record of a candidate file (null when none completes within
481
+ * `maxBytes`, blank lines included, or it is not JSON) and the bytes read for it.
482
+ */
483
+ async function firstRecord(candidate, { maxBytes, maxLineBytes }) {
484
+ const handle = await openCandidate(candidate);
485
+ try {
486
+ let first = null;
487
+ const read = await streamJsonlLines(
488
+ handle,
489
+ (line) => {
490
+ first = line.replace(/^\uFEFF/, '').trim();
491
+ return !first;
492
+ },
493
+ { maxLineBytes, chunkBytes: META_CHUNK_BYTES, maxBytes }
494
+ );
495
+ let record = null;
496
+ try {
497
+ record = first ? JSON.parse(first) : null;
498
+ } catch {
499
+ /* not a record */
500
+ }
501
+ return { record, bytes: read.bytesRead };
502
+ } finally {
503
+ await handle.close();
504
+ }
505
+ }
506
+
507
+ // Whether a cutoff, in (decompressed) file bytes, falls on a record boundary: the end of the file or just after a line feed.
508
+ async function endsOnRecord(candidate, end) {
509
+ const compressed = isCompressed(candidate.path);
510
+ if (!compressed && end === candidate.size) return true;
511
+ const handle = await openCandidate(candidate);
512
+ try {
513
+ const byte = Buffer.alloc(1);
514
+ if (!compressed) {
515
+ const { bytesRead } = await handle.read(byte, 0, 1, end - 1);
516
+ return bytesRead === 1 && byte[0] === 10;
517
+ }
518
+ // The decompressed size is unknown: read up to the cutoff, then see whether the file ends there.
519
+ const chunk = Buffer.allocUnsafe(IMPORT_CHUNK_BYTES);
520
+ let position = 0;
521
+ let last = 0;
522
+ while (position < end) {
523
+ const { bytesRead } = await handle.read(chunk, 0, Math.min(chunk.length, end - position), position);
524
+ if (!bytesRead) return false;
525
+ position += bytesRead;
526
+ last = chunk[bytesRead - 1];
527
+ }
528
+ return last === 10 || (await handle.read(byte, 0, 1, position)).bytesRead === 0;
529
+ } finally {
530
+ await handle.close();
531
+ }
532
+ }
533
+
534
+ /** The rollout file with this rollout id under the live, then the archived, sessions folder (walking lists a plain file instead of its compressed sibling). */
535
+ async function findRolloutById(home, rolloutId) {
536
+ for (const folder of ['sessions', 'archived_sessions']) {
537
+ let rootReal;
538
+ try {
539
+ rootReal = await realpath(join(home, folder));
540
+ } catch {
541
+ continue;
542
+ }
543
+ const paths = [];
544
+ await walkCodex(rootReal, rootReal, paths, new Set([caseFold(rootReal)]));
545
+ for (const path of paths) {
546
+ if (rolloutIdOf(path) !== rolloutId) continue;
547
+ const real = await containedRealpath(rootReal, path);
548
+ const info = real ? await stat(real).catch(() => null) : null;
549
+ if (info?.isFile()) {
550
+ return { source: 'codex', root: rootReal, path: real, size: info.size, fileId: fileIdentity(info) };
551
+ }
552
+ }
553
+ }
554
+ return null;
555
+ }
556
+
557
+ // Size of a compressed file once decompressed, counting no further than `cap`.
558
+ async function decompressedLength(candidate, cap) {
559
+ const handle = await openCandidate(candidate);
560
+ try {
561
+ const chunk = Buffer.allocUnsafe(IMPORT_CHUNK_BYTES);
562
+ let length = 0;
563
+ while (length < cap) {
564
+ const { bytesRead } = await handle.read(chunk, 0, Math.min(chunk.length, cap - length), length);
565
+ if (!bytesRead) break;
566
+ length += bytesRead;
567
+ }
568
+ return length;
569
+ } finally {
570
+ await handle.close();
571
+ }
572
+ }
573
+
574
+ /**
575
+ * Rollouts a `history_base` leads to, nearest first, each with the byte offset
576
+ * its contribution ends at, and `metaBytes`, what reading their metadata took.
577
+ * Cutoffs and byte counts are of the decompressed rollout. Stops (`partial`,
578
+ * the warning code saying why) at a base that is missing or unreadable
579
+ * (`historyUnavailable`), that would take the total read (the file, the
580
+ * `spent` metadata reads so far, every cutoff and every metadata read) beyond
581
+ * `maxFileBytes` (`historySizeLimit`), or is not paginated, already visited,
582
+ * past the depth cap, with a cutoff that excludes the session metadata,
583
+ * passes the end of the file or does not end a record (`historyPartial`).
584
+ */
585
+ async function resolveHistoryChain(candidate, firstBase, { maxFileBytes, maxLineBytes, spent }) {
586
+ const home = dirname(candidate.root ?? sourceRoot('codex'));
587
+ const seen = new Set([rolloutIdOf(candidate.path)]);
588
+ const segments = [];
589
+ // The file itself is reserved at its decompressed size, which only reading it tells.
590
+ let used =
591
+ (isCompressed(candidate.path) ? await decompressedLength(candidate, maxFileBytes) : candidate.size) + spent;
592
+ let metaBytes = 0;
593
+ const partial = (warning = IMPORT_WARNING_HISTORY_PARTIAL) => ({ segments, partial: warning, metaBytes });
594
+ for (let base = firstBase; base; ) {
595
+ const id = typeof base.thread_id === 'string' ? base.thread_id.toLowerCase() : '';
596
+ const end = base.end_byte_offset;
597
+ const ordinal = base.end_ordinal_exclusive;
598
+ if (!UUID_RE.test(id) || seen.has(id) || segments.length >= HISTORY_CHAIN_MAX_DEPTH) return partial();
599
+ seen.add(id);
600
+ if (!Number.isSafeInteger(ordinal) || ordinal <= 0 || !Number.isSafeInteger(end) || end <= 0) return partial();
601
+ const found = await findRolloutById(home, id);
602
+ if (!found) return partial(IMPORT_WARNING_HISTORY_UNAVAILABLE);
603
+ if (!isCompressed(found.path) && end > found.size) return partial();
604
+ if (used + end > maxFileBytes) return partial(IMPORT_WARNING_HISTORY_SIZE_LIMIT);
605
+ let head;
606
+ try {
607
+ if (!(await endsOnRecord(found, end))) return partial();
608
+ head = await firstRecord(found, { maxBytes: Math.min(end, maxFileBytes - used - end), maxLineBytes });
609
+ } catch (error) {
610
+ // A runtime without zstd names its own fix; any other failure to open or read the base is "unavailable".
611
+ return partial(
612
+ error?.code === IMPORT_ERROR_NEEDS_NEWER_NODE
613
+ ? IMPORT_ERROR_NEEDS_NEWER_NODE
614
+ : IMPORT_WARNING_HISTORY_UNAVAILABLE
615
+ );
616
+ }
617
+ used += end + head.bytes;
618
+ metaBytes += head.bytes;
619
+ if (head.record?.type !== 'session_meta' || head.record.payload?.history_mode !== 'paginated') return partial();
620
+ segments.push({ candidate: found, end });
621
+ const next = head.record.payload.history_base;
622
+ base = next && typeof next === 'object' ? next : null;
623
+ }
624
+ return { segments, partial: null, metaBytes };
625
+ }
626
+
358
627
  /**
359
628
  * Stream-convert a whole candidate file under the import limits. A file over
360
629
  * `maxFileBytes`, a line over `maxLineBytes` or a conversion over the
361
630
  * converter's limits fails the file with `tooLarge` without reading the rest.
362
- * `bytesRead` is how far the read got.
631
+ * A Codex rollout continuing a `history_base` is preceded by the records of
632
+ * each base up to its cutoff, oldest first (their session metadata dropped),
633
+ * all within the one `maxFileBytes` (counted in decompressed bytes for a
634
+ * compressed rollout); a base that cannot be followed leaves a `historyPartial`,
635
+ * `historySizeLimit` or `historyUnavailable` warning. `bytesRead` is how far
636
+ * the read got.
363
637
  */
364
638
  export async function convertCandidateFile(
365
639
  candidate,
@@ -377,32 +651,74 @@ export async function convertCandidateFile(
377
651
  maxRetainedEntries,
378
652
  maxMetadataBytes,
379
653
  });
654
+ // Bytes read beyond the file itself: metadata pre-reads and the bases' cutoffs. All count against `maxFileBytes`.
655
+ let inheritedBytes = 0;
656
+ if (candidate.source === 'codex') {
657
+ const head = await firstRecord(candidate, { maxBytes: maxFileBytes, maxLineBytes });
658
+ const base = head.record?.payload?.history_base;
659
+ if (base && typeof base === 'object') {
660
+ inheritedBytes += head.bytes;
661
+ const chain =
662
+ head.record.payload.history_mode === 'paginated'
663
+ ? await resolveHistoryChain(candidate, base, { maxFileBytes, maxLineBytes, spent: inheritedBytes })
664
+ : { segments: [], partial: IMPORT_WARNING_HISTORY_PARTIAL, metaBytes: 0 };
665
+ inheritedBytes += chain.metaBytes;
666
+ const { segments, partial } = chain;
667
+ if (partial) converter.markHistoryPartial(partial);
668
+ for (const { candidate: baseFile, end } of segments.reverse()) {
669
+ const baseHandle = await openCandidate(baseFile);
670
+ try {
671
+ const read = await streamJsonlLines(baseHandle, (line) => converter.pushLine(line, { inherited: true }), {
672
+ maxLineBytes,
673
+ chunkBytes,
674
+ maxBytes: end,
675
+ });
676
+ if (read.overflow) converter.markTooLarge();
677
+ inheritedBytes += read.bytesRead;
678
+ } finally {
679
+ await baseHandle.close();
680
+ }
681
+ }
682
+ }
683
+ }
380
684
  const handle = await openCandidate(candidate);
381
685
  try {
382
686
  const read = await streamJsonlLines(handle, (line) => converter.pushLine(line), {
383
687
  maxLineBytes,
384
688
  chunkBytes,
385
- maxBytes: maxFileBytes,
689
+ maxBytes: maxFileBytes - inheritedBytes,
386
690
  });
387
691
  if (read.overflow || read.truncated) converter.markTooLarge();
388
- return { ...converter.result(), bytesRead: read.bytesRead };
692
+ return { ...converter.result(), bytesRead: read.bytesRead + inheritedBytes };
389
693
  } finally {
390
694
  await handle.close();
391
695
  }
392
696
  }
393
697
 
698
+ // On a runtime without zstd, a listed conversation whose history base is compressed says so on its row.
699
+ async function withCompressedBaseNotice(candidate, meta) {
700
+ if (!meta.historyBaseId || zstdSupported()) return meta;
701
+ const found = await findRolloutById(dirname(candidate.root ?? sourceRoot('codex')), meta.historyBaseId);
702
+ return found && isCompressed(found.path)
703
+ ? { ...meta, warnings: [...meta.warnings, IMPORT_ERROR_NEEDS_NEWER_NODE] }
704
+ : meta;
705
+ }
706
+
394
707
  function pumpMetaQueue() {
395
708
  while (state.active < META_CONCURRENCY && state.queue.length) {
396
709
  const job = state.queue.shift();
397
710
  state.active += 1;
398
711
  readPrefixMeta(job.candidate)
399
- .catch(() => ({
712
+ .then((meta) => withCompressedBaseNotice(job.candidate, meta))
713
+ .catch((error) => ({
400
714
  foreignSessionId: null,
401
715
  cwd: null,
402
716
  title: null,
717
+ internalKind: null,
718
+ historyBaseId: null,
403
719
  messageCount: null,
404
720
  warnings: [],
405
- errorCode: 'unreadable',
721
+ errorCode: error?.code === IMPORT_ERROR_NEEDS_NEWER_NODE ? IMPORT_ERROR_NEEDS_NEWER_NODE : 'unreadable',
406
722
  }))
407
723
  .then((value) => {
408
724
  state.meta.set(job.candidate.path, { stampKey: stampKey(job.candidate), value });
@@ -470,14 +786,14 @@ export function importedKeyMap(summaries) {
470
786
  return map;
471
787
  }
472
788
 
473
- function rowFor(candidate, meta, cwdInfo, imported) {
789
+ function rowFor(candidate, meta, cwdInfo, imported, titles) {
474
790
  const warnings = new Set(meta?.warnings || []);
475
791
  if (meta && !meta.errorCode && !cwdInfo?.exists) warnings.add(IMPORT_WARNING_CWD_MISSING);
476
792
  if (meta?.errorCode) warnings.add(meta.errorCode);
477
793
  return {
478
794
  id: candidate.id,
479
795
  source: candidate.source,
480
- title: meta?.title ?? null,
796
+ title: displayTitle(meta, titles),
481
797
  cwd: cwdInfo?.path ?? null,
482
798
  modifiedAt: candidate.mtimeMs,
483
799
  messageCount: meta?.messageCount ?? null,
@@ -486,10 +802,10 @@ function rowFor(candidate, meta, cwdInfo, imported) {
486
802
  };
487
803
  }
488
804
 
489
- export async function buildRow(candidate, imported, cwdCache = new Map()) {
805
+ async function buildRow(candidate, imported, cwdCache = new Map(), titles = null) {
490
806
  const meta = cachedMeta(candidate);
491
807
  const cwdInfo = meta?.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
492
- return rowFor(candidate, meta, cwdInfo, imported);
808
+ return rowFor(candidate, meta, cwdInfo, imported, titles);
493
809
  }
494
810
 
495
811
  // ── views & paging ───────────────────────────────────────────────────────────
@@ -553,7 +869,7 @@ async function pageResult(view, offset, limit, imported) {
553
869
  const rows = [];
554
870
  for (const id of ids) {
555
871
  const candidate = state.candidates.get(id);
556
- if (candidate) rows.push(await buildRow(candidate, imported, cwdCache));
872
+ if (candidate) rows.push(await buildRow(candidate, imported, cwdCache, view.titles));
557
873
  }
558
874
  const nextOffset = offset + ids.length;
559
875
  return {
@@ -568,31 +884,76 @@ async function pageResult(view, offset, limit, imported) {
568
884
  };
569
885
  }
570
886
 
887
+ /**
888
+ * Ids of Codex rollouts that an earlier revision of the same thread (same
889
+ * session id) names as its history base. Codex lists one rollout per thread,
890
+ * the current one, so these stay out of the list; a fork's parent is another
891
+ * thread and stays listed.
892
+ */
893
+ function supersededBaseIds(candidates) {
894
+ const byRollout = new Map();
895
+ for (const candidate of candidates) {
896
+ const meta = cachedMeta(candidate);
897
+ const rolloutId = candidate.source === 'codex' ? rolloutIdOf(candidate.path) : null;
898
+ if (meta?.foreignSessionId && rolloutId) byRollout.set(`${meta.foreignSessionId}\0${rolloutId}`, candidate.id);
899
+ }
900
+ const hidden = new Set();
901
+ for (const candidate of candidates) {
902
+ const meta = cachedMeta(candidate);
903
+ const base = meta?.historyBaseId && byRollout.get(`${meta.foreignSessionId}\0${meta.historyBaseId}`);
904
+ if (base && base !== candidate.id) hidden.add(base);
905
+ }
906
+ // A cycle of revisions must not hide a thread entirely: its newest one (first in order) stays.
907
+ const newest = new Map();
908
+ const visible = new Set();
909
+ const indexed = new Set(byRollout.values());
910
+ for (const candidate of candidates) {
911
+ const sessionId = cachedMeta(candidate)?.foreignSessionId;
912
+ if (!sessionId || !indexed.has(candidate.id)) continue;
913
+ if (!newest.has(sessionId)) newest.set(sessionId, candidate.id);
914
+ if (!hidden.has(candidate.id)) visible.add(sessionId);
915
+ }
916
+ for (const [sessionId, id] of newest) if (!visible.has(sessionId)) hidden.delete(id);
917
+ return hidden;
918
+ }
919
+
571
920
  /**
572
921
  * Apply a view's filters (time range, query on title/cwd, project) to
573
922
  * candidates using their cached metadata; a candidate whose metadata is still
574
- * pending cannot match a query or project and leaves the result incomplete.
923
+ * pending is neither a member nor counted and leaves the result incomplete.
924
+ * The source app's internal and empty records are never members.
575
925
  */
576
- async function filterCandidates(candidates, filters, imported) {
926
+ async function filterCandidates(candidates, filters, imported, titles) {
577
927
  const { needle, projectFilter, modifiedAfter, modifiedBefore } = filters;
578
928
  const cwdCache = new Map();
579
929
  const memberIds = [];
580
930
  const selectableIds = [];
581
931
  const projectCounts = new Map();
582
932
  let complete = true;
933
+ const superseded = supersededBaseIds(candidates);
583
934
  for (const candidate of candidates) {
935
+ if (superseded.has(candidate.id)) continue;
584
936
  if (modifiedAfter !== null && candidate.mtimeMs < modifiedAfter) continue;
585
937
  if (modifiedBefore !== null && candidate.mtimeMs > modifiedBefore) continue;
586
938
  const meta = cachedMeta(candidate);
587
- if (!meta) complete = false;
588
- const cwdInfo = meta?.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
589
- if (needle && (!meta || !(foldText(meta.title).includes(needle) || foldText(cwdInfo?.path).includes(needle)))) {
939
+ // Unclassified yet: neither a member nor counted until its metadata is read.
940
+ if (!meta) {
941
+ complete = false;
942
+ continue;
943
+ }
944
+ // The source app's own records and files read to the end without a message are not conversations.
945
+ if (meta.internalKind || meta.errorCode === 'empty') continue;
946
+ const cwdInfo = meta.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
947
+ if (
948
+ needle &&
949
+ !(foldText(displayTitle(meta, titles)).includes(needle) || foldText(cwdInfo?.path).includes(needle))
950
+ ) {
590
951
  continue;
591
952
  }
592
953
  if (cwdInfo?.path) projectCounts.set(cwdInfo.path, (projectCounts.get(cwdInfo.path) || 0) + 1);
593
954
  if (projectFilter && !samePath(cwdInfo?.path, projectFilter)) continue;
594
955
  // Already-imported conversations are left out of the list entirely.
595
- if (meta && imported.has(candidateKey(candidate, meta))) continue;
956
+ if (imported.has(candidateKey(candidate, meta))) continue;
596
957
  memberIds.push(candidate.id);
597
958
  selectableIds.push(candidate.id);
598
959
  }
@@ -624,7 +985,9 @@ export async function listConversations(params, { imported, env = process.env }
624
985
  projectFilter: typeof project === 'string' && project.trim() ? project.trim() : null,
625
986
  };
626
987
  const { root, candidates } = await enumerateCandidates(source, env);
988
+ const titles = await loadStoredTitles(source, env);
627
989
  // Background metadata for everything, newest first; the first page is awaited.
990
+ // Candidates still unclassified stay out of the view (it is incomplete) and join it on a later listing.
628
991
  for (const candidate of candidates) void resolveMeta(candidate);
629
992
  if (!filters.needle && !filters.projectFilter) await Promise.all(candidates.slice(0, limit).map(resolveMeta));
630
993
  const view = {
@@ -632,8 +995,9 @@ export async function listConversations(params, { imported, env = process.env }
632
995
  source,
633
996
  root,
634
997
  filters,
998
+ titles,
635
999
  candidateIds: candidates.map((candidate) => candidate.id),
636
- ...(await filterCandidates(candidates, filters, imported)),
1000
+ ...(await filterCandidates(candidates, filters, imported, titles)),
637
1001
  touchedAt: Date.now(),
638
1002
  };
639
1003
  state.views.set(view.id, view);
@@ -651,21 +1015,22 @@ export async function resolveViewSelection(viewId, excludeIds, imported) {
651
1015
  const view = requireView(viewId);
652
1016
  const candidates = view.candidateIds.map((id) => state.candidates.get(id)).filter(Boolean);
653
1017
  await Promise.all(candidates.map(resolveMeta));
654
- const { selectableIds } = await filterCandidates(candidates, view.filters, imported);
1018
+ const { selectableIds } = await filterCandidates(candidates, view.filters, imported, view.titles);
655
1019
  const excluded = new Set(Array.isArray(excludeIds) ? excludeIds : []);
656
1020
  return selectableIds.filter((id) => !excluded.has(id));
657
1021
  }
658
1022
 
659
1023
  // ── picked files ─────────────────────────────────────────────────────────────
660
- /** Validate and register a file chosen in a native picker (a local regular .jsonl). */
661
- export async function registerFile({ source, path } = {}, { imported, limits } = {}) {
1024
+ /** Validate and register a file chosen in a native picker (a local regular .jsonl, or a Codex .jsonl.zst). */
1025
+ export async function registerFile({ source, path } = {}, { imported, limits, env = process.env } = {}) {
662
1026
  requireSource(source);
663
1027
  if (typeof path !== 'string' || !path.trim() || path.includes('\0')) throw importError('invalidPath');
664
1028
  const requested = path.trim();
665
1029
  // Network/device forms are refused before any filesystem call.
666
1030
  if (isNetworkOrDevicePath(requested)) throw importError('unsupported');
667
1031
  if (!isAbsolute(requested)) throw importError('invalidPath');
668
- if (extname(basename(requested)).toLowerCase() !== '.jsonl') throw importError('unsupported');
1032
+ const pickable = (path) => /\.jsonl$/i.test(path) || (source === 'codex' && isCompressed(path));
1033
+ if (!pickable(basename(requested))) throw importError('unsupported');
669
1034
  let real;
670
1035
  let info;
671
1036
  try {
@@ -674,7 +1039,7 @@ export async function registerFile({ source, path } = {}, { imported, limits } =
674
1039
  } catch {
675
1040
  throw importError('unreadable');
676
1041
  }
677
- if (isNetworkOrDevicePath(real) || !info.isFile() || extname(real).toLowerCase() !== '.jsonl') {
1042
+ if (isNetworkOrDevicePath(real) || !info.isFile() || !pickable(basename(real))) {
678
1043
  throw importError('unsupported');
679
1044
  }
680
1045
  const candidate = registerCandidate({
@@ -689,10 +1054,10 @@ export async function registerFile({ source, path } = {}, { imported, limits } =
689
1054
  let conversion;
690
1055
  try {
691
1056
  conversion = await convertCandidateFile(candidate, limits);
692
- } catch {
693
- throw importError('unreadable');
1057
+ } catch (error) {
1058
+ throw importError(error?.code === IMPORT_ERROR_NEEDS_NEWER_NODE ? IMPORT_ERROR_NEEDS_NEWER_NODE : 'unreadable');
694
1059
  }
695
1060
  if (conversion.errorCode) throw importError(conversion.errorCode);
696
1061
  recordConversion(candidate, conversion);
697
- return { row: await buildRow(candidate, imported) };
1062
+ return { row: await buildRow(candidate, imported, new Map(), await loadStoredTitles(source, env)) };
698
1063
  }