mixdog 0.9.2 → 0.9.4

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 (417) hide show
  1. package/package.json +8 -3
  2. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  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/review-tasks.json +20 -0
  7. package/scripts/bench/round-codex.json +114 -0
  8. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  9. package/scripts/bench/round-mixdog-lead.json +269 -0
  10. package/scripts/bench/round-mixdog.json +126 -0
  11. package/scripts/bench/round-r10-bigsample.json +679 -0
  12. package/scripts/bench/round-r11-codexalign.json +257 -0
  13. package/scripts/bench/round-r4-codex.json +114 -0
  14. package/scripts/bench/round-r4-mixed.json +225 -0
  15. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  16. package/scripts/bench/round-r6-codex.json +114 -0
  17. package/scripts/bench/round-r6-solo.json +257 -0
  18. package/scripts/bench/round-r7-full.json +254 -0
  19. package/scripts/bench/round-r8-fulldefault.json +255 -0
  20. package/scripts/bench-run.mjs +215 -29
  21. package/scripts/build-tui.mjs +13 -1
  22. package/scripts/explore-bench.mjs +124 -0
  23. package/scripts/freevar-smoke.mjs +95 -0
  24. package/scripts/hook-bus-test.mjs +191 -0
  25. package/scripts/internal-comms-bench.mjs +1 -0
  26. package/scripts/internal-comms-smoke.mjs +10 -9
  27. package/scripts/mouse-probe.mjs +45 -0
  28. package/scripts/output-style-bench.mjs +13 -6
  29. package/scripts/output-style-smoke.mjs +4 -4
  30. package/scripts/path-suffix-test.mjs +57 -0
  31. package/scripts/provider-toolcall-test.mjs +7 -3
  32. package/scripts/recall-bench.mjs +207 -0
  33. package/scripts/recall-usecase-cases.json +18 -0
  34. package/scripts/recall-usecase-probe.json +6 -0
  35. package/scripts/session-bench.mjs +152 -6
  36. package/scripts/tool-smoke.mjs +30 -67
  37. package/scripts/tui-render-smoke.mjs +90 -0
  38. package/scripts/webhook-smoke.mjs +208 -0
  39. package/src/agents/debugger/AGENT.md +5 -2
  40. package/src/agents/heavy-worker/AGENT.md +21 -11
  41. package/src/agents/maintainer/AGENT.md +4 -0
  42. package/src/agents/reviewer/AGENT.md +3 -2
  43. package/src/agents/worker/AGENT.md +21 -11
  44. package/src/lib/rules-builder.cjs +4 -0
  45. package/src/mixdog-session-runtime.mjs +933 -3731
  46. package/src/output-styles/default.md +34 -9
  47. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  48. package/src/output-styles/minimal.md +4 -1
  49. package/src/output-styles/simple.md +22 -7
  50. package/src/repl.mjs +5 -5
  51. package/src/rules/agent/00-common.md +2 -0
  52. package/src/rules/agent/30-explorer.md +8 -11
  53. package/src/rules/lead/lead-brief.md +12 -0
  54. package/src/rules/lead/lead-tool.md +2 -9
  55. package/src/rules/shared/01-tool.md +11 -5
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  59. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  60. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  61. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  62. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  63. package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
  66. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
  70. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  71. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
  72. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
  73. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  74. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  75. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  76. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  77. package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
  78. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  80. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  81. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  82. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  83. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  84. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
  85. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
  92. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  94. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  96. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
  99. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
  100. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  101. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  102. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  103. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  107. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  108. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  109. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
  110. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  111. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  112. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  113. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  114. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
  115. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  116. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  117. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  118. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  120. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  121. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  122. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  123. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  124. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  125. package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
  126. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  127. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
  128. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  129. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
  130. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  131. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  132. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  133. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
  136. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  137. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  138. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  141. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
  142. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  143. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  145. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  146. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  148. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  149. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
  152. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
  155. package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
  156. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  157. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  158. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  159. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  161. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  163. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  164. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  165. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  166. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
  167. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  168. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  169. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  170. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  171. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  172. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  173. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
  174. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  175. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  176. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  177. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  178. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  179. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  180. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  181. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  182. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  183. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  184. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
  185. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
  186. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  187. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  188. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  189. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  190. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  191. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  192. package/src/runtime/channels/backends/discord.mjs +12 -292
  193. package/src/runtime/channels/index.mjs +241 -894
  194. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  195. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  196. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  197. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  198. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  199. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  200. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  201. package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
  202. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  203. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  204. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  205. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  206. package/src/runtime/channels/lib/telegram-format.mjs +19 -22
  207. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  208. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  209. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  210. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  211. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  212. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  213. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  214. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  215. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  216. package/src/runtime/channels/lib/webhook.mjs +36 -570
  217. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  218. package/src/runtime/channels/tool-defs.mjs +11 -130
  219. package/src/runtime/memory/index.mjs +258 -2050
  220. package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
  221. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  222. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  223. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  224. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  225. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  226. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  227. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  228. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  229. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  230. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  231. package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
  232. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  233. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  234. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  235. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  236. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  237. package/src/runtime/memory/lib/memory.mjs +20 -0
  238. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  239. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  240. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  241. package/src/runtime/memory/lib/recall-format.mjs +238 -0
  242. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  243. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  244. package/src/runtime/memory/tool-defs.mjs +6 -14
  245. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  246. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  247. package/src/runtime/search/lib/web-tools.mjs +24 -602
  248. package/src/runtime/shared/abort-controller.mjs +1 -1
  249. package/src/runtime/shared/atomic-file.mjs +26 -1
  250. package/src/runtime/shared/background-tasks.mjs +2 -3
  251. package/src/runtime/shared/buffered-appender.mjs +149 -0
  252. package/src/runtime/shared/launcher-control.mjs +2 -2
  253. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  254. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  255. package/src/runtime/shared/tool-primitives.mjs +308 -0
  256. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  257. package/src/runtime/shared/tool-surface.mjs +80 -898
  258. package/src/runtime/shared/transcript-writer.mjs +52 -2
  259. package/src/runtime/shared/update-checker.mjs +7 -4
  260. package/src/session-runtime/config-helpers.mjs +291 -0
  261. package/src/session-runtime/config-lifecycle.mjs +232 -0
  262. package/src/session-runtime/cwd-plugins.mjs +226 -0
  263. package/src/session-runtime/effort.mjs +128 -0
  264. package/src/session-runtime/fs-utils.mjs +10 -0
  265. package/src/session-runtime/mcp-glue.mjs +177 -0
  266. package/src/session-runtime/model-capabilities.mjs +130 -0
  267. package/src/session-runtime/model-recency.mjs +111 -0
  268. package/src/session-runtime/native-search.mjs +247 -0
  269. package/src/session-runtime/output-styles.mjs +126 -0
  270. package/src/session-runtime/plugin-mcp.mjs +114 -0
  271. package/src/session-runtime/prewarm.mjs +142 -0
  272. package/src/session-runtime/provider-models.mjs +278 -0
  273. package/src/session-runtime/provider-usage.mjs +120 -0
  274. package/src/session-runtime/quick-model-rows.mjs +170 -0
  275. package/src/session-runtime/quick-search-models.mjs +46 -0
  276. package/src/session-runtime/session-hooks.mjs +93 -0
  277. package/src/session-runtime/session-text.mjs +100 -0
  278. package/src/session-runtime/settings-api.mjs +319 -0
  279. package/src/session-runtime/statusline-route.mjs +35 -0
  280. package/src/session-runtime/tool-catalog.mjs +720 -0
  281. package/src/session-runtime/tool-defs.mjs +84 -0
  282. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  283. package/src/session-runtime/workflow.mjs +358 -0
  284. package/src/standalone/agent-tool/helpers.mjs +237 -0
  285. package/src/standalone/agent-tool/notify.mjs +107 -0
  286. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  287. package/src/standalone/agent-tool/render.mjs +152 -0
  288. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  289. package/src/standalone/agent-tool.mjs +155 -677
  290. package/src/standalone/channel-worker.mjs +7 -9
  291. package/src/standalone/explore-tool.mjs +40 -12
  292. package/src/standalone/hook-bus/config.mjs +207 -0
  293. package/src/standalone/hook-bus/constants.mjs +90 -0
  294. package/src/standalone/hook-bus/handlers.mjs +481 -0
  295. package/src/standalone/hook-bus/payload.mjs +31 -0
  296. package/src/standalone/hook-bus/rules.mjs +77 -0
  297. package/src/standalone/hook-bus.mjs +110 -746
  298. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  299. package/src/standalone/opencode-go-login.mjs +125 -0
  300. package/src/standalone/provider-admin.mjs +15 -19
  301. package/src/standalone/usage-dashboard.mjs +3 -1
  302. package/src/tui/App.jsx +1163 -7571
  303. package/src/tui/app/app-format.mjs +206 -0
  304. package/src/tui/app/channel-pickers.mjs +510 -0
  305. package/src/tui/app/clipboard.mjs +67 -0
  306. package/src/tui/app/core-memory-picker.mjs +210 -0
  307. package/src/tui/app/extension-pickers.mjs +506 -0
  308. package/src/tui/app/input-parsers.mjs +193 -0
  309. package/src/tui/app/maintenance-pickers.mjs +324 -0
  310. package/src/tui/app/model-options.mjs +330 -0
  311. package/src/tui/app/model-picker.mjs +365 -0
  312. package/src/tui/app/onboarding-steps.mjs +400 -0
  313. package/src/tui/app/project-picker.mjs +247 -0
  314. package/src/tui/app/provider-setup-picker.mjs +580 -0
  315. package/src/tui/app/resume-picker.mjs +55 -0
  316. package/src/tui/app/route-pickers.mjs +419 -0
  317. package/src/tui/app/settings-picker.mjs +490 -0
  318. package/src/tui/app/slash-commands.mjs +101 -0
  319. package/src/tui/app/slash-dispatch.mjs +427 -0
  320. package/src/tui/app/text-layout.mjs +46 -0
  321. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  322. package/src/tui/app/transcript-window.mjs +671 -0
  323. package/src/tui/app/use-mouse-input.mjs +460 -0
  324. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  325. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  326. package/src/tui/app/use-transcript-window.mjs +589 -0
  327. package/src/tui/components/ConfirmBar.jsx +1 -1
  328. package/src/tui/components/Picker.jsx +32 -4
  329. package/src/tui/components/PromptInput.jsx +259 -80
  330. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  331. package/src/tui/components/StatusLine.jsx +63 -12
  332. package/src/tui/components/TextEntryPanel.jsx +11 -0
  333. package/src/tui/components/ToolExecution.jsx +56 -588
  334. package/src/tui/components/TranscriptItem.jsx +105 -0
  335. package/src/tui/components/UsagePanel.jsx +18 -4
  336. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  337. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  338. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  339. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  340. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  341. package/src/tui/components/tool-output-format.mjs +2 -2
  342. package/src/tui/display-width.mjs +20 -3
  343. package/src/tui/dist/index.mjs +18034 -17188
  344. package/src/tui/engine/agent-envelope.mjs +296 -0
  345. package/src/tui/engine/agent-job-feed.mjs +133 -0
  346. package/src/tui/engine/boot-profile.mjs +21 -0
  347. package/src/tui/engine/labels.mjs +67 -0
  348. package/src/tui/engine/notice-text.mjs +112 -0
  349. package/src/tui/engine/notification-plan.mjs +76 -0
  350. package/src/tui/engine/queue-helpers.mjs +161 -0
  351. package/src/tui/engine/render-timing.mjs +17 -0
  352. package/src/tui/engine/session-stats.mjs +46 -0
  353. package/src/tui/engine/tool-approval.mjs +94 -0
  354. package/src/tui/engine/tool-call-fields.mjs +23 -0
  355. package/src/tui/engine/tool-card-results.mjs +234 -0
  356. package/src/tui/engine/tool-result-status.mjs +135 -0
  357. package/src/tui/engine/tool-result-text.mjs +126 -0
  358. package/src/tui/engine.mjs +405 -1385
  359. package/src/tui/figures.mjs +5 -0
  360. package/src/tui/index.jsx +105 -0
  361. package/src/tui/input-editing.mjs +60 -10
  362. package/src/tui/keyboard-protocol.mjs +2 -2
  363. package/src/tui/lib/voice-recorder.mjs +35 -19
  364. package/src/tui/markdown/format-token.mjs +11 -9
  365. package/src/tui/paste-attachments.mjs +38 -0
  366. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  367. package/src/tui/theme.mjs +6 -0
  368. package/src/tui/themes/base.mjs +2 -2
  369. package/src/tui/themes/kanagawa.mjs +4 -4
  370. package/src/tui/themes/teal.mjs +4 -5
  371. package/src/tui/themes/utils.mjs +1 -1
  372. package/src/ui/statusline-agents.mjs +213 -0
  373. package/src/ui/statusline-format.mjs +146 -0
  374. package/src/ui/statusline-segments.mjs +148 -0
  375. package/src/ui/statusline.mjs +77 -462
  376. package/src/ui/tool-card.mjs +0 -1
  377. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  378. package/src/workflows/default/WORKFLOW.md +16 -9
  379. package/src/workflows/sequential/WORKFLOW.md +16 -11
  380. package/src/workflows/solo/WORKFLOW.md +5 -1
  381. package/vendor/ink/build/display-width.js +19 -3
  382. package/vendor/ink/build/ink.js +103 -6
  383. package/vendor/ink/build/log-update.js +17 -3
  384. package/vendor/ink/build/wrap-text.js +125 -0
  385. package/scripts/_test-folder-dialog.mjs +0 -30
  386. package/scripts/fix-brief-fn.mjs +0 -35
  387. package/scripts/fix-format-tool-surface.mjs +0 -24
  388. package/scripts/fix-tool-exec-visible.mjs +0 -42
  389. package/scripts/patch-agent-brief.mjs +0 -48
  390. package/scripts/patch-app.mjs +0 -21
  391. package/scripts/patch-app2.mjs +0 -18
  392. package/scripts/patch-dist-brief.mjs +0 -96
  393. package/scripts/patch-tool-exec.mjs +0 -70
  394. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  395. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  396. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  397. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  398. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  399. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  400. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  401. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  402. package/src/standalone/agent-task-status.test.mjs +0 -76
  403. package/src/tui/components/tool-output-format.test.mjs +0 -399
  404. package/src/tui/display-width.test.mjs +0 -35
  405. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  406. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  407. package/src/tui/markdown/format-token.test.mjs +0 -354
  408. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  409. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  410. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  411. package/src/tui/prompt-history-store.test.mjs +0 -52
  412. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  413. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  414. package/src/ui/markdown.test.mjs +0 -70
  415. package/src/ui/statusline-context-label.test.mjs +0 -15
  416. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  417. 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
