mixdog 1.3.5 → 1.4.1

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 (515) hide show
  1. package/NOTICE.md +2 -2
  2. package/README.md +2 -2
  3. package/package.json +2 -11
  4. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  5. package/scripts/daemon-stub.mjs +1 -1
  6. package/scripts/gate-local.mjs +16 -5
  7. package/scripts/latest-guard.mjs +21 -9
  8. package/scripts/lib/isolated-test-env.mjs +10 -5
  9. package/scripts/lib/run-node-tests.mjs +37 -37
  10. package/scripts/lib/scoped-temp.d.mts +16 -0
  11. package/scripts/lib/scoped-temp.mjs +123 -0
  12. package/scripts/lib/stale-temp.mjs +44 -0
  13. package/scripts/native-release-status.mjs +2 -2
  14. package/scripts/post-release-check.mjs +1 -6
  15. package/scripts/pptx-kit-index.mjs +17 -4
  16. package/scripts/recall-history-cases.json +1 -1
  17. package/scripts/suite-health.mjs +20 -2
  18. package/scripts/test-data-dir.mjs +10 -7
  19. package/scripts/test.mjs +7 -11
  20. package/src/agents/reviewer/AGENT.md +7 -11
  21. package/src/agents/reviewer/agent.json +1 -1
  22. package/src/app.mjs +4 -0
  23. package/src/defaults/shipped-history.json +22 -1
  24. package/src/defaults/skills/browser-use/SKILL.md +62 -12
  25. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  26. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  27. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  28. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  29. package/src/defaults/skills/computer-use/SKILL.md +35 -9
  30. package/src/defaults/skills/setup/SKILL.md +22 -0
  31. package/src/defaults/skills/setup/references/actions.md +39 -5
  32. package/src/defaults/skills/setup/references/surfaces.md +11 -2
  33. package/src/defaults/skills/visualize/SKILL.md +2 -1
  34. package/src/lib/rules-builder.cjs +39 -20
  35. package/src/output-styles/common.md +2 -2
  36. package/src/output-styles/simple.md +1 -1
  37. package/src/rules/agent/AGENT.md +11 -10
  38. package/src/rules/lead/LEAD.md +23 -20
  39. package/src/rules/shared/05-tool-calls.md +5 -6
  40. package/src/rules/shared/10-tool-workflow.md +4 -0
  41. package/src/rules/shared/45-destructive.md +0 -2
  42. package/src/rules/shared/50-execution.md +2 -0
  43. package/src/rules/shared/60-verification.md +7 -5
  44. package/src/rules/shared/70-delivery.md +7 -9
  45. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -6
  46. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  47. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  48. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  49. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  50. package/src/runtime/agent/orchestrator/config.mjs +17 -1
  51. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  52. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +73 -68
  53. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +57 -2
  54. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  55. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
  56. package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
  57. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +5 -2
  58. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +192 -133
  59. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
  60. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  61. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +7 -3
  62. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  63. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  66. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  67. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  70. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +159 -69
  71. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  72. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +70 -33
  73. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  74. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  75. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
  76. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  77. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  78. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  79. package/src/runtime/agent/orchestrator/providers/cursor.mjs +25 -13
  80. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +1 -1
  81. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  82. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +210 -20
  83. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  84. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  85. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  87. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  88. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  89. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  90. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  91. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  92. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  93. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
  94. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  96. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  98. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  103. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  104. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  105. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  106. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  107. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +136 -5
  109. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  110. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  111. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  112. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  113. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +20 -12
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +48 -22
  117. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  118. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  119. package/src/runtime/agent/orchestrator/providers/registry.mjs +38 -0
  120. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  121. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  122. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +49 -16
  123. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  124. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  125. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  126. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -6
  127. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +9 -3
  128. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  129. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  130. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -1
  131. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  132. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  133. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +798 -0
  134. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  135. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  136. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  137. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  138. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +280 -45
  139. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  140. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  141. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  142. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +163 -22
  143. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +132 -17
  144. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  145. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  146. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +90 -22
  147. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
  148. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
  149. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
  150. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  151. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
  152. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +51 -25
  153. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +37 -3
  154. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
  155. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
  156. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  157. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  158. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +31 -3
  159. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +20 -3
  160. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  161. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +5 -4
  162. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  163. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  164. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +49 -21
  165. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  166. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
  167. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  168. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  169. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  170. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  171. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  172. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  173. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  174. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +24 -14
  175. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  176. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  177. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +130 -0
  178. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +27 -9
  179. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +81 -8
  180. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  181. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +27 -8
  182. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +12 -5
  183. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  184. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +9 -4
  185. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  186. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  187. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +21 -5
  188. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +9 -0
  189. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +24 -9
  190. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  191. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +150 -0
  192. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  193. package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
  194. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  195. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  196. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  197. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  198. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  199. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  200. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  201. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  202. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
  203. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  204. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
  206. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  207. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +81 -22
  208. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +92 -3
  209. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  210. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  211. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  212. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  213. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +79 -3
  214. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  215. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  216. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  217. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  218. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  219. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  221. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  222. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  224. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
  225. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +31 -6
  226. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +110 -26
  227. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  228. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +31 -5
  229. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +22 -8
  230. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  231. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  232. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  233. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  234. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  235. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  236. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  237. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  238. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  239. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  240. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  241. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  242. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +26 -4
  243. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  244. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  245. package/src/runtime/browser-bridge/client.mjs +73 -8
  246. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  247. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  248. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  249. package/src/runtime/channels/lib/config.mjs +6 -61
  250. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  251. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  252. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  253. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  254. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  255. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  256. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  257. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  258. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  259. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  260. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  261. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  262. package/src/runtime/channels/lib/scheduler.mjs +33 -147
  263. package/src/runtime/channels/lib/schedules-db.mjs +34 -11
  264. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  265. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
  266. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  267. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  268. package/src/runtime/channels/lib/whisper-port.mjs +10 -1
  269. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  270. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  271. package/src/runtime/channels/lib/worker-main.mjs +10 -34
  272. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  273. package/src/runtime/computer-bridge/actions.mjs +9 -1
  274. package/src/runtime/computer-bridge/client.mjs +180 -8
  275. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  276. package/src/runtime/computer-bridge/error-recovery.mjs +28 -2
  277. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  278. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  279. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  280. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  281. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  282. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  283. package/src/runtime/connectors/google-mcp.mjs +10 -12
  284. package/src/runtime/local-provider/server-process.mjs +2 -1
  285. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  286. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  287. package/src/runtime/memory/lib/embedding-worker.mjs +6 -3
  288. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  289. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  290. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  291. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  292. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  293. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  294. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  295. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  296. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  297. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  298. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  299. package/src/runtime/office/quality/assurance-structure-docx.mjs +3 -1
  300. package/src/runtime/office/quality/design-review.mjs +1 -5
  301. package/src/runtime/office/quality/document-brief.mjs +1 -5
  302. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  303. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  304. package/src/runtime/session-search/session-search-db.mjs +73 -9
  305. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  306. package/src/runtime/shared/abort-race.mjs +89 -0
  307. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  308. package/src/runtime/shared/agent-kind.mjs +4 -3
  309. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  310. package/src/runtime/shared/bounded-download.mjs +8 -3
  311. package/src/runtime/shared/config.mjs +2 -13
  312. package/src/runtime/shared/connector-accounts.mjs +20 -10
  313. package/src/runtime/shared/connector-catalog.d.mts +29 -0
  314. package/src/runtime/shared/connector-catalog.mjs +150 -0
  315. package/src/runtime/shared/connector-triggers.mjs +22 -19
  316. package/src/runtime/shared/defaults-separation.mjs +2 -17
  317. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  318. package/src/runtime/shared/err-text.mjs +24 -15
  319. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  320. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  321. package/src/runtime/shared/file-permissions.d.mts +0 -8
  322. package/src/runtime/shared/file-permissions.mjs +0 -11
  323. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  324. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  325. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  326. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  327. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  328. package/src/runtime/shared/idle-auto-clear-gate.mjs +126 -0
  329. package/src/runtime/shared/llm/cost.mjs +5 -1
  330. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  331. package/src/runtime/shared/loopback-listener.mjs +25 -2
  332. package/src/runtime/shared/mixy-presence.mjs +22 -0
  333. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  334. package/src/runtime/shared/model-route-chain.mjs +22 -4
  335. package/src/runtime/shared/open-url.mjs +10 -0
  336. package/src/runtime/shared/orchestration.d.mts +2 -0
  337. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  338. package/src/runtime/shared/profile-config.cjs +1 -1
  339. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  340. package/src/runtime/shared/prompt-origin.mjs +21 -0
  341. package/src/runtime/shared/provider-accounts.mjs +7 -1
  342. package/src/runtime/shared/provider-display-name.mjs +29 -0
  343. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  344. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  345. package/src/runtime/shared/routine-trigger.mjs +10 -0
  346. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  347. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  348. package/src/runtime/shared/session-activity-signal.mjs +48 -1
  349. package/src/runtime/shared/session-scratch.mjs +37 -0
  350. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  351. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  352. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  353. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  354. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  355. package/src/runtime/shared/tool-result-summary.mjs +2 -15
  356. package/src/runtime/shared/tool-surface.mjs +3 -0
  357. package/src/runtime/shared/tool-work-units.mjs +2 -0
  358. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  359. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  360. package/src/runtime/shared/worktrees/git.mjs +15 -0
  361. package/src/runtime/shared/worktrees/manager.mjs +87 -38
  362. package/src/runtime/shared/worktrees/test-support.mjs +14 -13
  363. package/src/runtime/terminal-bridge/client.mjs +10 -1
  364. package/src/runtime/visualize-bridge/client.mjs +29 -8
  365. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  366. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  367. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  368. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  369. package/src/session-runtime/agent-home-delivery.mjs +489 -185
  370. package/src/session-runtime/agent-task-sessions.mjs +467 -229
  371. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  372. package/src/session-runtime/boot/apis.mjs +9 -0
  373. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  374. package/src/session-runtime/boot/providers.mjs +3 -0
  375. package/src/session-runtime/boot/tools.mjs +11 -2
  376. package/src/session-runtime/bot-status.mjs +12 -6
  377. package/src/session-runtime/bot-target.mjs +38 -0
  378. package/src/session-runtime/channel-config-api.mjs +6 -1
  379. package/src/session-runtime/config-lifecycle.mjs +6 -0
  380. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  381. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  382. package/src/session-runtime/conversation-import/discovery.mjs +271 -25
  383. package/src/session-runtime/conversation-import/service.mjs +26 -5
  384. package/src/session-runtime/handoff-attachments.mjs +82 -31
  385. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +144 -41
  386. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -0
  387. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  388. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +8 -2
  389. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  390. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  391. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  392. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  393. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  394. package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
  395. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  396. package/src/session-runtime/mixy-heartbeat.mjs +153 -33
  397. package/src/session-runtime/model-route/set-route.mjs +11 -2
  398. package/src/session-runtime/notification-bus.mjs +31 -117
  399. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  400. package/src/session-runtime/notification-listeners.mjs +76 -0
  401. package/src/session-runtime/owned-automation-run.mjs +2 -1
  402. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  403. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  404. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  405. package/src/session-runtime/provider-models.mjs +4 -2
  406. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  407. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  408. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  409. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  410. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  411. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  412. package/src/session-runtime/resource-recall-api.mjs +6 -1
  413. package/src/session-runtime/routine-owner.mjs +40 -9
  414. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  415. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  416. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  417. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  418. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  419. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  420. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +13 -4
  421. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  422. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  423. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  424. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  425. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  426. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  427. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  428. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  429. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  430. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  431. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  432. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  433. package/src/session-runtime/services/channel-admin.mjs +7 -13
  434. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  435. package/src/session-runtime/services/provider-admin.mjs +2 -1
  436. package/src/session-runtime/services/seeds.mjs +0 -28
  437. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  438. package/src/session-runtime/session-create/session-options.mjs +1 -0
  439. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  440. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  441. package/src/session-runtime/settings-system-api.mjs +13 -1
  442. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  443. package/src/session-runtime/setup-tool/executor.mjs +145 -22
  444. package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
  445. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  446. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
  447. package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
  448. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  449. package/src/session-runtime/tool-policy/policy-stage.mjs +17 -2
  450. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  451. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  452. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -4
  453. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  454. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  455. package/src/session-runtime/workspace/transitions.mjs +25 -0
  456. package/src/session-runtime/workspace/worktree-tool.mjs +26 -9
  457. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  458. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  459. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  460. package/src/standalone/channel-transport.mjs +1 -1
  461. package/src/standalone/daemon-channels-loader.mjs +10 -8
  462. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  463. package/src/standalone/daemon-session-control/request.mjs +51 -0
  464. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  465. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  466. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  467. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  468. package/src/standalone/daemon-session-control.mjs +142 -190
  469. package/src/standalone/daemon.mjs +26 -3
  470. package/src/standalone/session-client.mjs +7 -3
  471. package/src/standalone/session-protocol.mjs +5 -0
  472. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  473. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  474. package/src/standalone/session-service/quota-resume.mjs +3 -0
  475. package/src/standalone/session-service/retention/busy.mjs +65 -1
  476. package/src/standalone/session-service/session-calls/session-action.mjs +30 -2
  477. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  478. package/src/standalone/session-tool-output.mjs +15 -7
  479. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  480. package/src/tui/app/app-view/floating-panel.jsx +7 -4
  481. package/src/tui/app/model-options.mjs +3 -28
  482. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  483. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  484. package/src/tui/app/provider-setup/oauth-actions.mjs +38 -20
  485. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  486. package/src/tui/app/provider-setup-picker.mjs +9 -5
  487. package/src/tui/components/QuestionPanel.jsx +13 -4
  488. package/src/tui/dist/index.mjs +133 -92
  489. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  490. package/src/tui/session/initial-state.mjs +3 -0
  491. package/src/tui/session/notice-surface.mjs +7 -1
  492. package/src/tui/session/queue-helpers.mjs +3 -42
  493. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  494. package/src/tui/session/session-api/routes-write.mjs +5 -1
  495. package/src/tui/session/session-api/routes.mjs +1 -0
  496. package/src/tui/session/session-api/settings.mjs +17 -1
  497. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  498. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  499. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  500. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  501. package/src/tui/session/session-flow/drain.mjs +118 -149
  502. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  503. package/src/tui/session/session-flow/queue/take-entries.mjs +16 -2
  504. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  505. package/src/tui/session/tool-approval-request.mjs +39 -0
  506. package/src/tui/session/tool-approval-settle.mjs +62 -0
  507. package/src/tui/session/tool-approval.mjs +69 -115
  508. package/src/tui/session/tui-steering-persist.mjs +8 -1
  509. package/src/tui/session/turn-stream.mjs +19 -2
  510. package/src/tui/session/turn.mjs +1 -0
  511. package/src/ui/statusline-agents.mjs +1 -3
  512. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  513. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  514. package/scripts/code-graph-description-contract.mjs +0 -188
  515. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The thread titles Codex itself stores, by thread id (read-only):
