mixdog 1.3.1 → 1.3.3

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 (468) hide show
  1. package/README.md +8 -4
  2. package/package.json +8 -8
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/scripts/verify-runtime-dependency-versions.mjs +158 -0
  5. package/src/agents/reviewer/AGENT.md +5 -7
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +9 -1
  8. package/src/defaults/skills/browser-use/SKILL.md +6 -3
  9. package/src/defaults/skills/local-provider/SKILL.md +33 -6
  10. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  11. package/src/defaults/skills/setup/SKILL.md +23 -1
  12. package/src/defaults/skills/setup/references/actions.md +50 -3
  13. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  14. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  15. package/src/defaults/skills/visualize/SKILL.md +9 -6
  16. package/src/defaults/skills/visualize/references/craft.md +8 -1
  17. package/src/help.mjs +1 -1
  18. package/src/lib/keychain-cjs.cjs +4 -1
  19. package/src/lib/rules-builder.cjs +14 -7
  20. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  21. package/src/rules/agent-mode/MIXY.md +6 -8
  22. package/src/rules/lead/LEAD.md +7 -10
  23. package/src/rules/shared/10-tool-workflow.md +4 -0
  24. package/src/rules/shared/20-research.md +2 -0
  25. package/src/rules/shared/45-destructive.md +5 -1
  26. package/src/rules/shared/60-verification.md +6 -4
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  30. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  31. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  32. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  33. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  35. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  37. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  38. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  39. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  40. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  41. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
  45. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  46. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
  48. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  49. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
  50. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  51. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  52. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  54. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  55. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
  56. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  57. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  59. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  60. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  62. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  64. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  65. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  69. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  70. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  72. package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
  73. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
  74. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  75. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
  78. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  79. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  80. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
  81. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  82. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  83. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
  85. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
  86. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  87. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
  90. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  92. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  93. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  94. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
  95. package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
  96. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  97. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  98. package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
  99. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
  100. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
  102. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
  103. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
  107. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
  108. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  109. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  110. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
  111. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
  112. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  113. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
  114. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  115. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  116. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  117. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  118. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
  119. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
  120. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
  123. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
  124. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  125. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  126. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
  127. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  128. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
  129. package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
  130. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
  131. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  132. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  134. package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
  135. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
  136. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
  137. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  138. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
  139. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
  140. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  143. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
  144. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
  145. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  146. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
  147. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  148. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  149. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  150. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
  151. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
  155. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  156. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
  157. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
  158. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  160. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  161. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
  163. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
  166. package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
  167. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
  168. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  169. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  170. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  171. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  172. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  173. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
  174. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
  178. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
  179. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  180. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  181. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
  182. package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
  183. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
  184. package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
  185. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  186. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  189. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  190. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
  191. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  196. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
  199. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
  200. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  201. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  202. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  203. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  204. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  205. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
  206. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  207. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  208. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  209. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  210. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  211. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  212. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  213. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  214. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  215. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  216. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  217. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  218. package/src/runtime/attachments/legacy-office.mjs +110 -0
  219. package/src/runtime/attachments/limits.mjs +5 -1
  220. package/src/runtime/attachments/office-extract.mjs +3 -6
  221. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  222. package/src/runtime/attachments/prompt-restore.mjs +70 -0
  223. package/src/runtime/attachments/store.mjs +109 -33
  224. package/src/runtime/browser-bridge/client.mjs +73 -20
  225. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  226. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  227. package/src/runtime/channels/lib/state-file.mjs +15 -15
  228. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  229. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  230. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  231. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  232. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  233. package/src/runtime/effort-judge/judge-client.mjs +66 -16
  234. package/src/runtime/effort-judge/model-install.mjs +47 -7
  235. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  236. package/src/runtime/local-provider/catalog.mjs +5 -2
  237. package/src/runtime/local-provider/registered-models.mjs +64 -27
  238. package/src/runtime/local-provider/server.mjs +2 -3
  239. package/src/runtime/media/adapters/image-api.mjs +89 -0
  240. package/src/runtime/media/auth.mjs +64 -1
  241. package/src/runtime/media/catalog.mjs +66 -4
  242. package/src/runtime/media/jobs.mjs +4 -0
  243. package/src/runtime/media/lanes.mjs +46 -5
  244. package/src/runtime/media/media-usage.mjs +17 -0
  245. package/src/runtime/media/renditions.mjs +15 -15
  246. package/src/runtime/media/store.mjs +36 -35
  247. package/src/runtime/media/tool.mjs +8 -1
  248. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  249. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  250. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  251. package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
  252. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  253. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  254. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  255. package/src/runtime/office/core/journal.mjs +7 -14
  256. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  257. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  258. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  259. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  260. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  261. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  262. package/src/runtime/office/portable/portable-soffice.mjs +135 -29
  263. package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
  264. package/src/runtime/shared/agent-avatar.mjs +3 -2
  265. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  266. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  267. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  268. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  269. package/src/runtime/shared/agent-room-host.d.mts +31 -0
  270. package/src/runtime/shared/agent-room-host.mjs +58 -0
  271. package/src/runtime/shared/agent-store.mjs +24 -7
  272. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  273. package/src/runtime/shared/atomic-file.d.mts +50 -0
  274. package/src/runtime/shared/atomic-file.mjs +394 -132
  275. package/src/runtime/shared/background-tasks.mjs +7 -4
  276. package/src/runtime/shared/bot-hops.mjs +35 -0
  277. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  278. package/src/runtime/shared/bounded-download.mjs +4 -2
  279. package/src/runtime/shared/config.mjs +39 -9
  280. package/src/runtime/shared/defaults-separation.mjs +6 -2
  281. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  282. package/src/runtime/shared/err-text.mjs +4 -0
  283. package/src/runtime/shared/error-presentation.mjs +22 -4
  284. package/src/runtime/shared/file-has-data.mjs +28 -0
  285. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  286. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  287. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  288. package/src/runtime/shared/projects.mjs +50 -41
  289. package/src/runtime/shared/provider-accounts.mjs +17 -12
  290. package/src/runtime/shared/redact-directories.mjs +18 -0
  291. package/src/runtime/shared/remove-directory.mjs +28 -0
  292. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  293. package/src/runtime/shared/routine-trigger.mjs +14 -6
  294. package/src/runtime/shared/run-child.d.mts +3 -0
  295. package/src/runtime/shared/run-child.mjs +24 -3
  296. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  297. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  298. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  299. package/src/runtime/shared/singleton-owner.mjs +7 -6
  300. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  301. package/src/runtime/shared/staged-update.mjs +4 -2
  302. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  303. package/src/runtime/shared/tool-execution-contract.mjs +10 -0
  304. package/src/runtime/shared/tool-work-units.mjs +12 -9
  305. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  306. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  307. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  308. package/src/runtime/shared/turn-snapshot.mjs +277 -124
  309. package/src/runtime/shared/user-cwd.mjs +9 -4
  310. package/src/runtime/shared/user-data-guard.mjs +92 -61
  311. package/src/runtime/shared/utf8-cut.mjs +10 -0
  312. package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
  313. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  314. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  315. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  316. package/src/runtime/shared/worktrees/registry.mjs +29 -27
  317. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  318. package/src/runtime/tidy/core-install.mjs +26 -4
  319. package/src/runtime/tidy/install.mjs +91 -23
  320. package/src/runtime/tidy/process.mjs +41 -8
  321. package/src/runtime/tidy/resolve.mjs +10 -7
  322. package/src/runtime/web-search/lib/config.mjs +4 -16
  323. package/src/session-runtime/agent-home-delivery.mjs +542 -204
  324. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  325. package/src/session-runtime/agent-room-delivery.mjs +81 -35
  326. package/src/session-runtime/agent-task-sessions.mjs +811 -179
  327. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  328. package/src/session-runtime/boot/apis.mjs +48 -24
  329. package/src/session-runtime/boot/config.mjs +1 -0
  330. package/src/session-runtime/boot/facade.mjs +16 -7
  331. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  332. package/src/session-runtime/boot/providers.mjs +2 -2
  333. package/src/session-runtime/boot/shared.mjs +12 -6
  334. package/src/session-runtime/boot/tools.mjs +23 -3
  335. package/src/session-runtime/bot-status.mjs +432 -0
  336. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  337. package/src/session-runtime/config-lifecycle.mjs +60 -55
  338. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  339. package/src/session-runtime/held-requests.mjs +31 -33
  340. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  341. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  342. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
  343. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  344. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  345. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  346. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  347. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  348. package/src/session-runtime/media-api.mjs +5 -2
  349. package/src/session-runtime/model-recency.mjs +1 -0
  350. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  351. package/src/session-runtime/model-route/set-route.mjs +61 -1
  352. package/src/session-runtime/owned-automation-run.mjs +8 -1
  353. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  354. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  355. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  356. package/src/session-runtime/route-resolution.mjs +16 -8
  357. package/src/session-runtime/route-state.mjs +1 -59
  358. package/src/session-runtime/routine-home-turn.mjs +5 -3
  359. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  360. package/src/session-runtime/runtime-paths.mjs +11 -5
  361. package/src/session-runtime/runtime-review-api.mjs +4 -1
  362. package/src/session-runtime/runtime-tunables.mjs +6 -0
  363. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  364. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  365. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  366. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  367. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
  368. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  369. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  370. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
  371. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  372. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  373. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  374. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  375. package/src/session-runtime/services/agent-tool.mjs +6 -0
  376. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  377. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  378. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  379. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  380. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  381. package/src/session-runtime/services/provider-admin.mjs +26 -6
  382. package/src/session-runtime/services/schedule-access.mjs +33 -4
  383. package/src/session-runtime/services/seeds.mjs +4 -3
  384. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  385. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  386. package/src/session-runtime/services/worktree-access.mjs +27 -0
  387. package/src/session-runtime/session-create/materialize.mjs +2 -3
  388. package/src/session-runtime/session-create/session-options.mjs +15 -2
  389. package/src/session-runtime/session-hooks.mjs +9 -17
  390. package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
  391. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  392. package/src/session-runtime/setup-tool/executor.mjs +12 -2
  393. package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
  394. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  395. package/src/session-runtime/skills-api.mjs +12 -5
  396. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
  397. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  398. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  399. package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
  400. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  401. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  402. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  403. package/src/session-runtime/tool-policy-refresh.mjs +48 -369
  404. package/src/session-runtime/tool-surface.mjs +10 -4
  405. package/src/session-runtime/turn/session-ops.mjs +8 -0
  406. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  407. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  408. package/src/session-runtime/turn/turn-run.mjs +7 -2
  409. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  410. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
  411. package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
  412. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  413. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  414. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  415. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  416. package/src/session-runtime/workspace/transitions.mjs +279 -67
  417. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  418. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  419. package/src/standalone/daemon-agent-control.mjs +4 -1
  420. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  421. package/src/standalone/daemon-session-control.mjs +173 -15
  422. package/src/standalone/daemon-telemetry.mjs +9 -0
  423. package/src/standalone/daemon.mjs +33 -10
  424. package/src/standalone/local-session-runtime.mjs +33 -16
  425. package/src/standalone/session-client.mjs +7 -2
  426. package/src/standalone/session-protocol.mjs +8 -0
  427. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  428. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  429. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  430. package/src/standalone/session-service.mjs +21 -0
  431. package/src/standalone/session-state-projection.mjs +19 -9
  432. package/src/standalone/session-tool-output.mjs +36 -9
  433. package/src/standalone/staged-import.mjs +47 -0
  434. package/src/tui/App.jsx +2 -0
  435. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  436. package/src/tui/app/message-selector.mjs +15 -2
  437. package/src/tui/app/model-options.mjs +16 -2
  438. package/src/tui/app/model-picker.mjs +2 -1
  439. package/src/tui/app/route-pickers.mjs +3 -1
  440. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  441. package/src/tui/app/slash-commands.mjs +1 -1
  442. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  443. package/src/tui/dist/index.mjs +617 -369
  444. package/src/tui/paste-attachments.mjs +0 -1
  445. package/src/tui/paste-text-policy.mjs +4 -5
  446. package/src/tui/prompt-history-store.mjs +16 -21
  447. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  448. package/src/tui/session/context-state.mjs +4 -0
  449. package/src/tui/session/notice-surface.mjs +3 -0
  450. package/src/tui/session/session-api/intake/abort.mjs +52 -18
  451. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  452. package/src/tui/session/session-api/media.mjs +2 -1
  453. package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
  454. package/src/tui/session/session-api/routes-write.mjs +203 -0
  455. package/src/tui/session/session-api/routes.mjs +23 -247
  456. package/src/tui/session/session-api/settings.mjs +14 -1
  457. package/src/tui/session/session-flow/drain.mjs +28 -3
  458. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  459. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  460. package/src/tui/session/session-flow/steering.mjs +8 -3
  461. package/src/tui/session/tool-approval.mjs +142 -14
  462. package/src/tui/session/transcript-intake.mjs +2 -0
  463. package/src/tui/session/turn.mjs +23 -14
  464. package/src/tui/transcript-tool-failures.mjs +8 -2
  465. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  466. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  467. package/src/rules/agent-mode/ROUTINE.md +0 -6
  468. package/src/session-runtime/room-member-gate.mjs +0 -48
