mixdog 0.9.151 → 0.9.153

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 (307) hide show
  1. package/LICENSES/browser-import-NOTICE.txt +11 -0
  2. package/NOTICE.md +11 -0
  3. package/README.md +108 -68
  4. package/package.json +17 -4
  5. package/scripts/computer-task-cost-report.mjs +144 -0
  6. package/scripts/run-office-live-tests.mjs +32 -0
  7. package/scripts/tool-stress.mjs +0 -2
  8. package/src/agents/front-worker/AGENT.md +13 -0
  9. package/src/agents/front-worker/agent.json +6 -0
  10. package/src/agents/heavy-worker/AGENT.md +10 -11
  11. package/src/agents/heavy-worker/agent.json +1 -1
  12. package/src/agents/maintainer/AGENT.md +0 -2
  13. package/src/agents/reviewer/AGENT.md +9 -13
  14. package/src/agents/reviewer/agent.json +1 -1
  15. package/src/agents/security/AGENT.md +14 -0
  16. package/src/agents/security/agent.json +6 -0
  17. package/src/agents/worker/AGENT.md +8 -8
  18. package/src/agents/worker/agent.json +1 -1
  19. package/src/agents/writer/AGENT.md +14 -0
  20. package/src/agents/writer/agent.json +6 -0
  21. package/src/defaults/skills/setup/SKILL.md +18 -27
  22. package/src/headless-exec.mjs +3 -10
  23. package/src/headless-exec.test.mjs +2 -7
  24. package/src/rules/agent/00-common.md +4 -7
  25. package/src/rules/lead/01-general.md +0 -3
  26. package/src/rules/shared/10-tool-workflow.md +10 -2
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +183 -26
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.test.mjs +378 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +109 -0
  31. package/src/runtime/agent/orchestrator/cache-break-trace.test.mjs +115 -0
  32. package/src/runtime/agent/orchestrator/config.mjs +13 -68
  33. package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
  34. package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
  35. package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
  36. package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
  37. package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
  38. package/src/runtime/agent/orchestrator/providers/admission-scheduler-metrics.test.mjs +47 -0
  39. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +16 -3
  40. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
  41. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  43. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
  44. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
  45. package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -3
  47. package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
  48. package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
  49. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -9
  50. package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +59 -0
  51. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
  52. package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +71 -0
  53. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +18 -26
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
  56. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
  57. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
  58. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +47 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +103 -12
  60. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/openai-turn-state.mjs +68 -0
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs +122 -6
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +36 -11
  64. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +144 -93
  65. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +18 -13
  66. package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +288 -0
  67. package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
  68. package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
  69. package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
  70. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +86 -7
  71. package/src/runtime/agent/orchestrator/session/agent-loop.test.mjs +222 -0
  72. package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.mjs +61 -0
  73. package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.test.mjs +83 -0
  74. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
  75. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +26 -2
  76. package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +112 -0
  77. package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +192 -0
  78. package/src/runtime/agent/orchestrator/session/context-usage-restart.test.mjs +72 -0
  79. package/src/runtime/agent/orchestrator/session/context-utils.mjs +100 -15
  80. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +17 -1
  81. package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +74 -0
  82. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +12 -8
  83. package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +20 -1
  84. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +110 -2
  85. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +3 -16
  86. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.test.mjs +28 -0
  87. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
  88. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +6 -0
  89. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  90. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -0
  91. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +111 -39
  92. package/src/runtime/agent/orchestrator/session/manager/ask-session.test.mjs +46 -0
  93. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -6
  94. package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
  95. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +27 -3
  96. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +26 -54
  97. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +63 -1
  98. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +3 -0
  99. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -110
  100. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +580 -0
  101. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +593 -0
  102. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +119 -190
  103. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
  104. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +43 -8
  105. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +129 -12
  106. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -2
  107. package/src/runtime/agent/orchestrator/session/store/listing.mjs +8 -1
  108. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +15 -1
  109. package/src/runtime/agent/orchestrator/session/store-live-publication.test.mjs +83 -0
  110. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +36 -2
  111. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +157 -30
  112. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +64 -0
  113. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +57 -0
  114. package/src/runtime/agent/orchestrator/session/store-summary-visibility.test.mjs +219 -0
  115. package/src/runtime/agent/orchestrator/session/store.mjs +26 -2
  116. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +22 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
  118. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
  119. package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
  120. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
  121. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
  122. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
  123. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
  124. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
  125. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
  126. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
  127. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
  128. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +6 -6
  129. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
  130. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
  131. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
  132. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
  135. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
  136. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
  137. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  138. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  139. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +12 -0
  140. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  141. package/src/runtime/attachments/pdf-extract.mjs +3 -0
  142. package/src/runtime/attachments/pdfjs-runtime.mjs +13 -0
  143. package/src/runtime/bridge-clients.test.mjs +827 -0
  144. package/src/runtime/browser-bridge/action-schema.mjs +251 -0
  145. package/src/runtime/browser-bridge/client.mjs +152 -0
  146. package/src/runtime/browser-bridge/tool-defs.mjs +159 -0
  147. package/src/runtime/computer-bridge/action-schema.mjs +653 -0
  148. package/src/runtime/computer-bridge/client.mjs +231 -0
  149. package/src/runtime/computer-bridge/tool-defs.mjs +33 -0
  150. package/src/runtime/media/store.mjs +18 -8
  151. package/src/runtime/media/store.test.mjs +14 -0
  152. package/src/runtime/memory/index.mjs +1 -1
  153. package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
  154. package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
  155. package/src/runtime/office/assurance-benchmark.mjs +358 -0
  156. package/src/runtime/office/assurance.mjs +833 -0
  157. package/src/runtime/office/benchmark.mjs +496 -0
  158. package/src/runtime/office/capabilities.mjs +551 -0
  159. package/src/runtime/office/com-adapter.mjs +283 -0
  160. package/src/runtime/office/content-model.mjs +183 -0
  161. package/src/runtime/office/contract-benchmark.mjs +105 -0
  162. package/src/runtime/office/design-library.mjs +1562 -0
  163. package/src/runtime/office/design-system.mjs +1966 -0
  164. package/src/runtime/office/index.mjs +2413 -0
  165. package/src/runtime/office/journal.mjs +82 -0
  166. package/src/runtime/office/office-assurance.test.mjs +371 -0
  167. package/src/runtime/office/office-com-host.ps1 +3781 -0
  168. package/src/runtime/office/office-com-session-host.ps1 +855 -0
  169. package/src/runtime/office/office-live-runtime.test.mjs +819 -0
  170. package/src/runtime/office/office-runtime.test.mjs +2070 -0
  171. package/src/runtime/office/ooxml-validator.mjs +271 -0
  172. package/src/runtime/office/pagination.mjs +132 -0
  173. package/src/runtime/office/pdf-adapter.mjs +591 -0
  174. package/src/runtime/office/pdf-analysis.mjs +372 -0
  175. package/src/runtime/office/pdf-render.mjs +167 -0
  176. package/src/runtime/office/pdf-security.mjs +63 -0
  177. package/src/runtime/office/polish-benchmark.mjs +190 -0
  178. package/src/runtime/office/portable-ooxml.mjs +1867 -0
  179. package/src/runtime/office/quality-live-benchmark.mjs +486 -0
  180. package/src/runtime/office/quality-pipeline.mjs +136 -0
  181. package/src/runtime/office/tabular.mjs +302 -0
  182. package/src/runtime/office/templates/mixdog-executive.pptx +0 -0
  183. package/src/runtime/office/templates/mixdog-executive.pptx.mixdog.json +190 -0
  184. package/src/runtime/office/tool-defs.mjs +113 -0
  185. package/src/runtime/office/visual-diff.mjs +89 -0
  186. package/src/runtime/office/xlsx-assertions.mjs +172 -0
  187. package/src/runtime/office/xlsx-contract.mjs +147 -0
  188. package/src/runtime/shared/agent-route-config.mjs +9 -0
  189. package/src/runtime/shared/child-spawn-gate.mjs +17 -0
  190. package/src/runtime/shared/child-spawn-remote.mjs +69 -7
  191. package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
  192. package/src/runtime/shared/pristine-execution-contract.json +2 -1
  193. package/src/runtime/shared/provider-api-key.mjs +1 -0
  194. package/src/runtime/shared/session-runtime-health.mjs +91 -0
  195. package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
  196. package/src/runtime/shared/skill-document.mjs +86 -0
  197. package/src/runtime/shared/skill-document.test.mjs +81 -0
  198. package/src/runtime/shared/tool-execution-contract.mjs +16 -0
  199. package/src/runtime/shared/tool-surface.mjs +27 -0
  200. package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
  201. package/src/runtime/shared/turn-snapshot.mjs +32 -9
  202. package/src/runtime/shared/user-cwd.mjs +33 -14
  203. package/src/runtime/shared/user-cwd.test.mjs +43 -0
  204. package/src/session-runtime/agent-disable.test.mjs +20 -1
  205. package/src/session-runtime/context-status.mjs +9 -3
  206. package/src/session-runtime/cwd-plugins.mjs +26 -26
  207. package/src/session-runtime/cwd-plugins.test.mjs +75 -0
  208. package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
  209. package/src/session-runtime/deferred-tool-delta.mjs +104 -0
  210. package/src/session-runtime/deferred-tool-delta.test.mjs +40 -0
  211. package/src/session-runtime/global-extensions.mjs +26 -0
  212. package/src/session-runtime/global-extensions.test.mjs +28 -0
  213. package/src/session-runtime/global-settings-session-identity.test.mjs +76 -0
  214. package/src/session-runtime/goal-runtime.mjs +1141 -0
  215. package/src/session-runtime/goal-runtime.test.mjs +645 -0
  216. package/src/session-runtime/lifecycle-api.mjs +58 -11
  217. package/src/session-runtime/lifecycle-api.test.mjs +183 -0
  218. package/src/session-runtime/mcp-glue.mjs +49 -47
  219. package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
  220. package/src/session-runtime/plugin-mcp.mjs +96 -22
  221. package/src/session-runtime/prewarm.mjs +1 -10
  222. package/src/session-runtime/provider-request-snapshot.mjs +33 -9
  223. package/src/session-runtime/provider-request-snapshot.test.mjs +27 -5
  224. package/src/session-runtime/resource-api-global.test.mjs +109 -0
  225. package/src/session-runtime/resource-api.mjs +168 -116
  226. package/src/session-runtime/runtime-core.mjs +208 -29
  227. package/src/session-runtime/runtime-tunables.mjs +6 -8
  228. package/src/session-runtime/session-lifecycle.mjs +34 -12
  229. package/src/session-runtime/session-title.mjs +2 -2
  230. package/src/session-runtime/session-turn-api.mjs +37 -25
  231. package/src/session-runtime/skills-api.mjs +79 -25
  232. package/src/session-runtime/skills-api.test.mjs +99 -0
  233. package/src/session-runtime/tool-catalog-data.mjs +1 -1
  234. package/src/session-runtime/tool-catalog-schema.mjs +0 -5
  235. package/src/session-runtime/tool-catalog.mjs +42 -112
  236. package/src/session-runtime/tool-defs.mjs +6 -1
  237. package/src/session-runtime/tool-policy-surface.test.mjs +93 -17
  238. package/src/session-runtime/tool-surface.mjs +30 -8
  239. package/src/session-runtime/workflow-agents-api.mjs +8 -25
  240. package/src/session-runtime/workflow.mjs +8 -6
  241. package/src/standalone/agent-dispatch-broker.mjs +5 -9
  242. package/src/standalone/agent-tool/helpers.mjs +2 -2
  243. package/src/standalone/agent-tool/job-views.mjs +3 -1
  244. package/src/standalone/agent-tool/notify.mjs +11 -9
  245. package/src/standalone/agent-tool/notify.test.mjs +37 -0
  246. package/src/standalone/agent-tool/render.mjs +3 -0
  247. package/src/standalone/agent-tool/spawn-flow.mjs +61 -16
  248. package/src/standalone/agent-tool/spawn-flow.test.mjs +41 -0
  249. package/src/standalone/agent-tool/tool-def.mjs +1 -1
  250. package/src/standalone/agent-tool/worker-index.mjs +123 -0
  251. package/src/standalone/agent-tool/worker-index.test.mjs +132 -0
  252. package/src/standalone/agent-tool/worker-rows.mjs +2 -1
  253. package/src/standalone/agent-tool.mjs +57 -10
  254. package/src/standalone/daemon.mjs +113 -15
  255. package/src/standalone/hook-bus/config.mjs +1 -0
  256. package/src/standalone/plugin-admin.mjs +17 -0
  257. package/src/standalone/plugin-admin.test.mjs +7 -0
  258. package/src/standalone/provider-admin.mjs +1 -0
  259. package/src/standalone/session-protocol.mjs +5 -0
  260. package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
  261. package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
  262. package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
  263. package/src/standalone/session-runtime-host-factory.mjs +17 -0
  264. package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
  265. package/src/standalone/session-runtime-host-health.test.mjs +5 -0
  266. package/src/standalone/session-runtime-host.mjs +709 -58
  267. package/src/standalone/session-runtime-inline-host.mjs +258 -0
  268. package/src/standalone/session-runtime-inline-host.test.mjs +385 -0
  269. package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
  270. package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
  271. package/src/standalone/session-runtime-shard-host.test.mjs +642 -0
  272. package/src/standalone/session-runtime-shard-router.mjs +149 -0
  273. package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
  274. package/src/standalone/session-runtime-worker.mjs +302 -26
  275. package/src/standalone/session-service-agent-tree.test.mjs +984 -0
  276. package/src/standalone/session-service-goal-recovery.test.mjs +177 -0
  277. package/src/standalone/session-service.mjs +711 -11
  278. package/src/tui/app/app-view.jsx +1 -0
  279. package/src/tui/app/core-memory-picker.mjs +2 -0
  280. package/src/tui/app/extension-pickers.mjs +1 -1
  281. package/src/tui/app/maintenance-pickers.mjs +3 -3
  282. package/src/tui/app/model-options.mjs +2 -0
  283. package/src/tui/app/model-picker.mjs +14 -4
  284. package/src/tui/app/panel-handoff.test.mjs +311 -0
  285. package/src/tui/app/project-picker.mjs +1 -0
  286. package/src/tui/app/route-pickers.mjs +21 -6
  287. package/src/tui/app/settings-picker.mjs +26 -3
  288. package/src/tui/app/slash-commands.mjs +1 -0
  289. package/src/tui/app/slash-dispatch.mjs +73 -22
  290. package/src/tui/app/theme-effort-pickers.mjs +7 -2
  291. package/src/tui/components/Picker.jsx +10 -1
  292. package/src/tui/dist/index.mjs +187 -45
  293. package/src/tui/lib/voice-setup.mjs +4 -1
  294. package/src/tui/session/agent-message-source.test.mjs +37 -0
  295. package/src/tui/session/completion-card-restore.test.mjs +50 -0
  296. package/src/tui/session/context-state.mjs +3 -13
  297. package/src/tui/session/goal-continuation.mjs +141 -0
  298. package/src/tui/session/goal-continuation.test.mjs +203 -0
  299. package/src/tui/session/queue-helpers.mjs +5 -2
  300. package/src/tui/session/session-action-surface.test.mjs +190 -0
  301. package/src/tui/session/session-api-ext.mjs +48 -8
  302. package/src/tui/session/session-api.mjs +73 -1
  303. package/src/tui/session/session-flow.mjs +31 -1
  304. package/src/tui/session/turn.mjs +88 -15
  305. package/src/tui/session-local.mjs +39 -3
  306. package/src/vendor/statusline/src/gateway/route-meta.mjs +31 -0
  307. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