3
+ *
4
+ * state_<N>.sqlite `threads.title`, overridden by `threads.name` (a rename)
5
+ * session_index.jsonl append-only `{ id, thread_name }`; the latest
6
+ * non-empty name of an id wins and overrides the DB
7
+ *
8
+ * Anything unreadable is skipped: a missing or locked store simply leaves the
9
+ * conversation to the title derived from its messages.
10
+ */
11
+ import { DatabaseSync } from 'node:sqlite';
12
+ import { readFile, readdir } from 'node:fs/promises';
13
+ import { join } from 'node:path';
14
+ import { titleFromText } from '../../runtime/agent/orchestrator/session/conversation-import/turns.mjs';
15
+
16
+ const STATE_DB_RE = /^state_(\d+)\.sqlite$/i;
17
+
18
+ async function newestStateDb(home) {
19
+ let newest = null;
20
+ for (const name of await readdir(home).catch(() => [])) {
21
+ const version = STATE_DB_RE.exec(name)?.[1];
22
+ if (version !== undefined && (!newest || Number(version) > newest.version)) {
23
+ newest = { name, version: Number(version) };
24
+ }
25
+ }
26
+ return newest ? join(home, newest.name) : null;
27
+ }
28
+
29
+ function readStateRows(path) {
30
+ const db = new DatabaseSync(path, { readOnly: true });
31
+ try {
32
+ const columns = new Set(
33
+ db
34
+ .prepare('PRAGMA table_info(threads)')
35
+ .all()
36
+ .map((column) => column.name)
37
+ );
38
+ const wanted = ['title', 'name'].filter((column) => columns.has(column));
39
+ if (!wanted.length) return [];
40
+ return db.prepare(`SELECT id, ${wanted.join(', ')} FROM threads`).all();
41
+ } finally {
42
+ db.close();
43
+ }
44
+ }
45
+
46
+ async function readStateTitles(home, titles) {
47
+ try {
48
+ const path = await newestStateDb(home);
49
+ if (!path) return;
50
+ const rows = readStateRows(path);
51
+ for (const column of ['title', 'name']) {
52
+ for (const row of rows) {
53
+ const title = titleFromText(row[column]);
54
+ if (title && typeof row.id === 'string') titles.set(row.id, title);
55
+ }
56
+ }
57
+ } catch {
58
+ /* unreadable store: fall back to the session index and derived titles */
59
+ }
60
+ }
61
+
62
+ async function readIndexTitles(home, titles) {
63
+ let text;
64
+ try {
65
+ text = await readFile(join(home, 'session_index.jsonl'), 'utf8');
66
+ } catch {
67
+ return;
68
+ }
69
+ for (const line of text.split('\n')) {
70
+ if (!line.trim()) continue;
71
+ let entry;
72
+ try {
73
+ entry = JSON.parse(line);
74
+ } catch {
75
+ continue;
76
+ }
77
+ const title = titleFromText(entry?.thread_name);
78
+ if (title && typeof entry.id === 'string') titles.set(entry.id, title);
79
+ }
80
+ }
81
+
82
+ /** Map of Codex thread id → its stored title (whitespace-normalized, ≤100 chars). */
83
+ export async function loadCodexThreadTitles(home) {
84
+ const titles = new Map();
85
+ await readStateTitles(home, titles);
86
+ await readIndexTitles(home, titles);
87
+ return titles;
88
+ }
@@ -13,7 +13,7 @@
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, extname, isAbsolute, join, relative, resolve } from 'node:path';
17
17
  import {
18
18
  IMPORT_MAX_FILE_BYTES,
19
19
  IMPORT_MAX_LINE_BYTES,
@@ -26,6 +26,7 @@ import {
26
26
  import { codexMetaFromTruncatedLine } from '../../runtime/agent/orchestrator/session/conversation-import/codex.mjs';
27
27
  import { IMPORT_WARNING_CWD_MISSING } from '../../runtime/agent/orchestrator/session/conversation-import/turns.mjs';
28
28
  import { importOriginKey } from '../../runtime/agent/orchestrator/session/conversation-import/imported-session.mjs';
29
+ import { loadCodexThreadTitles } from './codex-titles.mjs';
29
30
 
30
31
  export const PREFIX_CHUNK_BYTES = 64 * 1024;
31
32
  export const PREFIX_MAX_BYTES = 512 * 1024;
@@ -49,10 +50,12 @@ const state = {
49
50
  queue: [],
50
51
  active: 0,
51
52
  views: new Map(), // viewId → view
53
+ scanMaxBytes: IMPORT_MAX_FILE_BYTES, // per-file budget of a sidechain-only scan
52
54
  };
53
55
 
54
- /** Test seam: forget every cached candidate, metadata entry and view. */
55
- export function resetConversationDiscovery() {
56
+ /** Test seam: forget every cached candidate, metadata entry and view; `scanMaxBytes` overrides the scan budget. */
57
+ export function resetConversationDiscovery({ scanMaxBytes = IMPORT_MAX_FILE_BYTES } = {}) {
58
+ state.scanMaxBytes = scanMaxBytes;
56
59
  state.candidates.clear();
57
60
  state.meta.clear();
58
61
  state.pendingMeta.clear();
@@ -74,6 +77,16 @@ export function requireSource(source) {
74
77
  return source;
75
78
  }
76
79
 
80
+ /** Titles the source app stores per foreign session id (Codex only), read fresh; else null. */
81
+ export function loadStoredTitles(source, env = process.env) {
82
+ return source === 'codex' ? loadCodexThreadTitles(dirname(sourceRoot(source, env))) : null;
83
+ }
84
+
85
+ /** The app's stored title of a conversation, else the one derived from its messages. */
86
+ export function displayTitle(meta, titles) {
87
+ return (meta?.foreignSessionId && titles?.get(meta.foreignSessionId)) || meta?.title || null;
88
+ }
89
+
77
90
  const caseFold = (value) => (process.platform === 'win32' ? value.toLowerCase() : value);
78
91
 
79
92
  function isInside(root, path) {
@@ -227,6 +240,11 @@ function metaFromConversion(conversion, whole) {
227
240
  foreignSessionId: conversion.foreignSessionId,
228
241
  cwd: conversion.cwd,
229
242
  title: conversion.title,
243
+ // A Codex marker sits in the first line; a Claude transcript is sidechain-only only once read to the end.
244
+ internalKind: whole || conversion.source === 'codex' ? (conversion.internalKind ?? null) : null,
245
+ // Rollout id of the history base this file continues, else null.
246
+ historyBaseId:
247
+ typeof conversion.historyBase?.thread_id === 'string' ? conversion.historyBase.thread_id.toLowerCase() : null,
230
248
  messageCount: whole && !conversion.errorCode ? conversion.messageCount : null,
231
249
  warnings: whole ? conversion.warnings : conversion.warnings.filter((code) => code !== 'malformed'),
232
250
  errorCode: whole ? conversion.errorCode : null,
@@ -324,42 +342,190 @@ async function streamJsonlLines(
324
342
  return outcome({ eof: true });
325
343
  }
326
344
 
345
+ // Complete lines of the last prefix-chunk of a file (the cut-off first line dropped).
346
+ async function readTailLines(handle, size) {
347
+ const length = Math.min(size, PREFIX_CHUNK_BYTES);
348
+ const buffer = Buffer.allocUnsafe(length);
349
+ const { bytesRead } = await handle.read(buffer, 0, length, size - length);
350
+ const text = buffer.subarray(0, bytesRead).toString('utf8');
351
+ return (size > length ? text.slice(text.indexOf('\n') + 1) : text).split('\n');
352
+ }
353
+
327
354
  async function readPrefixMeta(candidate) {
328
355
  const converter = createConversationConverter(candidate.source);
329
356
  const handle = await openCandidate(candidate);
357
+ // Only the end of a Claude transcript proves it is sidechain-only, so while
358
+ // nothing but sidechain messages has been seen the scan streams on, line by
359
+ // line, to EOF or to the import file-size limit. A file over that limit stays
360
+ // unclassified (listed); importing it fails as tooLarge.
361
+ const claude = candidate.source === 'claude';
330
362
  let read;
331
363
  try {
332
364
  read = await streamJsonlLines(handle, (line) => converter.pushLine(line), {
333
- maxLineBytes: PREFIX_MAX_BYTES,
365
+ maxLineBytes: claude ? IMPORT_MAX_LINE_BYTES : PREFIX_MAX_BYTES,
334
366
  chunkBytes: PREFIX_CHUNK_BYTES,
335
- maxBytes: PREFIX_MAX_BYTES,
367
+ maxBytes: claude ? state.scanMaxBytes : PREFIX_MAX_BYTES,
336
368
  onChunk: (position) => {
337
369
  // A file read to its known size keeps going to EOF (whole-file metadata).
338
370
  if (position >= candidate.size) return true;
371
+ if (claude) {
372
+ if (converter.sidechainOnly) return true;
373
+ if (position >= PREFIX_MAX_BYTES) return false;
374
+ }
339
375
  const partial = converter.result();
340
376
  return !(partial.foreignSessionId && partial.cwd && partial.title);
341
377
  },
342
378
  });
379
+ // The stored title records sit near the end of a transcript.
380
+ if (claude && !read.eof) {
381
+ for (const line of await readTailLines(handle, candidate.size)) converter.pushTitleLine(line);
382
+ }
343
383
  } finally {
344
384
  await handle.close();
345
385
  }
346
386
  const conversion = converter.result();
347
- if (read.eof) return metaFromConversion(conversion, true);
348
- const meta = metaFromConversion(conversion, false);
387
+ // A thread continuing a history base is only counted, named and judged empty once the base is followed (on import).
388
+ const listing = (meta) =>
389
+ conversion.historyBase
390
+ ? { ...meta, title: null, messageCount: null, errorCode: meta.errorCode === 'empty' ? null : meta.errorCode }
391
+ : meta;
392
+ if (read.eof) return listing(metaFromConversion(conversion, true));
393
+ const meta = listing(metaFromConversion(conversion, false));
349
394
  if (candidate.source === 'codex' && (!meta.foreignSessionId || !meta.cwd) && read.firstPartial) {
350
395
  // A session_meta line longer than the prefix still names its id/cwd.
351
396
  const fromLine = codexMetaFromTruncatedLine(read.firstPartial.replace(/^\uFEFF/, ''));
352
397
  meta.foreignSessionId ||= fromLine?.foreignSessionId || null;
353
398
  meta.cwd ||= fromLine?.cwd || null;
399
+ meta.internalKind ||= fromLine?.internalKind || null;
354
400
  }
355
401
  return meta;
356
402
  }
357
403
 
404
+ // ── Codex history base chain ─────────────────────────────────────────────────
405
+ const HISTORY_CHAIN_MAX_DEPTH = 32;
406
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
407
+ 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$/i;
408
+
409
+ /** Rollout id a rollout file name ends with (`…-<thread>.jsonl` or `…-<thread>_<rollout>.jsonl`). */
410
+ const rolloutIdOf = (path) => basename(path).match(ROLLOUT_ID_RE)?.[1].toLowerCase() ?? null;
411
+
412
+ const META_CHUNK_BYTES = 8 * 1024;
413
+
414
+ /**
415
+ * First JSON record of a candidate file (null when none completes within
416
+ * `maxBytes`, blank lines included, or it is not JSON) and the bytes read for it.
417
+ */
418
+ async function firstRecord(candidate, { maxBytes, maxLineBytes }) {
419
+ const handle = await openCandidate(candidate);
420
+ try {
421
+ let first = null;
422
+ const read = await streamJsonlLines(
423
+ handle,
424
+ (line) => {
425
+ first = line.replace(/^\uFEFF/, '').trim();
426
+ return !first;
427
+ },
428
+ { maxLineBytes, chunkBytes: META_CHUNK_BYTES, maxBytes }
429
+ );
430
+ let record = null;
431
+ try {
432
+ record = first ? JSON.parse(first) : null;
433
+ } catch {
434
+ /* not a record */
435
+ }
436
+ return { record, bytes: read.bytesRead };
437
+ } finally {
438
+ await handle.close();
439
+ }
440
+ }
441
+
442
+ // Whether a cutoff falls on a record boundary: the end of the file or just after a line feed.
443
+ async function endsOnRecord(candidate, end) {
444
+ if (end === candidate.size) return true;
445
+ const handle = await openCandidate(candidate);
446
+ try {
447
+ const byte = Buffer.alloc(1);
448
+ const { bytesRead } = await handle.read(byte, 0, 1, end - 1);
449
+ return bytesRead === 1 && byte[0] === 10;
450
+ } finally {
451
+ await handle.close();
452
+ }
453
+ }
454
+
455
+ /** The plain rollout file with this rollout id under the live, then the archived, sessions folder. */
456
+ async function findRolloutById(home, rolloutId) {
457
+ for (const folder of ['sessions', 'archived_sessions']) {
458
+ let rootReal;
459
+ try {
460
+ rootReal = await realpath(join(home, folder));
461
+ } catch {
462
+ continue;
463
+ }
464
+ const paths = [];
465
+ await walkCodex(rootReal, rootReal, paths, new Set([caseFold(rootReal)]));
466
+ for (const path of paths) {
467
+ if (rolloutIdOf(path) !== rolloutId) continue;
468
+ const real = await containedRealpath(rootReal, path);
469
+ const info = real ? await stat(real).catch(() => null) : null;
470
+ if (info?.isFile()) {
471
+ return { source: 'codex', root: rootReal, path: real, size: info.size, fileId: fileIdentity(info) };
472
+ }
473
+ }
474
+ }
475
+ return null;
476
+ }
477
+
478
+ /**
479
+ * Rollouts a `history_base` leads to, nearest first, each with the byte offset
480
+ * its contribution ends at, and `metaBytes`, what reading their metadata took.
481
+ * Stops (`partial`) at a base that is missing, unreadable (compressed rollouts
482
+ * are not read), not paginated, already visited, past the depth cap, with a
483
+ * cutoff that excludes the session metadata, passes the end of the file or
484
+ * does not end a record, or that would take the total read (the file, the
485
+ * `spent` metadata reads so far, every cutoff and every metadata read) beyond
486
+ * `maxFileBytes`.
487
+ */
488
+ async function resolveHistoryChain(candidate, firstBase, { maxFileBytes, maxLineBytes, spent }) {
489
+ const home = dirname(candidate.root ?? sourceRoot('codex'));
490
+ const seen = new Set([rolloutIdOf(candidate.path)]);
491
+ const segments = [];
492
+ let used = candidate.size + spent;
493
+ let metaBytes = 0;
494
+ const partial = () => ({ segments, partial: true, metaBytes });
495
+ for (let base = firstBase; base; ) {
496
+ const id = typeof base.thread_id === 'string' ? base.thread_id.toLowerCase() : '';
497
+ const end = base.end_byte_offset;
498
+ const ordinal = base.end_ordinal_exclusive;
499
+ if (!UUID_RE.test(id) || seen.has(id) || segments.length >= HISTORY_CHAIN_MAX_DEPTH) return partial();
500
+ seen.add(id);
501
+ if (!Number.isSafeInteger(ordinal) || ordinal <= 0 || !Number.isSafeInteger(end) || end <= 0) return partial();
502
+ const found = await findRolloutById(home, id);
503
+ if (!found || end > found.size || used + end > maxFileBytes) return partial();
504
+ let head;
505
+ try {
506
+ if (!(await endsOnRecord(found, end))) return partial();
507
+ head = await firstRecord(found, { maxBytes: Math.min(end, maxFileBytes - used - end), maxLineBytes });
508
+ } catch {
509
+ return partial();
510
+ }
511
+ used += end + head.bytes;
512
+ metaBytes += head.bytes;
513
+ if (head.record?.type !== 'session_meta' || head.record.payload?.history_mode !== 'paginated') return partial();
514
+ segments.push({ candidate: found, end });
515
+ const next = head.record.payload.history_base;
516
+ base = next && typeof next === 'object' ? next : null;
517
+ }
518
+ return { segments, partial: false, metaBytes };
519
+ }
520
+
358
521
  /**
359
522
  * Stream-convert a whole candidate file under the import limits. A file over
360
523
  * `maxFileBytes`, a line over `maxLineBytes` or a conversion over the
361
524
  * converter's limits fails the file with `tooLarge` without reading the rest.
362
- * `bytesRead` is how far the read got.
525
+ * A Codex rollout continuing a `history_base` is preceded by the records of
526
+ * each base up to its cutoff, oldest first (their session metadata dropped),
527
+ * all within the one `maxFileBytes`; a base that cannot be followed leaves the
528
+ * `historyPartial` warning. `bytesRead` is how far the read got.
363
529
  */
364
530
  export async function convertCandidateFile(
365
531
  candidate,
@@ -377,15 +543,45 @@ export async function convertCandidateFile(
377
543
  maxRetainedEntries,
378
544
  maxMetadataBytes,
379
545
  });
546
+ // Bytes read beyond the file itself: metadata pre-reads and the bases' cutoffs. All count against `maxFileBytes`.
547
+ let inheritedBytes = 0;
548
+ if (candidate.source === 'codex') {
549
+ const head = await firstRecord(candidate, { maxBytes: maxFileBytes, maxLineBytes });
550
+ const base = head.record?.payload?.history_base;
551
+ if (base && typeof base === 'object') {
552
+ inheritedBytes += head.bytes;
553
+ const chain =
554
+ head.record.payload.history_mode === 'paginated'
555
+ ? await resolveHistoryChain(candidate, base, { maxFileBytes, maxLineBytes, spent: inheritedBytes })
556
+ : { segments: [], partial: true, metaBytes: 0 };
557
+ inheritedBytes += chain.metaBytes;
558
+ const { segments, partial } = chain;
559
+ if (partial) converter.markHistoryPartial();
560
+ for (const { candidate: baseFile, end } of segments.reverse()) {
561
+ const baseHandle = await openCandidate(baseFile);
562
+ try {
563
+ const read = await streamJsonlLines(baseHandle, (line) => converter.pushLine(line, { inherited: true }), {
564
+ maxLineBytes,
565
+ chunkBytes,
566
+ maxBytes: end,
567
+ });
568
+ if (read.overflow) converter.markTooLarge();
569
+ inheritedBytes += read.bytesRead;
570
+ } finally {
571
+ await baseHandle.close();
572
+ }
573
+ }
574
+ }
575
+ }
380
576
  const handle = await openCandidate(candidate);
381
577
  try {
382
578
  const read = await streamJsonlLines(handle, (line) => converter.pushLine(line), {
383
579
  maxLineBytes,
384
580
  chunkBytes,
385
- maxBytes: maxFileBytes,
581
+ maxBytes: maxFileBytes - inheritedBytes,
386
582
  });
387
583
  if (read.overflow || read.truncated) converter.markTooLarge();
388
- return { ...converter.result(), bytesRead: read.bytesRead };
584
+ return { ...converter.result(), bytesRead: read.bytesRead + inheritedBytes };
389
585
  } finally {
390
586
  await handle.close();
391
587
  }
@@ -400,6 +596,8 @@ function pumpMetaQueue() {
400
596
  foreignSessionId: null,
401
597
  cwd: null,
402
598
  title: null,
599
+ internalKind: null,
600
+ historyBaseId: null,
403
601
  messageCount: null,
404
602
  warnings: [],
405
603
  errorCode: 'unreadable',
@@ -470,14 +668,14 @@ export function importedKeyMap(summaries) {
470
668
  return map;
471
669
  }
472
670
 
473
- function rowFor(candidate, meta, cwdInfo, imported) {
671
+ function rowFor(candidate, meta, cwdInfo, imported, titles) {
474
672
  const warnings = new Set(meta?.warnings || []);
475
673
  if (meta && !meta.errorCode && !cwdInfo?.exists) warnings.add(IMPORT_WARNING_CWD_MISSING);
476
674
  if (meta?.errorCode) warnings.add(meta.errorCode);
477
675
  return {
478
676
  id: candidate.id,
479
677
  source: candidate.source,
480
- title: meta?.title ?? null,
678
+ title: displayTitle(meta, titles),
481
679
  cwd: cwdInfo?.path ?? null,
482
680
  modifiedAt: candidate.mtimeMs,
483
681
  messageCount: meta?.messageCount ?? null,
@@ -486,10 +684,10 @@ function rowFor(candidate, meta, cwdInfo, imported) {
486
684
  };
487
685
  }
488
686
 
489
- export async function buildRow(candidate, imported, cwdCache = new Map()) {
687
+ export async function buildRow(candidate, imported, cwdCache = new Map(), titles = null) {
490
688
  const meta = cachedMeta(candidate);
491
689
  const cwdInfo = meta?.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
492
- return rowFor(candidate, meta, cwdInfo, imported);
690
+ return rowFor(candidate, meta, cwdInfo, imported, titles);
493
691
  }
494
692
 
495
693
  // ── views & paging ───────────────────────────────────────────────────────────
@@ -553,7 +751,7 @@ async function pageResult(view, offset, limit, imported) {
553
751
  const rows = [];
554
752
  for (const id of ids) {
555
753
  const candidate = state.candidates.get(id);
556
- if (candidate) rows.push(await buildRow(candidate, imported, cwdCache));
754
+ if (candidate) rows.push(await buildRow(candidate, imported, cwdCache, view.titles));
557
755
  }
558
756
  const nextOffset = offset + ids.length;
559
757
  return {
@@ -568,31 +766,76 @@ async function pageResult(view, offset, limit, imported) {
568
766
  };
569
767
  }
570
768
 
769
+ /**
770
+ * Ids of Codex rollouts that an earlier revision of the same thread (same
771
+ * session id) names as its history base. Codex lists one rollout per thread,
772
+ * the current one, so these stay out of the list; a fork's parent is another
773
+ * thread and stays listed.
774
+ */
775
+ function supersededBaseIds(candidates) {
776
+ const byRollout = new Map();
777
+ for (const candidate of candidates) {
778
+ const meta = cachedMeta(candidate);
779
+ const rolloutId = candidate.source === 'codex' ? rolloutIdOf(candidate.path) : null;
780
+ if (meta?.foreignSessionId && rolloutId) byRollout.set(`${meta.foreignSessionId}\0${rolloutId}`, candidate.id);
781
+ }
782
+ const hidden = new Set();
783
+ for (const candidate of candidates) {
784
+ const meta = cachedMeta(candidate);
785
+ const base = meta?.historyBaseId && byRollout.get(`${meta.foreignSessionId}\0${meta.historyBaseId}`);
786
+ if (base && base !== candidate.id) hidden.add(base);
787
+ }
788
+ // A cycle of revisions must not hide a thread entirely: its newest one (first in order) stays.
789
+ const newest = new Map();
790
+ const visible = new Set();
791
+ const indexed = new Set(byRollout.values());
792
+ for (const candidate of candidates) {
793
+ const sessionId = cachedMeta(candidate)?.foreignSessionId;
794
+ if (!sessionId || !indexed.has(candidate.id)) continue;
795
+ if (!newest.has(sessionId)) newest.set(sessionId, candidate.id);
796
+ if (!hidden.has(candidate.id)) visible.add(sessionId);
797
+ }
798
+ for (const [sessionId, id] of newest) if (!visible.has(sessionId)) hidden.delete(id);
799
+ return hidden;
800
+ }
801
+
571
802
  /**
572
803
  * Apply a view's filters (time range, query on title/cwd, project) to
573
804
  * candidates using their cached metadata; a candidate whose metadata is still
574
- * pending cannot match a query or project and leaves the result incomplete.
805
+ * pending is neither a member nor counted and leaves the result incomplete.
806
+ * The source app's internal and empty records are never members.
575
807
  */
576
- async function filterCandidates(candidates, filters, imported) {
808
+ async function filterCandidates(candidates, filters, imported, titles) {
577
809
  const { needle, projectFilter, modifiedAfter, modifiedBefore } = filters;
578
810
  const cwdCache = new Map();
579
811
  const memberIds = [];
580
812
  const selectableIds = [];
581
813
  const projectCounts = new Map();
582
814
  let complete = true;
815
+ const superseded = supersededBaseIds(candidates);
583
816
  for (const candidate of candidates) {
817
+ if (superseded.has(candidate.id)) continue;
584
818
  if (modifiedAfter !== null && candidate.mtimeMs < modifiedAfter) continue;
585
819
  if (modifiedBefore !== null && candidate.mtimeMs > modifiedBefore) continue;
586
820
  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)))) {
821
+ // Unclassified yet: neither a member nor counted until its metadata is read.
822
+ if (!meta) {
823
+ complete = false;
824
+ continue;
825
+ }
826
+ // The source app's own records and files read to the end without a message are not conversations.
827
+ if (meta.internalKind || meta.errorCode === 'empty') continue;
828
+ const cwdInfo = meta.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
829
+ if (
830
+ needle &&
831
+ !(foldText(displayTitle(meta, titles)).includes(needle) || foldText(cwdInfo?.path).includes(needle))
832
+ ) {
590
833
  continue;
591
834
  }
592
835
  if (cwdInfo?.path) projectCounts.set(cwdInfo.path, (projectCounts.get(cwdInfo.path) || 0) + 1);
593
836
  if (projectFilter && !samePath(cwdInfo?.path, projectFilter)) continue;
594
837
  // Already-imported conversations are left out of the list entirely.
595
- if (meta && imported.has(candidateKey(candidate, meta))) continue;
838
+ if (imported.has(candidateKey(candidate, meta))) continue;
596
839
  memberIds.push(candidate.id);
597
840
  selectableIds.push(candidate.id);
598
841
  }
@@ -624,7 +867,9 @@ export async function listConversations(params, { imported, env = process.env }
624
867
  projectFilter: typeof project === 'string' && project.trim() ? project.trim() : null,
625
868
  };
626
869
  const { root, candidates } = await enumerateCandidates(source, env);
870
+ const titles = await loadStoredTitles(source, env);
627
871
  // Background metadata for everything, newest first; the first page is awaited.
872
+ // Candidates still unclassified stay out of the view (it is incomplete) and join it on a later listing.
628
873
  for (const candidate of candidates) void resolveMeta(candidate);
629
874
  if (!filters.needle && !filters.projectFilter) await Promise.all(candidates.slice(0, limit).map(resolveMeta));
630
875
  const view = {
@@ -632,8 +877,9 @@ export async function listConversations(params, { imported, env = process.env }
632
877
  source,
633
878
  root,
634
879
  filters,
880
+ titles,
635
881
  candidateIds: candidates.map((candidate) => candidate.id),
636
- ...(await filterCandidates(candidates, filters, imported)),
882
+ ...(await filterCandidates(candidates, filters, imported, titles)),
637
883
  touchedAt: Date.now(),
638
884
  };
639
885
  state.views.set(view.id, view);
@@ -651,14 +897,14 @@ export async function resolveViewSelection(viewId, excludeIds, imported) {
651
897
  const view = requireView(viewId);
652
898
  const candidates = view.candidateIds.map((id) => state.candidates.get(id)).filter(Boolean);
653
899
  await Promise.all(candidates.map(resolveMeta));
654
- const { selectableIds } = await filterCandidates(candidates, view.filters, imported);
900
+ const { selectableIds } = await filterCandidates(candidates, view.filters, imported, view.titles);
655
901
  const excluded = new Set(Array.isArray(excludeIds) ? excludeIds : []);
656
902
  return selectableIds.filter((id) => !excluded.has(id));
657
903
  }
658
904
 
659
905
  // ── picked files ─────────────────────────────────────────────────────────────
660
906
  /** Validate and register a file chosen in a native picker (a local regular .jsonl). */
661
- export async function registerFile({ source, path } = {}, { imported, limits } = {}) {
907
+ export async function registerFile({ source, path } = {}, { imported, limits, env = process.env } = {}) {
662
908
  requireSource(source);
663
909
  if (typeof path !== 'string' || !path.trim() || path.includes('\0')) throw importError('invalidPath');
664
910
  const requested = path.trim();
@@ -694,5 +940,5 @@ export async function registerFile({ source, path } = {}, { imported, limits } =
694
940
  }
695
941
  if (conversion.errorCode) throw importError(conversion.errorCode);
696
942
  recordConversion(candidate, conversion);
697
- return { row: await buildRow(candidate, imported) };
943
+ return { row: await buildRow(candidate, imported, new Map(), await loadStoredTitles(source, env)) };
698
944
  }
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import { randomUUID } from 'node:crypto';
16
16
  import { access, stat } from 'node:fs/promises';
17
+ import { basename } from 'node:path';
17
18
  import {
18
19
  buildImportOrigin,
19
20
  createImportedSession,
@@ -24,10 +25,12 @@ import { sessionPath } from '../../runtime/agent/orchestrator/session/store/sess
24
25
  import {
25
26
  cachedMeta,
26
27
  convertCandidateFile,
28
+ displayTitle,
27
29
  getCandidate,
28
30
  importError,
29
31
  importedKeyMap,
30
32
  listConversations,
33
+ loadStoredTitles,
31
34
  recordConversion,
32
35
  registerFile,
33
36
  resolveSourceCwd,
@@ -131,6 +134,17 @@ function touch(job, patch = {}) {
131
134
 
132
135
  const COUNTER = { imported: 'imported', alreadyImported: 'skippedAlreadyImported', failed: 'failed' };
133
136
 
137
+ const titlesFor = (candidate, context) => (candidate.source === 'codex' ? context.titles : null);
138
+
139
+ /** Which record a result is about: its title and source file name, for the failure report. */
140
+ function describeCandidate(candidateId, context) {
141
+ const candidate = getCandidate(candidateId);
142
+ return {
143
+ title: candidate ? displayTitle(cachedMeta(candidate), titlesFor(candidate, context)) : null,
144
+ sourceName: candidate ? basename(candidate.path) : null,
145
+ };
146
+ }
147
+
134
148
  function record(job, { projectPath = null, ...result }) {
135
149
  const counter = COUNTER[result.status];
136
150
  job[counter] += 1;
@@ -162,6 +176,7 @@ async function importOne(candidateId, context) {
162
176
  if (!candidate) return failed(candidateId, 'unknownCandidate');
163
177
  // The chat keeps the title the user picked it by: a long file's listing
164
178
  // title comes from its prefix and can differ from the converted branch.
179
+ // Codex's own stored title, when it has one, names the chat first.
165
180
  const listedTitle = cachedMeta(candidate)?.title ?? null;
166
181
  let conversion;
167
182
  try {
@@ -173,7 +188,11 @@ async function importOne(candidateId, context) {
173
188
  } catch {
174
189
  return failed(candidateId, 'unreadable');
175
190
  }
176
- if (listedTitle) conversion = { ...conversion, title: listedTitle };
191
+ const title = displayTitle(
192
+ { foreignSessionId: conversion.foreignSessionId, title: listedTitle || conversion.title },
193
+ titlesFor(candidate, context)
194
+ );
195
+ if (title) conversion = { ...conversion, title };
177
196
  recordConversion(candidate, conversion);
178
197
  const cwdInfo = await resolveSourceCwd(conversion.cwd);
179
198
  const projectPath = cwdInfo.exists ? cwdInfo.path : null;
@@ -221,6 +240,7 @@ async function runJob(job, candidateIds, context) {
221
240
  try {
222
241
  // One catalog read per batch; later commits are seen through committedKeys.
223
242
  context.imported = await context.importedLookup();
243
+ context.titles = await context.loadTitles();
224
244
  for (const candidateId of candidateIds) {
225
245
  if (job.cancelRequested) break;
226
246
  let result;
@@ -228,8 +248,7 @@ async function runJob(job, candidateIds, context) {
228
248
  result = await admit(async () => {
229
249
  // A cancel that arrived while queued behind other jobs wins here.
230
250
  if (job.cancelRequested) return null;
231
- const candidate = getCandidate(candidateId);
232
- touch(job, { current: { candidateId, title: (candidate && cachedMeta(candidate)?.title) ?? null } });
251
+ touch(job, { current: { candidateId, title: describeCandidate(candidateId, context).title } });
233
252
  try {
234
253
  return await importOne(candidateId, context);
235
254
  } finally {
@@ -241,7 +260,7 @@ async function runJob(job, candidateIds, context) {
241
260
  result = failed(candidateId, 'importFailed');
242
261
  }
243
262
  if (result === null) break;
244
- record(job, result);
263
+ record(job, { ...result, ...describeCandidate(candidateId, context) });
245
264
  }
246
265
  touch(job, { state: job.cancelRequested ? 'stopped' : 'completed' });
247
266
  } catch (error) {
@@ -296,7 +315,7 @@ export function createConversationImportApi({
296
315
  return listConversations(requireParams(params), { imported: await imported(), env });
297
316
  },
298
317
  async registerConversationImportFile(params) {
299
- return registerFile(requireParams(params), { imported: await imported(), limits });
318
+ return registerFile(requireParams(params), { imported: await imported(), limits, env });
300
319
  },
301
320
  getConversationImportJobs(params = {}) {
302
321
  const { jobId } = requireParams(params ?? {});
@@ -334,6 +353,8 @@ export function createConversationImportApi({
334
353
  void runJob(job, candidateIds, {
335
354
  importedLookup: imported,
336
355
  imported: null,
356
+ titles: null,
357
+ loadTitles: () => loadStoredTitles('codex', env),
337
358
  sessionExists,
338
359
  limits,
339
360
  prepareLeadOptions,