@@ -54,6 +54,7 @@ import {
54
54
  reviewSectionTruncated,
55
55
  } from './review-diff.mjs';
56
56
  import { PATCH_EDIT_MAX_BYTES } from './bounded-file-read.mjs';
57
+ import { runGitOffThread } from './git-runner.mjs';
57
58
  import { cleanString as clean } from './clean.mjs';
58
59
  import { createLineDiffBudget, lineDiffHunks, unifiedHunksBody } from './bounded-line-diff.mjs';
59
60
 
@@ -74,7 +75,11 @@ import { createLineDiffBudget, lineDiffHunks, unifiedHunksBody } from './bounded
74
75
  // baseline of the session-wide review.
75
76
  //
76
77
  // Worker trackers remain separate until the worker completes: then its
77
- // segments, owned paths and counts join the owning Lead turn's.
78
+ // segments, owned paths and counts join the Lead turn that is running at that
79
+ // moment, or — when the Lead's turn already ended (sealed or sealing) — the
80
+ // next Lead turn that opens or reopens: the turn that receives the worker's
81
+ // completion and reports it. A worker's edits so land in exactly one turn
82
+ // record, and never in a record that was already frozen.
78
83
 
79
84
  const DISABLED = /^(0|false|off)$/i.test(String(process.env.MIXDOG_TURN_SNAPSHOT || ''));
