mixdog 0.9.3 → 0.9.5

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 (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Session summary index: the compact `session-summaries.json` sidecar that
3
+ * lists every session's lightweight metadata (preview, counts, lifecycle) so
4
+ * the status aggregator and session listers avoid parsing every full session
5
+ * file. Extracted from store.mjs verbatim; store.mjs re-exports these so
6
+ * importers stay unchanged.
7
+ */
8
+ import { mkdirSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { getPluginData } from '../config.mjs';
11
+ import { updateJsonAtomicSync, writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
12
+
13
+ export const SESSION_SUMMARY_INDEX_VERSION = 1;
14
+
15
+ export function summaryIndexPath() {
16
+ const dir = getPluginData();
17
+ mkdirSync(dir, { recursive: true });
18
+ return join(dir, 'session-summaries.json');
19
+ }
20
+
21
+ function _messageText(content) {
22
+ if (typeof content === 'string') return content;
23
+ if (Array.isArray(content)) {
24
+ return content.map((part) => {
25
+ if (typeof part === 'string') return part;
26
+ if (part?.type === 'text') return part.text || '';
27
+ if (typeof part?.text === 'string') return part.text;
28
+ try { return JSON.stringify(part); } catch { return ''; }
29
+ }).filter(Boolean).join('\n');
30
+ }
31
+ if (content && typeof content === 'object') {
32
+ if (typeof content.text === 'string') return content.text;
33
+ try { return JSON.stringify(content); } catch { return ''; }
34
+ }
35
+ return '';
36
+ }
37
+
38
+ export function _cleanPreview(text, max = 240) {
39
+ const value = String(text || '').replace(/\s+/g, ' ').trim();
40
+ return value.length > max ? value.slice(0, max).replace(/\s+\S*$/, '').trim() : value;
41
+ }
42
+
43
+ function _isPreviewNoise(text) {
44
+ const value = String(text || '').trim();
45
+ if (!value) return true;
46
+ if (value.startsWith('<system-reminder>')) return true;
47
+ if (/^Reference files:/i.test(value)) return true;
48
+ return false;
49
+ }
50
+
51
+ function _sessionPreview(session) {
52
+ const messages = Array.isArray(session?.messages) ? session.messages : [];
53
+ let first = '';
54
+ for (const m of messages) {
55
+ if (m?.role !== 'user') continue;
56
+ const text = _cleanPreview(_messageText(m.content));
57
+ if (_isPreviewNoise(text)) continue;
58
+ if (!first) first = text;
59
+ }
60
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
61
+ const m = messages[i];
62
+ if (m?.role !== 'user') continue;
63
+ const text = _cleanPreview(_messageText(m.content));
64
+ if (_isPreviewNoise(text)) continue;
65
+ return text || first;
66
+ }
67
+ return first;
68
+ }
69
+
70
+ function _sessionMessageCount(session) {
71
+ const messages = Array.isArray(session?.messages) ? session.messages : [];
72
+ let count = 0;
73
+ for (const m of messages) {
74
+ if (m && (m.role === 'user' || m.role === 'assistant')) count++;
75
+ }
76
+ return count;
77
+ }
78
+
79
+ function _positiveNumber(value, fallback = 0) {
80
+ const n = Number(value);
81
+ return Number.isFinite(n) && n > 0 ? n : fallback;
82
+ }
83
+
84
+ export function _sessionSummary(session) {
85
+ if (!session?.id) return null;
86
+ return {
87
+ id: String(session.id),
88
+ updatedAt: _positiveNumber(session.updatedAt, Date.now()),
89
+ createdAt: _positiveNumber(session.createdAt, 0),
90
+ lastHeartbeatAt: _positiveNumber(session.lastHeartbeatAt, 0),
91
+ closed: session.closed === true,
92
+ status: String(session.status || (session.closed === true ? 'closed' : 'idle')),
93
+ owner: session.owner || 'user',
94
+ agent: session.agent || null,
95
+ sourceType: session.sourceType || null,
96
+ sourceName: session.sourceName || null,
97
+ scopeKey: session.scopeKey || null,
98
+ ownerSessionId: session.ownerSessionId || null,
99
+ clientHostPid: _positiveNumber(session.clientHostPid, 0) || null,
100
+ cwd: session.cwd || '',
101
+ provider: session.provider || null,
102
+ model: session.model || null,
103
+ agentTag: session.agentTag || null,
104
+ task_id: session.task_id || session.taskId || null,
105
+ permission: session.permission || null,
106
+ toolPermission: session.toolPermission || null,
107
+ messageCount: _sessionMessageCount(session),
108
+ preview: _sessionPreview(session),
109
+ generation: typeof session.generation === 'number' ? session.generation : 0,
110
+ implicitBashSessionId: session.implicitBashSessionId || null,
111
+ };
112
+ }
113
+
114
+ function _normalizeSummaryRow(row) {
115
+ if (!row?.id || typeof row.id !== 'string' || !/^[A-Za-z0-9_-]+$/.test(row.id)) return null;
116
+ return {
117
+ id: row.id,
118
+ updatedAt: _positiveNumber(row.updatedAt, 0),
119
+ createdAt: _positiveNumber(row.createdAt, 0),
120
+ lastHeartbeatAt: _positiveNumber(row.lastHeartbeatAt, 0),
121
+ closed: row.closed === true,
122
+ status: String(row.status || (row.closed === true ? 'closed' : 'idle')),
123
+ owner: row.owner || 'user',
124
+ agent: row.agent || null,
125
+ sourceType: row.sourceType || null,
126
+ sourceName: row.sourceName || null,
127
+ scopeKey: row.scopeKey || null,
128
+ ownerSessionId: row.ownerSessionId || null,
129
+ clientHostPid: _positiveNumber(row.clientHostPid, 0) || null,
130
+ cwd: row.cwd || '',
131
+ provider: row.provider || null,
132
+ model: row.model || null,
133
+ agentTag: row.agentTag || null,
134
+ task_id: row.task_id || null,
135
+ permission: row.permission || null,
136
+ toolPermission: row.toolPermission || null,
137
+ messageCount: Math.max(0, Math.floor(Number(row.messageCount) || 0)),
138
+ preview: _cleanPreview(row.preview || ''),
139
+ generation: typeof row.generation === 'number' ? row.generation : 0,
140
+ implicitBashSessionId: row.implicitBashSessionId || null,
141
+ };
142
+ }
143
+
144
+ export function _normalizeSummaryIndex(raw) {
145
+ const rows = Array.isArray(raw?.rows) ? raw.rows.map(_normalizeSummaryRow).filter(Boolean) : [];
146
+ rows.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
147
+ return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: _positiveNumber(raw?.updatedAt, 0), rows };
148
+ }
149
+
150
+ export function _writeSummaryIndex(rows) {
151
+ const cleanRows = (rows || []).map(_normalizeSummaryRow).filter(Boolean)
152
+ .sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
153
+ writeJsonAtomicSync(summaryIndexPath(), {
154
+ version: SESSION_SUMMARY_INDEX_VERSION,
155
+ updatedAt: Date.now(),
156
+ rows: cleanRows,
157
+ }, { compact: true, lock: true });
158
+ return cleanRows;
159
+ }
160
+
161
+ export function _upsertSessionSummary(session) {
162
+ const row = _sessionSummary(session);
163
+ if (!row) return;
164
+ try {
165
+ updateJsonAtomicSync(summaryIndexPath(), (cur) => {
166
+ const index = _normalizeSummaryIndex(cur);
167
+ const rows = index.rows.filter((r) => r.id !== row.id);
168
+ const cleanRow = _normalizeSummaryRow(row);
169
+ if (!cleanRow) return undefined;
170
+ const existing = index.rows.find((r) => r.id === row.id) || null;
171
+ if (existing && JSON.stringify(existing) === JSON.stringify(cleanRow)) return undefined;
172
+ rows.push(cleanRow);
173
+ rows.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
174
+ return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: Date.now(), rows };
175
+ }, { compact: true, lock: true });
176
+ } catch { /* summary index is best-effort */ }
177
+ }
178
+
179
+ export function _removeSessionSummary(id) {
180
+ if (!id) return;
181
+ try {
182
+ updateJsonAtomicSync(summaryIndexPath(), (cur) => {
183
+ const index = _normalizeSummaryIndex(cur);
184
+ const rows = index.rows.filter((r) => r.id !== id);
185
+ if (rows.length === index.rows.length) return undefined;
186
+ return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: Date.now(), rows };
187
+ }, { compact: true, lock: true });
188
+ } catch { /* summary index is best-effort */ }
189
+ }
@@ -10,11 +10,29 @@ import { join } from 'path';
10
10
  import { Worker } from 'worker_threads';
