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,182 @@
1
+ // Recall-fasttrack compaction pipeline, extracted from loop.mjs.
2
+ // Hydrates the session transcript into the memory pipeline (ingest_session),
3
+ // dumps chunked/raw roots, drains cycle1 until no raw rows remain, and folds
4
+ // the combined recall text back into a compacted message array capped at
5
+ // CONTEXT_SHARE_RATIO of the model context window. No behavior change: this is
6
+ // the same body that lived inline in loop.mjs, re-exported via the facade so
7
+ // existing importers keep working.
8
+ import { createHash } from 'crypto';
9
+ import { executeInternalTool } from '../../internal-tools.mjs';
10
+ import { loadConfig as loadOrchestratorConfig } from '../../config.mjs';
11
+ import {
12
+ recallFastTrackCompactMessages,
13
+ CONTEXT_SHARE_RATIO,
14
+ RECALL_TOKEN_CAP_FLOOR_TOKENS,
15
+ drainSessionCycle1,
16
+ countRawPendingRows,
17
+ } from '../compact.mjs';
18
+ import {
19
+ compactDiagnosticError,
20
+ compactByteLength,
21
+ compactDebugLog,
22
+ } from './compact-debug.mjs';
23
+ import { positiveTokenInt } from './env.mjs';
24
+
25
+ export async function runRecallFastTrackCompact({ sessionRef, messages, compactBudgetTokens, compactPolicy, sessionId, signal }) {
26
+ if (!sessionId) throw new Error('recall-fasttrack requires a session id');
27
+ const startedAt = Date.now();
28
+ const diagnostics = {
29
+ hydrateLimit: null,
30
+ ingestMs: null,
31
+ ingestSkipped: false,
32
+ ingestError: null,
33
+ initialDumpMs: null,
34
+ initialDumpBytes: null,
35
+ initialDumpChars: null,
36
+ initialRawPending: null,
37
+ cycle1Ms: null,
38
+ cycle1Skipped: false,
39
+ cycle1SkipReason: null,
40
+ cycle1Passes: null,
41
+ cycle1RawRemaining: null,
42
+ cycle1TextBytes: null,
43
+ cycle1Error: null,
44
+ finalRecallBytes: null,
45
+ finalRecallChars: null,
46
+ totalMs: null,
47
+ };
48
+ const query = `session:${sessionId}:all-chunks`;
49
+ const querySha = createHash('sha256').update(query).digest('hex').slice(0, 16);
50
+ const callerCtx = {
51
+ callerSessionId: sessionId || null,
52
+ callerCwd: sessionRef?.cwd || undefined,
53
+ routingSessionId: sessionId || null,
54
+ clientHostPid: sessionRef?.clientHostPid,
55
+ signal: signal || null,
56
+ };
57
+ const hydrateLimit = positiveTokenInt(sessionRef?.compaction?.recallIngestLimit)
58
+ || Math.max(500, Math.min(5000, messages.length || 0));
59
+ diagnostics.hydrateLimit = hydrateLimit;
60
+ let t0 = Date.now();
61
+ try {
62
+ await executeInternalTool('memory', {
63
+ action: 'ingest_session',
64
+ sessionId,
65
+ messages,
66
+ cwd: sessionRef?.cwd,
67
+ limit: hydrateLimit,
68
+ }, callerCtx);
69
+ } catch (err) {
70
+ diagnostics.ingestSkipped = true;
71
+ diagnostics.ingestError = compactDiagnosticError(err);
72
+ try { process.stderr.write(`[loop] recall-fasttrack ingest skipped (sess=${sessionId || 'unknown'}): ${err?.message || err}\n`); } catch {}
73
+ } finally {
74
+ diagnostics.ingestMs = Date.now() - t0;
75
+ }
76
+ const dumpArgs = {
77
+ action: 'dump_session_roots',
78
+ sessionId,
79
+ includeRaw: true,
80
+ limit: positiveTokenInt(sessionRef?.compaction?.recallChunkLimit ?? sessionRef?.compaction?.recallLimit) || hydrateLimit,
81
+ };
82
+ const runTool = (name, args) => executeInternalTool(name, args, callerCtx);
83
+ t0 = Date.now();
84
+ let recallText = await executeInternalTool('memory', dumpArgs, callerCtx);
85
+ diagnostics.initialDumpMs = Date.now() - t0;
86
+ diagnostics.initialDumpChars = String(recallText || '').length;
87
+ diagnostics.initialDumpBytes = compactByteLength(recallText);
88
+ diagnostics.initialRawPending = countRawPendingRows(recallText);
89
+ let cycle1Text = '';
90
+ const hasRawRows = /(?:^|\n)# raw_pending\s+\d+\s+id=/i.test(String(recallText || ''));
91
+ // Recap off = NO memory-pipeline LLM calls: skip the cycle1 drain entirely
92
+ // and let the dump's raw transcript lines (includeRaw:true above) ride into
93
+ // the injected summary as-is. Poll-on-use: re-read the flag per compact so
94
+ // a runtime toggle applies without restart. Default on if config read fails.
95
+ let recapOn = true;
96
+ try { recapOn = loadOrchestratorConfig({ secrets: false })?.recap?.enabled !== false; } catch { /* default on */ }
97
+ if (hasRawRows && !recapOn) {
98
+ diagnostics.cycle1Skipped = true;
99
+ diagnostics.cycle1SkipReason = 'recap disabled';
100
+ diagnostics.cycle1Passes = 0;
101
+ diagnostics.cycle1RawRemaining = countRawPendingRows(recallText);
102
+ cycle1Text = 'cycle1: skipped (recap disabled — raw transcript lines kept as-is)';
103
+ } else if (hasRawRows) {
104
+ t0 = Date.now();
105
+ try {
106
+ // Drain this session's cycle1 in window×concurrency units until no
107
+ // raw rows remain, so the injected root is fully chunked rather than
108
+ // carrying the unprocessed transcript tail (single-pass left raw in).
109
+ const drained = await drainSessionCycle1(runTool, {
110
+ sessionId,
111
+ dumpArgs,
112
+ deadlineMs: positiveTokenInt(sessionRef?.compaction?.recallCycle1DeadlineMs) || 120_000,
113
+ maxPasses: positiveTokenInt(sessionRef?.compaction?.recallCycle1MaxPasses) || 0,
114
+ cycleArgs: {
115
+ min_batch: 1,
116
+ session_cap: 1,
117
+ batch_size: positiveTokenInt(sessionRef?.compaction?.recallCycle1BatchSize) || 100,
118
+ rows_per_session: positiveTokenInt(sessionRef?.compaction?.recallRowsPerSession) || 100,
119
+ window_size: positiveTokenInt(sessionRef?.compaction?.recallWindowSize) || 20,
120
+ concurrency: positiveTokenInt(sessionRef?.compaction?.recallConcurrency) || 5,
121
+ },
122
+ });
123
+ recallText = drained.recallText;
124
+ cycle1Text = drained.cycle1Text;
125
+ diagnostics.cycle1Passes = drained.passes;
126
+ diagnostics.cycle1RawRemaining = drained.rawRemaining;
127
+ diagnostics.cycle1TextBytes = compactByteLength(cycle1Text);
128
+ if (drained.error) {
129
+ diagnostics.cycle1Error = drained.error;
130
+ try { process.stderr.write(`[loop] recall-fasttrack cycle1 error (sess=${sessionId || 'unknown'}): ${drained.error}\n`); } catch {}
131
+ }
132
+ if (drained.rawRemaining > 0) {
133
+ try { process.stderr.write(`[loop] recall-fasttrack drained passes=${drained.passes} rawRemaining=${drained.rawRemaining} (sess=${sessionId || 'unknown'})\n`); } catch {}
134
+ }
135
+ } catch (err) {
136
+ diagnostics.cycle1Error = compactDiagnosticError(err);
137
+ try { process.stderr.write(`[loop] recall-fasttrack cycle1 skipped (sess=${sessionId || 'unknown'}): ${err?.message || err}\n`); } catch {}
138
+ } finally {
139
+ diagnostics.cycle1Ms = Date.now() - t0;
140
+ }
141
+ } else {
142
+ diagnostics.cycle1Skipped = true;
143
+ diagnostics.cycle1SkipReason = 'session chunks already hydrated';
144
+ diagnostics.cycle1Passes = 0;
145
+ diagnostics.cycle1RawRemaining = 0;
146
+ cycle1Text = 'cycle1: skipped (session chunks already hydrated)';
147
+ }
148
+ const combinedRecallText = [`session_id=${sessionId}`, cycle1Text, recallText].map(v => String(v || '').trim()).filter(Boolean).join('\n\n');
149
+ diagnostics.finalRecallChars = combinedRecallText.length;
150
+ diagnostics.finalRecallBytes = compactByteLength(combinedRecallText);
151
+ // Recall injection (chunked-summary + raw-fallback text, combined above)
152
+ // never exceeds CONTEXT_SHARE_RATIO (5%) of the model context window
153
+ // (floor RECALL_TOKEN_CAP_FLOOR_TOKENS); the rest of the budget belongs to
154
+ // live conversation. Same unified 5% as the compact target ratio
155
+ // (compact-policy.mjs COMPACT_TARGET_RATIO). Omitted when contextWindow is
156
+ // unknown/0 so current no-cap behavior is preserved.
157
+ const _recallCapWindow = Number(compactPolicy.contextWindow) || 0;
158
+ const recallTokenCap = _recallCapWindow > 0
159
+ ? Math.max(RECALL_TOKEN_CAP_FLOOR_TOKENS, Math.floor(_recallCapWindow * CONTEXT_SHARE_RATIO))
160
+ : undefined;
161
+ const result = recallFastTrackCompactMessages(messages, compactBudgetTokens, {
162
+ reserveTokens: compactPolicy.reserveTokens,
163
+ force: true,
164
+ recallText: combinedRecallText,
165
+ query,
166
+ querySha,
167
+ allowEmptyRecall: true,
168
+ tailTurns: compactPolicy.tailTurns,
169
+ keepTokens: compactPolicy.keepTokens,
170
+ preserveRecentTokens: compactPolicy.preserveRecentTokens,
171
+ recallTokenCap,
172
+ });
173
+ diagnostics.totalMs = Date.now() - startedAt;
174
+ if (result && typeof result === 'object') {
175
+ result.diagnostics = {
176
+ ...(result.diagnostics || {}),
177
+ pipeline: diagnostics,
178
+ };
179
+ }
180
+ compactDebugLog('recall-fasttrack pipeline', diagnostics);
181
+ return result;
182
+ }
@@ -0,0 +1,173 @@
1
+ // Completion-first steering ladder (worker runaway prevention), extracted from
2
+ // loop.mjs. Owns the mutable ladder counters and the post-batch steering-hint
3
+ // emitters. State is threaded live via a context object of getters/setters so
4
+ // no snapshot goes stale — the loop mutates `messages`/`iterations` in place and
5
+ // this module reads them through the accessors on each call. No behavior change:
6
+ // the counters, thresholds, and emitted messages are verbatim from agentLoop.
7
+ import { appendAgentTrace } from '../../agent-trace.mjs';
8
+ import { level2SteerMessage } from './completion-guards.mjs';
9
+ import { isEagerDispatchable } from './tool-helpers.mjs';
10
+
11
+ // Consecutive ignored level-2 steers (zero edits) that force the wrap-up early.
12
+ export const EARLY_SOFT_CAP_LEVEL2_FIRES = 3;
13
+
14
+ // Build the completion-first steering-ladder controller. `ctx` supplies live
15
+ // accessors so every read reflects the loop's current mutable state:
16
+ // - messages, sessionId, sessionAgent, tools (stable refs/values)
17
+ // - getIterations() (current iteration)
18
+ // - softCapEnabled (constant per loop)
19
+ // - getEditCount() (mutated by the loop)
20
+ // - pushSystemReminder(text) → push a meta:'hook' user message
21
+ // - pushUserMessage(msg) → push a raw user message (level-2 latch text)
22
+ export function createSteeringLadder(ctx) {
23
+ const {
24
+ sessionId,
25
+ sessionAgent,
26
+ tools,
27
+ getIterations,
28
+ softCapEnabled,
29
+ getEditCount,
30
+ } = ctx;
31
+ const pushSystemReminder = ctx.pushSystemReminder;
32
+ const pushUserMessage = ctx.pushUserMessage;
33
+ // Permission-based role detection (agent names are user-definable):
34
+ // read-permission sessions legitimately never edit, so they get the
35
+ // report-oriented level-2 text and never arm the early soft-cap.
36
+ const readOnlyRole = ctx.readOnlyRole === true;
37
+
38
+ // Step 1: escalation ladder. _level1FireCount is CUMULATIVE (never reset)
39
+ // so repeated batching reminders accumulate across the whole session.
40
+ // _level2LatchAtIteration latches level-2 steering to at most once / 5 turns.
41
+ let _level1FireCount = 0;
42
+ let _level2LatchAtIteration = -Infinity;
43
+ // Independent ladder counter: consecutive turns where EVERY call is
44
+ // read-only (any count) with zero edits. Catches multi-call read-only
45
+ // turns that the single-call level-1 streak misses. Reset on any edit.
46
+ let _allReadOnlyStreak = 0;
47
+ // Tracks consecutive assistant turns that ran exactly one read-only tool
48
+ // call (missed parallelism). Not reset per-iteration — only by the
49
+ // steering-hint fire below or by a turn that batches/edits.
50
+ let _serialReadOnlyStreak = 0;
51
+ // Tracks consecutive grep calls scoped to the SAME path (any patterns) —
52
+ // the "serial rewording" spiral: re-grepping one file with reworded
53
+ // patterns instead of reading it. Only counts turns whose every call is a
54
+ // grep on that path; any other tool/path resets it.
55
+ let _sameFileGrepStreak = 0;
56
+ let _sameFileGrepPath = null;
57
+ // Early soft-cap: N ignored level-2 steers with zero edits forces the
58
+ // wrap-up early; any edit disarms it (level-2 requires editCount === 0).
59
+ let _level2FireCount = 0;
60
+
61
+ // Level-2 steering emitter shared by both ladder paths (single-call
62
+ // level-1 streak and the independent all-read-only streak). Sets the latch
63
+ // so it fires at most once per 5 turns regardless of which path triggered.
64
+ const _emitLevel2Steer = () => {
65
+ const iterations = getIterations();
66
+ _level2LatchAtIteration = iterations;
67
+ _level2FireCount += 1;
68
+ // When this fire arms the early soft-cap, the wrap-up injected at the
69
+ // next loop head supersedes the level-2 text — skip the redundant hint.
70
+ const _armsEarlyCap = !readOnlyRole && softCapEnabled && _level2FireCount >= EARLY_SOFT_CAP_LEVEL2_FIRES && getEditCount() === 0;
71
+ if (!_armsEarlyCap) pushUserMessage({ role: 'user', content: level2SteerMessage(_level1FireCount, readOnlyRole), meta: 'hook' });
72
+ try {
73
+ appendAgentTrace({
74
+ sessionId,
75
+ iteration: iterations,
76
+ kind: 'steer',
77
+ payload: { tag: 'level2_steer', level1_fires: _level1FireCount, level2_fires: _level2FireCount, edit_count: getEditCount(), all_read_only_streak: _allReadOnlyStreak },
78
+ agent: sessionAgent || null,
79
+ });
80
+ } catch { /* best-effort */ }
81
+ };
82
+
83
+ return {
84
+ // Loop head reads: is the early soft-cap armed?
85
+ get level2FireCount() { return _level2FireCount; },
86
+ earlySoftCapArmed() {
87
+ return !readOnlyRole && _level2FireCount >= EARLY_SOFT_CAP_LEVEL2_FIRES && getEditCount() === 0;
88
+ },
89
+ // Post-batch steering hint gate. `hintAlreadyFired` seeds the once-per-
90
+ // turn latch (soft-cap active suppresses all hints). Returns nothing;
91
+ // pushes at most one steering message via the ctx push callbacks.
92
+ emitPostBatchSteering(calls, hintAlreadyFired) {
93
+ const iterations = getIterations();
94
+ const editCount = getEditCount();
95
+ // Steering hint gate: at most ONE hint per turn (priority: soft-cap >
96
+ // level-2 > same-file grep > level-1), and none once the soft-cap
97
+ // wrap-up is active — its "text only" directive must not share a send
98
+ // with a "keep exploring" hint.
99
+ let _hintFiredThisTurn = hintAlreadyFired;
100
+ // Missed-parallelism steering: 3+ consecutive turns of a single
101
+ // read-only tool call suggest the model isn't batching independent
102
+ // lookups. Nudge once, then reset (fires again after 3 more).
103
+ if (calls.length === 1 && isEagerDispatchable(calls[0].name, tools)) {
104
+ _serialReadOnlyStreak += 1;
105
+ if (_serialReadOnlyStreak >= 3 && !_hintFiredThisTurn) {
106
+ _serialReadOnlyStreak = 0;
107
+ // Escalation ladder (Step 1). Cumulative level-1 fires are
108
+ // tracked and NEVER reset. Once level-1 has fired >=3 times with
109
+ // ZERO edits, escalate to level-2 steering (blocked-report is a
110
+ // valid completion) instead of the batching nudge — latched to at
111
+ // most once per 5 turns.
112
+ _level1FireCount += 1;
113
+ if (_level1FireCount >= 3 && editCount === 0 && (iterations - _level2LatchAtIteration) >= 5) {
114
+ _emitLevel2Steer();
115
+ } else {
116
+ pushSystemReminder('Last 3 turns each ran a single read-only tool. Batch independent lookups (read/grep/glob/code_graph) into ONE turn, or start editing if you have enough context.');
117
+ }
118
+ _hintFiredThisTurn = true;
119
+ }
120
+ } else {
121
+ _serialReadOnlyStreak = 0;
122
+ }
123
+ // Independent all-read-only escalation (audit finding): the level-1
124
+ // streak above only counts single-call turns, so a worker that runs
125
+ // 2+ read-only calls per turn escapes the ladder entirely. Track a
126
+ // cumulative count of consecutive turns where EVERY call is read-only
127
+ // (any count) and no edit has been made; at 12 such turns fire level-2
128
+ // directly (same once-per-5-turn latch), reset on any edit.
129
+ {
130
+ const _allReadOnly = calls.length > 0 && calls.every((c) => isEagerDispatchable(c.name, tools));
131
+ if (_allReadOnly && editCount === 0) {
132
+ _allReadOnlyStreak += 1;
133
+ if (_allReadOnlyStreak >= 12 && (iterations - _level2LatchAtIteration) >= 5 && !_hintFiredThisTurn) {
134
+ _emitLevel2Steer();
135
+ _hintFiredThisTurn = true;
136
+ }
137
+ } else {
138
+ _allReadOnlyStreak = 0;
139
+ }
140
+ }
141
+ // Serial-rewording steering: 4+ consecutive turns grepping the SAME
142
+ // path with reworded patterns = a search spiral that single-call
143
+ // batching cannot catch. Crisis-only: fires once per spiral, then
144
+ // resets. Read-the-file is almost always the answer at that point.
145
+ {
146
+ const _grepPathOf = (c) => {
147
+ if (c?.name !== 'grep') return null;
148
+ const p = c?.arguments?.path;
149
+ return typeof p === 'string' && p ? p : null;
150
+ };
151
+ const _turnPaths = calls.map(_grepPathOf);
152
+ const _uniq = [...new Set(_turnPaths)];
153
+ if (_uniq.length === 1 && _uniq[0] !== null) {
154
+ if (_uniq[0] === _sameFileGrepPath) _sameFileGrepStreak += 1;
155
+ else { _sameFileGrepPath = _uniq[0]; _sameFileGrepStreak = 1; }
156
+ if (_sameFileGrepStreak >= 4 && !_hintFiredThisTurn) {
157
+ pushSystemReminder(`4+ consecutive grep turns on the same path (${_sameFileGrepPath}). Rewording patterns is not converging — read the relevant span directly (read with offset/limit) or act on what you have.`);
158
+ _sameFileGrepStreak = 0;
159
+ _sameFileGrepPath = null;
160
+ _hintFiredThisTurn = true;
161
+ }
162
+ } else {
163
+ _sameFileGrepStreak = 0;
164
+ _sameFileGrepPath = null;
165
+ }
166
+ }
167
+ },
168
+ // Reviewer fix: a zero-tool turn must not bridge the all-read-only
169
+ // streak across non-tool turns — that would fire level-2 early on a
170
+ // worker that paused to synthesize text mid-run.
171
+ resetAllReadOnlyStreak() { _allReadOnlyStreak = 0; },
172
+ };
173
+ }
@@ -0,0 +1,63 @@
1
+ // Steering-message normalization/merge helpers extracted from loop.mjs.
2
+ // Merges queued steering entries into a single content payload + display text.
3
+
4
+ export function steeringContentText(content) {
5
+ if (typeof content === 'string') return content;
6
+ if (Array.isArray(content)) {
7
+ return content.map((part) => {
8
+ if (typeof part === 'string') return part;
9
+ if (part?.type === 'text') return part.text || '';
10
+ if (part?.type === 'image') return '[Image]';
11
+ return part?.text || '';
12
+ }).filter(Boolean).join('\n');
13
+ }
14
+ return String(content ?? '');
15
+ }
16
+
17
+ export function normalizeSteeringEntry(entry) {
18
+ if (typeof entry === 'string') {
19
+ const text = entry.trim();
20
+ return text ? { content: text, text } : null;
21
+ }
22
+ if (!entry || typeof entry !== 'object') return null;
23
+ const content = Object.prototype.hasOwnProperty.call(entry, 'content') ? entry.content : entry;
24
+ const text = typeof entry.text === 'string' ? entry.text.trim() : steeringContentText(content).trim();
25
+ if (Array.isArray(content)) return content.length > 0 ? { content, text } : null;
26
+ if (typeof content === 'string') {
27
+ const value = content.trim();
28
+ return value ? { content: value, text: text || value } : null;
29
+ }
30
+ const fallback = steeringContentText(content).trim();
31
+ return fallback ? { content: fallback, text: text || fallback } : null;
32
+ }
33
+
34
+ export function mergeSteeringEntries(entries) {
35
+ const normalized = (Array.isArray(entries) ? entries : [])
36
+ .map(normalizeSteeringEntry)
37
+ .filter(Boolean);
38
+ if (normalized.length === 0) return null;
39
+ const displayText = normalized.map((entry) => entry.text || steeringContentText(entry.content))
40
+ .filter((text) => String(text || '').trim())
41
+ .join('\n');
42
+ if (normalized.every((entry) => typeof entry.content === 'string')) {
43
+ return {
44
+ content: normalized.map((entry) => entry.content).filter(Boolean).join('\n'),
45
+ text: displayText,
46
+ count: normalized.length,
47
+ };
48
+ }
49
+ const parts = [];
50
+ for (const entry of normalized) {
51
+ if (typeof entry.content === 'string') {
52
+ if (entry.content.trim()) parts.push({ type: 'text', text: entry.content });
53
+ } else if (Array.isArray(entry.content)) {
54
+ parts.push(...entry.content);
55
+ } else {
56
+ const text = steeringContentText(entry.content);
57
+ if (text.trim()) parts.push({ type: 'text', text });
58
+ }
59
+ parts.push({ type: 'text', text: '\n' });
60
+ }
61
+ while (parts.length && parts[parts.length - 1]?.type === 'text' && parts[parts.length - 1]?.text === '\n') parts.pop();
62
+ return { content: parts, text: displayText || steeringContentText(parts), count: normalized.length };
63
+ }
@@ -0,0 +1,100 @@
1
+ // Stored tool-call argument compaction/restoration, extracted from loop.mjs.
2
+ // Long body/command args are truncated with a sha256-tagged head/tail preview
3
+ // when persisted into assistant history; the FULL body of a single call can be
4
+ // restored on retry (e.g. a failed edit) so the model sees the original patch.
5
+ import { createHash } from 'crypto';
6
+
7
+ const STORED_TOOL_ARG_BODY_KEY_RE = /^(?:content|old_string|new_string|patch|rewrite)$/i;
8
+ const STORED_TOOL_ARG_LONG_KEY_RE = /^(?:command|script)$/i;
9
+ const STORED_TOOL_ARG_BODY_LIMIT = 2_000;
10
+ const STORED_TOOL_ARG_LONG_LIMIT = 8_000;
11
+ const STORED_TOOL_ARG_PREVIEW_HEAD = 360;
12
+ const STORED_TOOL_ARG_PREVIEW_TAIL = 160;
13
+
14
+ function compactStoredToolArgString(value, key = '') {
15
+ if (typeof value !== 'string') return value;
16
+ const isBody = STORED_TOOL_ARG_BODY_KEY_RE.test(key);
17
+ const isLong = isBody || STORED_TOOL_ARG_LONG_KEY_RE.test(key);
18
+ const limit = isBody ? STORED_TOOL_ARG_BODY_LIMIT : (isLong ? STORED_TOOL_ARG_LONG_LIMIT : Infinity);
19
+ if (value.length <= limit) return value;
20
+ const hash = createHash('sha256').update(value).digest('hex').slice(0, 16);
21
+ const head = value.slice(0, STORED_TOOL_ARG_PREVIEW_HEAD).replace(/\r\n/g, '\n');
22
+ const tail = value.slice(-STORED_TOOL_ARG_PREVIEW_TAIL).replace(/\r\n/g, '\n');
23
+ return `[mixdog compacted ${key || 'string'}: ${value.length} chars, sha256:${hash}]\n${head}\n... [middle omitted from stored tool-call args] ...\n${tail}`;
24
+ }
25
+
26
+ function compactStoredToolArgValue(value, key = '', depth = 0) {
27
+ if (value === null || value === undefined) return value;
28
+ if (typeof value === 'string') return compactStoredToolArgString(value, key);
29
+ if (typeof value !== 'object') return value;
30
+ if (depth >= 6) return Array.isArray(value) ? `[${value.length} items]` : '{...}';
31
+ if (Array.isArray(value)) {
32
+ return value.map((item) => compactStoredToolArgValue(item, key, depth + 1));
33
+ }
34
+ const out = {};
35
+ for (const [k, v] of Object.entries(value)) {
36
+ out[k] = compactStoredToolArgValue(v, k, depth + 1);
37
+ }
38
+ return out;
39
+ }
40
+
41
+ export function compactToolCallsForHistory(calls) {
42
+ if (!Array.isArray(calls)) return calls;
43
+ return calls.map((call) => {
44
+ if (!call || typeof call !== 'object') return call;
45
+ return {
46
+ ...call,
47
+ arguments: compactStoredToolArgValue(call.arguments),
48
+ };
49
+ });
50
+ }
51
+
52
+ // Restore the FULL body of ONE tool call inside a history assistant message
53
+ // whose toolCalls were compacted at push time. Used for a failed edit call so
54
+ // the model sees the original patch/old_string on retry instead of a
55
+ // `[mixdog compacted …]` placeholder it cannot act on. Must run BEFORE the
56
+ // message is first transmitted so it never mutates an already-cached prefix
57
+ // (the prompt cache is content-prefix matched).
58
+ //
59
+ // Only the compactable body/long keys (patch, old_string, new_string, content,
60
+ // rewrite, command, script) are restored, and at ANY depth — compaction is
61
+ // recursive (compactStoredToolArgValue), so batch shapes like edits[].old_string
62
+ // or writes[].content carry nested compacted bodies too. Every other field
63
+ // (e.g. `path`, which a tool may mutate in place during execution) is taken from
64
+ // the compacted snapshot captured at push time, before any mutation. The
65
+ // compacted args tree is built fresh by compactToolCallsForHistory and is not
66
+ // shared with originalCalls, so rebuilding it here is safe.
67
+ export function restoreToolCallBodyForId(assistantMsg, originalCalls, callId) {
68
+ if (!assistantMsg || !Array.isArray(assistantMsg.toolCalls) || !callId) return;
69
+ if (!Array.isArray(originalCalls)) return;
70
+ const tc = assistantMsg.toolCalls.find((t) => t && t.id === callId);
71
+ const orig = originalCalls.find((c) => c && c.id === callId);
72
+ if (!tc || !orig) return;
73
+ if (!tc.arguments || typeof tc.arguments !== 'object'
74
+ || !orig.arguments || typeof orig.arguments !== 'object') return;
75
+ tc.arguments = _restoreCompactedBodies(tc.arguments, orig.arguments, '');
76
+ }
77
+
78
+ // Recursively rebuild a compacted args tree: replace ONLY compactable body/long
79
+ // string fields (matched by key at any depth) with their full originals, and
80
+ // keep every other field from the compacted snapshot. tcVal and origVal share
81
+ // the same structure (compaction only shortens body strings), so the walk
82
+ // descends them in parallel; a missing or non-object origVal falls back to the
83
+ // compacted value rather than throwing.
84
+ function _restoreCompactedBodies(tcVal, origVal, key) {
85
+ if ((STORED_TOOL_ARG_BODY_KEY_RE.test(key) || STORED_TOOL_ARG_LONG_KEY_RE.test(key))
86
+ && typeof origVal === 'string') {
87
+ return origVal;
88
+ }
89
+ if (Array.isArray(tcVal) && Array.isArray(origVal)) {
90
+ return tcVal.map((item, i) => _restoreCompactedBodies(item, origVal[i], key));
91
+ }
92
+ if (tcVal && typeof tcVal === 'object' && origVal && typeof origVal === 'object') {
93
+ const out = {};
94
+ for (const k of Object.keys(tcVal)) {
95
+ out[k] = (k in origVal) ? _restoreCompactedBodies(tcVal[k], origVal[k], k) : tcVal[k];
96
+ }
97
+ return out;
98
+ }
99
+ return tcVal;
100
+ }
@@ -0,0 +1,58 @@
1
+ // Loop termination-reason classification, extracted from loop.mjs.
2
+ // Pure function over the final response + loop-end flags. No behavior change:
3
+ // the classification ladder is verbatim from the tail of agentLoop.
4
+ import { HIDDEN_AGENT_NAMES } from './hidden-agents.mjs';
5
+
6
+ // Stop reasons that signal the turn was cut short mid-synthesis (token cap,
7
+ // provider pause). Empty content + one of these reasons means the worker
8
+ // was not done. Covers Anthropic (pause_turn, max_tokens), OpenAI (length),
9
+ // Gemini (MAX_TOKENS, OTHER), and case variants.
10
+ export const INCOMPLETE_STOP_REASONS = new Set([
11
+ 'pause_turn', 'max_tokens', 'length', 'MAX_TOKENS', 'OTHER',
12
+ ]);
13
+
14
+ // Classify WHY the loop ended so agent-tool can promote an empty/abnormal
15
+ // finish to an explicit Lead-facing error instead of a silent empty
16
+ // "completed". Determine "has content" exactly the way the no-tool-call
17
+ // branch in agentLoop does (trimmed string content, or any reasoning content).
18
+ export function classifyTerminationReason(response, {
19
+ terminatedByCap,
20
+ terminatedBySoftCap,
21
+ softCapActive,
22
+ sessionAgent,
23
+ } = {}) {
24
+ const _finalHasContent = (typeof response?.content === 'string' && response.content.trim().length > 0)
25
+ || (typeof response?.reasoningContent === 'string' && response.reasoningContent.trim().length > 0);
26
+ const _finalStopReason = response?.stopReason ?? response?.stop_reason ?? null;
27
+ const _finalIncompleteStop = _finalStopReason && INCOMPLETE_STOP_REASONS.has(_finalStopReason);
28
+ const _finalIsHidden = HIDDEN_AGENT_NAMES.has(sessionAgent);
29
+ if (terminatedByCap) {
30
+ // Real problem regardless of hidden/public: the loop never terminated
31
+ // on its own contract.
32
+ return 'iteration_cap';
33
+ }
34
+ if (terminatedBySoftCap || softCapActive) {
35
+ // Worker soft-cap wrap-up path: non-lead session hit the exploration
36
+ // budget and was steered to a text-only finish. Distinct from the hard
37
+ // cap (iteration_cap) — this is an expected, budget-driven termination,
38
+ // not a runaway. Covers BOTH the grace-exhausted path
39
+ // (terminatedBySoftCap) AND the compliant path where the model emitted
40
+ // the final text with no more tool calls while the soft cap was active
41
+ // (loop ends via the no-tool-call break, so terminatedBySoftCap stays
42
+ // false but softCapActive is still true).
43
+ return 'soft_cap_wrapup';
44
+ }
45
+ if (!_finalHasContent && _finalIncompleteStop) {
46
+ // Cut short mid-synthesis (token cap / provider pause). Real problem
47
+ // for hidden agents too.
48
+ return 'truncated';
49
+ }
50
+ if (!_finalHasContent && !_finalIsHidden) {
51
+ // Empty terminal turn. Only public agents violate their contract by
52
+ // finishing empty — hidden agents (explorer/cycle/…) legitimately emit
53
+ // text-only/empty terminal turns per their own role contract, so leave
54
+ // terminationReason undefined for them.
55
+ return 'empty';
56
+ }
57
+ return undefined;
58
+ }
@@ -0,0 +1,52 @@
1
+ // Tool-name classification + intra-turn signature helpers, extracted from
2
+ // loop.mjs. These drive cross-turn read dedup, scoped caching, shell routing,
3
+ // and duplicate-call detection. Strips the MCP prefix so direct calls and
4
+ // MCP-wrapped calls share the same cache.
5
+ import { createHash } from 'crypto';
6
+
7
+ export const MCP_TOOL_PREFIX = 'mcp__plugin_mixdog_mixdog__';
8
+
9
+ export function _stripMcpPrefix(name) {
10
+ return typeof name === 'string' && name.startsWith(MCP_TOOL_PREFIX)
11
+ ? name.slice(MCP_TOOL_PREFIX.length) : name;
12
+ }
13
+ export function _isReadTool(name) {
14
+ return _stripMcpPrefix(name) === 'read';
15
+ }
16
+ export function _isMutationTool(name) {
17
+ const n = _stripMcpPrefix(name);
18
+ return n === 'apply_patch';
19
+ }
20
+ export const SCOPED_CACHEABLE_TOOLS = new Set([
21
+ 'code_graph',
22
+ 'grep',
23
+ 'list',
24
+ 'glob',
25
+ ]);
26
+ export function _isScopedCacheableTool(name) {
27
+ const n = _stripMcpPrefix(name);
28
+ return SCOPED_CACHEABLE_TOOLS.has(n);
29
+ }
30
+ export function _isShellTool(name) {
31
+ const n = _stripMcpPrefix(name);
32
+ return n === 'shell' || n === 'bash_session';
33
+ }
34
+
35
+ // Canonical signature for intra-turn duplicate detection. Sorting keys
36
+ // produces a stable hash regardless of arg-object key order. Anything
37
+ // non-serializable falls back to String(args) — still deterministic for
38
+ // the model's typical structured-arg shape.
39
+ export function _canonicalArgs(args) {
40
+ if (args == null || typeof args !== 'object') {
41
+ try { return JSON.stringify(args); } catch { return String(args); }
42
+ }
43
+ try {
44
+ const keys = Object.keys(args).sort();
45
+ const sorted = {};
46
+ for (const k of keys) sorted[k] = args[k];
47
+ return JSON.stringify(sorted);
48
+ } catch { return String(args); }
49
+ }
50
+ export function _intraTurnSig(name, args) {
51
+ return createHash('sha256').update(`${name}:${_canonicalArgs(args)}`).digest('hex').slice(0, 16);
52
+ }