@@ -181,9 +181,9 @@ export const PROVIDER_WS_ACQUIRE_TIMEOUT_MS = resolveTimeoutMs(
181
181
  { minMs: 5_000, maxMs: PROVIDER_MAX_BEFORE_WARN_MS },
182
182
  );
183
183
 
184
- // Single inter-chunk idle timer, matching codex's DEFAULT_STREAM_IDLE_TIMEOUT_MS
185
- // (300s). codex resets one idle timer on every received WS frame; mixdog does
186
- // the same (openai-oauth-ws messageHandler resets on every parsed event). There
184
+ // Single inter-chunk idle timer (300s), matching the upstream WS provider's
185
+ // default stream idle timeout. Mixdog resets one idle timer on every received
186
+ // WS frame (openai-oauth-ws messageHandler resets on every parsed event). There
187
187
  // is deliberately no separate "first-meaningful" watchdog — a live stream
188
188
  // (including server-side reasoning ACKed via response.created) keeps this timer
189
189
  // fresh, and only true socket silence trips it. Env-tunable.
@@ -85,7 +85,7 @@ function isStringOrStringArray(v) {
85
85
  return true;
86
86
  }
87
87
 
88
- function hasUnsupportedRipgrepRegex(value) {
88
+ export function hasUnsupportedRipgrepRegex(value) {
89
89
  const values = Array.isArray(value) ? value : [value];
90
90
  return values.some((item) => {
91
91
  if (typeof item !== 'string') return false;
@@ -99,6 +99,30 @@ function hasMultipleAbsoluteWindowsPaths(value) {
99
99
  return Array.isArray(matches) && matches.length > 1;
100
100
  }
101
101
 
102
+ // Rescue for hasMultipleAbsoluteWindowsPaths: a single string arg that packs
103
+ // multiple absolute Windows paths together (space/comma/semicolon separated,
104
+ // e.g. "C:\a\b C:\c\d" or "C:\a\b, C:\c\d") is a common model mistake meaning
105
+ // two separate search roots, not one malformed path. Split on each drive-
106
+ // letter boundary and trim separator debris instead of hard-erroring;
107
+ // executeGrepTool's own array fan-out (search-tool.mjs) already knows how to
108
+ // run one grep per path and join the results. Returns null when the value
109
+ // does not cleanly resolve to 2+ non-empty segments.
110
+ function splitMultipleAbsoluteWindowsPaths(value) {
111
+ if (typeof value !== 'string') return null;
112
+ const starts = [];
113
+ const re = /[A-Za-z]:[\\/]/g;
114
+ let m;
115
+ while ((m = re.exec(value)) !== null) starts.push(m.index);
116
+ if (starts.length < 2) return null;
117
+ const parts = [];
118
+ for (let i = 0; i < starts.length; i++) {
119
+ const end = i + 1 < starts.length ? starts[i + 1] : value.length;
120
+ const part = value.slice(starts[i], end).trim().replace(/[,;]+$/, '').trim();
121
+ if (part) parts.push(part);
122
+ }
123
+ return parts.length > 1 ? parts : null;
124
+ }
125
+
102
126
  function hasWindowsDrivePath(value) {
103
127
  return typeof value === 'string' && /[A-Za-z]:[\\/]/.test(value);
104
128
  }
@@ -124,9 +148,15 @@ function coerceIntegerString(v) {
124
148
  return n;
125
149
  }
126
150
 
151
+ // Range-shaped strings ("1,40", "1-40", "1:40") are a common model mistake
152
+ // when trying to pass a line range through a single-integer field. They are
153
+ // not coerced (ambiguous which bound is intended); instead the rejection
154
+ // tells the model exactly what to do instead.
155
+ const RANGE_SHAPED_INT_STRING = /^\d+\s*[,\-:]\s*\d+$/;
156
+
127
157
  // Mutates a[field] in place when it is a lossless integer string, then
128
158
  // validates the (possibly coerced) value against [min, max].
129
- function checkIntInRange(a, field, min, max) {
159
+ function checkIntInRange(a, field, min, max, opts = {}) {
130
160
  let value = a[field];
131
161
  if (value === undefined || value === null) return null;
132
162
  if (typeof value === 'string') {
@@ -136,8 +166,20 @@ function checkIntInRange(a, field, min, max) {
136
166
  a[field] = coerced;
137
167
  }
138
168
  }
169
+ if (typeof value === 'string' && RANGE_SHAPED_INT_STRING.test(value.trim())) {
170
+ return `Error: builtin arg "${field}" takes a single integer; for a line range use offset+limit as separate integers or "path#L<start>-L<end>" in the path arg (got "${value}")`;
171
+ }
139
172
  if (!isFiniteInt(value)) {
140
- return `Error: builtin arg "${field}" must be a finite integer (got ${describeType(value)})`;
173
+ return `Error: builtin arg "${field}" must be a finite integer (got ${describeType(value)}); expected a single integer like 40`;
174
+ }
175
+ // Soft-cap fields (grep -A/-B/-C/context): silently clamp out-of-range
176
+ // instead of erroring — a model-guessed "300" for a 200-line cap is not
177
+ // a shape violation worth a retry turn, just clamp to the boundary.
178
+ if (opts.clamp) {
179
+ if (value < min) { a[field] = min; return null; }
180
+ if (value > max) { a[field] = max; return null; }
181
+ a[field] = value;
182
+ return null;
141
183
  }
142
184
  if (value < min) {
143
185
  return `Error: builtin arg "${field}" must be >= ${min} (got ${value})`;
@@ -216,10 +258,11 @@ function guardGrep(a) {
216
258
  if (hasOwn(a, k) && !isStringOrStringArray(a[k])) {
217
259
  return `Error: grep arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
218
260
  }
219
- if (hasOwn(a, k) && hasUnsupportedRipgrepRegex(a[k])) {
220
- return `Error: grep arg "${k}" uses regex syntax ripgrep does not support here (lookaround/backrefs). Use plain pattern arrays or simpler regex.`;
221
- }
222
261
  }
262
+ // Lookaround/backreference patterns are no longer hard-rejected here:
263
+ // executeGrepTool (search-tool.mjs) detects them at runtime and routes to
264
+ // rg's PCRE2 engine (-P/--pcre2) when the installed rg build supports it,
265
+ // falling back to this same error text only when PCRE2 is unavailable.
223
266
  for (const k of globKeys) {
224
267
  if (hasOwn(a, k) && !isStringOrStringArray(a[k])) {
225
268
  return `Error: grep arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
@@ -231,7 +274,11 @@ function guardGrep(a) {
231
274
  return `Error: grep arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
232
275
  }
233
276
  if (hasOwn(a, k) && hasMultipleAbsoluteWindowsPaths(a[k])) {
234
- return `Error: grep arg "${k}" contains multiple absolute paths in one string. Use one common parent path plus glob, or separate grep calls.`;
277
+ const split = splitMultipleAbsoluteWindowsPaths(a[k]);
278
+ if (!split) {
279
+ return `Error: grep arg "${k}" contains multiple absolute paths in one string. Use one common parent path plus glob, or separate grep calls.`;
280
+ }
281
+ a[k] = split;
235
282
  }
236
283
  }
237
284
  for (const k of ['head_limit', 'offset']) {
@@ -239,7 +286,7 @@ function guardGrep(a) {
239
286
  if (err) return err;
240
287
  }
241
288
  for (const k of ['-A', '-B', '-C', 'context']) {
242
- const err = checkIntInRange(a, k, 0, GREP_CONTEXT_MAX);
289
+ const err = checkIntInRange(a, k, 0, GREP_CONTEXT_MAX, { clamp: true });
243
290
  if (err) return err;
244
291
  }
245
292
  // output_mode / mode enum
@@ -291,13 +338,19 @@ function applyLineContextWindow(obj, labelPrefix) {
291
338
  if (!hasLine) {
292
339
  return `Error: read arg "${labelPrefix}context" requires "${labelPrefix}line" to compute a window`;
293
340
  }
341
+ if (typeof obj.line === 'string' && RANGE_SHAPED_INT_STRING.test(obj.line.trim())) {
342
+ return `Error: read arg "${labelPrefix}line" takes a single integer; for a line range use offset+limit as separate integers or "path#L<start>-L<end>" in the path arg (got "${obj.line}")`;
343
+ }
294
344
  if (!isFiniteInt(obj.line) || obj.line < 1) {
295
- return `Error: read arg "${labelPrefix}line" must be a finite integer >= 1 (got ${describeType(obj.line)})`;
345
+ return `Error: read arg "${labelPrefix}line" must be a finite integer >= 1 (got ${describeType(obj.line)}); expected a single integer like 40`;
296
346
  }
297
347
  let ctx = 0;
298
348
  if (hasContext) {
349
+ if (typeof obj.context === 'string' && RANGE_SHAPED_INT_STRING.test(obj.context.trim())) {
350
+ return `Error: read arg "${labelPrefix}context" takes a single integer; for a line range use offset+limit as separate integers or "path#L<start>-L<end>" in the path arg (got "${obj.context}")`;
351
+ }
299
352
  if (!isFiniteInt(obj.context) || obj.context < 0) {
300
- return `Error: read arg "${labelPrefix}context" must be a non-negative finite integer (got ${describeType(obj.context)})`;
353
+ return `Error: read arg "${labelPrefix}context" must be a non-negative finite integer (got ${describeType(obj.context)}); expected a single integer like 40`;
301
354
  }
302
355
  ctx = obj.context;
303
356
  }
@@ -381,13 +434,6 @@ function guardRead(a) {
381
434
  return null;
382
435
  }
383
436
 
384
- function guardDiagnostics(a) {
385
- if (hasOwn(a, 'path') && a.path !== undefined && a.path !== null && !isNonEmptyString(a.path)) {
386
- return `Error: diagnostics arg "path" must be a non-empty string (got ${describeType(a.path)})`;
387
- }
388
- return null;
389
- }
390
-
391
437
  function guardShell(a) {
392
438
  if (!hasOwn(a, 'command')) {
393
439
  return 'Error: shell requires "command"';
@@ -458,8 +504,13 @@ function guardList(a) {
458
504
  }
459
505
 
460
506
  function guardFind(a) {
461
- if (!hasOwn(a, 'query') || typeof a.query !== 'string' || a.query.trim().length === 0) {
462
- return `Error: find requires non-empty string "query" (got ${describeType(a.query)})`;
507
+ // query accepts string or string[] (query[] fans out per-lookup downstream).
508
+ const queryOk = hasOwn(a, 'query') && (
509
+ (typeof a.query === 'string' && a.query.trim().length > 0)
510
+ || (Array.isArray(a.query) && a.query.length > 0 && a.query.every((q) => typeof q === 'string' && q.trim().length > 0))
511
+ );
512
+ if (!queryOk) {
513
+ return `Error: find requires non-empty string (or string[]) "query" (got ${describeType(a.query)})`;
463
514
  }
464
515
  if (hasOwn(a, 'path') && !isString(a.path)) {
465
516
  return `Error: find arg "path" must be a string (got ${describeType(a.path)})`;
@@ -541,7 +592,6 @@ function guardCodeGraph(a) {
541
592
  const GUARDS = {
542
593
  grep: guardGrep,
543
594
  read: guardRead,
544
- diagnostics: guardDiagnostics,
545
595
  shell: guardShell,
546
596
  task: guardTask,
547
597
  list: guardList,