11
11
  import { getPluginData } from '../config.mjs';
12
12
  import { isAgentOwner } from '../agent-owner.mjs';
13
- import { renameWithRetrySync, updateJsonAtomicSync, writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
13
+ import { renameWithRetrySync } from '../../../shared/atomic-file.mjs';
14
14
  import { sanitizeContentForStoredHistory } from '../providers/media-normalization.mjs';
15
+ import {
16
+ summaryIndexPath,
17
+ _sessionSummary,
18
+ _normalizeSummaryIndex,
19
+ _writeSummaryIndex,
20
+ _upsertSessionSummary,
21
+ _removeSessionSummary,
22
+ } from './store-summary-index.mjs';
23
+ // Facade re-export: summary-index API moved to store-summary-index.mjs; keep
24
+ // prior importers of store.mjs unchanged.
25
+ export {
26
+ SESSION_SUMMARY_INDEX_VERSION,
27
+ summaryIndexPath,
28
+ _sessionSummary,
29
+ _normalizeSummaryIndex,
30
+ _writeSummaryIndex,
31
+ _upsertSessionSummary,
32
+ _removeSessionSummary,
33
+ } from './store-summary-index.mjs';
15
34
 
16
35
  const _lastSaveError = new Map(); // id -> { message, at }
17
- const SESSION_SUMMARY_INDEX_VERSION = 1;
18
36
 
19
37
  // The live in-memory session (and every model request)
20
38
  // retains attached image bytes across turns so multi-turn recognition works.
@@ -71,11 +89,6 @@ function sessionPath(id) {
71
89
  }
72
90
  return join(getStoreDir(), `${id}.json`);
73
91
  }
