mixdog 1.4.1 → 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 (326) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/gate-local.mjs +172 -61
  4. package/scripts/lib/gate-scheduler.mjs +138 -32
  5. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  6. package/scripts/lib/run-node-tests.mjs +54 -29
  7. package/scripts/lib/scoped-temp.mjs +1 -0
  8. package/scripts/lib/test-failure-records.mjs +14 -1
  9. package/scripts/lib/test-failure-summary.mjs +16 -4
  10. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  11. package/scripts/pptx-kit-index.mjs +5 -6
  12. package/scripts/suite-health.mjs +110 -28
  13. package/scripts/test.mjs +9 -14
  14. package/src/agents/advisor/AGENT.md +4 -4
  15. package/src/agents/advisor/agent.json +1 -1
  16. package/src/agents/explore/AGENT.md +22 -0
  17. package/src/agents/explore/agent.json +6 -0
  18. package/src/cli.mjs +7 -0
  19. package/src/defaults/agents.json +7 -0
  20. package/src/defaults/shipped-history.json +18 -2
  21. package/src/defaults/skills/browser-use/SKILL.md +4 -1
  22. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  23. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  24. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  25. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  26. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  27. package/src/defaults/skills/computer-use/SKILL.md +12 -2
  28. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  29. package/src/defaults/skills/setup/references/actions.md +12 -6
  30. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  31. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  32. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  33. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  34. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  35. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  36. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  37. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  38. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  39. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  40. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  41. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  42. package/src/defaults/skills/visualize/SKILL.md +6 -2
  43. package/src/lib/rules-builder.cjs +6 -30
  44. package/src/rules/lead/LEAD.md +3 -3
  45. package/src/rules/shared/20-research.md +3 -6
  46. package/src/rules/shared/40-editing.md +1 -0
  47. package/src/rules/shared/60-verification.md +10 -9
  48. package/src/rules/shared/70-delivery.md +4 -3
  49. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  50. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  51. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +130 -21
  52. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  53. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  54. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  55. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +7 -7
  56. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  57. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  58. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  59. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  60. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  61. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  62. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  63. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +83 -52
  64. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  65. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +43 -8
  66. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +30 -28
  67. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  69. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  70. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  74. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  75. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  78. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  79. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  80. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +48 -31
  81. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  82. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  83. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  84. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  85. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  86. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +26 -28
  88. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +3 -1
  89. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  92. package/src/runtime/agent/orchestrator/providers/registry.mjs +33 -2
  93. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  94. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  95. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  96. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  97. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  98. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +2 -1
  99. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  100. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  102. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  103. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  104. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  105. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +94 -10
  106. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +3 -3
  107. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +10 -5
  108. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +5 -5
  109. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +11 -1
  110. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  111. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  112. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +5 -3
  113. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +21 -2
  114. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  115. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  116. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  117. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  118. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  119. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +3 -4
  120. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  123. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +25 -14
  124. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +17 -14
  125. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  126. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  127. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -3
  128. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +47 -29
  129. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  130. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  131. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +1 -1
  132. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +243 -38
  133. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  134. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  135. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  136. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  137. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +36 -26
  138. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +3 -5
  139. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  141. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  142. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  143. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  144. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  145. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  147. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  148. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  149. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  150. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  151. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  154. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  155. package/src/runtime/channels/lib/whisper-port.mjs +19 -10
  156. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  157. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  158. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  159. package/src/runtime/explore/explorer.mjs +93 -0
  160. package/src/runtime/explore/tool-defs.mjs +35 -0
  161. package/src/runtime/explore/tool.mjs +123 -0
  162. package/src/runtime/memory/index.mjs +4 -0
  163. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  164. package/src/runtime/memory/lib/embedding-worker.mjs +4 -0
  165. package/src/runtime/office/quality/assurance-structure-docx.mjs +1 -1
  166. package/src/runtime/shared/agent-route-config.mjs +17 -2
  167. package/src/runtime/shared/atomic-file.mjs +103 -2
  168. package/src/runtime/shared/background-tasks.mjs +15 -6
  169. package/src/runtime/shared/connector-accounts.mjs +5 -5
  170. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  171. package/src/runtime/shared/connector-catalog.mjs +60 -52
  172. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  173. package/src/runtime/shared/file-has-data.mjs +26 -2
  174. package/src/runtime/shared/file-lock.mjs +13 -15
  175. package/src/runtime/shared/git-launch.d.mts +9 -0
  176. package/src/runtime/shared/git-launch.mjs +213 -0
  177. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  178. package/src/runtime/shared/llm/cost.mjs +6 -1
  179. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  180. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  181. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  182. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  183. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  184. package/src/runtime/shared/provider-accounts.mjs +52 -2
  185. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  186. package/src/runtime/shared/staged-update.mjs +47 -2
  187. package/src/runtime/shared/tool-surface.mjs +110 -36
  188. package/src/runtime/shared/tool-work-units.mjs +4 -0
  189. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  190. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  191. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  192. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  193. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  194. package/src/runtime/shared/worktrees/git.mjs +57 -0
  195. package/src/runtime/shared/worktrees/manager.mjs +205 -137
  196. package/src/runtime/tidy/install.mjs +95 -15
  197. package/src/runtime/tidy/languages.mjs +17 -15
  198. package/src/runtime/tidy/process.mjs +7 -4
  199. package/src/runtime/tidy/resolve.mjs +19 -16
  200. package/src/runtime/tidy/runners/biome.mjs +3 -3
  201. package/src/runtime/tidy/tool.mjs +1 -1
  202. package/src/runtime/tidy/worktree.mjs +10 -6
  203. package/src/runtime/visualize-bridge/client.mjs +4 -1
  204. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  205. package/src/runtime/web-search/index.mjs +1 -1
  206. package/src/session-runtime/agent-task-sessions.mjs +410 -353
  207. package/src/session-runtime/boot/tools.mjs +2 -0
  208. package/src/session-runtime/bot-status.mjs +103 -103
  209. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  210. package/src/session-runtime/conversation-import/discovery.mjs +159 -40
  211. package/src/session-runtime/conversation-import/service.mjs +9 -3
  212. package/src/session-runtime/handoff-attachments.mjs +1 -1
  213. package/src/session-runtime/held-requests.mjs +3 -1
  214. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  215. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  216. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +143 -116
  217. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  218. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  219. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  220. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  221. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  222. package/src/session-runtime/provider-auth/login.mjs +2 -2
  223. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  224. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  225. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  226. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  227. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  228. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  229. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  230. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  231. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  232. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  233. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  234. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  235. package/src/session-runtime/services/provider-admin.mjs +281 -10
  236. package/src/session-runtime/session-turn-api.mjs +2 -0
  237. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  238. package/src/session-runtime/settings-system-api.mjs +1 -1
  239. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  240. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  241. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  242. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  243. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  244. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  245. package/src/session-runtime/workflow-agents-api/agent-route.mjs +10 -1
  246. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  247. package/src/session-runtime/workspace/session-view.mjs +149 -0
  248. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  249. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  250. package/src/session-runtime/workspace/transitions.mjs +108 -1066
  251. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  252. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  253. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  254. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  255. package/src/session-runtime/workspace/worktree-tool.mjs +54 -54
  256. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  257. package/src/standalone/daemon-session-control.mjs +151 -136
  258. package/src/standalone/daemon.mjs +7 -0
  259. package/src/standalone/desktop-service-registry.mjs +9 -0
  260. package/src/standalone/local-session-runtime.mjs +5 -1
  261. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  262. package/src/standalone/session-client.mjs +5 -1
  263. package/src/standalone/session-frame-stream.mjs +83 -11
  264. package/src/standalone/session-protocol.mjs +4 -0
  265. package/src/standalone/session-service/agent-tree.mjs +45 -9
  266. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  267. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  268. package/src/standalone/session-service/stored-reader.mjs +127 -40
  269. package/src/tui/App.jsx +154 -164
  270. package/src/tui/app/app-view/floating-panel.jsx +3 -0
  271. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  272. package/src/tui/app/clipboard.mjs +21 -10
  273. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  274. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  275. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  276. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  277. package/src/tui/app/provider-setup/oauth-actions.mjs +25 -6
  278. package/src/tui/app/provider-setup-picker.mjs +20 -13
  279. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  280. package/src/tui/app/slash-commands.mjs +1 -0
  281. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  282. package/src/tui/app/slash-dispatch.mjs +1 -1
  283. package/src/tui/app/use-btw-panel.mjs +29 -0
  284. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  285. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  286. package/src/tui/app/use-mouse-input.mjs +11 -0
  287. package/src/tui/app/use-onboarding-start.mjs +8 -3
  288. package/src/tui/app/use-panel-transition.mjs +10 -1
  289. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  290. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  291. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  292. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  293. package/src/tui/app/use-selection-focus.mjs +11 -2
  294. package/src/tui/app/use-selection-geometry.mjs +18 -9
  295. package/src/tui/app/use-selection-paint.mjs +4 -4
  296. package/src/tui/app/use-selection-stitch.mjs +3 -3
  297. package/src/tui/app/use-slash-palette.mjs +62 -0
  298. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  299. package/src/tui/app/use-transcript-activity.mjs +47 -32
  300. package/src/tui/app/use-transcript-follow.mjs +12 -4
  301. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  302. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  303. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  304. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  305. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  306. package/src/tui/app/use-transcript-window.mjs +21 -11
  307. package/src/tui/app/use-tui-ready.mjs +18 -0
  308. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  309. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  310. package/src/tui/components/Picker.jsx +27 -3
  311. package/src/tui/components/SignInUrl.jsx +68 -0
  312. package/src/tui/components/TextEntryPanel.jsx +76 -46
  313. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  314. package/src/tui/dist/index.mjs +1896 -1341
  315. package/src/tui/session/goal-turn-state.mjs +6 -2
  316. package/src/tui/session/session-api/controls.mjs +4 -0
  317. package/src/tui/session/session-api/extensions.mjs +8 -0
  318. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  319. package/src/tui/session/session-api/intake.mjs +2 -0
  320. package/src/tui/session/session-api/integrations.mjs +2 -2
  321. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  322. package/src/tui/session/session-api/settings.mjs +2 -2
  323. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  324. package/src/tui/session/transcript-spill.mjs +92 -4
  325. package/src/tui/session/transcript-store.mjs +2 -0
  326. 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, dirname, 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,7 +26,13 @@ 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';