@@ -14,6 +14,11 @@ import { join } from 'path';
14
14
  // authoritative store uses, so the cold catalog cannot disagree with it.
15
15
  import { probePath, readTextFile, PROBE_PRESENT, PROBE_ABSENT } from './store/fs-probe.mjs';
16
16
  import { readTopLevelLifecycleRecord, isLifecycleUnreadable } from './lifecycle-scan.mjs';
17
+ import {
18
+ isAgentOnlySession,
19
+ isRootLeadSession,
20
+ sessionVisibility,
21
+ } from './store-summary-visibility.mjs';
17
22
 
18
23
  const SESSION_SUMMARY_INDEX_VERSION = 2;
19
24
  const DEAD_AGENT_STATUS =
@@ -45,7 +50,8 @@ const LEAD_OWNERS = new Set(['cli', 'user', 'mixdog']);
45
50
 
46
51
  function isLeadVisibleRow(row) {
47
52
  const owner = String(row.owner || 'user').trim().toLowerCase();
48
- if (owner && !LEAD_OWNERS.has(owner)) return false;
53
+ if (isAgentOnlySession(row)) return false;
54
+ if (!isRootLeadSession(row) && owner && !LEAD_OWNERS.has(owner)) return false;
49
55
  // Mirror listLeadSessions: a previewless zero-message row is an unusable
50
56
  // scratch (desktop boot leftovers, crashed first turns) — resuming it
51
57
  // shows an empty conversation, so the catalog hides it.
@@ -72,10 +78,35 @@ function positiveNumber(value, fallback = 0) {
72
78
  return Number.isFinite(number) && number > 0 ? number : fallback;
73
79
  }
74
80
 
81
+ function runtimeAlive(pid) {
82
+ const id = positiveNumber(pid, 0);
83
+ if (id <= 0 || id === process.pid) return true;
84
+ try {
85
+ process.kill(id, 0);
86
+ return true;
87
+ } catch (error) {
88
+ return error?.code === 'EPERM';
89
+ }
90
+ }
91
+
75
92
  function cleanValue(value) {
76
93
  return String(value || '').trim();
77
94
  }
78
95
 
96
+ function workerOwnerSessionId(row, session) {
97
+ return cleanValue(
98
+ row?.ownerSessionId || session?.ownerSessionId
99
+ || row?.parentSessionId || session?.parentSessionId,
100
+ ) || null;
101
+ }
102
+
103
+ function workerParentSessionId(row, session) {
104
+ return cleanValue(
105
+ row?.parentSessionId || session?.parentSessionId
106
+ || row?.ownerSessionId || session?.ownerSessionId,
107
+ ) || null;
108
+ }
109
+
79
110
  function archivedAgentNotification(content, sessionId) {
80
111
  const text = typeof content === 'string' ? content : '';
81
112
  const marker = '\n\nResult:\n';
@@ -265,23 +296,52 @@ export function storedLeadWorkerIndexPath() {
265
296
  return join(dataDir(), 'lead-workers.json');
266
297
  }
267
298
 
268
- /** Process-global active agent pool. Fresh child heartbeat sidecars are the
269
- * cross-process running source even when their durable session is detached
270
- * (`closed`) and a terminal reaper has already removed the worker-index row.
271
- * The child and Lead indexes remain additive for living rows (running or idle)
272
- * published before the heartbeat or by runtimes without a sidecar. No runtime
273
- * starts and durable session history is never projected into either pool. */
274
- export function listStoredAgentWorkers() {
299
+ function storedAgentWorkerIndexRows() {
275
300
  let parsed = null;
276
301
  try {
277
302
  parsed = JSON.parse(readFileSync(storedAgentWorkerIndexPath(), 'utf8'));
278
- } catch { /* heartbeat-backed rows remain authoritative without the index */ }
279
- const source = Array.isArray(parsed?.workers)
303
+ } catch {
304
+ return [];
305
+ }
306
+ return Array.isArray(parsed?.workers)
280
307
  ? parsed.workers
281
308
  : parsed?.workers && typeof parsed.workers === 'object'
282
309
  ? Object.values(parsed.workers)
283
310
  : [];
311
+ }
312
+
313
+ /** Lightweight ancestry seam used to migrate pre-parentSessionId summary
314
+ * rows. It reads only agent-workers.json and never opens session transcripts. */
315
+ export function listStoredAgentWorkerLinks() {
316
+ return storedAgentWorkerIndexRows()
317
+ .map((row) => {
318
+ if (!row || typeof row !== 'object') return null;
319
+ const sessionId = cleanValue(row.sessionId);
320
+ const parentSessionId = cleanValue(row.parentSessionId);
321
+ const ownerSessionId = cleanValue(row.ownerSessionId || row.parentSessionId);
322
+ if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || (!parentSessionId && !ownerSessionId)) {
323
+ return null;
324
+ }
325
+ return {
326
+ sessionId,
327
+ parentSessionId: parentSessionId || ownerSessionId,
328
+ ownerSessionId: ownerSessionId || parentSessionId,
329
+ };
330
+ })
331
+ .filter(Boolean);
332
+ }
333
+
334
+ /** Process-global active agent pool. Fresh child heartbeat sidecars are the
335
+ * cross-process running source even when their durable session is detached
336
+ * (`closed`) and a terminal reaper has already removed the worker-index row.
337
+ * The child and Lead indexes remain additive for living rows (running or idle)
338
+ * published before the heartbeat or by runtimes without a sidecar. No runtime
339
+ * starts and durable session history is never projected into either pool. */
340
+ export function listStoredAgentWorkers() {
341
+ const source = storedAgentWorkerIndexRows();
284
342
  const bySessionId = new Map();
343
+ const now = Date.now();
344
+ const heartbeatMtimes = sessionHeartbeatMtimes();
285
345
  for (const row of source) {
286
346
  if (!row || typeof row !== 'object') continue;
287
347
  // Cancelled rows stay visible so the 2-minute heartbeat lease cannot
@@ -300,28 +360,45 @@ export function listStoredAgentWorkers() {
300
360
  'utf8',
301
361
  ));
302
362
  } catch { /* a new row may precede its first session save */ }
363
+ const declaredStatus = cleanValue(row.status) || 'running';
364
+ const declaredStage = cleanValue(row.stage || row.status) || 'running';
365
+ const declaredWorking = WORKING_AGENT_STATUS.test(declaredStatus)
366
+ || WORKING_AGENT_STATUS.test(declaredStage);
367
+ const heartbeatAt = heartbeatMtimes.get(sessionId) || 0;
368
+ const heartbeatFresh = heartbeatAt > 0
369
+ && now - heartbeatAt <= AGENT_POOL_HEARTBEAT_FRESH_MS;
370
+ const updatedAt = Date.parse(cleanValue(row.updatedAt)) || 0;
371
+ const recentlyUpdated = updatedAt > 0
372
+ && now - updatedAt <= AGENT_POOL_HEARTBEAT_FRESH_MS;
373
+ const runtimePid = positiveNumber(row.runtimePid, 0);
374
+ const working = declaredWorking
375
+ && (!runtimePid || runtimeAlive(runtimePid))
376
+ && (heartbeatFresh || recentlyUpdated);
377
+ const projectedStatus = declaredWorking && !working ? 'idle' : declaredStatus;
378
+ const projectedStage = declaredWorking && !working ? 'idle' : declaredStage;
303
379
  bySessionId.set(sessionId, {
304
380
  tag,
305
381
  sessionId,
306
- ownerSessionId: cleanValue(
307
- row.ownerSessionId || row.parentSessionId
308
- || session?.ownerSessionId || session?.parentSessionId,
309
- ) || null,
382
+ // Root ownership and immediate ancestry are independent. Nested
383
+ // descendants keep the Lead/root owner while pointing at the Agent
384
+ // session that directly spawned them.
385
+ ownerSessionId: workerOwnerSessionId(row, session),
386
+ parentSessionId: workerParentSessionId(row, session),
310
387
  title: cleanValue(row.title || session?.title) || null,
311
388
  agent: cleanValue(row.agent || session?.agent) || null,
312
389
  provider: cleanValue(row.provider || session?.provider) || null,
313
390
  model: cleanValue(row.model || session?.model) || null,
314
391
  effort: cleanValue(row.effort || session?.effort) || null,
315
392
  fast: row.fast === true || session?.fast === true,
316
- status: cleanValue(row.status) || 'running',
317
- stage: cleanValue(row.stage || row.status) || 'running',
393
+ status: projectedStatus,
394
+ stage: projectedStage,
318
395
  startedAt: row.startedAt || row.createdAt || session?.createdAt || null,
319
- turnStartedAt: row.turnStartedAt || null,
396
+ turnStartedAt: working ? (row.turnStartedAt || null) : null,
320
397
  createdAt: row.createdAt || session?.createdAt || null,
321
398
  updatedAt: row.updatedAt || session?.updatedAt || null,
322
399
  idleSince: frozenIdleSince(
323
400
  sessionId,
324
- WORKING_AGENT_STATUS.test(cleanValue(row.stage || row.status)),
401
+ working,
325
402
  row.finishedAt || row.updatedAt || session?.updatedAt || null,
326
403
  ),
327
404
  reapAt: row.reapAt || null,
@@ -330,8 +407,6 @@ export function listStoredAgentWorkers() {
330
407
  taskId: cleanValue(row.task_id || row.taskId) || null,
331
408
  });
332
409
  }
333
- const now = Date.now();
334
- const heartbeatMtimes = sessionHeartbeatMtimes();
335
410
  for (const [sessionId, heartbeatAt] of heartbeatMtimes) {
336
411
  if (now - heartbeatAt > AGENT_POOL_HEARTBEAT_FRESH_MS) continue;
337
412
  let session;
@@ -343,13 +418,12 @@ export function listStoredAgentWorkers() {
343
418
  } catch {
344
419
  continue;
345
420
  }
346
- const ownerSessionId = cleanValue(
347
- session?.ownerSessionId || session?.parentSessionId,
348
- );
421
+ const current = bySessionId.get(sessionId) || {};
422
+ const ownerSessionId = workerOwnerSessionId(current, session);
423
+ const parentSessionId = workerParentSessionId(current, session);
349
424
  const owner = cleanValue(session?.owner).toLowerCase();
350
425
  const agent = cleanValue(session?.agent);
351
426
  if (!ownerSessionId || (owner !== 'agent' && (!agent || agent === 'lead'))) continue;
352
- const current = bySessionId.get(sessionId) || {};
353
427
  // The durable index row is authoritative for FINISHED work: a worker's
354
428
  // runtime unloads at turn end but its heartbeat sidecar stays fresh for
355
429
  // up to the 2-minute window, and overwriting an idle row to `running`
@@ -381,6 +455,7 @@ export function listStoredAgentWorkers() {
381
455
  || `${agent || 'agent'}:${sessionId}`,
382
456
  sessionId,
383
457
  ownerSessionId,
458
+ parentSessionId,
384
459
  title: cleanValue(session?.title) || current.title || null,
385
460
  agent: agent || current.agent || null,
386
461
  provider: cleanValue(session?.provider) || current.provider || null,
@@ -409,6 +484,7 @@ export function listStoredAgentWorkers() {
409
484
  || `${agent || 'agent'}:${sessionId}`,
410
485
  sessionId,
411
486
  ownerSessionId,
487
+ parentSessionId,
412
488
  title: cleanValue(session?.title) || current.title || null,
413
489
  agent: agent || current.agent || null,
414
490
  provider: cleanValue(session?.provider) || current.provider || null,
@@ -467,6 +543,7 @@ export function listStoredAgentWorkers() {
467
543
  tag: `lead:${sessionId}`,
468
544
  sessionId,
469
545
  ownerSessionId: sessionId,
546
+ parentSessionId: null,
470
547
  agent: 'lead',
471
548
  provider: cleanValue(row.provider) || null,
472
549
  model: cleanValue(row.model) || null,
@@ -548,7 +625,8 @@ function normalizedRow(row, heartbeatAt = 0) {
548
625
  sourceName: row.sourceName || null,
549
626
  sourceDelivery: row.sourceDelivery || null,
550
627
  scopeKey: row.scopeKey || null,
551
- ownerSessionId: row.ownerSessionId || null,
628
+ ownerSessionId: row.ownerSessionId || row.parentSessionId || null,
629
+ visibility: sessionVisibility(row),
552
630
  clientHostPid: positiveNumber(row.clientHostPid, 0) || null,
553
631
  cwd: row.cwd || '',
554
632
  desktopSession: desktopSession(row.desktopSession, row.cwd),
@@ -761,11 +839,47 @@ export async function readStoredSessionTranscript(id, options = {}) {
761
839
  // Same strict authority as the store, and the same fail-closed rule: an
762
840
  // absent, unreadable, ambiguous or foreign record yields no transcript.
763
841
  const read = readTextFile(join(dataDir(), 'sessions', `${sessionId}.json`));
764
- if (read.state === PROBE_ABSENT) return readArchivedAgentResult(sessionId);
842
+ if (read.state === PROBE_ABSENT) {
843
+ return options.metadataOnly === true ? null : readArchivedAgentResult(sessionId);
844
+ }
765
845
  if (read.state !== PROBE_PRESENT) return null;
766
846
  const record = readTopLevelLifecycleRecord(read.text);
767
847
  if (isLifecycleUnreadable(record) || record.id !== sessionId) return null;
768
848
  let session = record.doc;
849
+ if (options.metadataOnly === true) {
850
+ return {
851
+ id: sessionId,
852
+ sessionId,
853
+ owner: session.owner || null,
854
+ agent: session.agent || null,
855
+ parentSessionId: session.parentSessionId || null,
856
+ ownerSessionId: session.ownerSessionId || session.parentSessionId || null,
857
+ visibility: sessionVisibility(session),
858
+ agentTag: session.agentTag || null,
859
+ cwd: session.cwd || '',
860
+ provider: session.provider || null,
861
+ model: session.model || null,
862
+ presetName: session.presetName || session.profileId || null,
863
+ effort: session.effort || null,
864
+ fast: session.fast === true,
865
+ modelParameters: session.modelParameters || null,
866
+ taskType: session.taskType || null,
867
+ maxLoopIterations: session.maxLoopIterations ?? null,
868
+ permission: session.permission || null,
869
+ permissionMode: session.permissionMode || null,
870
+ toolPermission: session.toolPermission || null,
871
+ schemaAllowedTools: Array.isArray(session.schemaAllowedTools)
872
+ ? session.schemaAllowedTools
873
+ : null,
874
+ sourceType: session.sourceType || null,
875
+ sourceName: session.sourceName || null,
876
+ clientHostPid: session.clientHostPid || null,
877
+ createdAt: session.createdAt || null,
878
+ updatedAt: session.updatedAt || session.lastUsedAt || null,
879
+ status: session.status || (session.closed === true ? 'closed' : 'idle'),
880
+ closed: session.closed === true,
881
+ };
882
+ }
769
883
  const owner = cleanValue(session.owner).toLowerCase();
770
884
  const agent = cleanValue(session.agent).toLowerCase();
771
885
  const liveDetachedAgent = session.closed === true
@@ -774,11 +888,11 @@ export async function readStoredSessionTranscript(id, options = {}) {
774
888
  // Only a PROVABLY absent checkpoint skips recovery: an unreadable probe
775
889
  // must not silently downgrade an interrupted turn to a plain cold read.
776
890
  if (probePath(join(dataDir(), 'turn-checkpoints', `${sessionId}.json`)).state !== PROBE_ABSENT) {
891
+ const {
892
+ projectTurnCheckpointMessages,
893
+ readTurnCheckpoint,
894
+ } = await import('./manager/turn-checkpoint.mjs');
777
895
  if (liveDetachedAgent) {
778
- const {
779
- projectTurnCheckpointMessages,
780
- readTurnCheckpoint,
781
- } = await import('./manager/turn-checkpoint.mjs');
782
896
  const checkpoint = readTurnCheckpoint(sessionId);
783
897
  if (checkpoint) {
784
898
  session = {
@@ -789,6 +903,19 @@ export async function readStoredSessionTranscript(id, options = {}) {
789
903
  } else {
790
904
  const { recoverSessionAfterProcessRestart } = await import('./manager.mjs');
791
905
  session = recoverSessionAfterProcessRestart(sessionId) || session;
906
+ // A live foreign owner is intentionally not recovered/mutated.
907
+ // Project its durable working checkpoint read-only so the cold pane
908
+ // never repaints the stale pre-compaction session.messages while it
909
+ // waits for the owner's live-share frame.
910
+ if (session?.activeTurnCheckpoint) {
911
+ const checkpoint = readTurnCheckpoint(sessionId);
912
+ if (checkpoint) {
913
+ session = {
914
+ ...session,
915
+ messages: projectTurnCheckpointMessages(session, checkpoint),
916
+ };
917
+ }
918
+ }
792
919
  }
793
920
  }
794
921
  const {
@@ -581,3 +581,67 @@ test('cancel-unconfirmed stays distinguishable from a confirmed cancel under a f
581
581
  assert.notEqual(row?.status, 'cancelled');
582
582
  });
583
583
  });
584
+
585
+ test('stored Agent pool preserves root ownership and immediate nested parent links', () => {
586
+ withAgentPoolRoot('mixdog-agent-pool-nested-', (root) => {
587
+ writeAgentChild(root, 'child-direct', {
588
+ ownerSessionId: 'root-lead',
589
+ parentSessionId: 'root-lead',
590
+ tag: 'direct',
591
+ });
592
+ writeAgentChild(root, 'grandchild', {
593
+ ownerSessionId: 'root-lead',
594
+ parentSessionId: 'child-direct',
595
+ tag: 'nested',
596
+ closed: true,
597
+ status: 'closed',
598
+ stage: 'closed',
599
+ });
600
+ writeFileSync(join(root, 'sessions', 'root-lead.json'), JSON.stringify({
601
+ id: 'root-lead',
602
+ owner: 'user',
603
+ ownerSessionId: 'root-lead',
604
+ agent: 'lead',
605
+ status: 'idle',
606
+ messages: [{ role: 'user', content: 'root task' }],
607
+ }));
608
+ writeAgentWorkerIndex(root, {
609
+ direct: {
610
+ tag: 'direct',
611
+ sessionId: 'child-direct',
612
+ ownerSessionId: 'root-lead',
613
+ parentSessionId: 'root-lead',
614
+ agent: 'reviewer',
615
+ status: 'idle',
616
+ stage: 'idle',
617
+ },
618
+ });
619
+ writeFileSync(join(root, 'lead-workers.json'), JSON.stringify({
620
+ workers: {
621
+ root: {
622
+ sessionId: 'root-lead',
623
+ ownerSessionId: 'root-lead',
624
+ agent: 'lead',
625
+ status: 'idle',
626
+ stage: 'idle',
627
+ updatedAt: new Date().toISOString(),
628
+ reapAt: new Date(Date.now() + 60_000).toISOString(),
629
+ },
630
+ },
631
+ }));
632
+ // No worker-index row: the heartbeat/session fallback must carry the same
633
+ // production ancestry shape for a live nested descendant.
634
+ writeFreshHeartbeat(root, 'grandchild');
635
+
636
+ const rows = listStoredAgentWorkers();
637
+ const rootLead = rows.find((row) => row.sessionId === 'root-lead');
638
+ const direct = rows.find((row) => row.sessionId === 'child-direct');
639
+ const nested = rows.find((row) => row.sessionId === 'grandchild');
640
+ assert.equal(rootLead?.ownerSessionId, 'root-lead');
641
+ assert.equal(rootLead?.parentSessionId, null);
642
+ assert.equal(direct?.ownerSessionId, 'root-lead');
643
+ assert.equal(direct?.parentSessionId, 'root-lead');
644
+ assert.equal(nested?.ownerSessionId, 'root-lead');
645
+ assert.equal(nested?.parentSessionId, 'child-direct');
646
+ });
647
+ });
@@ -0,0 +1,57 @@
1
+ export const SESSION_VISIBILITY_ORDINARY = 'ordinary';
2
+ export const SESSION_VISIBILITY_AGENT_ONLY = 'agent-only';
3
+
4
+ const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
5
+
6
+ function clean(value) {
7
+ return String(value || '').trim();
8
+ }
9
+
10
+ export function linkedParentSessionId(value) {
11
+ if (!value || typeof value !== 'object') return '';
12
+ for (const candidate of [value.ownerSessionId, value.parentSessionId]) {
13
+ const id = clean(candidate);
14
+ if (SESSION_ID_PATTERN.test(id)) return id;
15
+ }
16
+ return '';
17
+ }
18
+
19
+ /** A root Lead may carry a self owner link, but it is not a child session. */
20
+ export function isRootLeadSession(value) {
21
+ if (!value || typeof value !== 'object') return false;
22
+ if (clean(value.agent).toLowerCase() !== 'lead') return false;
23
+ const id = clean(value.id);
24
+ const parentId = linkedParentSessionId(value);
25
+ return !parentId || parentId === id;
26
+ }
27
+
28
+ /** Backward-compatible classification for child records written before the
29
+ * durable visibility field existed. Both Agent ownership and an external,
30
+ * valid parent link are required so ownerless/root sessions remain ordinary. */
31
+ export function isCanonicalAgentChildSession(value) {
32
+ if (!value || typeof value !== 'object') return false;
33
+ const id = clean(value.id);
34
+ const parentId = linkedParentSessionId(value);
35
+ return clean(value.owner).toLowerCase() === 'agent'
36
+ && Boolean(parentId)
37
+ && parentId !== id;
38
+ }
39
+
40
+ export function sessionVisibility(value) {
41
+ // Root Lead sessions are ordinary catalog entries even if stale metadata
42
+ // accidentally retained an agent-only declaration.
43
+ if (isRootLeadSession(value)) return SESSION_VISIBILITY_ORDINARY;
44
+ const declared = clean(value?.visibility || value?.sessionVisibility).toLowerCase();
45
+ if (declared === SESSION_VISIBILITY_AGENT_ONLY || isCanonicalAgentChildSession(value)) {
46
+ return SESSION_VISIBILITY_AGENT_ONLY;
47
+ }
48
+ return SESSION_VISIBILITY_ORDINARY;
49
+ }
50
+
51
+ export function isAgentOnlySession(value) {
52
+ return sessionVisibility(value) === SESSION_VISIBILITY_AGENT_ONLY;
53
+ }
54
+
55
+ export function isOrdinarySession(value) {
56
+ return !isAgentOnlySession(value);
57
+ }
@@ -0,0 +1,219 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ import {
8
+ _normalizeSummaryIndex,
9
+ _sessionSummary,
10
+ } from './store-summary-index.mjs';
11
+ import {
12
+ listStoredAgentWorkerLinks,
13
+ listStoredAgentWorkers,
14
+ listStoredSessionSummaries as listOrdinarySummaries,
15
+ readStoredSessionTranscript,
16
+ storedSessionExists,
17
+ } from './store-summary-reader.mjs';
18
+ import {
19
+ listStoredSessions,
20
+ listStoredSessionSummaries as listInternalSummaries,
21
+ } from './store/listing.mjs';
22
+
23
+ function session(id, extra = {}) {
24
+ return {
25
+ id,
26
+ createdAt: 1_000,
27
+ updatedAt: 2_000,
28
+ lastUsedAt: 2_000,
29
+ status: 'idle',
30
+ owner: 'user',
31
+ messages: [{ role: 'user', content: `prompt for ${id}` }],
32
+ ...extra,
33
+ };
34
+ }
35
+
36
+ test('summary visibility is durable and classifies legacy Agent-owned children', () => {
37
+ const legacy = session('legacy-child', {
38
+ owner: 'agent',
39
+ parentSessionId: 'root-lead',
40
+ agent: 'reviewer',
41
+ });
42
+ const summary = _sessionSummary(legacy);
43
+ assert.equal(summary.ownerSessionId, 'root-lead');
44
+ assert.equal(summary.visibility, 'agent-only');
45
+
46
+ const [normalized] = _normalizeSummaryIndex({
47
+ version: 2,
48
+ rows: [{
49
+ ...summary,
50
+ visibility: undefined,
51
+ ownerSessionId: 'root-lead',
52
+ }],
53
+ }).rows;
54
+ assert.equal(normalized.visibility, 'agent-only');
55
+
56
+ const nested = _sessionSummary(session('nested-child', {
57
+ owner: 'agent',
58
+ parentSessionId: 'parent-child',
59
+ ownerSessionId: 'root-lead',
60
+ agent: 'reviewer',
61
+ visibility: 'agent-only',
62
+ provider: 'test-provider',
63
+ model: 'test-model',
64
+ presetName: 'Test route',
65
+ effort: 'high',
66
+ fast: true,
67
+ modelParameters: { temperature: 0.2 },
68
+ permissionMode: 'strict',
69
+ schemaAllowedTools: ['read', 'grep'],
70
+ taskType: 'review',
71
+ maxLoopIterations: 7,
72
+ }));
73
+ const [normalizedNested] = _normalizeSummaryIndex({
74
+ version: 2,
75
+ rows: [nested],
76
+ }).rows;
77
+ assert.equal(normalizedNested.parentSessionId, 'parent-child');
78
+ assert.equal(normalizedNested.ownerSessionId, 'root-lead');
79
+ assert.equal(normalizedNested.presetName, 'Test route');
80
+ assert.equal(normalizedNested.effort, 'high');
81
+ assert.equal(normalizedNested.fast, true);
82
+ assert.deepEqual(normalizedNested.modelParameters, { temperature: 0.2 });
83
+ assert.equal(normalizedNested.permissionMode, 'strict');
84
+ assert.deepEqual(normalizedNested.schemaAllowedTools, ['read', 'grep']);
85
+ assert.equal(normalizedNested.taskType, 'review');
86
+ assert.equal(normalizedNested.maxLoopIterations, 7);
87
+
88
+ const rootLead = _sessionSummary(session('root-lead', {
89
+ owner: 'agent',
90
+ ownerSessionId: 'root-lead',
91
+ agent: 'lead',
92
+ visibility: 'agent-only',
93
+ }));
94
+ assert.equal(rootLead.visibility, 'ordinary');
95
+ });
96
+
97
+ test('ordinary catalogs hide durable and legacy children while Agent and exact-ID discovery remain available', async () => {
98
+ const root = mkdtempSync(join(tmpdir(), 'mixdog-agent-only-catalog-'));
99
+ const previous = process.env.MIXDOG_DATA_DIR;
100
+ process.env.MIXDOG_DATA_DIR = root;
101
+ try {
102
+ const sessionsDir = join(root, 'sessions');
103
+ mkdirSync(sessionsDir);
104
+ const records = [
105
+ session('root-lead', {
106
+ owner: 'agent',
107
+ ownerSessionId: 'root-lead',
108
+ agent: 'lead',
109
+ sourceType: 'lead',
110
+ }),
111
+ session('ordinary-user'),
112
+ session('declared-child', {
113
+ ownerSessionId: 'root-lead',
114
+ agent: 'reviewer',
115
+ visibility: 'agent-only',
116
+ }),
117
+ session('legacy-child', {
118
+ owner: 'agent',
119
+ parentSessionId: 'root-lead',
120
+ agent: 'reviewer',
121
+ agentTag: 'review',
122
+ closed: true,
123
+ status: 'closed',
124
+ }),
125
+ ];
126
+ for (const record of records) {
127
+ writeFileSync(
128
+ join(sessionsDir, `${record.id}.json`),
129
+ JSON.stringify(record),
130
+ );
131
+ }
132
+
133
+ const indexRows = records.map((record) => _sessionSummary(record));
134
+ delete indexRows.find((row) => row.id === 'legacy-child').visibility;
135
+ writeFileSync(join(root, 'session-summaries.json'), JSON.stringify({
136
+ version: 2,
137
+ updatedAt: Date.now(),
138
+ rows: indexRows,
139
+ }));
140
+ writeFileSync(join(root, 'agent-workers.json'), JSON.stringify({
141
+ workers: {
142
+ review: {
143
+ tag: 'review',
144
+ sessionId: 'legacy-child',
145
+ parentSessionId: 'root-lead',
146
+ ownerSessionId: 'root-lead',
147
+ agent: 'reviewer',
148
+ status: 'idle',
149
+ stage: 'idle',
150
+ },
151
+ },
152
+ }));
153
+
154
+ const expectedOrdinaryIds = ['ordinary-user', 'root-lead'];
155
+ assert.deepEqual(
156
+ listOrdinarySummaries({ rebuildIfMissing: false })
157
+ .map((row) => row.id)
158
+ .sort(),
159
+ expectedOrdinaryIds,
160
+ );
161
+ assert.deepEqual(
162
+ listOrdinarySummaries({ refreshFromStorage: true })
163
+ .map((row) => row.id)
164
+ .sort(),
165
+ expectedOrdinaryIds,
166
+ );
167
+ assert.deepEqual(
168
+ listStoredSessions().map((row) => row.id).sort(),
169
+ expectedOrdinaryIds,
170
+ );
171
+ assert.deepEqual(
172
+ listStoredSessions({ includeAgentOnly: true }).map((row) => row.id).sort(),
173
+ records.map((row) => row.id).sort(),
174
+ );
175
+
176
+ const internal = listInternalSummaries({ refreshFromStorage: true });
177
+ assert.deepEqual(
178
+ internal.map((row) => row.id).sort(),
179
+ records.map((row) => row.id).sort(),
180
+ );
181
+ assert.equal(
182
+ internal.find((row) => row.id === 'declared-child')?.visibility,
183
+ 'agent-only',
184
+ );
185
+ assert.equal(
186
+ internal.find((row) => row.id === 'legacy-child')?.visibility,
187
+ 'agent-only',
188
+ );
189
+ const durableIndex = JSON.parse(readFileSync(
190
+ join(root, 'session-summaries.json'),
191
+ 'utf8',
192
+ ));
193
+ assert.equal(
194
+ durableIndex.rows.find((row) => row.id === 'legacy-child')?.visibility,
195
+ 'agent-only',
196
+ );
197
+
198
+ assert.equal(
199
+ listStoredAgentWorkers().some((row) => row.sessionId === 'legacy-child'),
200
+ true,
201
+ );
202
+ assert.deepEqual(listStoredAgentWorkerLinks(), [{
203
+ sessionId: 'legacy-child',
204
+ parentSessionId: 'root-lead',
205
+ ownerSessionId: 'root-lead',
206
+ }]);
207
+ assert.equal(storedSessionExists('legacy-child'), true);
208
+ const metadata = await readStoredSessionTranscript('legacy-child', { metadataOnly: true });
209
+ assert.equal(metadata?.parentSessionId, 'root-lead');
210
+ assert.equal(Object.hasOwn(metadata || {}, 'items'), false);
211
+ const exact = await readStoredSessionTranscript('legacy-child');
212
+ assert.equal(exact?.sessionId, 'legacy-child');
213
+ assert.equal(exact?.readOnlyDetachedAgent, true);
214
+ } finally {
215
+ if (previous === undefined) delete process.env.MIXDOG_DATA_DIR;
216
+ else process.env.MIXDOG_DATA_DIR = previous;
217
+ rmSync(root, { recursive: true, force: true });
218
+ }
219
+ });