74
- function summaryIndexPath() {
75
- const dir = getPluginData();
76
- mkdirSync(dir, { recursive: true });
77
- return join(dir, 'session-summaries.json');
78
- }
79
92
  /**
80
93
  * Ensure generation/closed defaults on every session object.
81
94
  * Older persisted sessions predate these fields; we normalise at load and save.
@@ -88,176 +101,6 @@ function _ensureLifecycleFields(session) {
88
101
  return session;
89
102
  }
90
103
 
91
- function _messageText(content) {
92
- if (typeof content === 'string') return content;
93
- if (Array.isArray(content)) {
94
- return content.map((part) => {
95
- if (typeof part === 'string') return part;
96
- if (part?.type === 'text') return part.text || '';
97
- if (typeof part?.text === 'string') return part.text;
98
- try { return JSON.stringify(part); } catch { return ''; }
99
- }).filter(Boolean).join('\n');
100
- }
101
- if (content && typeof content === 'object') {
102
- if (typeof content.text === 'string') return content.text;
103
- try { return JSON.stringify(content); } catch { return ''; }
104
- }
105
- return '';
106
- }
107
-
108
- function _cleanPreview(text, max = 240) {
109
- const value = String(text || '').replace(/\s+/g, ' ').trim();
110
- return value.length > max ? value.slice(0, max).replace(/\s+\S*$/, '').trim() : value;
111
- }
112
-
113
- function _isPreviewNoise(text) {
114
- const value = String(text || '').trim();
115
- if (!value) return true;
116
- if (value.startsWith('<system-reminder>')) return true;
117
- if (/^Reference files:/i.test(value)) return true;
118
- return false;
119
- }
120
-
121
- function _sessionPreview(session) {
122
- const messages = Array.isArray(session?.messages) ? session.messages : [];
123
- let first = '';
124
- for (const m of messages) {
125
- if (m?.role !== 'user') continue;
126
- const text = _cleanPreview(_messageText(m.content));
127
- if (_isPreviewNoise(text)) continue;
128
- if (!first) first = text;
129
- }
130
- for (let i = messages.length - 1; i >= 0; i -= 1) {
131
- const m = messages[i];
132
- if (m?.role !== 'user') continue;
133
- const text = _cleanPreview(_messageText(m.content));
134
- if (_isPreviewNoise(text)) continue;
135
- return text || first;
136
- }
137
- return first;
138
- }
139
-
140
- function _sessionMessageCount(session) {
141
- const messages = Array.isArray(session?.messages) ? session.messages : [];
142
- let count = 0;
143
- for (const m of messages) {
144
- if (m && (m.role === 'user' || m.role === 'assistant')) count++;
145
- }
146
- return count;
147
- }
148
-
149
- function _positiveNumber(value, fallback = 0) {
150
- const n = Number(value);
151
- return Number.isFinite(n) && n > 0 ? n : fallback;
152
- }
153
-
154
- function _sessionSummary(session) {
155
- if (!session?.id) return null;
156
- return {
157
- id: String(session.id),
158
- updatedAt: _positiveNumber(session.updatedAt, Date.now()),
159
- createdAt: _positiveNumber(session.createdAt, 0),
160
- lastHeartbeatAt: _positiveNumber(session.lastHeartbeatAt, 0),
161
- closed: session.closed === true,
162
- status: String(session.status || (session.closed === true ? 'closed' : 'idle')),
163
- owner: session.owner || 'user',
164
- agent: session.agent || null,
165
- sourceType: session.sourceType || null,
166
- sourceName: session.sourceName || null,
167
- scopeKey: session.scopeKey || null,
168
- ownerSessionId: session.ownerSessionId || null,
169
- clientHostPid: _positiveNumber(session.clientHostPid, 0) || null,
170
- cwd: session.cwd || '',
171
- provider: session.provider || null,
172
- model: session.model || null,
173
- agentTag: session.agentTag || null,
174
- task_id: session.task_id || session.taskId || null,
175
- permission: session.permission || null,
176
- toolPermission: session.toolPermission || null,
177
- messageCount: _sessionMessageCount(session),
178
- preview: _sessionPreview(session),
179
- generation: typeof session.generation === 'number' ? session.generation : 0,
180
- implicitBashSessionId: session.implicitBashSessionId || null,
181
- };
182
- }
183
-
184
- function _normalizeSummaryRow(row) {
185
- if (!row?.id || typeof row.id !== 'string' || !/^[A-Za-z0-9_-]+$/.test(row.id)) return null;
186
- return {
187
- id: row.id,
188
- updatedAt: _positiveNumber(row.updatedAt, 0),
189
- createdAt: _positiveNumber(row.createdAt, 0),
190
- lastHeartbeatAt: _positiveNumber(row.lastHeartbeatAt, 0),
191
- closed: row.closed === true,
192
- status: String(row.status || (row.closed === true ? 'closed' : 'idle')),
193
- owner: row.owner || 'user',
194
- agent: row.agent || null,
195
- sourceType: row.sourceType || null,
196
- sourceName: row.sourceName || null,
197
- scopeKey: row.scopeKey || null,
198
- ownerSessionId: row.ownerSessionId || null,
199
- clientHostPid: _positiveNumber(row.clientHostPid, 0) || null,
200
- cwd: row.cwd || '',
201
- provider: row.provider || null,
202
- model: row.model || null,
203
- agentTag: row.agentTag || null,
204
- task_id: row.task_id || null,
205
- permission: row.permission || null,
206
- toolPermission: row.toolPermission || null,
207
- messageCount: Math.max(0, Math.floor(Number(row.messageCount) || 0)),
208
- preview: _cleanPreview(row.preview || ''),
209
- generation: typeof row.generation === 'number' ? row.generation : 0,
210
- implicitBashSessionId: row.implicitBashSessionId || null,
211
- };
212
- }
213
-
214
- function _normalizeSummaryIndex(raw) {
215
- const rows = Array.isArray(raw?.rows) ? raw.rows.map(_normalizeSummaryRow).filter(Boolean) : [];
216
- rows.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
217
- return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: _positiveNumber(raw?.updatedAt, 0), rows };
218
- }
219
-
220
- function _writeSummaryIndex(rows) {
221
- const cleanRows = (rows || []).map(_normalizeSummaryRow).filter(Boolean)
222
- .sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
223
- writeJsonAtomicSync(summaryIndexPath(), {
224
- version: SESSION_SUMMARY_INDEX_VERSION,
225
- updatedAt: Date.now(),
226
- rows: cleanRows,
227
- }, { compact: true, lock: true });
228
- return cleanRows;
229
- }
230
-
231
- function _upsertSessionSummary(session) {
232
- const row = _sessionSummary(session);
233
- if (!row) return;
234
- try {
235
- updateJsonAtomicSync(summaryIndexPath(), (cur) => {
236
- const index = _normalizeSummaryIndex(cur);
237
- const rows = index.rows.filter((r) => r.id !== row.id);
238
- const cleanRow = _normalizeSummaryRow(row);
239
- if (!cleanRow) return undefined;
240
- const existing = index.rows.find((r) => r.id === row.id) || null;
241
- if (existing && JSON.stringify(existing) === JSON.stringify(cleanRow)) return undefined;
242
- rows.push(cleanRow);
243
- rows.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
244
- return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: Date.now(), rows };
245
- }, { compact: true, lock: true });
246
- } catch { /* summary index is best-effort */ }
247
- }
248
-
249
- function _removeSessionSummary(id) {
250
- if (!id) return;
251
- try {
252
- updateJsonAtomicSync(summaryIndexPath(), (cur) => {
253
- const index = _normalizeSummaryIndex(cur);
254
- const rows = index.rows.filter((r) => r.id !== id);
255
- if (rows.length === index.rows.length) return undefined;
256
- return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: Date.now(), rows };
257
- }, { compact: true, lock: true });
258
- } catch { /* summary index is best-effort */ }
259
- }
260
-
261
104
  /** Module-level map tracking in-flight saves per session ID to prevent concurrent write corruption. */