29
37
  import { loadCodexThreadTitles } from './codex-titles.mjs';
30
38
 
@@ -106,6 +114,51 @@ export function isNetworkOrDevicePath(path) {
106
114
 
107
115
  const fileIdentity = (info) => `${info.dev}:${info.ino}`;
108
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
+
109
162
  export function candidateId(source, canonicalPath) {
110
163
  return createHash('sha256')
111
164
  .update(`${source}\0${caseFold(canonicalPath)}`)
@@ -144,7 +197,10 @@ async function walkCodex(rootReal, dir, files, seenDirs) {
144
197
  continue;
145
198
  }
146
199
  }
147
- 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);
148
204
  }
149
205
  }
150
206
 
@@ -275,7 +331,7 @@ async function openCandidate(candidate) {
275
331
  ) {
276
332
  throw importError('unreadable');
277
333
  }
278
- return handle;
334
+ return isCompressed(candidate.path) ? zstdReader(handle) : handle;
279
335
  } catch (error) {
280
336
  await handle.close();
281
337
  throw error;
@@ -351,6 +407,10 @@ async function readTailLines(handle, size) {
351
407
  return (size > length ? text.slice(text.indexOf('\n') + 1) : text).split('\n');
352
408
  }
353
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
+
354
414
  async function readPrefixMeta(candidate) {
355
415
  const converter = createConversationConverter(candidate.source);
356
416
  const handle = await openCandidate(candidate);
@@ -385,12 +445,14 @@ async function readPrefixMeta(candidate) {
385
445
  }
386
446
  const conversion = converter.result();
387
447
  // 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
448
+ const listing = async (meta) =>
449
+ conversion.historyBase && !(await baseIsMissing(candidate, meta))
390
450
  ? { ...meta, title: null, messageCount: null, errorCode: meta.errorCode === 'empty' ? null : meta.errorCode }
391
- : meta;
451
+ : conversion.historyBase
452
+ ? { ...meta, warnings: [...meta.warnings, IMPORT_WARNING_HISTORY_UNAVAILABLE] }
453
+ : meta;
392
454
  if (read.eof) return listing(metaFromConversion(conversion, true));
393
- const meta = listing(metaFromConversion(conversion, false));
455
+ const meta = await listing(metaFromConversion(conversion, false));
394
456
  if (candidate.source === 'codex' && (!meta.foreignSessionId || !meta.cwd) && read.firstPartial) {
395
457
  // A session_meta line longer than the prefix still names its id/cwd.
396
458
  const fromLine = codexMetaFromTruncatedLine(read.firstPartial.replace(/^\uFEFF/, ''));
@@ -404,10 +466,13 @@ async function readPrefixMeta(candidate) {
404
466
  // ── Codex history base chain ─────────────────────────────────────────────────
405
467
  const HISTORY_CHAIN_MAX_DEPTH = 32;
406
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;
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;
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;
408
470
 
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;
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
+ };
411
476
 
412
477
  const META_CHUNK_BYTES = 8 * 1024;
413
478
 
@@ -439,20 +504,34 @@ async function firstRecord(candidate, { maxBytes, maxLineBytes }) {
439
504
  }
440
505
  }
441
506
 
442
- // Whether a cutoff falls on a record boundary: the end of the file or just after a line feed.
507
+ // Whether a cutoff, in (decompressed) file bytes, falls on a record boundary: the end of the file or just after a line feed.
443
508
  async function endsOnRecord(candidate, end) {
444
- if (end === candidate.size) return true;
509
+ const compressed = isCompressed(candidate.path);
510
+ if (!compressed && end === candidate.size) return true;
445
511
  const handle = await openCandidate(candidate);
446
512
  try {
447
513
  const byte = Buffer.alloc(1);
448
- const { bytesRead } = await handle.read(byte, 0, 1, end - 1);
449
- return bytesRead === 1 && byte[0] === 10;
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;
450
529
  } finally {
451
530
  await handle.close();
452
531
  }
453
532
  }
454
533
 
455
- /** The plain rollout file with this rollout id under the live, then the archived, sessions folder. */
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). */
456
535
  async function findRolloutById(home, rolloutId) {
457
536
  for (const folder of ['sessions', 'archived_sessions']) {
458
537
  let rootReal;
@@ -475,23 +554,43 @@ async function findRolloutById(home, rolloutId) {
475
554
  return null;
476
555
  }
477
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
+
478
574
  /**
479
575
  * Rollouts a `history_base` leads to, nearest first, each with the byte offset
480
576
  * 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
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
485
580
  * `spent` metadata reads so far, every cutoff and every metadata read) beyond
486
- * `maxFileBytes`.
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`).
487
584
  */
488
585
  async function resolveHistoryChain(candidate, firstBase, { maxFileBytes, maxLineBytes, spent }) {
489
586
  const home = dirname(candidate.root ?? sourceRoot('codex'));
490
587
  const seen = new Set([rolloutIdOf(candidate.path)]);
491
588
  const segments = [];
492
- let used = candidate.size + spent;
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;
493
592
  let metaBytes = 0;
494
- const partial = () => ({ segments, partial: true, metaBytes });
593
+ const partial = (warning = IMPORT_WARNING_HISTORY_PARTIAL) => ({ segments, partial: warning, metaBytes });
495
594
  for (let base = firstBase; base; ) {
496
595
  const id = typeof base.thread_id === 'string' ? base.thread_id.toLowerCase() : '';
497
596
  const end = base.end_byte_offset;
@@ -500,13 +599,20 @@ async function resolveHistoryChain(candidate, firstBase, { maxFileBytes, maxLine
500
599
  seen.add(id);
501
600
  if (!Number.isSafeInteger(ordinal) || ordinal <= 0 || !Number.isSafeInteger(end) || end <= 0) return partial();
502
601
  const found = await findRolloutById(home, id);
503
- if (!found || end > found.size || used + end > maxFileBytes) return partial();
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);
504
605
  let head;
505
606
  try {
506
607
  if (!(await endsOnRecord(found, end))) return partial();
507
608
  head = await firstRecord(found, { maxBytes: Math.min(end, maxFileBytes - used - end), maxLineBytes });
508
- } catch {
509
- return partial();
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
+ );
510
616
  }
511
617
  used += end + head.bytes;
512
618
  metaBytes += head.bytes;
@@ -515,7 +621,7 @@ async function resolveHistoryChain(candidate, firstBase, { maxFileBytes, maxLine
515
621
  const next = head.record.payload.history_base;
516
622
  base = next && typeof next === 'object' ? next : null;
517
623
  }
518
- return { segments, partial: false, metaBytes };
624
+ return { segments, partial: null, metaBytes };
519
625
  }
520
626
 
521
627
  /**
@@ -524,8 +630,10 @@ async function resolveHistoryChain(candidate, firstBase, { maxFileBytes, maxLine
524
630
  * converter's limits fails the file with `tooLarge` without reading the rest.
525
631
  * A Codex rollout continuing a `history_base` is preceded by the records of
526
632
  * 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.
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.
529
637
  */
530
638
  export async function convertCandidateFile(
531
639
  candidate,
@@ -553,10 +661,10 @@ export async function convertCandidateFile(
553
661
  const chain =
554
662
  head.record.payload.history_mode === 'paginated'
555
663
  ? await resolveHistoryChain(candidate, base, { maxFileBytes, maxLineBytes, spent: inheritedBytes })
556
- : { segments: [], partial: true, metaBytes: 0 };
664
+ : { segments: [], partial: IMPORT_WARNING_HISTORY_PARTIAL, metaBytes: 0 };
557
665
  inheritedBytes += chain.metaBytes;
558
666
  const { segments, partial } = chain;
559
- if (partial) converter.markHistoryPartial();
667
+ if (partial) converter.markHistoryPartial(partial);
560
668
  for (const { candidate: baseFile, end } of segments.reverse()) {
561
669
  const baseHandle = await openCandidate(baseFile);
562
670
  try {
@@ -587,12 +695,22 @@ export async function convertCandidateFile(
587
695
  }
588
696
  }
589
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
+
590
707
  function pumpMetaQueue() {
591
708
  while (state.active < META_CONCURRENCY && state.queue.length) {
592
709
  const job = state.queue.shift();
593
710
  state.active += 1;
594
711
  readPrefixMeta(job.candidate)
595
- .catch(() => ({
712
+ .then((meta) => withCompressedBaseNotice(job.candidate, meta))
713
+ .catch((error) => ({
596
714
  foreignSessionId: null,
597
715
  cwd: null,
598
716
  title: null,
@@ -600,7 +718,7 @@ function pumpMetaQueue() {
600
718
  historyBaseId: null,
601
719
  messageCount: null,
602
720
  warnings: [],
603
- errorCode: 'unreadable',
721
+ errorCode: error?.code === IMPORT_ERROR_NEEDS_NEWER_NODE ? IMPORT_ERROR_NEEDS_NEWER_NODE : 'unreadable',
604
722
  }))
605
723
  .then((value) => {
606
724
  state.meta.set(job.candidate.path, { stampKey: stampKey(job.candidate), value });
@@ -684,7 +802,7 @@ function rowFor(candidate, meta, cwdInfo, imported, titles) {
684
802
  };
685
803
  }
686
804
 
687
- export async function buildRow(candidate, imported, cwdCache = new Map(), titles = null) {
805
+ async function buildRow(candidate, imported, cwdCache = new Map(), titles = null) {
688
806
  const meta = cachedMeta(candidate);
689
807
  const cwdInfo = meta?.cwd ? await resolveSourceCwd(meta.cwd, cwdCache) : null;
690
808
  return rowFor(candidate, meta, cwdInfo, imported, titles);
@@ -903,7 +1021,7 @@ export async function resolveViewSelection(viewId, excludeIds, imported) {
903
1021
  }
904
1022
 
905
1023
  // ── picked files ─────────────────────────────────────────────────────────────
906
- /** Validate and register a file chosen in a native picker (a local regular .jsonl). */
1024
+ /** Validate and register a file chosen in a native picker (a local regular .jsonl, or a Codex .jsonl.zst). */
907
1025
  export async function registerFile({ source, path } = {}, { imported, limits, env = process.env } = {}) {
908
1026
  requireSource(source);
909
1027
  if (typeof path !== 'string' || !path.trim() || path.includes('\0')) throw importError('invalidPath');
@@ -911,7 +1029,8 @@ export async function registerFile({ source, path } = {}, { imported, limits, en
911
1029
  // Network/device forms are refused before any filesystem call.
912
1030
  if (isNetworkOrDevicePath(requested)) throw importError('unsupported');
913
1031
  if (!isAbsolute(requested)) throw importError('invalidPath');
914
- 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');
915
1034
  let real;
916
1035
  let info;
917
1036
  try {
@@ -920,7 +1039,7 @@ export async function registerFile({ source, path } = {}, { imported, limits, en
920
1039
  } catch {
921
1040
  throw importError('unreadable');
922
1041
  }
923
- if (isNetworkOrDevicePath(real) || !info.isFile() || extname(real).toLowerCase() !== '.jsonl') {
1042
+ if (isNetworkOrDevicePath(real) || !info.isFile() || !pickable(basename(real))) {
924
1043
  throw importError('unsupported');
925
1044
  }
926
1045
  const candidate = registerCandidate({
@@ -935,8 +1054,8 @@ export async function registerFile({ source, path } = {}, { imported, limits, en
935
1054
  let conversion;
936
1055
  try {
937
1056
  conversion = await convertCandidateFile(candidate, limits);
938
- } catch {
939
- throw importError('unreadable');
1057
+ } catch (error) {
1058
+ throw importError(error?.code === IMPORT_ERROR_NEEDS_NEWER_NODE ? IMPORT_ERROR_NEEDS_NEWER_NODE : 'unreadable');
940
1059
  }
941
1060
  if (conversion.errorCode) throw importError(conversion.errorCode);
942
1061
  recordConversion(candidate, conversion);
@@ -20,7 +20,10 @@ import {
20
20
  createImportedSession,
21
21
  importOriginKey,
22
22
  } from '../../runtime/agent/orchestrator/session/conversation-import/imported-session.mjs';
23
- import { IMPORT_WARNING_CWD_MISSING } from '../../runtime/agent/orchestrator/session/conversation-import/turns.mjs';
23
+ import {
24
+ IMPORT_ERROR_NEEDS_NEWER_NODE,
25
+ IMPORT_WARNING_CWD_MISSING,
26
+ } from '../../runtime/agent/orchestrator/session/conversation-import/turns.mjs';
24
27
  import { sessionPath } from '../../runtime/agent/orchestrator/session/store/session-paths.mjs';
25
28
  import {
26
29
  cachedMeta,
@@ -185,8 +188,11 @@ async function importOne(candidateId, context) {
185
188
  candidate.size = info.size;
186
189
  candidate.mtimeMs = info.mtimeMs;
187
190
  conversion = await convertCandidateFile(candidate, context.limits);
188
- } catch {
189
- return failed(candidateId, 'unreadable');
191
+ } catch (error) {
192
+ return failed(
193
+ candidateId,
194
+ error?.code === IMPORT_ERROR_NEEDS_NEWER_NODE ? IMPORT_ERROR_NEEDS_NEWER_NODE : 'unreadable'
195
+ );
190
196
  }
191
197
  const title = displayTitle(
192
198
  { foreignSessionId: conversion.foreignSessionId, title: listedTitle || conversion.title },
@@ -523,7 +523,7 @@ export function turnSlice(messages, { runId = '', since = 0 } = {}) {
523
523
  * what its final reply names, then media/office/visualize outputs, then the
524
524
  * files it edited. `written`: only the files it created or modified (no
525
525
  * reply paths, no visualize open targets). */
526
- export function turnArtifactCandidates(messages, { written = false } = {}) {
526
+ function turnArtifactCandidates(messages, { written = false } = {}) {
527
527
  const list = Array.isArray(messages) ? messages : [];
528
528
  const reply = list.findLast((message) => message?.role === 'assistant' && textOf(message.content).trim());
529
529
  const produced = [];
@@ -139,11 +139,13 @@ export function createHeldRequests({
139
139
  tool: request.name,
140
140
  target: request.outbound?.target || '',
141
141
  reason: request.reason || '',
142
+ // The connector account the call is bound to (tool-exec.mjs).
143
+ account: request.account,
142
144
  ...(request.kind === 'outbound'
143
145
  ? {
144
146
  payload: request.outbound?.payload || '',
145
147
  action: request.outbound?.category,
146
- account: request.outbound?.account,
148
+ account: request.outbound?.account || request.account,
147
149
  editable: request.outbound?.editable,
148
150
  details: request.outbound?.details,
149
151
  }
@@ -50,6 +50,8 @@ export function createFeatureToolHandlers({ rt, setupTool, officeToolsEnabled, m
50
50
  await setupTool.execute(args || {}, {
51
51
  signal: signalFor(callerCtx),
52
52
  callerSessionId: callerCtx?.callerSessionId || null,
53
+ requestApproval: callerCtx?.toolApprovalHook || null,
54
+ toolCallId: callerCtx?.toolCallId || null,
53
55
  }),
54
56
  };
55
57
  }
@@ -88,6 +88,14 @@ export function createKnowledgeToolHandlers({
88
88
  if (!codeGraphMod?.executeCodeGraphTool) throw new Error('code_graph runtime is not available');
89
89
  return await codeGraphMod.executeCodeGraphTool(name, args || {}, args?.cwd || callerCwd);
90
90
  },
91
+ explore: async (args, { callerCtx, callerCwd }) => {
92
+ const { runExplore } = await import('../../runtime/explore/tool.mjs');
93
+ return await runExplore(args || {}, {
94
+ cwd: callerCwd,
95
+ signal: signalFor(callerCtx),
96
+ callerSessionId: callerCtx?.callerSessionId || callerCtx?.sessionId || rt.session?.id || null,
97
+ });
98
+ },
91
99
  load_tool: toolSearch,
92
100
  Skill: (args) => skillToolContent(args?.name, activeToolSurface(), rt.mode),
93
101
  };