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,237 @@
1
+ // Pure, dependency-light helpers extracted from the agent-tool facade. No
2
+ // module-level mutable state lives here (the frontmatter perm cache and all
3
+ // timers stay in the facade / their owner modules). Behavior-preserving:
4
+ // function bodies are identical to the originals.
5
+ import { existsSync, readFileSync } from 'node:fs';
6
+ import { isAbsolute, join, resolve } from 'node:path';
7
+ import {
8
+ normalizeAgentPermissionOrNone,
9
+ parseMarkdownFrontmatter,
10
+ } from '../../runtime/shared/markdown-frontmatter.mjs';
11
+ import { clearGatewaySessionRoute, writeGatewaySessionRoute } from '../../vendor/statusline/src/gateway/session-routes.mjs';
12
+ import { PRESET_ALIASES } from './tool-def.mjs';
13
+
14
+ export function envTimeoutMs(name, fallback) {
15
+ const raw = process.env[name];
16
+ if (raw === undefined || raw === '') return fallback;
17
+ const n = Number(raw);
18
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
19
+ }
20
+
21
+ export function clean(value) {
22
+ return String(value ?? '').trim();
23
+ }
24
+
25
+ export function agentTagOf(session) {
26
+ return clean(session?.agentTag);
27
+ }
28
+
29
+ export function normalizeAgentName(value) {
30
+ const id = clean(value).toLowerCase().replace(/[\s_]+/g, '-');
31
+ if (id === 'explorer') return 'explore';
32
+ if (id === 'maint' || id === 'maintenance' || id === 'memory') return 'maintainer';
33
+ if (id === 'heavy' || id === 'heavyworker') return 'heavy-worker';
34
+ if (id === 'review') return 'reviewer';
35
+ if (id === 'debug') return 'debugger';
36
+ return id;
37
+ }
38
+
39
+ export function positiveInt(value) {
40
+ const n = Number(value);
41
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
42
+ }
43
+
44
+ export function terminalPidForContext(context = {}) {
45
+ return positiveInt(context?.clientHostPid);
46
+ }
47
+
48
+ export function agentScope(args = {}, context = {}) {
49
+ const scope = clean(args.scope || args.terminal || args.term).toLowerCase();
50
+ if (args.allTerminals === true || scope === 'all' || scope === 'global') return {};
51
+ return context || {};
52
+ }
53
+
54
+ export function sessionMatchesContext(session, context = {}) {
55
+ const wantedPid = terminalPidForContext(context);
56
+ if (!wantedPid) return true;
57
+ const sessionPid = positiveInt(session?.clientHostPid);
58
+ return !!sessionPid && sessionPid === wantedPid;
59
+ }
60
+
61
+ export function rowMatchesContext(row, context = {}) {
62
+ const wantedPid = terminalPidForContext(context);
63
+ if (!wantedPid) return true;
64
+ const rowPid = positiveInt(row?.clientHostPid);
65
+ return !!rowPid && rowPid === wantedPid;
66
+ }
67
+
68
+ export function nonNegativeInt(value) {
69
+ if (value === undefined || value === null || value === '') return null;
70
+ const n = Number(value);
71
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : null;
72
+ }
73
+
74
+ export function presetKey(preset) {
75
+ return clean(preset?.id || preset?.name);
76
+ }
77
+
78
+ export function bridgeRouteForStatusline(preset = {}) {
79
+ const provider = clean(preset.provider);
80
+ const model = clean(preset.model);
81
+ if (!provider || !model) return null;
82
+ const out = {
83
+ mode: 'fixed',
84
+ defaultProvider: provider,
85
+ defaultModel: model,
86
+ };
87
+ const id = clean(preset.id);
88
+ const name = clean(preset.name);
89
+ const modelDisplay = clean(preset.modelDisplay || preset.display || preset.displayName);
90
+ const effort = clean(preset.effort);
91
+ if (id) out.presetId = id;
92
+ if (name) out.presetName = name;
93
+ if (modelDisplay) out.modelDisplay = modelDisplay;
94
+ if (effort) {
95
+ out.effort = effort;
96
+ out.displayEffort = effort;
97
+ }
98
+ if (preset.fast === true || preset.fast === false) out.fast = preset.fast;
99
+ return out;
100
+ }
101
+
102
+ export function writeAgentStatuslineRoute(sessionId, preset) {
103
+ const route = bridgeRouteForStatusline(preset);
104
+ if (!sessionId || !route) return false;
105
+ try { return writeGatewaySessionRoute(sessionId, route); } catch { return false; }
106
+ }
107
+
108
+ export function clearAgentStatuslineRoute(sessionId) {
109
+ if (!sessionId) return false;
110
+ try { return clearGatewaySessionRoute(sessionId); } catch { return false; }
111
+ }
112
+
113
+ export function findPreset(config, key) {
114
+ const wanted = clean(key).toLowerCase();
115
+ if (!wanted) return null;
116
+ const presets = Array.isArray(config?.presets) ? config.presets : [];
117
+ return presets.find((p) => {
118
+ return clean(p?.id).toLowerCase() === wanted || clean(p?.name).toLowerCase() === wanted;
119
+ }) || null;
120
+ }
121
+
122
+ export function synthesizePreset(config, key) {
123
+ const alias = PRESET_ALIASES.get(clean(key).toLowerCase());
124
+ if (!alias) return null;
125
+ const base = findPreset(config, alias.base);
126
+ if (!base) return null;
127
+ return {
128
+ ...base,
129
+ id: alias.id,
130
+ name: alias.name,
131
+ effort: alias.effort,
132
+ };
133
+ }
134
+
135
+ export function normalizeAgentRoute(routeLike, fallbackProvider = '') {
136
+ const provider = clean(routeLike?.provider) || clean(fallbackProvider);
137
+ const model = clean(routeLike?.model);
138
+ if (!provider || !model) return null;
139
+ return {
140
+ provider,
141
+ model,
142
+ effort: clean(routeLike?.effort) || undefined,
143
+ fast: routeLike?.fast === true,
144
+ };
145
+ }
146
+
147
+ export function agentPresetName(agent) {
148
+ return `AGENT ${String(agent || '').toUpperCase()}`;
149
+ }
150
+
151
+ export async function resolvePrompt(args, cwd) {
152
+ const prompt = clean(args.prompt || args.message);
153
+ const file = clean(args.file);
154
+ if (prompt && file) throw new Error('agent: provide only one of prompt/message or file');
155
+ if (prompt) return prompt;
156
+ if (file) {
157
+ const target = isAbsolute(file) ? file : resolve(cwd || process.cwd(), file);
158
+ return readFileSync(target, 'utf8');
159
+ }
160
+ throw new Error('agent: prompt/message/file is required');
161
+ }
162
+
163
+ export function withCwdHeader(prompt, cwd) {
164
+ if (!cwd) return prompt;
165
+ if (String(prompt).startsWith('[effective-cwd]')) return prompt;
166
+ return `[effective-cwd] ${cwd}\n\n${prompt}`;
167
+ }
168
+
169
+ export function compactIso(value) {
170
+ const text = clean(value);
171
+ if (!text) return '';
172
+ return text.replace('T', ' ').replace(/\.\d+Z$/, 'Z');
173
+ }
174
+
175
+ export function formatElapsedMs(ms) {
176
+ const n = Number(ms);
177
+ if (!Number.isFinite(n) || n < 0) return '';
178
+ const totalSec = Math.floor(n / 1000);
179
+ if (totalSec < 60) return `${totalSec}s`;
180
+ const min = Math.floor(totalSec / 60);
181
+ const sec = totalSec % 60;
182
+ if (min < 60) return sec ? `${min}m${sec}s` : `${min}m`;
183
+ const hr = Math.floor(min / 60);
184
+ const remMin = min % 60;
185
+ return remMin ? `${hr}h${remMin}m` : `${hr}h`;
186
+ }
187
+
188
+ export function elapsedFromStamps(startedAt, finishedAt, status) {
189
+ const start = Date.parse(clean(startedAt));
190
+ if (!Number.isFinite(start)) return null;
191
+ const finish = Date.parse(clean(finishedAt));
192
+ const end = Number.isFinite(finish) ? finish : Date.now();
193
+ const label = formatElapsedMs(end - start);
194
+ if (!label) return null;
195
+ return Number.isFinite(finish) ? label : `${label} (running)`;
196
+ }
197
+
198
+ export function stripFinalAnswerWrapper(value) {
199
+ const text = String(value ?? '').trim();
200
+ const match = /^<final-answer\b[^>]*>([\s\S]*?)<\/final-answer>\s*$/i.exec(text);
201
+ return match ? match[1].trim() : text;
202
+ }
203
+
204
+ // Process-wide TTL cache for agent AGENT.md frontmatter permission. The file
205
+ // rarely changes, but this otherwise pays several existsSync()+readFileSync()
206
+ // calls on EVERY spawn — multiplied across a parallel fanout that is pure
207
+ // redundant synchronous I/O on the event loop, which blocks every other
208
+ // concurrent spawn. Short TTL keeps edits picked up quickly while collapsing
209
+ // burst reads. Cache + TTL are owned here alongside the sole reader.
210
+ const _frontmatterPermCache = new Map(); // key -> { value, atMs }
211
+ const FRONTMATTER_PERM_CACHE_TTL_MS = envTimeoutMs('MIXDOG_AGENT_FRONTMATTER_TTL_MS', 5_000);
212
+
213
+ export function readAgentFrontmatterPermission(agent, dataDir, standaloneSourceRoot) {
214
+ const cleanAgent = clean(agent);
215
+ if (!cleanAgent) return null;
216
+ const cacheKey = `${dataDir || ''}\u0000${cleanAgent}`;
217
+ const cached = _frontmatterPermCache.get(cacheKey);
218
+ if (cached && Date.now() - cached.atMs < FRONTMATTER_PERM_CACHE_TTL_MS) {
219
+ return cached.value;
220
+ }
221
+ const candidates = [];
222
+ if (dataDir) {
223
+ candidates.push(join(dataDir, 'agents', cleanAgent, 'AGENT.md'));
224
+ candidates.push(join(dataDir, 'agents', `${cleanAgent}.md`));
225
+ }
226
+ candidates.push(join(standaloneSourceRoot, 'agents', cleanAgent, 'AGENT.md'));
227
+ candidates.push(join(standaloneSourceRoot, 'agents', `${cleanAgent}.md`));
228
+ let resolved = null;
229
+ for (const file of candidates) {
230
+ if (!existsSync(file)) continue;
231
+ const fm = parseMarkdownFrontmatter(readFileSync(file, 'utf8'));
232
+ const permission = normalizeAgentPermissionOrNone(fm.permission);
233
+ if (permission) { resolved = permission; break; }
234
+ }
235
+ _frontmatterPermCache.set(cacheKey, { value: resolved, atMs: Date.now() });
236
+ return resolved;
237
+ }
@@ -0,0 +1,107 @@
1
+ // Owner/worker completion-notification helpers, extracted from the agent-tool
2
+ // facade as a factory so the mgr-bound closures stay per agent instance.
3
+ // Behavior-preserving: bodies identical to the originals; deps injected.
4
+ import { modelVisibleToolCompletionMessage } from '../../runtime/shared/tool-execution-contract.mjs';
5
+ import { renderBackgroundTask, sanitizeTaskMeta, setBackgroundTaskEnqueueFallback } from '../../runtime/shared/background-tasks.mjs';
6
+ import { clean } from './helpers.mjs';
7
+
8
+ export function createNotify(mgr) {
9
+ function enqueueCompletionMessage(sessionId, text, meta = {}) {
10
+ const target = clean(sessionId);
11
+ if (!target || typeof mgr.enqueuePendingMessage !== 'function') return false;
12
+ try {
13
+ const visible = modelVisibleToolCompletionMessage(text, meta);
14
+ return Boolean(visible && mgr.enqueuePendingMessage(target, visible) > 0);
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ // Wire the canonical completion fallback to this agent surface's owner-session
21
+ // enqueue so notifyTaskCompletion can deliver via callerSessionId when no
22
+ // notifyFn is present or it declines. Registered once per agent (the closure
23
+ // captures mgr); signatures align: (callerSessionId, message, meta).
24
+ setBackgroundTaskEnqueueFallback((sessionId, text, meta) => enqueueCompletionMessage(sessionId, text, meta));
25
+
26
+ function workerNotifyFn(workerSessionId, notifyContext = {}) {
27
+ const workerId = clean(workerSessionId);
28
+ const ownerSessionId = clean(notifyContext?.callerSessionId || notifyContext?.sessionId);
29
+ const upstream = typeof notifyContext?.notifyFn === 'function' ? notifyContext.notifyFn : null;
30
+ return (text, meta = {}) => {
31
+ let ownerDelivered = false;
32
+ if (upstream) {
33
+ try {
34
+ const result = upstream(text, meta);
35
+ ownerDelivered = result !== false;
36
+ if (ownerDelivered) Promise.resolve(result).catch(() => {});
37
+ } catch {
38
+ ownerDelivered = false;
39
+ }
40
+ }
41
+ if (!ownerDelivered && ownerSessionId) {
42
+ ownerDelivered = enqueueCompletionMessage(ownerSessionId, text, meta);
43
+ }
44
+ const workerDelivered = workerId && workerId !== ownerSessionId
45
+ ? enqueueCompletionMessage(workerId, text, meta)
46
+ : ownerDelivered;
47
+ return ownerSessionId ? ownerDelivered : workerDelivered;
48
+ };
49
+ }
50
+
51
+ function notifyOwnerAgentCompletionEarly(job, resultValue, notifyContext = {}) {
52
+ if (!job || job._earlyCompletionNotified === true) return false;
53
+ const ownerSessionId = clean(notifyContext?.callerSessionId || notifyContext?.sessionId);
54
+ const upstream = typeof notifyContext?.notifyFn === 'function' ? notifyContext.notifyFn : null;
55
+ const finishedAt = new Date().toISOString();
56
+ // An abnormal-empty finish carries an `error` — the early preview must NOT
57
+ // present it as a benign `completed` card, or the Lead sees success before
58
+ // the later `failed` reconcile lands. Mirror the terminal status/instruction.
59
+ const earlyStatus = resultValue && resultValue.error ? 'failed' : 'completed';
60
+ const snapshot = {
61
+ ...job,
62
+ status: earlyStatus,
63
+ finishedAt,
64
+ finishedAtMs: Date.now(),
65
+ result: resultValue,
66
+ resultType: job.resultType || 'agent_task_result',
67
+ meta: sanitizeTaskMeta(job.meta || {}),
68
+ ...(resultValue && resultValue.error ? { error: resultValue.error } : {}),
69
+ };
70
+ // An early notification is only a header-only *preview*: it fires before
71
+ // the worker's session is persisted to signal the running→completed
72
+ // transition. It deliberately carries NO result body — the canonical
73
+ // notifyTaskCompletion delivers the body exactly once via the
74
+ // reconcile/finally path, so omitting it here keeps notifications
75
+ // exact-once with no duplicate body.
76
+ const text = renderBackgroundTask(snapshot, { includeResult: false });
77
+ const meta = {
78
+ type: snapshot.resultType,
79
+ execution_surface: 'agent',
80
+ execution_id: job.taskId || null,
81
+ status: earlyStatus,
82
+ instruction: `The async agent task ${job.taskId || ''} has finished (${earlyStatus}) - review this result in your next step.`,
83
+ ...(ownerSessionId ? { caller_session_id: ownerSessionId } : {}),
84
+ };
85
+ let delivered = false;
86
+ if (upstream) {
87
+ try {
88
+ const result = upstream(text, meta);
89
+ delivered = result !== false;
90
+ if (delivered) Promise.resolve(result).catch(() => {});
91
+ } catch {
92
+ delivered = false;
93
+ }
94
+ }
95
+ if (!delivered && ownerSessionId) {
96
+ delivered = enqueueCompletionMessage(ownerSessionId, text, meta);
97
+ }
98
+ if (delivered) {
99
+ // Mark only that a header-only preview fired. The canonical
100
+ // notifyTaskCompletion still owns the single body-carrying notification.
101
+ job._earlyCompletionNotified = true;
102
+ }
103
+ return delivered;
104
+ }
105
+
106
+ return { enqueueCompletionMessage, workerNotifyFn, notifyOwnerAgentCompletionEarly };
107
+ }
@@ -0,0 +1,143 @@
1
+ // Provider init de-dup, extracted from the agent-tool facade as a factory so
2
+ // the closure state (`_providerState` / `_providerInitPending`) stays private
3
+ // per agent instance. Behavior-preserving: function bodies are identical to the
4
+ // originals; only `reg` and the chain-gate timeout are injected.
5
+ //
6
+ // Provider init de-dup. Four goals that must not conflict:
7
+ // (a) a parallel spawn fanout that all targets the SAME provider with the
8
+ // SAME effective config performs at most ONE initProviders() pass
9
+ // instead of N serially-awaited registry rebuilds,
10
+ // (b) a provider CONFIG CHANGE still reaches initProviders() so the
11
+ // registry's own signature guard can re-initialize it,
12
+ // (c) two DIFFERENT config signatures for the same provider never init
13
+ // concurrently — otherwise a slow init of the OLD config could land
14
+ // after a fast init of the NEW config and revert the live registry to
15
+ // stale config, and
16
+ // (d) a SUPERSEDED request never resolves before the provider is actually
17
+ // ready: even when its own (stale) init is dropped to satisfy (c), the
18
+ // caller (a spawn about to run prepareSpawn) must still WAIT for the
19
+ // latest init to finish, or it would proceed against an unprepared /
20
+ // stale provider.
21
+ //
22
+ // Skip cache + in-flight collapse are keyed on `provider + signature(effective
23
+ // config)`. To satisfy (c) we SERIALIZE all inits per provider on a chain
24
+ // promise and re-check the latest-requested signature inside the chain: a
25
+ // request superseded by a newer signature drops its own init. To satisfy (d)
26
+ // such a dropped request does not resolve immediately — it awaits the
27
+ // provider's latest settled init (tracked as a rolling "ready" promise) so the
28
+ // caller only proceeds once the newest config is live.
29
+ export function createProviderInit(reg, providerChainGateMs) {
30
+ // Per-provider state. `chain` serializes the ACTUAL initProviders() calls so
31
+ // two different config signatures never run concurrently (goal c). `latestGen`
32
+ // / `latestSig` track the newest requested config. `ready` is a rolling
33
+ // deferred that resolves only when the LATEST requested init has completed —
34
+ // a superseded caller awaits the ready deferred captured at call time, and
35
+ // when a newer request arrives the older deferred ADOPTS the newer one, so a
36
+ // superseded caller transitively waits for the latest init (goal d).
37
+ const _providerState = new Map(); // provider -> state
38
+ const _providerInitPending = new Map(); // provider -> { sigKey, promise } identical-sig collapse
39
+ // Upper bound on how long a queued init waits for the PRIOR chain link before
40
+ // proceeding anyway. A prior init that HANGS (never settles) must not poison
41
+ // the chain and wedge every later request behind it. A hung init can never
42
+ // *complete* against the registry, so it cannot land-after and clobber a
43
+ // newer config (goal c only fears slow-but-completing inits) — so proceeding
44
+ // once the gate expires is safe. Defaults to the spawn-prep cap; 0 disables.
45
+ const PROVIDER_CHAIN_GATE_MS = providerChainGateMs;
46
+ function providerRegistered(provider) {
47
+ return typeof reg.getProvider !== 'function' || Boolean(reg.getProvider(provider));
48
+ }
49
+ function effectiveProviderConfig(config, provider) {
50
+ const providers = { ...(config.providers || {}) };
51
+ providers[provider] = { ...(providers[provider] || {}), enabled: true };
52
+ return providers;
53
+ }
54
+ function providerStateFor(provider) {
55
+ let s = _providerState.get(provider);
56
+ if (!s) {
57
+ s = { chain: Promise.resolve(), completedSig: null, latestSig: null, latestGen: 0, ready: null };
58
+ _providerState.set(provider, s);
59
+ }
60
+ return s;
61
+ }
62
+ function providerInitSignature(provider, effectiveProviders) {
63
+ let body;
64
+ try { body = JSON.stringify(effectiveProviders); }
65
+ catch { body = String(Date.now()); } // unserializable → force a fresh init
66
+ return `${provider}\u0000${body}`;
67
+ }
68
+ function gateOnPrior(prior) {
69
+ const settled = Promise.resolve(prior).catch(() => {});
70
+ if (!(PROVIDER_CHAIN_GATE_MS > 0)) return settled;
71
+ return new Promise((resolve) => {
72
+ let done = false;
73
+ const finish = () => { if (!done) { done = true; resolve(); } };
74
+ const timer = setTimeout(finish, PROVIDER_CHAIN_GATE_MS);
75
+ timer.unref?.();
76
+ settled.then(() => { clearTimeout(timer); finish(); }, () => { clearTimeout(timer); finish(); });
77
+ });
78
+ }
79
+ function ensureProvider(config, provider) {
80
+ const effective = effectiveProviderConfig(config, provider);
81
+ const sigKey = providerInitSignature(provider, effective);
82
+ const registered = () => providerRegistered(provider);
83
+ const s = providerStateFor(provider);
84
+ // Completed-skip: this exact effective config is already live for this
85
+ // provider. A config change flips sigKey so we fall through; a torn-down
86
+ // provider (no longer registered) also does.
87
+ if (s.completedSig === sigKey && registered()) return Promise.resolve();
88
+ // Identical-sig collapse: a request with the SAME sigKey is already in
89
+ // flight — share its caller promise.
90
+ const pending = _providerInitPending.get(provider);
91
+ if (pending && pending.sigKey === sigKey) return pending.promise;
92
+ // New generation. Repoint the rolling `ready` deferred to THIS gen and make
93
+ // the previous gen's deferred ADOPT the new one, so any superseded caller
94
+ // awaiting an older deferred transitively waits for the newest init (d).
95
+ const gen = ++s.latestGen;
96
+ s.latestSig = sigKey;
97
+ const prevReady = s.ready;
98
+ let resolveReady;
99
+ const readyPromise = new Promise((r) => { resolveReady = r; });
100
+ s.ready = { gen, promise: readyPromise, resolve: resolveReady };
101
+ if (prevReady && prevReady.gen < gen) {
102
+ try { prevReady.resolve(readyPromise); } catch { /* already settled */ }
103
+ }
104
+ // Serialize the ACTUAL init behind the prior chain link (gated so a hung
105
+ // prior cannot wedge the chain). A superseded gen's chain link settles
106
+ // quickly — it never awaits a later gen — so there is no deadlock.
107
+ const prior = s.chain;
108
+ const chainLink = gateOnPrior(prior).then(async () => {
109
+ if (s.latestGen !== gen) {
110
+ // Superseded before we ran: drop our (stale) init entirely (goal c).
111
+ // Our `ready` deferred already adopts the newer gen, so the caller below
112
+ // still waits for the latest init. Settle now to release the chain.
113
+ return;
114
+ }
115
+ try {
116
+ if (!(s.completedSig === sigKey && registered())) {
117
+ await reg.initProviders(effective);
118
+ s.completedSig = sigKey;
119
+ }
120
+ } finally {
121
+ // ALWAYS release this gen's waiters once we are the latest — even on a
122
+ // registry init failure. Adopting (superseded) callers chained onto this
123
+ // deferred would otherwise hang forever; instead they proceed and their
124
+ // own createSession()/prep-timeout surfaces the unprepared provider.
125
+ resolveReady();
126
+ }
127
+ });
128
+ // Next chain link waits on us (settled, never poisoned).
129
+ s.chain = chainLink.catch(() => {});
130
+ // The CALLER awaits the ready deferred (resolves only when the LATEST init
131
+ // for this provider completes), not just the chain link — so a superseded
132
+ // caller blocks until the newest config is live (goal d). chainLink is
133
+ // awaited first so a registry init error surfaces to this caller.
134
+ const callerPromise = chainLink.then(() => readyPromise).finally(() => {
135
+ const cur = _providerInitPending.get(provider);
136
+ if (cur && cur.promise === callerPromise) _providerInitPending.delete(provider);
137
+ });
138
+ _providerInitPending.set(provider, { sigKey, promise: callerPromise });
139
+ return callerPromise;
140
+ }
141
+
142
+ return { ensureProvider };
143
+ }
@@ -0,0 +1,152 @@
1
+ // Result-rendering + finish-classification helpers extracted from the
2
+ // agent-tool facade. Pure functions; behavior-preserving (bodies identical to
3
+ // the originals, only cross-module deps are now imported).
4
+ import { appendAgentProgressKv } from '../agent-task-status.mjs';
5
+ import { compactIso, elapsedFromStamps, stripFinalAnswerWrapper } from './helpers.mjs';
6
+
7
+ // A worker that hits the loop iteration ceiling, gets truncated mid-synthesis,
8
+ // or produces an empty terminal turn returns content:'' but never throws — so
9
+ // the background task would otherwise reconcile as a benign `completed` empty
10
+ // success. That is wrong: an empty final answer is an error. loop.mjs is the
11
+ // single classifier: it tags result.terminationReason for abnormal finishes
12
+ // (carried through manager.mjs terminalResultPreview). We key purely off that
13
+ // here. iteration_cap / truncated are real problems for EVERY agent (hidden
14
+ // too). The plain `empty` case is tagged by the loop ONLY for public agents;
15
+ // hidden agents (explorer/cycle/…) legitimately emit empty terminal turns and
16
+ // are left untagged, so they stay benign.
17
+ export function abnormalEmptyFinishError(result, agent) {
18
+ // The loop (loop.mjs) is the single classifier: it tags terminationReason
19
+ // ONLY for abnormal finishes, and gates the `empty` case behind !hidden.
20
+ // So we key purely off terminationReason here — no separate content/hidden
21
+ // check, which previously (a) exempted hidden agents from cap/truncated and
22
+ // (b) let a capped tool-call turn with preamble text slip through as success.
23
+ const reason = result?.terminationReason;
24
+ if (!reason) return null;
25
+ const iterations = result?.iterations ?? 0;
26
+ const toolCallsTotal = result?.toolCallsTotal ?? 0;
27
+ const maxLoopIterations = result?.maxLoopIterations ?? 0;
28
+ const stopReason = result?.stopReason ?? result?.stop_reason ?? null;
29
+ switch (reason) {
30
+ case 'iteration_cap':
31
+ // Real problem for EVERY agent (hidden too): the loop never terminated on
32
+ // its own contract, so any preamble text is not a trustworthy final answer.
33
+ return `agent '${agent}' hit the loop iteration ceiling (${maxLoopIterations} iterations, ${toolCallsTotal} tool calls) without producing a final answer`;
34
+ case 'truncated':
35
+ return `agent '${agent}' response was truncated (stopReason=${stopReason}) before a final answer (${iterations} iterations, ${toolCallsTotal} tool calls)`;
36
+ case 'empty':
37
+ // Only tagged for PUBLIC agents (hidden agents legitimately emit empty
38
+ // terminal turns and are left untagged by the loop).
39
+ return `agent '${agent}' finished without a final answer (stopReason=${stopReason ?? 'none'}, ${iterations} iterations, ${toolCallsTotal} tool calls)`;
40
+ default:
41
+ return null;
42
+ }
43
+ }
44
+
45
+ export function renderResult(value) {
46
+ if (value === undefined || value === null) return '';
47
+ if (typeof value === 'string') return value;
48
+ if (value && typeof value === 'object') {
49
+ const lines = [];
50
+
51
+ if (Array.isArray(value.workers) || Array.isArray(value.jobs)) {
52
+ const workers = Array.isArray(value.workers) ? value.workers : [];
53
+ lines.push(`agents: ${workers.length}`);
54
+ for (const worker of workers) {
55
+ const tokens = worker.windowTokens ? ` ctx=${worker.windowTokens}${worker.windowCap ? `/${worker.windowCap}` : ''}` : '';
56
+ const terminal = worker.clientHostPid ? ` term=${worker.clientHostPid}` : '';
57
+ const base = `- ${worker.tag} ${worker.agent || 'agent'} ${worker.status || 'idle'}/${worker.worker_stage || worker.stage || 'idle'} ${worker.provider}/${worker.model}${terminal}${tokens}`;
58
+ lines.push(appendAgentProgressKv(base, worker));
59
+ }
60
+ const jobs = Array.isArray(value.jobs) ? value.jobs : [];
61
+ lines.push(`tasks: ${jobs.length}`);
62
+ for (const job of jobs) {
63
+ const target = job.tag || job.sessionId || '-';
64
+ const terminal = job.clientHostPid ? ` term=${job.clientHostPid}` : '';
65
+ const base = `- ${job.task_id} ${job.type} ${job.status} target=${target}${terminal}${job.error ? ` error=${job.error}` : ''}`;
66
+ lines.push(appendAgentProgressKv(base, job));
67
+ }
68
+ if (workers.length === 0 && jobs.length === 0) lines.push('(no agents or tasks)');
69
+ return lines.join('\n');
70
+ }
71
+
72
+ if (value.task_id) {
73
+ // Spawn/send acks: the task just started, so runtime diagnostics
74
+ // (worker_stage/last_progress/diagnostic/watchdog/silent_for) are
75
+ // always in their "not started yet" placeholder state — pure token
76
+ // cost for the model. Keep the ack minimal (existing surface rule:
77
+ // minimum characters, maximum information); full diagnostics remain
78
+ // on explicit status/read recovery calls.
79
+ const isStartAck = value.status === 'running'
80
+ && (value.type === 'spawn' || value.type === 'send');
81
+ lines.push(`agent task: ${value.task_id}`);
82
+ lines.push(`status: ${value.status}`);
83
+ if (value.type) lines.push(`type: ${value.type}`);
84
+ if (value.reused) lines.push('reused: true');
85
+ if (value.tag || value.sessionId) lines.push(`target: ${value.tag || '-'} ${value.sessionId || ''}`.trim());
86
+ if (value.agent) lines.push(`agent: ${value.agent}`);
87
+ if (value.provider && value.model) lines.push(`model: ${value.provider}/${value.model}`);
88
+ if (value.effort) lines.push(`effort: ${value.effort}`);
89
+ if (value.fast === true || value.fast === false) lines.push(`fast: ${value.fast ? 'on' : 'off'}`);
90
+ if (value.maxLoopIterations) {
91
+ const limitParts = [];
92
+ if (value.maxLoopIterations) limitParts.push(`loop=${value.maxLoopIterations}`);
93
+ lines.push(`limits: ${limitParts.join(' ')}`);
94
+ }
95
+ if (!isStartAck) {
96
+ if (value.stage || value.workerStatus) lines.push(`worker: ${value.workerStatus || 'unknown'}/${value.stage || 'unknown'}`);
97
+ if (value.worker_stage) lines.push(`worker_stage: ${value.worker_stage}`);
98
+ if (value.last_progress) lines.push(`last_progress: ${value.last_progress}`);
99
+ if (Number.isFinite(value.silent_for)) lines.push(`silent_for: ${value.silent_for}s`);
100
+ if (value.watchdog) lines.push(`watchdog: ${value.watchdog}`);
101
+ if (Number.isFinite(value.queued_followups)) lines.push(`queued_followups: ${value.queued_followups}`);
102
+ if (value.diagnostic) lines.push(`diagnostic: ${value.diagnostic}`);
103
+ if (value.startedAt) lines.push(`started: ${compactIso(value.startedAt)}`);
104
+ if (value.finishedAt) lines.push(`finished: ${compactIso(value.finishedAt)}`);
105
+ {
106
+ const elapsed = elapsedFromStamps(value.startedAt, value.finishedAt, value.status);
107
+ if (elapsed) lines.push(`elapsed: ${elapsed}`);
108
+ }
109
+ }
110
+ if (value.error) lines.push(`error: ${value.error}`);
111
+ if (value.status === 'running') lines.push('notification: completion will be delivered; end the turn.');
112
+ if (value.result !== undefined) {
113
+ const result = value.result;
114
+ const content = typeof result === 'string' ? result : result?.content;
115
+ if (content) lines.push('', stripFinalAnswerWrapper(content));
116
+ else lines.push('', JSON.stringify(result, null, 2));
117
+ }
118
+ return lines.join('\n');
119
+ }
120
+
121
+ if (value.queued) {
122
+ return [
123
+ 'agent message queued',
124
+ value.reused ? 'reused: true' : null,
125
+ `target: ${value.tag || '-'} ${value.sessionId || ''}`.trim(),
126
+ value.agent ? `agent: ${value.agent}` : null,
127
+ `queueDepth: ${value.queueDepth ?? 1}`,
128
+ ].filter(Boolean).join('\n');
129
+ }
130
+
131
+ if (value.closed !== undefined) {
132
+ return [
133
+ `agent close: ${value.closed ? 'ok' : 'not closed'}`,
134
+ value.tag ? `tag: ${value.tag}` : null,
135
+ value.sessionId ? `sessionId: ${value.sessionId}` : null,
136
+ value.task_id ? `task_id: ${value.task_id}` : null,
137
+ value.forgotten ? 'forgotten: true' : null,
138
+ ].filter(Boolean).join('\n');
139
+ }
140
+
141
+ if (value.content !== undefined) {
142
+ const header = [
143
+ value.respawned ? 'agent respawned' : 'agent result',
144
+ value.tag ? `tag=${value.tag}` : null,
145
+ value.agent ? `agent=${value.agent}` : null,
146
+ value.provider && value.model ? `${value.provider}/${value.model}` : null,
147
+ ].filter(Boolean).join(' ');
148
+ return `${header}\n${stripFinalAnswerWrapper(value.content)}`;
149
+ }
150
+ }
151
+ return JSON.stringify(value, null, 2);
152
+ }