262
105
  const _savePending = new Map();
263
106
 
@@ -520,17 +363,34 @@ function _shouldDrop(id, opts) {
520
363
  // ran on every save). Fast-path a regex scan; only fall back to a full
521
364
  // parse if the lightweight scan is inconclusive.
522
365
  const closedMatch = /"closed"\s*:\s*(true|false)/.exec(raw);
523
- if (closedMatch && closedMatch[1] === 'false') return false;
524
366
  if (closedMatch && closedMatch[1] === 'true') {
525
367
  const genMatch = /"generation"\s*:\s*(-?\d+)/.exec(raw);
526
368
  const diskGen = genMatch ? Number(genMatch[1]) : 0;
527
369
  return diskGen >= expected;
528
370
  }
371
+ if (closedMatch && closedMatch[1] === 'false') {
372
+ // Not closed — still check the generation-as-ownership-counter
373
+ // rule (see full-parse branch below) via the same fast regex
374
+ // scan, so bumpSessionGeneration()'s detach bump is honoured
375
+ // without paying for a full JSON.parse on every save.
376
+ const genMatch = /"generation"\s*:\s*(-?\d+)/.exec(raw);
377
+ const diskGen = genMatch ? Number(genMatch[1]) : 0;
378
+ return diskGen > expected;
379
+ }
529
380
  // No `closed` key found by scan — not a tombstone, so nothing to drop.
530
381
  if (!closedMatch && !/"closed"/.test(raw)) return false;
531
382
  const onDisk = JSON.parse(raw);
532
383
  const diskGen = typeof onDisk.generation === 'number' ? onDisk.generation : 0;
533
- return onDisk.closed === true && diskGen >= expected;
384
+ if (onDisk.closed === true) return diskGen >= expected;
385
+ // Not closed, but `generation` also doubles as an ownership counter:
386
+ // normal in-place saves (updateSession/appendMessage/etc.) never bump
387
+ // it, only closeSession()-family calls do (markSessionClosed and its
388
+ // non-tombstoning sibling bumpSessionGeneration). So if disk
389
+ // generation is strictly greater than what this write expected,
390
+ // ownership moved on (session was detached-closed and possibly
391
+ // resumed) after our turn started — drop the stale write rather than
392
+ // let it clobber whatever happened after the handoff.
393
+ return diskGen > expected;
534
394
  } catch {
535
395
  return false;
536
396
  }