80
85
  const TURN_CACHE_MAX = 32;
@@ -98,18 +103,46 @@ const _changeMutations = new Map();
98
103
  // `${sessionId}\0${checkpointId}` → path keys whose worktree state is unknown
99
104
  // and whose record could not even be marked so (the store refused every write).
100
105
  const _unknownChangeStates = new Map();
106
+ // Owner session → worker key → completed worker review waiting for the
107
+ // owner's next turn (its turn had ended when they completed), in completion
108
+ // order. Bounded like a turn's agents (trimWorkerReviews).
109
+ const _parkedWorkerReviews = new Map();
110
+ // Owner session → how many times its change history was deleted: a worker
111
+ // bound before a delete never writes into the history after it.
112
+ const _historyEpochs = new Map();
113
+
114
+ /** A parked review owns its worker's bound tracker content (`release`). */
115
+ function releaseParkedReview(review) {
116
+ if (review.release) releaseDiffTrackerContent(review.source);
117
+ }
118
+
119
+ function dropParkedWorkerReviews(ownerSessionId) {
120
+ for (const review of _parkedWorkerReviews.get(ownerSessionId)?.values() || []) releaseParkedReview(review);
121
+ _parkedWorkerReviews.delete(ownerSessionId);
122
+ }
101
123
 
102
124
  function trimTurnCache() {
103
125
  while (_turnsBySession.size > TURN_CACHE_MAX) {
104
126
  const oldest = _turnsBySession.keys().next().value;
105
127
  if (oldest === undefined) break;
106
128
  _turnsBySession.delete(oldest);
129
+ dropParkedWorkerReviews(oldest);
107
130
  for (const [sessionId, tracker] of _diffTrackersBySession) {
108
131
  if (sessionId === oldest || tracker.ownerSessionId === oldest) {
109
132
  _diffTrackersBySession.delete(sessionId);
133
+ // A bound worker's content is read by nothing else; the Lead's own
134
+ // tracker may still be sealing, so it is only unregistered.
135
+ if (sessionId !== oldest) releaseDiffTrackerContent(tracker);
110
136
  }
111
137
  }
112
138
  }
139
+ // Parked reviews of a Lead with no turn in memory (a worker resumed after a
140
+ // restart) are bounded on their own: oldest owner first.
141
+ while (_parkedWorkerReviews.size > TURN_CACHE_MAX) {
142
+ const oldest = _parkedWorkerReviews.keys().next().value;
143
+ if (oldest === undefined) break;
144
+ dropParkedWorkerReviews(oldest);
145
+ }
113
146
  }
114
147
 
115
148
  function mergePatches(values) {
@@ -694,13 +727,17 @@ function mergeTrackedContent(target, source) {
694
727
  }
695
728
  }
696
729
 
