mixdog 1.2.1 → 1.2.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 (557) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/gate-local.mjs +19 -3
  4. package/scripts/model-catalog-audit.mjs +1 -15
  5. package/scripts/provider-pingpong-live.mjs +509 -0
  6. package/src/agents/advisor/AGENT.md +1 -1
  7. package/src/agents/advisor/agent.json +1 -1
  8. package/src/agents/reviewer/AGENT.md +3 -3
  9. package/src/agents/reviewer/agent.json +1 -1
  10. package/src/defaults/shipped-history.json +6 -0
  11. package/src/defaults/skills/setup/SKILL.md +6 -1
  12. package/src/defaults/skills/setup/references/actions.md +44 -22
  13. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  14. package/src/headless-exec.mjs +15 -8
  15. package/src/lib/keychain-cjs.cjs +7 -4
  16. package/src/output-styles/common.md +11 -10
  17. package/src/repl/slash-commands.mjs +16 -1
  18. package/src/rules/agent/AGENT.md +5 -5
  19. package/src/rules/lead/LEAD.md +5 -5
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  21. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  22. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  23. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  24. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
  33. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  34. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  35. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  36. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  37. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  38. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  39. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  40. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  41. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  43. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  44. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  45. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +98 -0
  46. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  47. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  48. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +48 -37
  49. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +10 -7
  50. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  51. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  52. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  54. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  55. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +102 -45
  58. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  61. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  63. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  65. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +10 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +40 -7
  70. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  71. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  72. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +88 -21
  73. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +30 -10
  74. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +106 -0
  75. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  76. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +94 -15
  77. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +117 -72
  79. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  80. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +75 -10
  81. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  83. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +103 -23
  84. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  85. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  86. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +32 -6
  87. package/src/runtime/agent/orchestrator/providers/cursor.mjs +27 -3
  88. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  89. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  90. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  91. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  92. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  93. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +80 -18
  94. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  95. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +46 -8
  96. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +273 -0
  97. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  99. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  100. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  101. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  102. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +369 -112
  103. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +3 -1
  104. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +140 -35
  105. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +70 -24
  106. package/src/runtime/agent/orchestrator/providers/gemini.mjs +80 -29
  107. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  108. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  109. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +69 -19
  110. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  113. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  114. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  115. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  116. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  117. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  118. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  119. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  120. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  121. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  122. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  123. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  124. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  125. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  126. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  127. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  128. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  129. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  130. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +123 -61
  132. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  133. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +27 -2
  134. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  135. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  136. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  137. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  138. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  139. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  140. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  141. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  142. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +64 -23
  143. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +79 -33
  144. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  145. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  146. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  147. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +144 -15
  148. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  149. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  150. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +9 -2
  151. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +109 -26
  153. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +27 -6
  154. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +34 -20
  155. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +23 -4
  156. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +37 -28
  157. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  158. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  159. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  160. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +43 -15
  161. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  163. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +5 -2
  164. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  165. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +7 -3
  166. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  167. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  168. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  169. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  170. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +37 -4
  171. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +12 -1
  172. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  173. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -1
  174. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +76 -33
  175. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  176. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  177. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  178. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  179. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  180. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  181. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  182. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  183. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  184. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  185. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  186. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  187. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  188. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +41 -22
  189. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  190. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  191. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  192. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +720 -0
  193. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  195. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +123 -219
  196. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  197. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  198. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  199. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  200. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  201. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  202. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  203. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  204. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  205. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  206. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  207. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  208. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  209. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  210. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  211. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  212. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
  213. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  214. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  215. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  216. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  217. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  218. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  219. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  220. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  221. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  222. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +5 -1
  223. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  224. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  225. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  226. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  227. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  228. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  229. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  230. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  231. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  232. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  233. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  234. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  235. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  236. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  237. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  238. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  239. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +67 -20
  240. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +104 -19
  241. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  242. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  243. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  244. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  245. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  246. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
  248. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
  249. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  250. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  251. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +57 -21
  252. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  253. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  254. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  255. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  256. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
  257. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  258. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +16 -4
  259. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  260. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  261. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  262. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  263. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  264. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  265. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  266. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  267. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +168 -29
  268. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  269. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  270. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  271. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  272. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  273. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  274. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  275. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  276. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  277. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  278. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  279. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  280. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  281. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +6 -0
  282. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +13 -0
  283. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +13 -1
  284. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  285. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  286. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  287. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  288. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  289. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  290. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  291. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  292. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
  293. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  294. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  295. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  296. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  297. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  298. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  299. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  300. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  301. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  302. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  303. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  304. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  305. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  306. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  307. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  308. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  309. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  310. package/src/runtime/github/tool.mjs +92 -2
  311. package/src/runtime/memory/index.mjs +1 -0
  312. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  313. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  314. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  315. package/src/runtime/memory/lib/pg/process.mjs +65 -50
  316. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  317. package/src/runtime/memory/lib/pg/supervisor.mjs +124 -16
  318. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  319. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  320. package/src/runtime/office/README.md +2 -2
  321. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  322. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  323. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  324. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  325. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  326. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  327. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  328. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  329. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  330. package/src/runtime/office/core/office-sessions.mjs +5 -0
  331. package/src/runtime/office/core/office-transactions.mjs +3 -0
  332. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  333. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  334. package/src/runtime/office/index.mjs +30 -0
  335. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  336. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  337. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  338. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  339. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  340. package/src/runtime/office/shared/values.mjs +0 -5
  341. package/src/runtime/shared/background-tasks.mjs +92 -14
  342. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  343. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  344. package/src/runtime/shared/buffered-appender.mjs +18 -0
  345. package/src/runtime/shared/config.mjs +78 -18
  346. package/src/runtime/shared/err-text.mjs +31 -0
  347. package/src/runtime/shared/error-presentation.mjs +159 -19
  348. package/src/runtime/shared/file-permissions.d.mts +6 -1
  349. package/src/runtime/shared/file-permissions.mjs +54 -6
  350. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  351. package/src/runtime/shared/llm/anthropic-effort.mjs +129 -134
  352. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  353. package/src/runtime/shared/llm/model-catalog.mjs +53 -40
  354. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  355. package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
  356. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  357. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  358. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  359. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  360. package/src/runtime/shared/loopback-listener.mjs +9 -0
  361. package/src/runtime/shared/model-display.mjs +13 -0
  362. package/src/runtime/shared/provider-api-key.mjs +2 -0
  363. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  364. package/src/runtime/shared/review-diff.mjs +52 -0
  365. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  366. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  367. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  368. package/src/runtime/shared/slow-span.mjs +114 -0
  369. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  370. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  371. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  372. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  373. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  374. package/src/runtime/shared/tool-card-model.mjs +5 -2
  375. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  376. package/src/runtime/shared/tool-primitives.mjs +13 -0
  377. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  378. package/src/runtime/shared/tool-surface.mjs +18 -3
  379. package/src/runtime/shared/tool-work-units.mjs +40 -4
  380. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  381. package/src/runtime/shared/transcript-writer.mjs +10 -2
  382. package/src/runtime/shared/turn-changes.mjs +670 -274
  383. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  384. package/src/runtime/shared/turn-snapshot-store.mjs +94 -2
  385. package/src/runtime/shared/turn-snapshot.mjs +986 -690
  386. package/src/runtime/shared/turn-worktree-snapshot.mjs +279 -262
  387. package/src/runtime/shared/user-data-guard.mjs +262 -49
  388. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  389. package/src/runtime/shared/worktrees/contract.mjs +34 -24
  390. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  391. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  392. package/src/runtime/shared/worktrees/git.mjs +8 -3
  393. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  394. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  395. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  396. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  397. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  398. package/src/runtime/terminal-bridge/client.mjs +44 -11
  399. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  400. package/src/runtime/tidy/apply.mjs +47 -2
  401. package/src/runtime/tidy/run-engines.mjs +11 -5
  402. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  403. package/src/session-runtime/boot/apis.mjs +19 -5
  404. package/src/session-runtime/boot/facade.mjs +1 -0
  405. package/src/session-runtime/boot/infrastructure.mjs +12 -1
  406. package/src/session-runtime/boot/session.mjs +0 -1
  407. package/src/session-runtime/boot/tools.mjs +4 -1
  408. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  409. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  410. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  411. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  412. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  413. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  414. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  415. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  416. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -3
  417. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  418. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  419. package/src/session-runtime/lifecycle/teardown.mjs +73 -8
  420. package/src/session-runtime/lifecycle-api.mjs +1 -0
  421. package/src/session-runtime/mcp-glue/connect.mjs +46 -14
  422. package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
  423. package/src/session-runtime/mcp-glue.mjs +3 -0
  424. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  425. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  426. package/src/session-runtime/model-route/set-route.mjs +85 -17
  427. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  428. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  429. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  430. package/src/session-runtime/native-web-search.mjs +10 -1
  431. package/src/session-runtime/new-session-config.mjs +4 -2
  432. package/src/session-runtime/provider-auth/login.mjs +4 -0
  433. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  434. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  435. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  436. package/src/session-runtime/resource-extension-sync.mjs +4 -0
  437. package/src/session-runtime/resource-mcp-api.mjs +98 -2
  438. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  439. package/src/session-runtime/runtime-review-api.mjs +21 -2
  440. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  441. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  442. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  443. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  444. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  445. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  446. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  447. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  448. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  449. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  450. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  451. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  452. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  453. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  454. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  455. package/src/session-runtime/services/agent-tool.mjs +3 -0
  456. package/src/session-runtime/services/channel-admin.mjs +19 -3
  457. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  458. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  459. package/src/session-runtime/services/provider-admin.mjs +81 -15
  460. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  461. package/src/session-runtime/session-turn-api.mjs +14 -5
  462. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  463. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  464. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  465. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  466. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  467. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  468. package/src/session-runtime/skills-api.mjs +3 -5
  469. package/src/session-runtime/tool-surface.mjs +5 -1
  470. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  471. package/src/session-runtime/turn/session-ops.mjs +7 -5
  472. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  473. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  474. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  475. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  476. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  477. package/src/session-runtime/turn/turn-run.mjs +25 -9
  478. package/src/session-runtime/usage-stats-api.mjs +43 -11
  479. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  480. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  481. package/src/session-runtime/workspace/transitions.mjs +190 -15
  482. package/src/standalone/daemon-agent-control.mjs +9 -1
  483. package/src/standalone/daemon-drain-policy.mjs +8 -0
  484. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  485. package/src/standalone/daemon-owner.mjs +17 -4
  486. package/src/standalone/daemon-pg-stop.mjs +51 -0
  487. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  488. package/src/standalone/daemon-stop-budget.mjs +18 -0
  489. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  490. package/src/standalone/daemon-telemetry.mjs +74 -0
  491. package/src/standalone/daemon.mjs +64 -8
  492. package/src/standalone/desktop-service-registry.mjs +5 -0
  493. package/src/standalone/local-session-runtime.mjs +70 -19
  494. package/src/standalone/session-client.mjs +42 -0
  495. package/src/standalone/session-protocol.mjs +14 -0
  496. package/src/standalone/session-proxy/projection.mjs +41 -4
  497. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  498. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  499. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  500. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  501. package/src/standalone/session-service/agent-tree.mjs +12 -0
  502. package/src/standalone/session-service/quota-resume.mjs +8 -0
  503. package/src/standalone/session-service/retention/busy.mjs +15 -1
  504. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  505. package/src/standalone/session-service-api.mjs +11 -1
  506. package/src/standalone/session-service.mjs +1 -0
  507. package/src/standalone/session-state-patch.d.mts +2 -1
  508. package/src/standalone/session-state-patch.mjs +41 -1
  509. package/src/tui/App.jsx +25 -0
  510. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  511. package/src/tui/app/app-view.jsx +1 -0
  512. package/src/tui/app/btw-controller.mjs +118 -0
  513. package/src/tui/app/create-app-pickers.mjs +6 -0
  514. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  515. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  516. package/src/tui/app/extension-pickers.mjs +2 -1
  517. package/src/tui/app/model-options.mjs +11 -16
  518. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  519. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  520. package/src/tui/app/slash-commands.mjs +20 -5
  521. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  522. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  523. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  524. package/src/tui/app/use-global-key-input.mjs +5 -12
  525. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  526. package/src/tui/components/BtwPanel.jsx +53 -0
  527. package/src/tui/components/QuestionPanel.jsx +275 -0
  528. package/src/tui/components/StatusLine.jsx +2 -0
  529. package/src/tui/components/question-panel-model.mjs +156 -0
  530. package/src/tui/components/status-line/local-line.mjs +5 -2
  531. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  532. package/src/tui/dist/index.mjs +3221 -1500
  533. package/src/tui/session/context-state.mjs +4 -2
  534. package/src/tui/session/quota-resume.mjs +46 -14
  535. package/src/tui/session/session-api/commands.mjs +43 -24
  536. package/src/tui/session/session-api/controls.mjs +2 -2
  537. package/src/tui/session/session-api/extensions.mjs +22 -0
  538. package/src/tui/session/session-api/integrations.mjs +16 -2
  539. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  540. package/src/tui/session/session-api/lifecycle.mjs +7 -2
  541. package/src/tui/session/session-api/routes.mjs +71 -7
  542. package/src/tui/session/session-api/settings.mjs +11 -14
  543. package/src/tui/session/session-api.mjs +1 -0
  544. package/src/tui/session/session-flow/drain.mjs +7 -1
  545. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  546. package/src/tui/session/tool-approval.mjs +10 -7
  547. package/src/tui/session/transcript-spill.mjs +14 -7
  548. package/src/tui/session/turn-stream.mjs +174 -31
  549. package/src/tui/session/turn.mjs +46 -16
  550. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  551. package/src/ui/statusline-agents.mjs +15 -4
  552. package/src/ui/statusline.mjs +7 -3
  553. package/src/ui/streaming-markdown-heal.mjs +34 -3
  554. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  555. package/src/workflows/default/WORKFLOW.md +3 -1
  556. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  557. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