@@ -666,6 +526,41 @@ export function markSessionClosed(id, reason = 'manual') {
666
526
  return newGen;
667
527
  }
668
528
 
529
+ /**
530
+ * Bump a session's generation WITHOUT planting a closed:true tombstone.
531
+ * Used by closeSession(id, reason, { tombstone: false }) — the runtime side
532
+ * (heartbeat, bash shells, controller, in-memory entry) is detached, but the
533
+ * session file itself stays valid/resumable. The generation bump alone is
534
+ * what protects it from a late save race: any saveSession() still in flight
535
+ * from the detached turn was issued with the OLD generation as its
536
+ * `expectedGeneration`, so once we bump the on-disk generation here, that
537
+ * late write's own _shouldDrop() check (generation-as-ownership-counter
538
+ * rule, see below) sees disk generation > expected and drops itself instead
539
+ * of clobbering whatever the resumed session writes next.
540
+ * Returns the new generation, or null if the session file doesn't exist.
541
+ */
542
+ export function bumpSessionGeneration(id, reason = 'detach') {
543
+ _clearDebounce(id);
544
+ const existing = loadSession(id);
545
+ if (!existing) return null;
546
+ const newGen = (typeof existing.generation === 'number' ? existing.generation : 0) + 1;
547
+ const detached = { ...existing, generation: newGen, updatedAt: Date.now(), detachedReason: reason };
548
+ const target = sessionPath(id);
549
+ const tmp = target + '.' + randomBytes(6).toString('hex') + '.tmp';
550
+ try {
551
+ writeFileSync(tmp, JSON.stringify(_sessionForDisk(detached)), 'utf-8');
552
+ _renameWithRetrySync(tmp, target);
553
+ } catch {
554
+ try { unlinkSync(tmp); } catch { /* ignore */ }
555
+ return null;
556
+ }
557
+ _savePending.delete(id);
558
+ _clearLiveSession(id);
559
+ _deleteHeartbeat(id);
560
+ _upsertSessionSummary(detached);
561
+ return newGen;
562
+ }
563
+
669
564
  export function loadSession(id) {
670
565
  const path = sessionPath(id);
671
566
  // Read-your-writes: if a save is pending (debouncing, scheduled, or queued
@@ -206,9 +206,28 @@ export const PROVIDER_RETRY_JITTER_RATIO = (() => {
206
206
  return 0.2;
207
207
  })();
208
208
 
209
+ // Public workflow agents are not in agents.json (that file only defines hidden
210
+ // roles), so they historically always fell back to the 600s default. A wedged
211
+ // anthropic SSE stream (HTTP 200 then zero deltas — observed 2026-07-03 on a
212
+ // sonnet-5 worker, twice in a row) then holds the owner for the full 10 min.
213
+ // Scoped workers/reviewers should be reaped faster; lead keeps the generous
214
+ // default because interactive xhigh turns legitimately think for minutes.
215
+ const WORKFLOW_AGENT_STALL_ABORT_S = {
216
+ 'worker': 300,
217
+ 'reviewer': 300,
218
+ 'debugger': 420,
219
+ 'heavy-worker': 420,
220
+ 'maintainer': 300,
221
+ 'explore': 240,
222
+ 'web-researcher': 300,
223
+ };
224
+
209
225
  export function resolveAgentStallThresholds(agent, env = process.env) {
210
226
  const cfg = agent ? getHiddenAgent(agent) : null;
211
- const cfgAbort = cfg?.stallCap?.idleSeconds > 0 ? cfg.stallCap.idleSeconds : STALL_ABORT_S;
227
+ const roleAbort = WORKFLOW_AGENT_STALL_ABORT_S[String(agent || '')] || null;
228
+ const cfgAbort = cfg?.stallCap?.idleSeconds > 0
229
+ ? cfg.stallCap.idleSeconds
230
+ : (roleAbort || STALL_ABORT_S);
212
231
  const envOverride = envThresholdSeconds(env);
213
232
  const abort = envOverride != null ? envOverride : cfgAbort;
214
233
  // Mid-stream "slow" warning disabled — an agent stall now notifies ONLY at