697
- function trimAgentReviews(turn) {
730
+ /** Bound worker reviews keyed by worker (a turn's agents, an owner's parked
731
+ * reviews): oldest out first, by count and retained patch bytes. */
732
+ function trimWorkerReviews(reviews, evicted = () => {}) {
698
733
  const retainedBytes = () =>
699
- [...turn.agents.values()].reduce((total, review) => total + Buffer.byteLength(review.patch || '', 'utf8'), 0);
700
- while (turn.agents.size > MAX_AGENT_REVIEWS_PER_TURN || retainedBytes() > MAX_AGENT_REVIEW_BYTES_PER_TURN) {
701
- const oldest = turn.agents.keys().next().value;
734
+ [...reviews.values()].reduce((total, review) => total + Buffer.byteLength(review.patch || '', 'utf8'), 0);
735
+ while (reviews.size > MAX_AGENT_REVIEWS_PER_TURN || retainedBytes() > MAX_AGENT_REVIEW_BYTES_PER_TURN) {
736
+ const oldest = reviews.keys().next().value;
702
737
  if (oldest === undefined) break;
703
- turn.agents.delete(oldest);
738
+ const review = reviews.get(oldest);
739
+ reviews.delete(oldest);
740
+ evicted(review);
704
741
  }
705
742
  }
706
743
 
@@ -740,6 +777,7 @@ function continueTurnSnapshot(ownerSessionId) {
740
777
  tracker.sealed = false;
741
778
  tracker.sealEpoch += 1;
742
779
  markWorktreeContention(ownerSessionId, tracker);
780
+ adoptParkedWorkerReviews(ownerSessionId, turn, tracker);
743
781
  return true;
744
782
  }
745
783
 
@@ -756,16 +794,8 @@ export async function beginTurnSnapshot(worktree, sessionId, options = {}) {
756
794
  // Turn start and Undo/Reapply share the per-session lock: a start waits for
757
795
  // an in-flight mutation, and a mutation queued after this start re-checks
758
796
  // under the lock and finds the turn running.
759
- // Late-worker re-seals still queued for the previous turn run first, in
760
- // this same step — including one a worker queued while an earlier one ran —
761
- // so the workers' edits stay in that turn's record and nothing the new turn
762
- // writes can reach it. (A queued re-seal implies a chain.)
763
797
  const { token, tracker } = _changeMutations.has(ownerSessionId)
764
- ? await serializeChangeMutation(ownerSessionId, async () => {
765
- const previous = _diffTrackersBySession.get(ownerSessionId);
766
- while (previous?.resealQueued) await runQueuedReseal(ownerSessionId, previous).catch(() => {});
767
- return openTurnSnapshot(worktree, ownerSessionId, options);
768
- })
798
+ ? await serializeChangeMutation(ownerSessionId, async () => openTurnSnapshot(worktree, ownerSessionId, options))
769
799
  : openTurnSnapshot(worktree, ownerSessionId, options);
770
800
  if (!tracker) return token;
771
801
  try {
@@ -795,15 +825,15 @@ function openTurnSnapshot(worktree, ownerSessionId, options) {
795
825
  // worktree lock ahead of this turn's baseline.
796
826
  const prior = _diffTrackersBySession.get(ownerSessionId);
797
827
  if (prior && prior.ownerGeneration > 0 && !prior.sealed) void sealTurn(ownerSessionId, prior).catch(() => {});
798
- for (const [trackedSessionId, tracker] of _diffTrackersBySession) {
799
- if (tracker.ownerSessionId === ownerSessionId) _diffTrackersBySession.delete(trackedSessionId);
800
- }
828
+ // Workers still running keep their bound trackers across the new turn:
829
+ // their edits join whichever turn is running when they complete.
801
830
  _turnsBySession.delete(ownerSessionId);
802
- _turnsBySession.set(ownerSessionId, {
831
+ const turn = {
803
832
  generation,
804
833
  checkpointId,
805
834
  agents: new Map(),
806
- });
835
+ };
836
+ _turnsBySession.set(ownerSessionId, turn);
807
837
  const worktreeRequest = pathKey(worktree);
808
838
  const tracker = resetDiffTracker(ownerSessionId, {
809
839
  ownerGeneration: generation,
@@ -812,7 +842,10 @@ function openTurnSnapshot(worktree, ownerSessionId, options) {
812
842
  worktreeRoot: resolve(clean(worktree) || '.'),
813
843
  });
814
844
  trimTurnCache();
815
- if (tracker) markWorktreeContention(ownerSessionId, tracker);
845
+ if (tracker) {
846
+ markWorktreeContention(ownerSessionId, tracker);
847
+ adoptParkedWorkerReviews(ownerSessionId, turn, tracker);
848
+ }
816
849
  _turnOpenedHookForTest?.(ownerSessionId, checkpointId);
817
850
  return { token: { generation, checkpointId }, tracker };
818
851
  }
@@ -824,7 +857,9 @@ export function _setTurnOpenedHookForTest(hook) {
824
857
  _turnOpenedHookForTest = typeof hook === 'function' ? hook : null;
825
858
  }
826
859
 
827
- /** Bind one worker execution to the Lead turn that launched it. */
860
+ /** Bind one worker execution to its Lead. A Lead with no turn in memory (a
861
+ * worker resumed after a restart) binds generation 0: its completion waits
862
+ * for the Lead's next turn. */
828
863
  export function beginAgentTurnReview(ownerSessionId, childSessionId, meta = {}) {
829
864
  if (DISABLED) return null;
830
865
  const owner = clean(ownerSessionId);
@@ -832,24 +867,21 @@ export function beginAgentTurnReview(ownerSessionId, childSessionId, meta = {})
832
867
  // Durable: the worker's edits join the Lead's session review even when it
833
868
  // runs its own session turn, and whatever the turn's agent list keeps.
834
869
  recordReviewOwner(child, owner);
835
- const turn = _turnsBySession.get(owner);
836
- if (!owner || !child || !turn) return null;
870
+ if (!owner || !child) return null;
871
+ const generation = _turnsBySession.get(owner)?.generation || 0;
837
872
  const existingTracker = _diffTrackersBySession.get(child);
838
- if (
839
- !existingTracker ||
840
- existingTracker.ownerSessionId !== owner ||
841
- existingTracker.ownerGeneration !== turn.generation
842
- ) {
873
+ if (!existingTracker || existingTracker.ownerSessionId !== owner || existingTracker.ownerGeneration !== generation) {
843
874
  resetDiffTracker(child, {
844
875
  ownerSessionId: owner,
845
- ownerGeneration: turn.generation,
876
+ ownerGeneration: generation,
846
877
  });
847
878
  }
848
879
  _agentTurnSeq += 1;
849
880
  return {
850
881
  id: `agent-review-${_agentTurnSeq}`,
851
882
  ownerSessionId: owner,
852
- generation: turn.generation,
883
+ generation,
884
+ historyEpoch: _historyEpochs.get(owner) || 0,
853
885
  sessionId: child,
854
886
  agent: clean(meta.agent) || null,
855
887
  tag: clean(meta.tag) || null,
@@ -857,62 +889,145 @@ export function beginAgentTurnReview(ownerSessionId, childSessionId, meta = {})
857
889
  };
858
890
  }
859
891
 
860
- /** Freeze successful worker apply_patch diffs into their owning Lead turn.
861
- * Everything a seal reads — the agent entry, the worker's owned paths and a
862
- * late re-seal — is published before this returns, so a seal started right
863
- * after it already includes the worker. */
892
+ /** The Lead turn is running: not sealed, not sealing. */
893
+ const turnRunning = (turn, tracker) =>
894
+ Boolean(turn && tracker) &&
895
+ tracker.ownerGeneration === turn.generation &&
896
+ !tracker.sealed &&
897
+ !tracker.freezing &&
898
+ !tracker.discarded;
899
+
900
+ /** Attribute one completed worker review to `turn` and fold its own segments,
901
+ * owned paths and counts into the turn's tracker. */
902
+ function joinWorkerReview(turn, ownerTracker, review) {
903
+ const { key, source, tracked, patch } = review;
904
+ const prior = turn.agents.get(key);
905
+ if (!patch) {
906
+ if (!(tracked && prior)) return false;
907
+ turn.agents.delete(key);
908
+ return true;
909
+ }
910
+ turn.agents.delete(key);
911
+ turn.agents.set(key, {
912
+ sessionId: key,
913
+ agent: review.agent || prior?.agent || null,
914
+ tag: review.tag || prior?.tag || null,
915
+ patch: tracked ? patch : mergePatches([prior?.patch, patch]),
916
+ });
917
+ if (source && ownerTracker !== source) foldWorkerTracker(ownerTracker, source);
918
+ trimWorkerReviews(turn.agents);
919
+ return true;
920
+ }
921
+
922
+ /** Fold a finished worker's owned paths, counts and own segments into
923
+ * `target`. */
924
+ function foldWorkerTracker(target, source) {
925
+ for (const path of source.ownedPaths) target.ownedPaths.add(path);
926
+ mergeOwnStats(target.ownStats, source.ownStats);
927
+ mergeTrackedContent(target, source);
928
+ }
929
+
930
+ /** One parked review of a worker that completed again before its Lead's next
931
+ * turn: both runs' sources fold into one tracker this review owns — the
932
+ * older run's own bound tracker, else a fresh one, never a worker session's
933
+ * own tracker — exactly as both would have joined a running turn. The patch
934
+ * is the merged tracker's cumulative view when tracked, else the merged
935
+ * apply_patch diffs. */
936
+ function mergeParkedReview(prior, review) {
937
+ const sources = [prior.source, review.source].filter(Boolean);
938
+ let source = null;
939
+ if (sources.length > 0) {
940
+ source = prior.release && prior.source ? prior.source : createDiffTracker();
941
+ for (const other of sources) if (other !== source) foldWorkerTracker(source, other);
942
+ }
943
+ releaseParkedReview(review);
944
+ const tracked = prior.tracked || review.tracked;
945
+ return {
946
+ key: review.key,
947
+ source,
948
+ tracked,
949
+ patch: tracked ? trackerView(source).patch : mergePatches([prior.patch, review.patch]),
950
+ agent: review.agent || prior.agent,
951
+ tag: review.tag || prior.tag,
952
+ release: Boolean(source),
953
+ };
954
+ }
955
+
956
+ /** Park a completed worker review for its Lead's next turn. A newer
957
+ * completion of the same worker merges into the parked one (every run's
958
+ * edits are kept, as a running turn keeps them); the parked reviews are
959
+ * bounded by the turn's own limits, and an evicted one releases its tracker
960
+ * content. */
961
+ function parkWorkerReview(ownerSessionId, review) {
962
+ const parked = _parkedWorkerReviews.get(ownerSessionId) || new Map();
963
+ const prior = parked.get(review.key);
964
+ if (!prior && !review.patch) {
965
+ releaseParkedReview(review);
966
+ return false;
967
+ }
968
+ parked.delete(review.key);
969
+ parked.set(review.key, prior ? mergeParkedReview(prior, review) : review);
970
+ trimWorkerReviews(parked, releaseParkedReview);
971
+ _parkedWorkerReviews.set(ownerSessionId, parked);
972
+ trimTurnCache();
973
+ return true;
974
+ }
975
+
976
+ /** A turn that just opened (or reopened) takes the worker reviews that
977
+ * completed while its Lead had no running turn. */
978
+ function adoptParkedWorkerReviews(ownerSessionId, turn, tracker) {
979
+ const parked = _parkedWorkerReviews.get(ownerSessionId);
980
+ if (!parked) return;
981
+ _parkedWorkerReviews.delete(ownerSessionId);
982
+ for (const review of parked.values()) {
983
+ joinWorkerReview(turn, tracker, review);
984
+ releaseParkedReview(review);
985
+ }
986
+ }
987
+
988
+ /** Hand a finished worker's own edits (its tracker, else its apply_patch
989
+ * diffs) to its Lead: into the Lead turn running now, or — when none runs —
990
+ * parked for the Lead's next turn to open or reopen. Everything a seal
991
+ * reads is published before this returns, so a seal started right after it
992
+ * already includes the worker. */
864
993
  export function completeAgentTurnReview(handle, patches = []) {
865
994
  if (!handle || handle.completed === true) return false;
866
995
  handle.completed = true;
867
996
  const ownerSessionId = clean(handle.ownerSessionId);
868
997
  const key = clean(handle.sessionId) || handle.id;
869
- const turn = _turnsBySession.get(ownerSessionId);
870
998
  const tracker = _diffTrackersBySession.get(key);
871
999
  const matchingTracker =
872
1000
  tracker && tracker.ownerSessionId === ownerSessionId && tracker.ownerGeneration === handle.generation;
1001
+ // This execution is over: its bound tracker leaves the registry, so a next
1002
+ // execution of the worker starts its own.
1003
+ if (matchingTracker) _diffTrackersBySession.delete(key);
1004
+ // The Lead's change history was deleted after this worker was bound.
1005
+ if ((_historyEpochs.get(ownerSessionId) || 0) !== (handle.historyEpoch || 0)) {
1006
+ if (matchingTracker) releaseDiffTrackerContent(tracker);
1007
+ return false;
1008
+ }
873
1009
  // A worker that runs its own session turn replaces the bound tracker with
874
1010
  // its turn's own one (no owner). beginAgentTurnReview reset any older
875
1011
  // tracker, so an unowned one here holds this execution's edits.
876
1012
  const source = matchingTracker || (tracker && !tracker.ownerSessionId) ? tracker : null;
1013
+ const tracked = Boolean(source) && source.revision > 0;
1014
+ const review = {
1015
+ key,
1016
+ source,
1017
+ tracked,
1018
+ patch: tracked ? trackerView(source).patch : mergePatches(patches),
1019
+ agent: handle.agent,
1020
+ tag: handle.tag,
1021
+ release: Boolean(matchingTracker),
1022
+ };
1023
+ const turn = _turnsBySession.get(ownerSessionId);
877
1024
  const ownerTracker = _diffTrackersBySession.get(ownerSessionId);
878
- try {
879
- if (!turn || turn.generation !== handle.generation) return false;
880
- const prior = turn.agents.get(key);
881
- const tracked = Boolean(source) && source.revision > 0;
882
- const patch = tracked ? trackerView(source).patch : mergePatches(patches);
883
- if (!patch) {
884
- if (tracked && prior) {
885
- turn.agents.delete(key);
886
- return true;
887
- }
888
- return false;
889
- }
890
- turn.agents.delete(key);
891
- turn.agents.set(key, {
892
- sessionId: key,
893
- agent: handle.agent || prior?.agent || null,
894
- tag: handle.tag || prior?.tag || null,
895
- patch: tracked ? patch : mergePatches([prior?.patch, patch]),
896
- });
897
- if (source && ownerTracker && ownerTracker !== source) {
898
- for (const path of source.ownedPaths) ownerTracker.ownedPaths.add(path);
899
- mergeOwnStats(ownerTracker.ownStats, source.ownStats);
900
- mergeTrackedContent(ownerTracker, source);
901
- }
902
- trimAgentReviews(turn);
903
- // A worker that finishes after its Lead turn sealed (or while it seals)
904
- // re-seals that turn's change record (its edits are part of the turn's net
905
- // worktree diff).
906
- if (ownerTracker && ownerTracker !== tracker && ownerTracker.ownerGeneration === handle.generation) {
907
- resealTurn(ownerSessionId, ownerTracker);
908
- }
909
- return true;
910
- } finally {
911
- if (matchingTracker && _diffTrackersBySession.get(key) === tracker) {
912
- releaseDiffTrackerContent(tracker);
913
- _diffTrackersBySession.delete(key);
914
- }
1025
+ if (turnRunning(turn, ownerTracker)) {
1026
+ const joined = joinWorkerReview(turn, ownerTracker, review);
1027
+ releaseParkedReview(review);
1028
+ return joined;
915
1029
  }
1030
+ return parkWorkerReview(ownerSessionId, review);
916
1031
  }
917
1032
 
918
1033
  /** Persist the durable half of a finished review: the baseline tree hash plus
@@ -1091,15 +1206,12 @@ const pendingFreezeKey = (ownerSessionId, checkpointId) => `${ownerSessionId}\0$
1091
1206
  * Seal a Lead turn: capture the worktree one last time, persist the review
1092
1207
  * baseline and freeze the change record. Single-flight per tracker; a seal
1093
1208
  * requested while another runs waits for it and then seals again only if a
1094
- * completion wake reopened the turn meanwhile. `reseal` (a late worker) only
1095
- * re-freezes a turn that is still sealed, after any seal in flight: that one
1096
- * captured its agents before the worker was attributed.
1209
+ * completion wake reopened the turn meanwhile.
1097
1210
  */
1098
- function sealTurn(ownerSessionId, tracker, { reseal = false } = {}) {
1211
+ function sealTurn(ownerSessionId, tracker) {
1099
1212
  // Its history was deleted (clearSessionChangeHistory): never written again.
1100
1213
  if (tracker.discarded) return Promise.resolve();
1101
1214
  if (tracker.freezing) {
1102
- if (reseal) return tracker.freezing.then(() => sealTurn(ownerSessionId, tracker, { reseal: true }));
1103
1215
  return tracker.freezing.then(() => (tracker.sealed ? undefined : sealTurn(ownerSessionId, tracker)));
1104
1216
  }
1105
1217
  const epoch = tracker.sealEpoch;
@@ -1114,9 +1226,7 @@ function sealTurn(ownerSessionId, tracker, { reseal = false } = {}) {
1114
1226
  // Best-effort refresh: seal with the last captured worktree state.
1115
1227
  }
1116
1228
  }
1117
- if (tracker.discarded || tracker.sealEpoch !== epoch || (reseal && !tracker.sealed)) return;
1118
- // A re-seal never rewrites a turn once the next one opened.
1119
- if (reseal && turnSuperseded(ownerSessionId, tracker)) return;
1229
+ if (tracker.discarded || tracker.sealEpoch !== epoch) return;
1120
1230
  tracker.sealed = true;
1121
1231
  await persistTurnSnapshotRecord(ownerSessionId, tracker);
1122
1232
  try {
@@ -1124,8 +1234,8 @@ function sealTurn(ownerSessionId, tracker, { reseal = false } = {}) {
1124
1234
  } catch {
1125
1235
  /* the change record adds Undo history; it never fails a turn */
1126
1236
  }
1127
- // The bounded own before/after content stays until the next turn: a late
1128
- // worker's re-seal and an Undo/Reapply of this turn fold into it.
1237
+ // The bounded own before/after content stays until the next turn: a
1238
+ // completion wake reopening it and an Undo/Reapply of this turn fold into it.
1129
1239
  })().finally(() => {
1130
1240
  if (tracker.freezing === run) tracker.freezing = null;
1131
1241
  const count = (_pendingFreezes.get(key) || 1) - 1;
@@ -1136,31 +1246,6 @@ function sealTurn(ownerSessionId, tracker, { reseal = false } = {}) {
1136
1246
  return run;
1137
1247
  }
1138
1248
 
1139
- function resealTurn(ownerSessionId, tracker) {
1140
- // A turn still running seals with every published agent; one sealed or
1141
- // sealing queues its re-seal (sealTurn runs it after a seal in flight).
1142
- if (!(tracker.sealed || tracker.freezing) || !clean(tracker.checkpointId) || tracker.resealQueued) return;
1143
- // On the session lock: an in-flight Undo/Reapply lands before the re-freeze
1144
- // reads the worktree. A turn start queued ahead of it runs it before opening
1145
- // (beginTurnSnapshot), so one queued re-seal covers every late worker.
1146
- tracker.resealQueued = true;
1147
- void serializeChangeMutation(ownerSessionId, () => runQueuedReseal(ownerSessionId, tracker)).catch(() => {});
1148
- }
1149
-
1150
- /** True once a newer turn of the session opened over `tracker`'s. */
1151
- function turnSuperseded(ownerSessionId, tracker) {
1152
- const turn = _turnsBySession.get(ownerSessionId);
1153
- return Boolean(turn) && turn.generation !== tracker.ownerGeneration;
1154
- }
1155
-
1156
- /** Run `tracker`'s queued re-seal once, whichever lock step reaches it first. */
1157
- function runQueuedReseal(ownerSessionId, tracker) {
1158
- if (!tracker.resealQueued) return undefined;
1159
- tracker.resealQueued = false;
1160
- if (turnSuperseded(ownerSessionId, tracker)) return undefined;
1161
- return sealTurn(ownerSessionId, tracker, { reseal: true });
1162
- }
1163
-
1164
1249
  /** Freeze a completed (or cancelled) Lead turn while retaining only its
1165
1250
  * bounded review data. `expected` — the `{ generation }` beginTurnSnapshot
1166
1251
  * resolved to — makes a delayed call a no-op once a newer turn began. */
@@ -1718,13 +1803,52 @@ export async function getTurnChanges(sessionId, options = {}) {
1718
1803
  let status = 'missing';
1719
1804
  if (ownerSessionId && turnChangesPending(ownerSessionId, checkpointId)) status = 'pending';
1720
1805
  else if (record) status = record.files?.length || record.outside?.length ? 'ready' : 'none';
1721
- return turnChangesReply({
1806
+ const reply = turnChangesReply({
1722
1807
  status,
1723
1808
  checkpointId,
1724
1809
  record,
1725
1810
  patch: options?.patch === true,
1726
1811
  file: clean(options?.file) || null,
1727
1812
  });
1813
+ return status === 'ready' ? withIgnoredChangeFiles(ownerSessionId, reply) : reply;
1814
+ }
1815
+
1816
+ // The gitignored files of each answered record revision (build output a file
1817
+ // tool wrote): flagged `ignored` so a viewer can leave them out of its turn
1818
+ // previews. Asked of Git once per revision; bounded.
1819
+ const IGNORED_CHANGE_CACHE_LIMIT = 200;
1820
+ const _ignoredChangeFiles = new Map();
1821
+
1822
+ async function ignoredChangePaths(key, root, paths) {
1823
+ const cached = _ignoredChangeFiles.get(key);
1824
+ if (cached) return cached;
1825
+ let ignored = new Set();
1826
+ try {
1827
+ const result = await runGitOffThread(['check-ignore', '-z', '--stdin'], {
1828
+ cwd: root,
1829
+ input: `${paths.join('\0')}\0`,
1830
+ timeoutMs: 10_000,
1831
+ maxBytes: 4 * 1024 * 1024,
1832
+ });
1833
+ // 0: some are ignored, 1: none; anything else (no repository) flags none.
1834
+ if (result.code === 0) ignored = new Set(result.stdout.split('\0').filter(Boolean));
1835
+ } catch {
1836
+ // No Git to ask: nothing is flagged.
1837
+ }
1838
+ _ignoredChangeFiles.set(key, ignored);
1839
+ while (_ignoredChangeFiles.size > IGNORED_CHANGE_CACHE_LIMIT) {
1840
+ _ignoredChangeFiles.delete(_ignoredChangeFiles.keys().next().value);
1841
+ }
1842
+ return ignored;
1843
+ }
1844
+
1845
+ async function withIgnoredChangeFiles(ownerSessionId, reply) {
1846
+ const paths = reply.files.map((file) => file.path).filter((path) => path && !isAbsolute(path));
1847
+ if (!reply.root || paths.length === 0) return reply;
1848
+ const key = `${ownerSessionId}\0${reply.checkpointId}\0${reply.revision}\0${reply.root}`;
1849
+ const ignored = await ignoredChangePaths(key, reply.root, paths);
1850
+ if (ignored.size === 0) return reply;
1851
+ return { ...reply, files: reply.files.map((file) => (ignored.has(file.path) ? { ...file, ignored: true } : file)) };
1728
1852
  }
1729
1853
 
1730
1854
  function serializeChangeMutation(ownerSessionId, task) {
@@ -1871,11 +1995,13 @@ async function mutateTurnChanges(sessionId, checkpointIdValue, fileValue, revisi
1871
1995
  if (targets.length === 0) throw new Error(`this turn has no changes to be ${verb}`);
1872
1996
  }
1873
1997
  const updated = await mutateRecordFiles(ownerSessionId, record, targets, reverse);
1874
- return turnChangesReply({
1998
+ const reply = turnChangesReply({
1875
1999
  status: updated ? 'ready' : 'missing',
1876
2000
  checkpointId,
1877
2001
  record: withUnknownChangeStates(ownerSessionId, updated),
1878
2002
  });
2003
+ // The viewer caches this reply as it caches a read: the same `ignored` flags.
2004
+ return updated ? withIgnoredChangeFiles(ownerSessionId, reply) : reply;
1879
2005
  });
1880
2006
  }
1881
2007
 
@@ -1905,20 +2031,8 @@ export async function clearSessionChangeHistory(sessionId) {
1905
2031
  throw new Error('change history cannot be deleted while this session is running a turn');
1906
2032
  }
1907
2033
  // Invalidate every producer of the old history BEFORE the awaited
1908
- // deletion: a worker finishing meanwhile finds no turn to fold into, and
1909
- // a re-seal it (or anything earlier) already queued finds its tracker
1910
- // discarded, so nothing begun before the delete can write it back.
1911
- for (const [trackedSessionId, tracker] of _diffTrackersBySession) {
1912
- if (trackedSessionId !== ownerSessionId && tracker.ownerSessionId !== ownerSessionId) continue;
1913
- tracker.discarded = true;
1914
- tracker.resealQueued = false;
1915
- releaseDiffTrackerContent(tracker);
1916
- _diffTrackersBySession.delete(trackedSessionId);
1917
- }
1918
- _turnsBySession.delete(ownerSessionId);
1919
- for (const key of _unknownChangeStates.keys()) {
1920
- if (key.startsWith(`${ownerSessionId}\0`)) _unknownChangeStates.delete(key);
1921
- }
2034
+ // deletion, so nothing begun before the delete can write it back.
2035
+ discardSessionChangeState(ownerSessionId);
1922
2036
  // The session's first-seen baselines go too: later edits start fresh ones.
1923
2037
  const [turns] = await Promise.all([
1924
2038
  clearStoredChangeHistory(ownerSessionId),
@@ -1928,12 +2042,51 @@ export async function clearSessionChangeHistory(sessionId) {
1928
2042
  });
1929
2043
  }
1930
2044
 
2045
+ /** Release a Lead session's in-memory change state: a worker bound before
2046
+ * this finds its history epoch moved on (its completion parks nothing), its
2047
+ * parked worker reviews are dropped, and its own and bound worker trackers
2048
+ * are discarded (anything earlier that seals writes nothing). */
2049
+ function discardSessionChangeState(ownerSessionId) {
2050
+ _historyEpochs.set(ownerSessionId, (_historyEpochs.get(ownerSessionId) || 0) + 1);
2051
+ dropParkedWorkerReviews(ownerSessionId);
2052
+ for (const [trackedSessionId, tracker] of _diffTrackersBySession) {
2053
+ if (trackedSessionId !== ownerSessionId && tracker.ownerSessionId !== ownerSessionId) continue;
2054
+ tracker.discarded = true;
2055
+ releaseDiffTrackerContent(tracker);
2056
+ _diffTrackersBySession.delete(trackedSessionId);
2057
+ }
2058
+ _turnsBySession.delete(ownerSessionId);
2059
+ for (const key of _unknownChangeStates.keys()) {
2060
+ if (key.startsWith(`${ownerSessionId}\0`)) _unknownChangeStates.delete(key);
2061
+ }
2062
+ }
2063
+
2064
+ /** The session was deleted: release its turn, parked worker reviews and
2065
+ * bound worker trackers (its stored records go with the session purge). */
2066
+ export function forgetTurnSnapshotSession(sessionId) {
2067
+ const ownerSessionId = clean(sessionId);
2068
+ if (ownerSessionId) discardSessionChangeState(ownerSessionId);
2069
+ }
2070
+
2071
+ export function _diffTrackerForTest(sessionId) {
2072
+ return _diffTrackersBySession.get(clean(sessionId));
2073
+ }
2074
+
2075
+ export function _parkedWorkerReviewsForTest(ownerSessionId) {
2076
+ return [...(_parkedWorkerReviews.get(clean(ownerSessionId))?.values() || [])].map(({ key, patch }) => ({
2077
+ key,
2078
+ patch,
2079
+ }));
2080
+ }
2081
+
1931
2082
  export function _resetTurnSnapshotForTest() {
1932
2083
  _turnsBySession.clear();
1933
2084
  _diffTrackersBySession.clear();
1934
2085
  _pendingFreezes.clear();
1935
2086
  _changeMutations.clear();
1936
2087
  _unknownChangeStates.clear();
2088
+ _parkedWorkerReviews.clear();
2089
+ _historyEpochs.clear();
1937
2090
  _turnOpenedHookForTest = null;
1938
2091
  _agentTurnSeq = 0;
1939
2092
  _resetTurnWorktreeSnapshotsForTest();
@@ -20,9 +20,10 @@
20
20
  */
21
21
 
22
22
  import { AsyncLocalStorage } from 'node:async_hooks';
23
- import { readFileSync, statSync, writeFileSync } from 'node:fs';
23
+ import { readFileSync, statSync } from 'node:fs';
24
24
  import { join, resolve } from 'node:path';
25
25
  import { homedir } from 'node:os';
26
+ import { holdsNoData, writeFileAtomicSync } from './atomic-file.mjs';
26
27
  import { resolvePluginData, mixdogRoot } from './plugin-paths.mjs';
27
28
 
28
29
  const _cwdOverride = new AsyncLocalStorage();
@@ -112,7 +113,10 @@ export function explicitSessionCwd() {
112
113
  if (sessionDir) return sessionDir;
113
114
  }
114
115
  try {
115
- const txt = readFileSync(_dataFile('user-cwd.txt'), 'utf8').trim();
116
+ const raw = readFileSync(_dataFile('user-cwd.txt'), 'utf8');
117
+ // A crash can leave NUL bytes in place of the path: that is no path.
118
+ if (holdsNoData(raw)) return null;
119
+ const txt = raw.trim();
116
120
  return (txt && _normalizePlatformCwd(txt)) || null;
117
121
  } catch {
118
122
  return null;
@@ -173,7 +177,7 @@ function _lastSessionCwdFile(keyPid) {
173
177
  */
174
178
  export function writeLastSessionCwd(cwd, keyPid) {
175
179
  try {
176
- writeFileSync(_lastSessionCwdFile(keyPid), String(cwd));
180
+ writeFileAtomicSync(_lastSessionCwdFile(keyPid), String(cwd));
177
181
  } catch {
178
182
  /* best-effort */
179
183
  }
@@ -188,7 +192,8 @@ export function writeLastSessionCwd(cwd, keyPid) {
188
192
  */
189
193
  export function readLastSessionCwd(keyPid) {
190
194
  try {
191
- return _existingDirectory(readFileSync(_lastSessionCwdFile(keyPid), 'utf8').trim());
195
+ const raw = readFileSync(_lastSessionCwdFile(keyPid), 'utf8');
196
+ return holdsNoData(raw) ? null : _existingDirectory(raw.trim());
192
197
  } catch {
193
198
  return null;
194
199
  }