@@ -5,26 +5,29 @@ import { sleep as delay } from '../../../shared/sleep.mjs';
5
5
  //
6
6
  // Public API:
7
7
  // startPg({ runtimeDir, pgdataDir, port?, logPath? }) → { pid, port }
8
- // stopPg({ runtimeDir, pgdataDir }) → void
8
+ // stopPg({ runtimeDir, pgdataDir, timeoutMs? }) → void
9
9
  // healthcheckPg({ port, host? }) → boolean
10
+ //
11
+ // Every child on the start/stop path (initdb, pg_ctl start/reload/status/stop)
12
+ // runs async; only stopPgSync — a process 'exit' hook — may block.
10
13
 
11
14
  import {
12
15
  existsSync,
13
16
  mkdirSync,
14
17
  mkdtempSync,
15
- readdirSync,
16
18
  readFileSync,
17
19
  rmdirSync,
18
20
  rmSync,
19
- statSync,
20
21
  symlinkSync,
21
22
  unlinkSync,
22
23
  writeFileSync,
23
24
  } from 'node:fs';
25
+ import { readdir, readFile, rm, stat } from 'node:fs/promises';
24
26
  import { join } from 'node:path';
25
- import { spawn, spawnSync } from 'node:child_process';
27
+ import { execFile, spawn, spawnSync } from 'node:child_process';
26
28
  import { createConnection, createServer } from 'node:net';
