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
@@ -124,6 +124,29 @@ export function _retireSessionIncarnation(id) {
124
124
  if (id) _sessionIncarnations.delete(id);
125
125
  }
126
126
 
127
+ // ── Delete fence ────────────────────────────────────────────────────────────
128
+ // An explicitly deleted session may still own a running turn whose later saves
129
+ // carry a brand-new write guard and incarnation, so neither the cancellation
130
+ // counter nor the retired incarnation stops them: an absent file reads as
131
+ // "creatable". The fence is the id-level refusal every write-admission check
132
+ // consults. Only explicit user deletes fence (ids are never reused); the cap
133
+ // only ever forgets the oldest, long-settled entries.
134
+ const SESSION_DELETE_FENCE_MAX = 4096;
135
+ const _deleteFence = new Set();
136
+
137
+ export function fenceSessionWrites(id) {
138
+ if (!id) return;
139
+ _deleteFence.delete(id);
140
+ _deleteFence.add(id);
141
+ if (_deleteFence.size > SESSION_DELETE_FENCE_MAX) {
142
+ _deleteFence.delete(_deleteFence.values().next().value);
143
+ }
144
+ }
145
+
146
+ export function isSessionWriteFenced(id) {
147
+ return _deleteFence.has(id);
148
+ }
149
+
127
150
  /** Clear every save marker for an id (successful hard delete). */
128
151
  export function _clearSessionSaveState(id) {
129
152
  if (!id) return;
@@ -83,6 +83,13 @@ function _readSidecarOwnerPid(path) {
83
83
  }
84
84
  }
85
85
 
86
+ /** True only when the sidecar recorded an owner pid that no longer exists
87
+ * (pid-less legacy sidecars prove nothing and read false). */
88
+ export function isSidecarOwnerDead(path) {
89
+ const pid = _readSidecarOwnerPid(path);
90
+ return pid > 0 && !isProcessAlive(pid);
91
+ }
92
+
86
93
  // ── Heartbeat publish ─────────────────────────────────────
87
94
  // Lightweight per-session timestamp file (`<id>.hb`) consumed by the
88
95
  // status aggregator for fresh-session detection. Decoupled from the