27
29
  import { isPidAlive } from '../../../shared/pid-liveness.mjs';
30
+ import { runChild } from './run-child.mjs';
28
31
 
29
32
  // ---------------------------------------------------------------------------
30
33
  // Helpers
@@ -202,14 +205,12 @@ function ensureConfV2(pgdataDir) {
202
205
  // callers route through this rather than re-implementing the conf/reload pair.
203
206
  // Reload-compatible settings apply immediately; postmaster settings such as
204
207
  // max_connections apply on the next normal PG start (never restart live work).
205
- export function reconcileConfV2(runtimeDir, pgdataDir) {
208
+ export async function reconcileConfV2(runtimeDir, pgdataDir) {
206
209
  const applied = ensureConfV2(pgdataDir);
207
210
  if (!applied) return { applied: false, reloaded: false };
208
- const reload = spawnSync(pgBin(runtimeDir, 'pg_ctl'), ['reload', '-D', pgdataDir], {
211
+ const reload = await runChild(pgBin(runtimeDir, 'pg_ctl'), ['reload', '-D', pgdataDir], {
209
212
  env: libEnv(runtimeDir),
210
- stdio: 'pipe',
211
- timeout: 5_000,
212
- windowsHide: true,
213
+ timeoutMs: 5_000,
213
214
  });
214
215
  const reloaded = reload.status === 0;
215
216
  if (reloaded) {
@@ -239,7 +240,7 @@ async function attachExistingPostmaster({ runtimeDir, pgdataDir, existingWaitMs,
239
240
  __mixdogMemoryLog(`[pg-process] attaching to existing PG pid=${existing.pid} port=${existing.port}\n`);
240
241
  // Route through the single reconcile entry point so v1 → v2 conf
241
242
  // upgrades land on already-running instances without restart.
242
- if (v2Applied) reconcileConfV2(runtimeDir, pgdataDir);
243
+ if (v2Applied) await reconcileConfV2(runtimeDir, pgdataDir);
243
244
  return { pid: existing.pid, port: existing.port, attached: true };
244
245
  }
245
246
  if (existing.state === 'alive-not-ready') {
@@ -260,6 +261,10 @@ async function attachExistingPostmaster({ runtimeDir, pgdataDir, existingWaitMs,
260
261
  // default_transaction_isolation: native PG default is read committed.
261
262
  // Native PG does not default to serializable; set isolation level explicitly
262
263
  // so behaviour is unambiguous across PG major versions.
264
+ // Carries every versioned block ensureConfV2 checks for, so a fresh cluster
265
+ // already holds what later boots expect: none of them rewrites the conf and
266
+ // pg_ctl-reloads it (a postmaster-only setting such as shared_buffers cannot
267
+ // change by reload, and every such reload logged an error).
263
268
  function freshClusterConfAppend() {
264
269
  return (
265
270
  [
@@ -280,7 +285,8 @@ function freshClusterConfAppend() {
280
285
  ].join('\n') +
281
286
  '\n' +
282
287
  buildV2Block() +
283
- buildV3Block()
288
+ buildV3Block() +
289
+ buildV4Block()
284
290
  );
285
291
  }
286
292
 
@@ -291,14 +297,14 @@ function freshClusterConfAppend() {
291
297
  * user profile). Such a share dir is passed through an ASCII junction under
292
298
  * %ProgramData% that is removed afterwards.
293
299
  */
294
- export function withInitdbShareDir(runtimeDir, run) {
300
+ export async function withInitdbShareDir(runtimeDir, run) {
295
301
  const shareDir = join(runtimeDir, 'share');
296
302
  if (process.platform !== 'win32' || /^[\x20-\x7e]*$/.test(shareDir)) return run(null);
297
303
  const linkRoot = mkdtempSync(join(process.env.ProgramData, 'mixdog-initdb-'));
298
304
  const link = join(linkRoot, 'share');
299
305
  try {
300
306
  symlinkSync(shareDir, link, 'junction');
301
- return run(link);
307
+ return await run(link);
302
308
  } finally {
303
309
  // Cleanup must not replace initdb's own result or error. Both removals are
304
310
  // non-recursive, so the junction target (the runtime share) is never touched.
@@ -313,11 +319,11 @@ export function withInitdbShareDir(runtimeDir, run) {
313
319
  }
314
320
 
315
321
  /** initdb if pgdata is not yet initialised (no PG_VERSION file). */
316
- function initClusterIfNeeded({ runtimeDir, pgdataDir, env }) {
322
+ async function initClusterIfNeeded({ runtimeDir, pgdataDir, env }) {
317
323
  if (existsSync(join(pgdataDir, 'PG_VERSION'))) return;
318
324
  __mixdogMemoryLog(`[pg-process] initdb → ${pgdataDir}\n`);
319
- const r = withInitdbShareDir(runtimeDir, (shareDir) =>
320
- spawnSync(
325
+ const r = await withInitdbShareDir(runtimeDir, (shareDir) =>
326
+ runChild(
321
327
  pgBin(runtimeDir, 'initdb'),
322
328
  [
323
329
  '-D',
@@ -330,7 +336,7 @@ function initClusterIfNeeded({ runtimeDir, pgdataDir, env }) {
330
336
  '-U',
331
337
  'postgres',
332
338
  ],
333
- { env, stdio: 'pipe', windowsHide: true }
339
+ { env }
334
340
  )
335
341
  );
336
342
  if (r.status !== 0) {
@@ -436,12 +442,7 @@ async function startAndWaitReady({ pgctl, startArgs, env, pgdataDir, port }) {
436
442
  */
437
443
  async function attachRaceWinner({ pgdataDir, pgctl, env, existingWaitMs }) {
438
444
  __mixdogMemoryLog(`[pg-process] pg_ctl start: "another server might be running" — probing status\n`);
439
- const statusR = spawnSync(pgctl, ['status', '-D', pgdataDir], {
440
- env,
441
- stdio: 'pipe',
442
- timeout: 3_000,
443
- windowsHide: true,
444
- });
445
+ const statusR = await runChild(pgctl, ['status', '-D', pgdataDir], { env, timeoutMs: 3_000 });
445
446
  __mixdogMemoryLog(
446
447
  `[pg-process] pg_ctl status: ${statusR.stdout?.toString() || statusR.stderr?.toString() || 'no output'}\n`
447
448
  );
@@ -481,7 +482,7 @@ export async function startPg({
481
482
  if (attached) return attached;
482
483
 
483
484
  const pgctl = pgBin(runtimeDir, 'pg_ctl');
484
- initClusterIfNeeded({ runtimeDir, pgdataDir, env });
485
+ await initClusterIfNeeded({ runtimeDir, pgdataDir, env });
485
486
  // PostgreSQL requires a completely empty target on first init. Creating the
486
487
  // Spotlight marker before initdb makes every fresh macOS cluster fail with
487
488
  // "directory exists but is not empty"; add it only after initialization.
@@ -512,15 +513,18 @@ export async function startPg({
512
513
  // stopPg
513
514
  // ---------------------------------------------------------------------------
514
515
 
515
- export async function stopPg({ runtimeDir, pgdataDir }) {
516
+ // Bounded graceful stop. `-m fast` rolls back open transactions and
517
+ // disconnects clients; pg_ctl's own wait (-t, whole seconds) and the child
518
+ // timeout share `timeoutMs`, so a wedged stop rejects instead of holding a
519
+ // shutdown sequence open.
520
+ export async function stopPg({ runtimeDir, pgdataDir, timeoutMs = 15_000 }) {
516
521
  const pgctl = pgBin(runtimeDir, 'pg_ctl');
517
522
  const env = libEnv(runtimeDir);
523
+ const waitSeconds = Math.max(1, Math.ceil(timeoutMs / 1000));
518
524
 
519
- const r = spawnSync(pgctl, ['stop', '-m', 'fast', '-w', '-D', pgdataDir], {
525
+ const r = await runChild(pgctl, ['stop', '-m', 'fast', '-w', '-t', String(waitSeconds), '-D', pgdataDir], {
520
526
  env,
521
- stdio: 'pipe',
522
- timeout: 15_000,
523
- windowsHide: true,
527
+ timeoutMs: waitSeconds * 1000 + 1_000,
524
528
  });
525
529
 
526
530
  if (r.status !== 0) {
@@ -607,22 +611,36 @@ export function classifyOrphanTempPostmaster({
607
611
  return Number(uptimeSec) >= Math.max(60, Number(minUptimeSec) || ORPHAN_TEMP_PG_MIN_UPTIME_SEC);
608
612
  }
609
613
 
610
- function tempPostmasterOwnerAlive(args) {
614
+ async function tempPostmasterOwnerAlive(args) {
611
615
  const rawDataDir = pgDataDirFromArgs(args);
612
616
  if (!rawDataDir) return false;
613
617
  const dataRoot = join(rawDataDir, '..');
614
618
  for (const name of ['memory-runtime-owner.json', 'daemon-owner.json']) {
615
619
  try {
616
- const owner = JSON.parse(readFileSync(join(dataRoot, name), 'utf8'));
620
+ const owner = JSON.parse(await readFile(join(dataRoot, name), 'utf8'));
617
621
  if (Number(owner?.pid) > 0 && isPidAlive(Number(owner.pid))) return true;
618
622
  } catch {}
619
623
  }
620
624
  return false;
621
625
  }
622
626
 
623
- function listPostmasterRows() {
627
+ // Async child run: stdout on exit 0, null on any failure/timeout (the old
628
+ // spawnSync `status !== 0` contract). Never blocks the event loop.
629
+ function execFileText(file, args, options) {
630
+ return new Promise((resolve) => {
631
+ try {
632
+ execFile(file, args, { encoding: 'utf8', ...options }, (error, stdout) => {
633
+ resolve(error ? null : String(stdout || ''));
634
+ });
635
+ } catch {
636
+ resolve(null);
637
+ }
638
+ });
639
+ }
640
+
641
+ async function listPostmasterRows() {
624
642
  if (process.platform === 'win32') {
625
- const result = spawnSync(
643
+ const stdout = await execFileText(
626
644
  'powershell',
627
645
  [
628
646
  '-NoProfile',
@@ -632,10 +650,10 @@ function listPostmasterRows() {
632
650
  'started = [int](New-TimeSpan -Start $_.CreationDate -End (Get-Date)).TotalSeconds; ' +
633
651
  'args = $_.CommandLine } }',
634
652
  ],
635
- { encoding: 'utf8', timeout: 15_000, windowsHide: true }
653
+ { timeout: 15_000, windowsHide: true }
636
654
  );
637
- if (result.status !== 0) return [];
638
- return String(result.stdout || '')
655
+ if (stdout === null) return [];
656
+ return stdout
639
657
  .split(/\r?\n/)
640
658
  .filter(Boolean)
641
659
  .flatMap((line) => {
@@ -647,12 +665,9 @@ function listPostmasterRows() {
647
665
  }
648
666
  });
649
667
  }
650
- const result = spawnSync('ps', ['-eo', 'pid=,etimes=,args='], {
651
- encoding: 'utf8',
652
- timeout: 15_000,
653
- });
654
- if (result.status !== 0) return [];
655
- return String(result.stdout || '')
668
+ const stdout = await execFileText('ps', ['-eo', 'pid=,etimes=,args='], { timeout: 15_000 });
669
+ if (stdout === null) return [];
670
+ return stdout
656
671
  .split(/\r?\n/)
657
672
  .flatMap((line) => {
658
673
  const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.*postgres.*)$/);
@@ -679,11 +694,11 @@ function pristineRootForArgs(args, tempRoot) {
679
694
  // (observed: 87 roots, 6.1GB, alongside 57 live postmasters). Roots a live
680
695
  // postmaster still owns are kept, and the same uptime margin that protects a
681
696
  // running harness from the process sweep protects its directory here.
682
- function sweepAbandonedPristineRoots(tempRoot, liveRoots, minAgeMs) {
697
+ async function sweepAbandonedPristineRoots(tempRoot, liveRoots, minAgeMs) {
683
698
  let removed = 0;
684
699
  let entries;
685
700
  try {
686
- entries = readdirSync(tempRoot, { withFileTypes: true });
701
+ entries = await readdir(tempRoot, { withFileTypes: true });
687
702
  } catch {
688
703
  return 0;
689
704
  }
@@ -693,12 +708,12 @@ function sweepAbandonedPristineRoots(tempRoot, liveRoots, minAgeMs) {
693
708
  const full = join(tempRoot, entry.name);
694
709
  if (liveRoots.has(normalizePathForMatch(full))) continue;
695
710
  try {
696
- if (statSync(full).mtimeMs > cutoff) continue;
711
+ if ((await stat(full)).mtimeMs > cutoff) continue;
697
712
  } catch {
698
713
  continue;
699
714
  }
700
715
  try {
701
- rmSync(full, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
716
+ await rm(full, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
702
717
  removed += 1;
703
718
  } catch {
704
719
  /* a process this sweep did not reap still holds it */
@@ -709,18 +724,18 @@ function sweepAbandonedPristineRoots(tempRoot, liveRoots, minAgeMs) {
709
724
 
710
725
  /** Best-effort machine hygiene: reap orphan temp postmasters, then drop the
711
726
  * throwaway roots no live postmaster owns. Runs periodically, not once. */
712
- export function sweepOrphanTempPostmasters({ tempRoot, minUptimeSec } = {}) {
727
+ export async function sweepOrphanTempPostmasters({ tempRoot, minUptimeSec } = {}) {
713
728
  let reaped = 0;
714
729
  try {
715
730
  const root = tempRoot || process.env.TMPDIR || process.env.TEMP || process.env.TMP || '/tmp';
716
731
  const liveRoots = new Set();
717
- for (const row of listPostmasterRows()) {
732
+ for (const row of await listPostmasterRows()) {
718
733
  if (!row.pid || row.pid === process.pid) continue;
719
734
  if (
720
735
  !classifyOrphanTempPostmaster({
721
736
  ...row,
722
737
  tempRoot: root,
723
- ownerAlive: tempPostmasterOwnerAlive(row.args),
738
+ ownerAlive: await tempPostmasterOwnerAlive(row.args),
724
739
  minUptimeSec,
725
740
  })
726
741
  ) {
@@ -737,7 +752,7 @@ export function sweepOrphanTempPostmasters({ tempRoot, minUptimeSec } = {}) {
737
752
  }
738
753
  }
739
754
  const minAgeMs = Math.max(60, Number(minUptimeSec) || ORPHAN_TEMP_PG_MIN_UPTIME_SEC) * 1000;
740
- const removed = sweepAbandonedPristineRoots(root, liveRoots, minAgeMs);
755
+ const removed = await sweepAbandonedPristineRoots(root, liveRoots, minAgeMs);
741
756
  if (removed > 0) __mixdogMemoryLog(`[pg-process] removed ${removed} abandoned pristine root(s)\n`);
742
757
  } catch {
743
758
  /* hygiene is best-effort */
@@ -0,0 +1,49 @@
1
+ import { spawn } from 'node:child_process';
2
+
3
+ /**
4
+ * Async child run with spawnSync's result shape ({ status, signal, stdout,
5
+ * stderr, error }) so the PG runtime's tar / initdb / pg_ctl steps never block
6
+ * the event loop of the process hosting them (the daemon serves every client
7
+ * from one loop). `timeoutMs` > 0 kills the child and reports ETIMEDOUT, like
8
+ * spawnSync's `timeout`. Never rejects.
9
+ */
10
+ export function runChild(file, args, { timeoutMs = 0, ...options } = {}) {
11
+ return new Promise((resolve) => {
12
+ const stdout = [];
13
+ const stderr = [];
14
+ let settled = false;
15
+ let timer = null;
16
+ let error = null;
17
+ const finish = (status, signal) => {
18
+ if (settled) return;
19
+ settled = true;
20
+ if (timer) clearTimeout(timer);
21
+ resolve({ status, signal, stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr), error });
22
+ };
23
+ let child;
24
+ try {
25
+ child = spawn(file, args, { windowsHide: true, ...options, stdio: ['ignore', 'pipe', 'pipe'] });
26
+ } catch (spawnError) {
27
+ error = spawnError;
28
+ finish(null, null);
29
+ return;
30
+ }
31
+ child.stdout?.on('data', (chunk) => stdout.push(chunk));
32
+ child.stderr?.on('data', (chunk) => stderr.push(chunk));
33
+ child.once('error', (childError) => {
34
+ error ??= childError;
35
+ finish(null, null);
36
+ });
37
+ // 'close' (not 'exit'): stdio is flushed, so captured output is complete.
38
+ child.once('close', (status, signal) => finish(status, signal));
39
+ if (timeoutMs > 0) {
40
+ timer = setTimeout(() => {
41
+ error = Object.assign(new Error(`${file} timed out after ${timeoutMs}ms`), { code: 'ETIMEDOUT' });
42
+ try {
43
+ child.kill();
44
+ } catch {}
45
+ finish(null, 'SIGTERM');
46
+ }, timeoutMs);
47
+ }
48
+ });
49
+ }
@@ -6,6 +6,9 @@ import { __mixdogMemoryLog } from '../memory-log.mjs';
6
6
  * Public API:
7
7
  * ensurePgInstance(dataDir) → Promise<{ host, port, runtimeDir, pgdataDir }>
8
8
  * stopPgForShutdown() → Promise<void> — call from server-main.mjs shutdown()
9
+ * stopOwnedPgForShutdown() → Promise<boolean> — a host stopping the cluster it ensured
10
+ * installOwnedPgExitStop() → void — last-chance sync stop on process exit
11
+ * pgClusterExists(dataDir) → boolean — initdb already ran for dataDir
9
12
  *
10
13
  * Uses process.mjs for startPg / stopPg / healthcheckPg (lazy-imported).
11
14
  *
@@ -17,7 +20,7 @@ import { __mixdogMemoryLog } from '../memory-log.mjs';
17
20
  */
18
21
 
19
22
  import { createServer } from 'node:net';
20
- import { unlinkSync, readFileSync, writeFileSync, renameSync, statSync, mkdirSync } from 'node:fs';
23
+ import { existsSync, unlinkSync, readFileSync, writeFileSync, renameSync, statSync, mkdirSync } from 'node:fs';
21
24
  import { join, resolve } from 'node:path';
22
25
  import {
23
26
  discoveryPath as _pgDiscoveryPath,
@@ -51,6 +54,10 @@ let _live = null;
51
54
  let _ensureInFlight = null;
52
55
  /** Per-process flag — postgresql.conf reconcile attempted once per supervisor. */
53
56
  let _v2ReconcileTried = false;
57
+ /** Set by stopOwnedPgForShutdown: this process is exiting, no further ensure. */
58
+ let _ownedShutdownStarted = false;
59
+ /** installOwnedPgExitStop registers its 'exit' hook once. */
60
+ let _ownedExitStopInstalled = false;
54
61
 
55
62
  // ── Constants ────────────────────────────────────────────────────────────────
56
63
  const PG_PORT_MIN = 55432;
@@ -372,6 +379,16 @@ function readPostmasterInfo(pgdata) {
372
379
  }
373
380
  }
374
381
 
382
+ // Async child run (never blocks the daemon loop): stdout on exit 0, else null.
383
+ async function _execFileText(file, args) {
384
+ const { execFile } = await import('node:child_process');
385
+ return new Promise((resolveText) => {
386
+ execFile(file, args, { encoding: 'utf8', windowsHide: true, timeout: 15_000 }, (error, stdout) => {
387
+ resolveText(error ? null : String(stdout || ''));
388
+ });
389
+ });
390
+ }
391
+
375
392
  /**
376
393
  * Best-effort process-name check: confirms the pid's comm name contains 'postgres'.
377
394
  * Falls back to true (alive) when the name cannot be determined.
@@ -383,19 +400,12 @@ async function isPostgresPid(pid) {
383
400
  return comm.includes('postgres');
384
401
  }
385
402
  if (process.platform === 'darwin') {
386
- const { spawnSync } = await import('node:child_process');
387
- const r = spawnSync('ps', ['-o', 'comm=', '-p', String(pid)], { encoding: 'utf8', windowsHide: true });
388
- if (r.status === 0) return (r.stdout || '').trim().includes('postgres');
389
- return true;
403
+ const out = await _execFileText('ps', ['-o', 'comm=', '-p', String(pid)]);
404
+ return out === null ? true : out.trim().includes('postgres');
390
405
  }
391
406
  if (process.platform === 'win32') {
392
- const { spawnSync } = await import('node:child_process');
393
- const r = spawnSync('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], {
394
- encoding: 'utf8',
395
- windowsHide: true,
396
- });
397
- if (r.status === 0) return (r.stdout || '').toLowerCase().includes('postgres');
398
- return true;
407
+ const out = await _execFileText('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH']);
408
+ return out === null ? true : out.toLowerCase().includes('postgres');
399
409
  }
400
410
  } catch {
401
411
  /* cannot read — fall back to alive */
@@ -463,8 +473,8 @@ function scheduleOrphanTempPostmasterSweep() {
463
473
  _orphanSweepStarted = true;
464
474
  const runSweep = () => {
465
475
  void _getPgProc()
466
- .then(({ sweepOrphanTempPostmasters }) => {
467
- const reaped = sweepOrphanTempPostmasters?.() || 0;
476
+ .then(async ({ sweepOrphanTempPostmasters }) => {
477
+ const reaped = (await sweepOrphanTempPostmasters?.()) || 0;
468
478
  if (reaped > 0) __mixdogMemoryLog(`[supervisor-pg] orphan sweep reaped ${reaped} temp postmaster(s)\n`);
469
479
  })
470
480
  .catch(() => {
@@ -534,9 +544,20 @@ async function tryReusePgInstance({ pgdata, runtimeDir, healthcheckPg, source =
534
544
 
535
545
  // ── Internal: full ensure logic (runs exclusively via _ensureInFlight) ────────
536
546
 
547
+ function _pgdataDir(dataDir) {
548
+ return join(dataDir, 'pgdata');
549
+ }
550
+
551
+ /** True once `dataDir` holds an initialized cluster (initdb ran). Before that
552
+ * no store in it can hold rows, so a read may answer empty without starting
553
+ * PostgreSQL — the first start downloads/extracts the runtime and runs initdb. */
554
+ export function pgClusterExists(dataDir) {
555
+ return existsSync(join(_pgdataDir(dataDir), 'PG_VERSION'));
556
+ }
557
+
537
558
  async function _doEnsure(dataDir) {
538
559
  const { healthcheckPg } = await _getPgProc();
539
- const pgdata = join(dataDir, 'pgdata');
560
+ const pgdata = _pgdataDir(dataDir);
540
561
 
541
562
  // Resolve runtimeDir via runtime-fetcher (cache-hits immediately when already downloaded).
542
563
  const { ensureRuntime } = await import('../runtime-fetcher.mjs');
@@ -549,7 +570,7 @@ async function _doEnsure(dataDir) {
549
570
  _v2ReconcileTried = true;
550
571
  try {
551
572
  const { reconcileConfV2 } = await _getPgProc();
552
- reconcileConfV2(runtimeDir, pgdata);
573
+ await reconcileConfV2(runtimeDir, pgdata);
553
574
  } catch (e) {
554
575
  __mixdogMemoryLog(`[supervisor-pg] reconcileConfV2 error (non-fatal): ${e?.message}\n`);
555
576
  }
@@ -696,6 +717,12 @@ async function _doEnsure(dataDir) {
696
717
  * @returns {Promise<{ host: string, port: number, runtimeDir: string, pgdataDir: string }>}
697
718
  */
698
719
  export function ensurePgInstance(dataDir) {
720
+ // Once this process began stopping the cluster it owns, nothing may start
721
+ // or re-attach one: a late connection-loss recovery would otherwise bring a
722
+ // postmaster back up right after the stop and leave it running past exit.
723
+ if (_ownedShutdownStarted) {
724
+ return Promise.reject(new Error('[supervisor-pg] process is shutting down; PG start refused'));
725
+ }
699
726
  // Every route to a live PG passes here — start, attach, reuse and the
700
727
  // in-process fast path — so this is the one place the sweep cannot be
701
728
  // skipped. Latched internally, so repeated calls schedule it only once.
@@ -803,3 +830,84 @@ export function stopPgForShutdownSync() {
803
830
  // async path: never another live owner's); otherwise pg.json outlives the run.
804
831
  patchActiveInstance({ pg_port: null, pg_started_at: null, pg_pgdata: null, pg_runtime_dir: null }, { timeoutMs: 1000 });
805
832
  }
833
+
834
+ // ── Owned-cluster shutdown (a host that starts PG through its stores) ────────
835
+ //
836
+ // `pg_ctl start` daemonizes the postmaster outside the starting process tree,
837
+ // so nothing ends it when that process exits: the process that brought a
838
+ // cluster up (or adopted one whose owner died) owns stopping it. Unlike
839
+ // stopPgForShutdown above, these never act on a cluster this process did not
840
+ // ensure, and never on one a different live process owns (pg_owner_pid) —
841
+ // that owner stops it on its own exit.
842
+
843
+ /** `_live` cluster's advert is held by another live process. */
844
+ function _liveClusterForeignOwned(snap) {
845
+ let advert = null;
846
+ try {
847
+ advert = _readPgAdvert();
848
+ } catch {}
849
+ if (!advert?.pg_pgdata || resolve(advert.pg_pgdata) !== resolve(snap.pgdata)) return false;
850
+ return _pgAdvertIsForeignLive(advert);
851
+ }
852
+
853
+ /**
854
+ * Graceful, bounded stop (`pg_ctl stop -m fast`) of the cluster this process
855
+ * ensured. Latches the process into shutdown first, so no later ensure can
856
+ * start another one. Resolves true when a cluster was stopped; rejects when
857
+ * pg_ctl failed (the 'exit' hook then retries synchronously).
858
+ */
859
+ export async function stopOwnedPgForShutdown({ timeoutMs = 6_000 } = {}) {
860
+ _ownedShutdownStarted = true;
861
+ // A start still under way (pg_ctl start already spawned, readiness pending)
862
+ // publishes its cluster only when it settles; stopping before that would
863
+ // miss the postmaster it is about to own. Bounded: a cold first start can
864
+ // be a download.
865
+ if (_ensureInFlight) {
866
+ let timer = null;
867
+ await Promise.race([
868
+ _ensureInFlight.catch(() => {}),
869
+ new Promise((resolveWait) => {
870
+ timer = setTimeout(resolveWait, timeoutMs);
871
+ timer.unref?.();
872
+ }),
873
+ ]);
874
+ clearTimeout(timer);
875
+ }
876
+ const snap = _live;
877
+ if (!snap) return false;
878
+ if (_liveClusterForeignOwned(snap)) {
879
+ __mixdogMemoryLog(`[supervisor-pg] PG on port ${snap.port} is owned by another live process; leaving it running\n`);
880
+ _live = null;
881
+ return false;
882
+ }
883
+ const { stopPg } = await _getPgProc();
884
+ await stopPg({ runtimeDir: snap.runtimeDir, pgdataDir: snap.pgdata, timeoutMs });
885
+ _live = null;
886
+ __mixdogMemoryLog(`[supervisor-pg] owned PG on port ${snap.port} stopped on shutdown\n`);
887
+ patchActiveInstance(
888
+ { pg_port: null, pg_started_at: null, pg_pgdata: null, pg_runtime_dir: null },
889
+ { timeoutMs: 2000, syncRetries: 1 }
890
+ );
891
+ return true;
892
+ }
893
+
894
+ /** Last-chance synchronous stop for a process 'exit' hook: acts only while the
895
+ * cluster this process ensured is still recorded as live (an exit the async
896
+ * stop never reached, or one where it failed). */
897
+ export function stopOwnedPgForShutdownSync() {
898
+ if (!_live) return;
899
+ stopPgForShutdownSync();
900
+ _live = null;
901
+ }
902
+
903
+ /** Register stopOwnedPgForShutdownSync on process 'exit', once. Cheap when this
904
+ * process never ensures a cluster: the hook is then a no-op. */
905
+ export function installOwnedPgExitStop() {
906
+ if (_ownedExitStopInstalled) return;
907
+ _ownedExitStopInstalled = true;
908
+ process.on('exit', () => {
909
+ try {
910
+ stopOwnedPgForShutdownSync();
911
+ } catch {}
912
+ });
913
+ }
@@ -32,7 +32,7 @@ import {
32
32
  } from 'node:fs';
33
33
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
34
34
  import { fileURLToPath } from 'node:url';
35
- import { spawnSync } from 'node:child_process';
35
+ import { runChild } from './pg/run-child.mjs';
36
36
  import { renameWithRetrySync, writeFileAtomicSync, writeJsonAtomicSync } from '../../shared/atomic-file.mjs';
37
37
  import { downloadToFileWithRetry } from '../../shared/bounded-download.mjs';
38
38
  import { platformKey, platformKeyCandidates, verifySha256File } from '../../shared/native-asset.mjs';
@@ -250,21 +250,23 @@ export function _validateRuntimeTarEntries(entries, details, stagingBase) {
250
250
  }
251
251
  }
252
252
 
253
- export function _extractRuntimeTarGz(tarPath, destDir, stagingBase) {
253
+ // Async throughout: the first boot extracts the whole PG runtime here, and a
254
+ // synchronous tar froze the daemon loop for seconds.
255
+ export async function _extractRuntimeTarGz(tarPath, destDir, stagingBase) {
254
256
  mkdirSync(destDir, { recursive: true });
255
257
  const resolvedTarPath = resolve(tarPath);
256
- const tarOptions = { cwd: dirname(resolvedTarPath), stdio: 'pipe', windowsHide: true };
258
+ const tarOptions = { cwd: dirname(resolvedTarPath) };
257
259
  const tarName = basename(resolvedTarPath);
258
260
 
259
261
  // List entries first and validate — reject traversal, external links, and
260
262
  // special files before the extractor can materialize archive content.
261
263
  // Keep the archive argument relative to cwd: GNU tar otherwise interprets
262
264
  // an absolute Windows path such as C:\runtime.tar.gz as a remote host path.
263
- const listResult = spawnSync('tar', ['-tzf', tarName], tarOptions);
265
+ const listResult = await runChild('tar', ['-tzf', tarName], tarOptions);
264
266
  if (listResult.status !== 0) {
265
267
  throw new Error(`[runtime-fetcher] tar list failed: ${listResult.stderr?.toString() || 'unknown'}`);
266
268
  }
267
- const detailResult = spawnSync('tar', ['-tvzf', tarName], tarOptions);
269
+ const detailResult = await runChild('tar', ['-tvzf', tarName], tarOptions);
268
270
  if (detailResult.status !== 0) {
269
271
  throw new Error(`[runtime-fetcher] tar detail list failed: ${detailResult.stderr?.toString() || 'unknown'}`);
270
272
  }
@@ -274,10 +276,7 @@ export function _extractRuntimeTarGz(tarPath, destDir, stagingBase) {
274
276
 
275
277
  const resolvedDestDir = resolve(destDir);
276
278
  const extractionArchive = relative(resolvedDestDir, resolvedTarPath);
277
- const r = spawnSync('tar', ['-xzf', extractionArchive], {
278
- ...tarOptions,
279
- cwd: resolvedDestDir,
280
- });
279
+ const r = await runChild('tar', ['-xzf', extractionArchive], { cwd: resolvedDestDir });
281
280
  if (r.status !== 0) {
282
281
  throw new Error(`[runtime-fetcher] tar extraction failed: ${r.stderr?.toString() || 'unknown error'}`);
283
282
  }
@@ -438,9 +437,9 @@ const runtimeCache = new Map();
438
437
 
439
438
  // One-shot tar availability probe; result cached after first call.
440
439
  let _tarProbed = false;
441
- function probeTar() {
440
+ async function probeTar() {
442
441
  if (_tarProbed) return;
443
- const r = spawnSync('tar', ['--version'], { stdio: 'pipe', windowsHide: true });
442
+ const r = await runChild('tar', ['--version']);
444
443
  if (r.status !== 0 || r.error) {
445
444
  throw new Error(
446
445
  '[runtime-fetcher] `tar` not found or not executable. ' +
@@ -532,7 +531,7 @@ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, versio
532
531
  // tar is only required for the download/extract path. Probe here (not at
533
532
  // ensureRuntime entry) so a machine without tar can still reuse an
534
533
  // already-extracted, sha-matching cached runtime via the fast path.
535
- probeTar();
534
+ await probeTar();
536
535
 
537
536
  __mixdogMemoryLog(`[runtime-fetcher] downloading runtime ${version} for ${pkey} (~${size} bytes) …\n`);
538
537
 
@@ -574,7 +573,7 @@ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, versio
574
573
  }
575
574
  }
576
575
  if (!downloadOk && lastDownloadErr) throw lastDownloadErr;
577
- _extractRuntimeTarGz(tarPath, stagingDir, stagingDir);
576
+ await _extractRuntimeTarGz(tarPath, stagingDir, stagingDir);
578
577
  } finally {
579
578
  try {
580
579
  rmSync(tarPath, { force: true });
@@ -53,7 +53,21 @@ export function createTranscriptIngest({
53
53
  return run;
54
54
  }
55
55
 
56
+ /** A deleted session's transcripts: wait out any ingest of each file, then
57
+ * drop its persisted read position. Returns the number of entries removed. */
58
+ async function forgetTranscripts(transcriptPaths) {
59
+ let removed = 0;
60
+ for (const transcriptPath of transcriptPaths) {
61
+ const resolved = path.resolve(transcriptPath);
62
+ await tails.get(resolved);
63
+ for (const key of new Set([transcriptPath, resolved])) if (offsets.forget(key)) removed += 1;
64
+ }
65
+ if (removed > 0) await offsets.persist();
66
+ return removed;
67
+ }
68
+
56
69
  return {
70
+ forgetTranscripts,
57
71
  loadTranscriptOffsets: offsets.load,
58
72
  persistTranscriptOffsets: offsets.persist,
59
73
  ingestTranscriptFile,