@@ -193,25 +200,74 @@ export function isSessionHeartbeatOwnerDead(id) {
193
200
  // writer on the same session file (dual-writer last-save-wins clobbering).
194
201
  // Refreshed by the engine share tick (~3s call, throttled here) and cleared
195
202
  // on session switch/dispose; staleness covers crashes.
203
+ //
204
+ // Presence is a sidecar OF a session record and never outlives it: it is
205
+ // only published while the record exists, and a publish that loses a race
206
+ // with a deletion (this process or another one unlinked the record and its
207
+ // sidecar while the write was in flight) removes what it just wrote.
196
208
  const _PRESENCE_THROTTLE_MS = 20_000;
197
209
  const _presenceLastAt = new Map();
210
+ // id -> in-flight publish; deleteSessionPresence cancels it so the write that
211
+ // lands after the unlink is removed again instead of resurrecting the file.
212
+ const _presenceInFlight = new Map();
198
213
 
199
214
  function _presencePath(id) {
200
215
  return _sessionFilePath(id, '.own');
201
216
  }
202
217
 
218
+ async function _sessionRecordExists(id) {
219
+ try {
220
+ await fsp.stat(sessionPath(id));
221
+ return true;
222
+ } catch (error) {
223
+ // Only a proven absence stops the beacon; an unreadable stat keeps it.
224
+ return error?.code !== 'ENOENT';
225
+ }
226
+ }
227
+
228
+ async function _publishPresence(id, now, flight) {
229
+ try {
230
+ // A record that does not exist (never saved yet, or deleted) gets no
231
+ // beacon; the throttle stays open so the next tick retries.
232
+ if (!(await _sessionRecordExists(id)) || flight.cancelled) {
233
+ if (_presenceLastAt.get(id) === now) _presenceLastAt.delete(id);
234
+ return;
235
+ }
236
+ const target = _presencePath(id);
237
+ await fsp.writeFile(target, `${now}\n${process.pid}\n`, 'utf8');
238
+ if (flight.cancelled || !(await _sessionRecordExists(id))) {
239
+ await fsp.unlink(target).catch(() => {});
240
+ if (_presenceLastAt.get(id) === now) _presenceLastAt.delete(id);
241
+ }
242
+ } catch {
243
+ /* best-effort beacon */
244
+ } finally {
245
+ if (_presenceInFlight.get(id) === flight) _presenceInFlight.delete(id);
246
+ }
247
+ }
248
+
203
249
  export function publishSessionPresence(id, ts) {
204
250
  if (!id) return;
205
251
  const now = ts || Date.now();
206
252
  const last = _presenceLastAt.get(id) || 0;
207
253
  if (now - last < _PRESENCE_THROTTLE_MS) return;
208
- const target = _presencePath(id);
254
+ if (_presenceInFlight.has(id)) return;
255
+ _assertSessionId(id);
209
256
  _presenceLastAt.set(id, now);
210
- void fsp.writeFile(target, `${now}\n${process.pid}\n`, 'utf8').catch(() => {});
257
+ const flight = { cancelled: false, done: null };
258
+ _presenceInFlight.set(id, flight);
259
+ flight.done = _publishPresence(id, now, flight);
260
+ }
261
+
262
+ /** Test/drain seam: resolves once the id's in-flight presence publish settled. */
263
+ export function settleSessionPresence(id) {
264
+ return _presenceInFlight.get(id)?.done || Promise.resolve();
211
265
  }
212
266
 
213
267
  export function deleteSessionPresence(id) {
214
268
  if (!id) return;
269
+ const flight = _presenceInFlight.get(id);
270
+ if (flight) flight.cancelled = true;
215
271
  try {
216
272
  unlinkSync(_presencePath(id));
217
273
  } catch {
@@ -1,5 +1,5 @@
1
1
  import { MessageChannel, Worker } from 'node:worker_threads';
2
- import { guardedSaveOptions as _guardedSaveOptions } from './write-guards.mjs';
2
+ import { guardedSaveOptions as _guardedSaveOptions, releaseSessionWriteControl } from './write-guards.mjs';
3
3
  import { _ensureLifecycleFields } from './serialize.mjs';
4
4
  import {
5
5
  _attemptEvidence,
@@ -63,9 +63,12 @@ export { _hasSessionSaveBaseline } from './save-delta.mjs';
63
63
  * the writes, so a write already posted for `id` still lands first; the next
64
64
  * save for `id`, if any, simply goes out as a full snapshot.
65
65
  */
66
- export function forgetSessionSaveBaseline(id) {
66
+ export function forgetSessionSaveBaseline(id, { releaseWriteControl = false } = {}) {
67
67
  if (!id) return false;
68
68
  const had = _dropDeltaBaseline(id);
69
+ // Only when the session itself is gone (close/teardown/delete): a mid-life
70
+ // forget (compaction) must keep the landed-epoch fence for in-flight writes.
71
+ if (releaseWriteControl) releaseSessionWriteControl(String(id));
69
72
  if (_saveWorker) {
70
73
  try {
71
74
  _saveWorker.postMessage({ __forgetBase: String(id) });
@@ -854,7 +857,7 @@ export function purgeSessionSaveBookkeeping(id) {
854
857
  }
855
858
  }
856
859
  // Also frees the worker-side base: a deleted id never saves again.
857
- forgetSessionSaveBaseline(id);
860
+ forgetSessionSaveBaseline(id, { releaseWriteControl: true });
858
861
  return purged;
859
862
  }
860
863
 
@@ -4,10 +4,11 @@
4
4
  * edge plus the full local-state purge that must happen under the same lock
5
5
  * (incarnation retirement, pending slots, purge hooks, markers, summary row).
6
6
  */
7
- import { readFileSync, readdirSync, unlinkSync } from 'node:fs';
8
- import { join } from 'node:path';
7
+ import { unlinkSync } from 'node:fs';
9
8
  import { isAgentOwner } from '../../agent-owner.mjs';
10
- import { readTopLevelLifecycleRecord, isLifecycleUnreadable } from '../lifecycle-scan.mjs';
9
+ import { listStoredAgentWorkerLinks } from '../store-agent-worker-rows.mjs';
10
+ import { listStoredSessionSummaries } from './listing.mjs';
11
+ import { STORED_SESSION_UNREADABLE, _storedSessionFromFile } from './serialize.mjs';
11
12
  import { getStoreDir, sessionPath, deleteSessionPresence } from './paths-heartbeat.mjs';
12
13
  import {
13
14
  readCanonicalSessionRecord as _readCanonicalRecord,
@@ -22,49 +23,58 @@ import {
22
23
  } from './write-guards.mjs';
23
24
  import {
24
25
  _clearLiveSession,
26
+ _liveSessions,
25
27
  _retireSessionIncarnation,
26
28
  _clearSessionSaveState,
27
29
  clearSessionLifecycleCommitError,
30
+ fenceSessionWrites,
28
31
  } from './live-state.mjs';
29
32
  import { purgeSessionSaveBookkeeping as _purgeSessionSaveBookkeeping } from './save-worker.mjs';
30
- import { _uncacheSessionSummary, _queueSessionSummaryRemoval } from './summary-cache.mjs';
33
+ import { _uncacheSessionSummary, _queueSessionSummaryRemoval, _cachedSummaryRows } from './summary-cache.mjs';
31
34
  import { _savePending, _clearDebounce, _releasePendingSlot } from './pending-saves.mjs';
32
35
  import { _runtimeLivenessVeto, _heartbeatLivenessVeto, _deleteHeartbeatUnlessNewer } from './liveness-veto.mjs';
33
36
  import { _runSessionPurgeHooks } from './session-observers.mjs';
34
37
  import { deleteCompactionArchive } from '../compaction-archive.mjs';
35
38
 
36
- /** Strictly enumerate child-agent session files linked to one visible parent.
37
- * Used only by explicit parent deletion; ordinary close/context switches keep
38
- * the relationship intact. */
39
+ /** Child-agent session ids linked to one visible parent. Candidates come from
40
+ * metadata only (the summary rows and the agent-worker links — no folder
41
+ * scan); each candidate's own file is then read strictly and must still be an
42
+ * agent-owned record linked to this parent, so a stale/foreign metadata row
43
+ * can never name a deletion target. Used only by explicit parent deletion;
44
+ * ordinary close/context switches keep the relationship intact. */
39
45
  export function listOwnedAgentSessionIds(ownerSessionId) {
40
46
  const ownerId = String(ownerSessionId || '').trim();
41
47
  if (!/^[A-Za-z0-9_-]+$/.test(ownerId)) return [];
42
- const dir = getStoreDir();
43
- if (probePath(dir).state !== PROBE_PRESENT) return [];
44
- let files;
45
- try {
46
- files = readdirSync(dir).filter((file) => file.endsWith('.json'));
47
- } catch {
48
- return [];
48
+ const linksTo = (row) => row?.ownerSessionId === ownerId || row?.parentSessionId === ownerId;
49
+ const candidates = new Set();
50
+ for (const row of [...listStoredSessionSummaries(), ...listStoredAgentWorkerLinks()]) {
51
+ const candidateId = String(row?.id || row?.sessionId || '').trim();
52
+ if (linksTo(row) && candidateId !== ownerId && /^[A-Za-z0-9_-]+$/.test(candidateId)) {
53
+ candidates.add(candidateId);
54
+ }
49
55
  }
56
+ const dir = getStoreDir();
50
57
  const ids = [];
51
- for (const file of files) {
52
- const candidateId = file.slice(0, -5);
53
- if (!candidateId || candidateId === ownerId || !/^[A-Za-z0-9_-]+$/.test(candidateId)) continue;
54
- try {
55
- const record = readTopLevelLifecycleRecord(readFileSync(join(dir, file), 'utf8'));
56
- if (isLifecycleUnreadable(record) || record.id !== candidateId) continue;
57
- const session = record.doc;
58
- if (!isAgentOwner(session)) continue;
59
- const linkedOwner = String(session.ownerSessionId || session.parentSessionId || '').trim();
60
- if (linkedOwner === ownerId) ids.push(candidateId);
61
- } catch {
62
- /* unreadable/vanished records are never deletion targets */
63
- }
58
+ for (const candidateId of candidates) {
59
+ const session = _storedSessionFromFile(dir, `${candidateId}.json`, false);
60
+ // Unreadable, vanished, corrupt or foreign records are never deletion targets.
61
+ if (!session || session === STORED_SESSION_UNREADABLE || !isAgentOwner(session)) continue;
62
+ const linkedOwner = String(session.ownerSessionId || session.parentSessionId || '').trim();
63
+ if (linkedOwner === ownerId) ids.push(candidateId);
64
64
  }
65
65
  return ids;
66
66
  }
67
67
 
68
+ /** Working folders the session is known to have used (its cached summary row
69
+ * and live snapshot — no file read, no scan); the purge hooks derive
70
+ * per-folder artifacts such as the transcript path from them. */
71
+ function knownSessionCwds(id) {
72
+ const row = _cachedSummaryRows()?.find((candidate) => candidate.id === id);
73
+ const live = _liveSessions.get(id);
74
+ const cwds = [row?.cwd, row?.workspace?.cwd, row?.workspace?.projectPath, live?.cwd];
75
+ return [...new Set(cwds.filter((cwd) => typeof cwd === 'string' && cwd))];
76
+ }
77
+
68
78
  export function deleteSession(id, options = {}) {
69
79
  // Keep caller probes and all vetoes ahead of the non-reentrant lock and
70
80
  // ahead of pending-save disruption, matching markSessionClosed().
@@ -122,12 +132,15 @@ export function deleteSession(id, options = {}) {
122
132
  // its settlement moves no marker and touches no bookkeeping — and a
123
133
  // re-created id gets a brand new incarnation of its own.
124
134
  _retireSessionIncarnation(id);
135
+ // An explicit user delete also fences the id: a still-running turn's later
136
+ // saves get fresh guards/incarnations and would otherwise recreate the file.
137
+ if (options.fenceWrites === true) fenceSessionWrites(id);
125
138
  _cancelSessionWrites(id);
126
139
  _clearDebounce(id);
127
140
  _releasePendingSlot(_savePending.get(id));
128
141
  _savePending.delete(id);
129
142
  _purgeSessionSaveBookkeeping(id);
130
- _runSessionPurgeHooks(id);
143
+ _runSessionPurgeHooks(id, { cwds: knownSessionCwds(id) });
131
144
  // Preserve a sidecar published strictly after the sweep's scan snapshot.
132
145
  _deleteHeartbeatUnlessNewer(id, options);
133
146
  deleteSessionPresence(id);
@@ -44,10 +44,12 @@ export function _publishLiveSession(session) {
44
44
 
45
45
  _setLiveSessionPublisher(_publishLiveSession);
46
46
 
47
- export function _runSessionPurgeHooks(id) {
47
+ /** `context` (optional, e.g. `{ cwds }`: working folders the session is known
48
+ * to have used) is passed to every hook as its second argument. */
49
+ export function _runSessionPurgeHooks(id, context) {
48
50
  for (const hook of _sessionPurgeHooks) {
49
51
  try {
50
- hook(id);
52
+ hook(id, context);
51
53
  } catch {
52
54
  /* a cleanup hook never breaks delete */
53
55
  }
@@ -7,6 +7,11 @@ import { probePath, PROBE_PRESENT, PROBE_ABSENT } from '../fs-probe.mjs';
7
7
  import { _queueSummaryIndexPrune } from '../summary-cache.mjs';
8
8
  import { deleteSession } from '../session-delete.mjs';
9
9
  import { COMPACTION_ARCHIVE_SUFFIX } from '../../compaction-archive.mjs';
10
+ import { isSidecarOwnerDead } from '../paths-heartbeat.mjs';
11
+
12
+ // A live presence owner rewrites `.own` every ~20s; one untouched for the
13
+ // attach guard's whole freshness window holds nothing open.
14
+ const PRESENCE_ORPHAN_AGE_MS = 2 * 60 * 1000;
10
15
 
11
16
  /** Retention cap: prune resumable open (non-tombstone) sessions newest-first
12
17
  * — keep the most recent openMaxCount, prune anything older than openMaxAgeMs
@@ -68,7 +73,13 @@ export function* reapOrphanSidecars({ plan, dir, now, tally }) {
68
73
  if (probePath(join(dir, recordOf(h))).state !== PROBE_ABSENT) continue;
69
74
  const sidecarProbe = probePath(join(dir, h));
70
75
  if (sidecarProbe.state !== PROBE_PRESENT) continue;
71
- if (now - sidecarProbe.mtimeMs > plan.maxAge) {
76
+ // A presence sidecar is only ever published while its record exists,
77
+ // so one without a record whose recorded owner is dead (or which no
78
+ // live owner refreshed within the presence window) is a leftover of a
79
+ // deleted session, not a session mid-create.
80
+ const orphanPresence =
81
+ h.endsWith('.own') && (now - sidecarProbe.mtimeMs > PRESENCE_ORPHAN_AGE_MS || isSidecarOwnerDead(join(dir, h)));
82
+ if (orphanPresence || now - sidecarProbe.mtimeMs > plan.maxAge) {
72
83
  try {
73
84
  unlinkSync(join(dir, h));
74
85
  tally.cleaned++;
@@ -14,6 +14,7 @@ import {
14
14
  } from './canonical-reader.mjs';
15
15
  import { isCancelledWrite as _isCancelledWrite } from './write-guards.mjs';
16
16
  import { _setSessionWriteAuthorityCheck } from './save-worker.mjs';
17
+ import { isSessionWriteFenced } from './live-state.mjs';
17
18
 
18
19
  const _observeStamp = (target) => {
19
20
  try {
@@ -71,7 +72,7 @@ function _writeAuthorityRecord(target, attempt) {
71
72
  * under their own authority read).
72
73
  */
73
74
  export function _shouldDrop(id, opts, attempt = null) {
74
- if (_isCancelledWrite(opts)) return true;
75
+ if (_isCancelledWrite(opts) || isSessionWriteFenced(id)) return true;
75
76
  const expected = typeof opts?.expectedGeneration === 'number' ? opts.expectedGeneration : null;
76
77
  const target = sessionPath(id);
77
78
  let record;
@@ -126,6 +127,7 @@ export function _shouldDrop(id, opts, attempt = null) {
126
127
  // under the lock by _shouldDrop.
127
128
  export function _sessionWriteAuthorityRefusal(id, attempt = null) {
128
129
  if (!id) return null;
130
+ if (isSessionWriteFenced(id)) return 'deleted';
129
131
  let authority;
130
132
  try {
131
133
  // A synchronous save passes its `attempt`: its pre-admission is then the
@@ -49,6 +49,22 @@ export function guardedSaveOptions(id, opts, epoch = null) {
49
49
  };
50
50
  }
51
51
 
52
+ /**
53
+ * Drop the id's control buffer once the session is gone (close/forget/delete)
54
+ * so the map does not grow by one SharedArrayBuffer per session ever saved.
55
+ * Kept while a commit holds the lock: that write still serialises on this
56
+ * buffer. A later save for the same id simply allocates a fresh control.
57
+ */
58
+ export function releaseSessionWriteControl(id) {
59
+ const control = _writeControls.get(id);
60
+ if (!control || Atomics.load(control, 1) !== 0) return false;
61
+ return _writeControls.delete(id);
62
+ }
63
+
64
+ export function _hasSessionWriteControl(id) {
65
+ return _writeControls.has(id);
66
+ }
67
+
52
68
  export function cancelSessionWrites(id) {
53
69
  Atomics.add(writeControl(id), 0, 1);
54
70
  }
@@ -106,13 +106,25 @@ function agentStatusValues(row) {
106
106
  .filter((status, index, values) => Boolean(status) && values.indexOf(status) === index);
107
107
  }
108
108
 
109
+ /** The worker's lifecycle. `status` decides; `stage` is only the runtime phase
110
+ * copied in by the progress sweep, so mid-work it may read idle/done/error
111
+ * beside a worker that is still running. Only a row with no status falls
112
+ * back to its stage. */
113
+ function workerLifecycle(row) {
114
+ return cleanValue(row?.status) || cleanValue(row?.stage);
115
+ }
116
+
109
117
  function activeAgentWorker(row) {
110
- const statuses = agentStatusValues(row);
111
- return (
112
- statuses.length > 0 &&
113
- !statuses.some((status) => DEAD_AGENT_STATUS.test(status)) &&
114
- statuses.some((status) => LIVING_AGENT_STATUS.test(status))
115
- );
118
+ // A cancel in either field ends the work (bare `cancelling` is still work).
119
+ if (
120
+ agentStatusValues(row).some(
121
+ (status) => CANCELLED_AGENT_STATUS.test(status) || CANCEL_UNCONFIRMED_AGENT_STATUS.test(status)
122
+ )
123
+ ) {
124
+ return false;
125
+ }
126
+ const lifecycle = workerLifecycle(row);
127
+ return Boolean(lifecycle) && !DEAD_AGENT_STATUS.test(lifecycle) && LIVING_AGENT_STATUS.test(lifecycle);
116
128
  }
117
129
 
118
130
  function stampMs(value) {
@@ -287,7 +299,7 @@ const withinPoolWindow = (at, now) => at > 0 && now - at <= AGENT_POOL_HEARTBEAT
287
299
  * a working status whose own stamp is still inside the pool window. */
288
300
  function poolRowWorking(row, heartbeatMtimes, now) {
289
301
  if (withinPoolWindow(heartbeatMtimes.get(cleanValue(row?.sessionId)) || 0, now)) return true;
290
- if (!WORKING_AGENT_STATUS.test(cleanValue(row?.stage || row?.status))) return false;
302
+ if (!WORKING_AGENT_STATUS.test(workerLifecycle(row))) return false;
291
303
  return withinPoolWindow(Date.parse(cleanValue(row?.updatedAt)) || 0, now);
292
304
  }
293
305
 
@@ -307,7 +319,7 @@ function projectChildWorkerRow(row, { now, heartbeatMtimes }) {
307
319
  const session = readWorkerSessionHeader(sessionId);
308
320
  const declaredStatus = cleanValue(row.status) || 'running';
309
321
  const declaredStage = cleanValue(row.stage || row.status) || 'running';
310
- const declaredWorking = WORKING_AGENT_STATUS.test(declaredStatus) || WORKING_AGENT_STATUS.test(declaredStage);
322
+ const declaredWorking = WORKING_AGENT_STATUS.test(workerLifecycle(row) || 'running');
311
323
  const heartbeatFresh = withinPoolWindow(heartbeatMtimes.get(sessionId) || 0, now);
312
324
  const recentlyUpdated = withinPoolWindow(Date.parse(cleanValue(row.updatedAt)) || 0, now);
313
325
  const runtimePid = positiveNumber(row.runtimePid, 0);
@@ -414,7 +426,7 @@ function promoteHeartbeatSidecar(bySessionId, sessionId, heartbeatAt) {
414
426
  bySessionId.set(sessionId, sidecarWorkerRow(current, session, { ...identity, status: cancel.status }));
415
427
  return;
416
428
  }
417
- const currentStatus = cleanValue(current.stage || current.status);
429
+ const currentStatus = workerLifecycle(current);
418
430
  if (currentStatus && !WORKING_AGENT_STATUS.test(currentStatus)) return;
419
431
  // The sidecar is the live lease. Durable child sessions are intentionally
420
432
  // detached/closed while their external owner runs.
@@ -452,7 +464,7 @@ function projectLeadWorkerRow(row, { now, heartbeatMtimes }) {
452
464
  const heartbeatAt = heartbeatMtimes.get(sessionId) || 0;
453
465
  const heartbeatFresh = withinPoolWindow(heartbeatAt, now);
454
466
  const recentlyUpdated = withinPoolWindow(Date.parse(cleanValue(row.updatedAt)) || 0, now);
455
- const declaredStatus = cleanValue(row.stage || row.status) || 'idle';
467
+ const declaredStatus = workerLifecycle(row) || 'idle';
456
468
  const working = WORKING_AGENT_STATUS.test(declaredStatus) && (heartbeatFresh || recentlyUpdated);
457
469
  const reapAt = Date.parse(cleanValue(row.reapAt)) || 0;
458
470
  if (!heartbeatFresh && reapAt > 0 && now >= reapAt) return null;
@@ -7,13 +7,17 @@
7
7
  * `label`, `agent`, `provider`, `model`, `effort`, `fast`, `finished`) and the
8
8
  * "\n\nResult:\n" marker are the wire format the notification writer emits;
9
9
  * they are matched verbatim.
10
+ *
11
+ * Only the parent(s) named by metadata are read — never the sessions
12
+ * directory: every vanished id reaches this path (deleted Lead records too),
13
+ * and a synchronous scan of every session file froze the daemon loop.
10
14
  */
11
- import { readFileSync, readdirSync } from 'node:fs';
15
+ import { readFile } from 'node:fs/promises';
12
16
  import { join } from 'node:path';
13
- import { probePath, PROBE_PRESENT } from './store/fs-probe.mjs';
14
17
  import { readTopLevelLifecycleRecord, isLifecycleUnreadable } from './lifecycle-scan.mjs';
15
18
  import { dataDir } from './store-summary-locations.mjs';
16
- import { cleanValue, desktopSession, positiveNumber } from './store-summary-fields.mjs';
19
+ import { listStoredAgentWorkerLinks } from './store-agent-worker-rows.mjs';
20
+ import { cleanValue, desktopSession, isStoredSessionId, positiveNumber } from './store-summary-fields.mjs';
17
21
 
18
22
  function archivedAgentNotification(content, sessionId) {
19
23
  const text = typeof content === 'string' ? content : '';
@@ -52,23 +56,38 @@ function archivedAgentNotification(content, sessionId) {
52
56
  };
53
57
  }
54
58
 
59
+ /** Parent session ids metadata names for `sessionId`: its agent-workers.json
60
+ * link and its summary-catalog row (`catalogRow(id)`, optional). */
61
+ function parentSessionIds(sessionId, catalogRow) {
62
+ const parents = new Set();
63
+ for (const link of listStoredAgentWorkerLinks()) {
64
+ if (link.sessionId !== sessionId) continue;
65
+ parents.add(link.parentSessionId);
66
+ parents.add(link.ownerSessionId);
67
+ }
68
+ const row = typeof catalogRow === 'function' ? catalogRow(sessionId) : null;
69
+ if (row) {
70
+ parents.add(cleanValue(row.parentSessionId));
71
+ parents.add(cleanValue(row.ownerSessionId));
72
+ }
73
+ parents.delete(sessionId);
74
+ return [...parents].filter(isStoredSessionId);
75
+ }
76
+
55
77
  /** Legacy recovery for child transcripts already unlinked by terminal reaping.
56
- * The parent owns one canonical body-carrying completion notification, so scan
57
- * only files containing the exact child id and project the newest valid body. */
58
- export function readArchivedAgentResult(sessionId) {
78
+ * The parent owns one canonical body-carrying completion notification: read
79
+ * only the parent(s) metadata links the child to, asynchronously, and project
80
+ * the newest valid body. An id with no parent link (a deleted Lead record)
81
+ * resolves to null without touching any session file. */
82
+ export async function readArchivedAgentResult(sessionId, { catalogRow } = {}) {
83
+ const parents = parentSessionIds(sessionId, catalogRow);
84
+ if (parents.length === 0) return null;
59
85
  const dir = join(dataDir(), 'sessions');
60
- if (probePath(dir).state !== PROBE_PRESENT) return null;
61
- let files;
62
- try {
63
- files = readdirSync(dir).filter((file) => file.endsWith('.json'));
64
- } catch {
65
- return null;
66
- }
67
86
  let best = null;
68
- for (const file of files) {
87
+ for (const parentId of parents) {
69
88
  let raw;
70
89
  try {
71
- raw = readFileSync(join(dir, file), 'utf8');
90
+ raw = await readFile(join(dir, `${parentId}.json`), 'utf8');
72
91
  } catch {
73
92
  continue;
74
93
  }
@@ -21,10 +21,13 @@ import { dataDir } from './store-summary-locations.mjs';
21
21
  import {
22
22
  desktopSession,
23
23
  importKey,
24
+ inheritedFromSessionId,
24
25
  isStoredSessionId,
25
26
  positiveNumber,
27
+ realTurnCount,
26
28
  workspaceSummary,
27
29
  } from './store-summary-fields.mjs';
30
+ import { isRealTurnMessage } from './compact/user-message-kind.mjs';
28
31
  import { byActivityThenId } from './catalog-db/schema.mjs';
29
32
  import {
30
33
  sessionStampOf,
@@ -181,6 +184,7 @@ export function normalizedRow(row, heartbeatAt = 0) {
181
184
  permission: row.permission || null,
182
185
  toolPermission: row.toolPermission || null,
183
186
  messageCount: Math.max(0, Math.floor(Number(row.messageCount) || 0)),
187
+ realTurnCount: realTurnCount(row),
184
188
  title: cleanText(row.title, 100),
185
189
  preview: cleanText(row.preview),
186
190
  generation: typeof row.generation === 'number' ? row.generation : 0,
@@ -188,6 +192,7 @@ export function normalizedRow(row, heartbeatAt = 0) {
188
192
  storageSize: positiveNumber(row.storageSize, 0),
189
193
  detachedReason: row.detachedReason || null,
190
194
  importKey: importKey(row),
195
+ inheritedFromSessionId: inheritedFromSessionId(row),
191
196
  };
192
197
  }
193
198
 
@@ -238,6 +243,7 @@ function rowFromSession(session, heartbeatAt = 0) {
238
243
  {
239
244
  ...session,
240
245
  messageCount: messages.filter((message) => message?.role === 'user' || message?.role === 'assistant').length,
246
+ realTurnCount: messages.filter(isRealTurnMessage).length,
241
247
  preview,
242
248
  },
243
249
  heartbeatAt
@@ -50,6 +50,19 @@ export function workspaceSummary(value) {
50
50
 
51
51
  /** Dedupe key of an imported conversation: a session's `importOrigin.key`,
52
52
  * or a summary row's projected `importKey`; null for ordinary sessions. */
53
+ export function inheritedFromSessionId(value) {
54
+ const id = typeof value?.inheritedFromSessionId === 'string' ? value.inheritedFromSessionId.trim() : '';
55
+ return id || null;
56
+ }
57
+
58
+ /** Count of real human turns a session persisted (actual user instructions
59
+ * plus compact summaries, which stand for earlier human turns); null for a
60
+ * row written before the field existed. */
61
+ export function realTurnCount(value) {
62
+ const count = value?.realTurnCount;
63
+ return typeof count === 'number' && Number.isFinite(count) && count >= 0 ? Math.floor(count) : null;
64
+ }
65
+
53
66
  export function importKey(value) {
54
67
  const key = typeof value?.importKey === 'string' ? value.importKey : value?.importOrigin?.key;
55
68
  return typeof key === 'string' && key.trim() ? key : null;
@@ -20,10 +20,13 @@ import { cleanSessionPreview, isSessionPreviewNoise, sessionMessageText } from '
20
20
  import { sessionVisibility } from './store-summary-visibility.mjs';
21
21
  import {
22
22
  importKey,
23
+ inheritedFromSessionId,
23
24
  isStoredSessionId,
24
25
  positiveNumber as _positiveNumber,
26
+ realTurnCount,
25
27
  workspaceSummary,
26
28
  } from './store-summary-fields.mjs';
29
+ import { isRealTurnMessage } from './compact/user-message-kind.mjs';
27
30
 
28
31
  export const SESSION_SUMMARY_INDEX_VERSION = 2;
29
32
 
@@ -91,6 +94,7 @@ function _sessionMessageProjection(session) {
91
94
  const cached = session && typeof session === 'object' ? sessionMessageProjectionMemo.get(session) : null;
92
95
  let start = 0;
93
96
  let count = 0;
97
+ let real = 0;
94
98
  let preview = '';
95
99
  let previewMessage = null;
96
100
  const canAppend =
@@ -100,6 +104,7 @@ function _sessionMessageProjection(session) {
100
104
  if (canAppend) {
101
105
  start = cached.length;
102
106
  count = cached.count;
107
+ real = cached.real;
103
108
  preview = cached.preview;
104
109
  previewMessage = cached.previewMessage;
105
110
  // The first real user message is stable in normal append-only session
@@ -111,6 +116,7 @@ function _sessionMessageProjection(session) {
111
116
  else {
112
117
  start = 0;
113
118
  count = 0;
119
+ real = 0;
114
120
  preview = '';
115
121
  previewMessage = null;
116
122
  }
@@ -119,6 +125,7 @@ function _sessionMessageProjection(session) {
119
125
  for (let index = start; index < messages.length; index += 1) {
120
126
  const message = messages[index];
121
127
  if (message && (message.role === 'user' || message.role === 'assistant')) count += 1;
128
+ if (isRealTurnMessage(message)) real += 1;
122
129
  if (!preview) {
123
130
  const candidate = _previewFromMessage(message);
124
131
  if (candidate) {
@@ -132,11 +139,12 @@ function _sessionMessageProjection(session) {
132
139
  length: messages.length,
133
140
  lastMessage: messages[messages.length - 1] || null,
134
141
  count,
142
+ real,
135
143
  preview,
136
144
  previewMessage,
137
145
  });
138
146
  }
139
- return { count, preview };
147
+ return { count, real, preview };
140
148
  }
141
149
 
142
150
  function _desktopSessionSummary(value, cwd = null) {
@@ -198,6 +206,7 @@ export function _sessionSummary(session) {
198
206
  : null,
199
207
  taskType: session.taskType || null,
200
208
  messageCount: messageProjection.count,
209
+ realTurnCount: messageProjection.real,
201
210
  title: _cleanPreview(session.title || '', 100),
202
211
  preview: messageProjection.preview,
203
212
  generation: typeof session.generation === 'number' ? session.generation : 0,
@@ -209,6 +218,7 @@ export function _sessionSummary(session) {
209
218
  // Imported-conversation identity, so "already imported" is answered
210
219
  // from summaries without loading sessions.
211
220
  importKey: importKey(session),
221
+ inheritedFromSessionId: inheritedFromSessionId(session),
212
222
  };
213
223
  }
214
224
 
@@ -251,6 +261,7 @@ function _normalizeSummaryRow(row) {
251
261
  : null,
252
262
  taskType: row.taskType || null,
253
263
  messageCount: Math.max(0, Math.floor(Number(row.messageCount) || 0)),
264
+ realTurnCount: realTurnCount(row),
254
265
  title: _cleanPreview(row.title || '', 100),
255
266
  preview: _cleanPreview(row.preview || ''),
256
267
  generation: typeof row.generation === 'number' ? row.generation : 0,
@@ -258,6 +269,7 @@ function _normalizeSummaryRow(row) {
258
269
  storageSize: _positiveNumber(row.storageSize, 0),
259
270
  detachedReason: row.detachedReason || null,
260
271
  importKey: importKey(row),
272
+ inheritedFromSessionId: inheritedFromSessionId(row),
261
273
  };
262
274
  }
263
275