mixdog 1.4.2 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (417) hide show
  1. package/package.json +2 -2
  2. package/scripts/build-shipped-history.mjs +15 -5
  3. package/scripts/gate-local.mjs +65 -19
  4. package/scripts/graph-parity.mjs +8 -3
  5. package/scripts/lib/gate-scheduler.mjs +2 -1
  6. package/scripts/lib/run-node-tests.mjs +17 -5
  7. package/scripts/lib/test-failure-summary.mjs +1 -8
  8. package/scripts/lib/test-spawn-ledger.mjs +33 -2
  9. package/scripts/post-release-check.mjs +26 -16
  10. package/scripts/provider-pingpong-live.mjs +1 -1
  11. package/scripts/prune-embedding-runtime.mjs +3 -1
  12. package/scripts/release-paths.mjs +7 -1
  13. package/src/defaults/shipped-history.json +20 -0
  14. package/src/defaults/skills/setup/SKILL.md +2 -2
  15. package/src/defaults/skills/setup/references/actions.md +16 -3
  16. package/src/defaults/skills/setup/references/surfaces.md +3 -0
  17. package/src/defaults/skills/setup-guide/SKILL.md +6 -1
  18. package/src/lib/instruction-text.cjs +36 -0
  19. package/src/lib/rules-builder.cjs +34 -20
  20. package/src/orchestration/balanced.md +23 -0
  21. package/src/orchestration/focused.md +22 -0
  22. package/src/orchestration/none.md +5 -0
  23. package/src/orchestration/swarm.md +24 -0
  24. package/src/rules/agent-mode/AGENT_MODE.md +3 -1
  25. package/src/rules/agent-mode/MIXY.md +2 -2
  26. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +3 -3
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +0 -1
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +4 -2
  29. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +0 -9
  30. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +2 -2
  31. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +67 -0
  32. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +1 -0
  33. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +4 -4
  34. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -1
  35. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +199 -46
  36. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +3 -5
  37. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +9 -3
  38. package/src/runtime/agent/orchestrator/mcp/client.mjs +3 -5
  39. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +8 -2
  41. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +1 -1
  42. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +14 -0
  43. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-stop.mjs +3 -3
  46. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +2 -2
  48. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +4 -6
  49. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2784 -2872
  50. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +5 -8
  51. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +288 -300
  52. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +10 -1
  53. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +10 -30
  54. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +30 -0
  55. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +0 -11
  56. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +8 -0
  57. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +17 -3
  58. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +47 -27
  59. package/src/runtime/agent/orchestrator/runtime-core/goal-deadlines.mjs +141 -10
  60. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +26 -63
  61. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +28 -3
  62. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +36 -11
  63. package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +88 -56
  64. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +4 -1
  65. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +51 -32
  66. package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +45 -145
  67. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +15 -98
  68. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +32 -99
  69. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +23 -78
  70. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +69 -48
  71. package/src/runtime/agent/orchestrator/runtime-core/todo-list.mjs +367 -0
  72. package/src/runtime/agent/orchestrator/runtime-core/todo-tool-defs.mjs +47 -0
  73. package/src/runtime/agent/orchestrator/runtime-core/tool-availability.mjs +155 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +9 -0
  75. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +119 -35
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +28 -22
  79. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +30 -0
  80. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +1 -1
  81. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +2 -2
  82. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -2
  83. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +1 -1
  84. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -1
  85. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +3 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +1 -1
  87. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -12
  88. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +7 -3
  92. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +1 -1
  93. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +1 -2
  94. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +1 -1
  95. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +11 -1
  97. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
  99. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/todo-check.mjs +196 -0
  100. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +116 -50
  101. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +8 -1
  102. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +1 -1
  104. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -1
  105. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +5 -6
  106. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +6 -1
  107. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  108. package/src/runtime/agent/orchestrator/session/loop/turn-report.mjs +35 -0
  109. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +57 -26
  111. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +9 -7
  112. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +21 -3
  113. package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +27 -11
  114. package/src/runtime/agent/orchestrator/session/manager/ask-turn-open.mjs +37 -34
  115. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +15 -0
  116. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +25 -15
  117. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +79 -24
  118. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +3 -0
  119. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +12 -1
  120. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +1 -1
  121. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +6 -1
  122. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +9 -1
  123. package/src/runtime/agent/orchestrator/session/manager/pending-spool-path.mjs +2 -2
  124. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +1 -1
  125. package/src/runtime/agent/orchestrator/session/manager/prompt-settings.mjs +598 -0
  126. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +19 -0
  127. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +19 -10
  128. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +5 -0
  129. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +1 -1
  130. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +31 -0
  131. package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +31 -0
  132. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +42 -6
  133. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +2 -0
  134. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +11 -1
  135. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +8 -9
  136. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +339 -88
  137. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +135 -11
  138. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +377 -104
  139. package/src/runtime/agent/orchestrator/session/manager/turn-durability.mjs +144 -0
  140. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +1 -1
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +4 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +6 -0
  143. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +8 -1
  145. package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +5 -1
  146. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +2 -2
  148. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +23 -2
  149. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +3 -3
  150. package/src/runtime/agent/orchestrator/session/store/transcript-durability.mjs +88 -0
  151. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +5 -0
  152. package/src/runtime/agent/orchestrator/session/store-summary-log.mjs +1 -1
  153. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +14 -5
  154. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +2 -2
  155. package/src/runtime/agent/orchestrator/session/store.mjs +5 -0
  156. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +7 -1
  157. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +9 -1
  158. package/src/runtime/agent/orchestrator/session/transcript-restore/agent-envelope.mjs +1 -1
  159. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +3 -1
  160. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +2 -2
  161. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -5
  162. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -1
  163. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  165. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/worktree-add-guard.mjs +77 -0
  166. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -1
  167. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +7 -3
  168. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +27 -9
  169. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +2 -2
  170. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +1 -1
  171. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +0 -1
  172. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +0 -10
  173. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1 -2
  174. package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -24
  175. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary/child-kill.mjs +4 -3
  176. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +1 -2
  177. package/src/runtime/agent/orchestrator/tools/code-graph/modes/calls.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/tools/code-graph/modes/symbols.mjs +2 -2
  179. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +4 -5
  180. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +3 -4
  181. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4 -22
  182. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +2 -2
  183. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +1 -1
  184. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -4
  185. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +0 -1
  186. package/src/runtime/attachments/attachment-reference.mjs +1 -1
  187. package/src/runtime/attachments/pdf-extract.mjs +1 -1
  188. package/src/runtime/attachments/store.d.mts +1 -0
  189. package/src/runtime/attachments/store.mjs +2 -2
  190. package/src/runtime/channels/lib/config-reload-watch.mjs +42 -0
  191. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +29 -9
  192. package/src/runtime/channels/lib/scheduler.mjs +33 -0
  193. package/src/runtime/channels/lib/schedules-db.mjs +11 -4
  194. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +4 -1
  195. package/src/runtime/channels/lib/webhooks-db.mjs +9 -3
  196. package/src/runtime/channels/lib/whisper-server.mjs +12 -12
  197. package/src/runtime/channels/lib/worker-main.mjs +9 -31
  198. package/src/runtime/computer-bridge/linux-readiness.mjs +10 -14
  199. package/src/runtime/connectors/connector-accounts-mcp.mjs +169 -33
  200. package/src/runtime/connectors/gmail-app-password.mjs +2 -2
  201. package/src/runtime/connectors/google-mcp.mjs +1 -1
  202. package/src/runtime/effort-judge/judge-client.mjs +2 -2
  203. package/src/runtime/effort-judge/judge-input.mjs +4 -4
  204. package/src/runtime/explore/explorer.mjs +0 -5
  205. package/src/runtime/explore/tool.mjs +3 -3
  206. package/src/runtime/local-provider/hardware.mjs +1 -1
  207. package/src/runtime/media/adapters/codex-image.mjs +3 -3
  208. package/src/runtime/media/adapters/gemini-video.mjs +1 -1
  209. package/src/runtime/media/media-usage.mjs +1 -1
  210. package/src/runtime/media/store.mjs +1 -1
  211. package/src/runtime/memory/index.mjs +11 -1
  212. package/src/runtime/memory/lib/embedding-reindex.mjs +27 -0
  213. package/src/runtime/memory/lib/memory-embed.mjs +83 -14
  214. package/src/runtime/memory/lib/memory-process-lock.mjs +159 -36
  215. package/src/runtime/memory/lib/memory-text-utils.mjs +6 -1
  216. package/src/runtime/memory/lib/memory.mjs +17 -0
  217. package/src/runtime/memory/lib/pg/process.mjs +1 -1
  218. package/src/runtime/memory/lib/pg/supervisor.mjs +16 -96
  219. package/src/runtime/memory/lib/recall-hybrid/hybrid-sql.mjs +12 -3
  220. package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
  221. package/src/runtime/office/authoring/pptx-author-action.mjs +1 -1
  222. package/src/runtime/office/authoring/xlsx-html-build.mjs +1 -1
  223. package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
  224. package/src/runtime/office/capabilities.mjs +1 -1
  225. package/src/runtime/office/core/office-core.mjs +1 -1
  226. package/src/runtime/office/design/content-model.mjs +3 -3
  227. package/src/runtime/office/design/docx/design-docx/section.mjs +6 -6
  228. package/src/runtime/office/design/xlsx/design-xlsx-components.mjs +1 -1
  229. package/src/runtime/office/design/xlsx/design-xlsx.mjs +1 -1
  230. package/src/runtime/office/pdf/ocr-engine.mjs +1 -1
  231. package/src/runtime/office/pdf/pdf-writer.mjs +4 -4
  232. package/src/runtime/office/portable/portable-docx-operations.mjs +2 -2
  233. package/src/runtime/office/portable/portable-soffice.mjs +8 -6
  234. package/src/runtime/office/portable/xlsx-contract.mjs +1 -1
  235. package/src/runtime/shared/atomic-file.d.mts +8 -0
  236. package/src/runtime/shared/background-tasks.mjs +22 -4
  237. package/src/runtime/shared/background-wait.mjs +66 -0
  238. package/src/runtime/shared/child-guardian.mjs +17 -7
  239. package/src/runtime/shared/config.mjs +313 -16
  240. package/src/runtime/shared/connector-catalog.d.mts +4 -5
  241. package/src/runtime/shared/connector-catalog.mjs +11 -1
  242. package/src/runtime/shared/file-lock.mjs +8 -3
  243. package/src/runtime/shared/git-runner.mjs +7 -2
  244. package/src/runtime/shared/idle-gc.mjs +30 -7
  245. package/src/runtime/shared/llm/http-agent.mjs +8 -5
  246. package/src/runtime/shared/llm/system-proxy.mjs +2 -1
  247. package/src/runtime/shared/llm/usage-ledger-quota.mjs +31 -12
  248. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +84 -8
  249. package/src/runtime/shared/llm/usage-ledger.mjs +3 -0
  250. package/src/runtime/shared/llm/usage-rollup.mjs +48 -0
  251. package/src/runtime/shared/maintenance-schedule.mjs +109 -0
  252. package/src/runtime/shared/process-runner.mjs +8 -4
  253. package/src/runtime/shared/routine-trigger.d.mts +13 -0
  254. package/src/runtime/shared/routine-trigger.mjs +34 -0
  255. package/src/runtime/shared/run-child.d.mts +2 -1
  256. package/src/runtime/shared/run-child.mjs +70 -18
  257. package/src/runtime/shared/session-scratch.mjs +14 -2
  258. package/src/runtime/shared/shipped-definitions.mjs +7 -1
  259. package/src/runtime/shared/tool-card-model.golden.json +3 -12
  260. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  261. package/src/runtime/shared/tool-work-units.mjs +31 -6
  262. package/src/runtime/shared/turn-snapshot.mjs +26 -4
  263. package/src/runtime/shared/turn-worktree-snapshot.mjs +6 -11
  264. package/src/runtime/shared/worker-requests.mjs +17 -3
  265. package/src/runtime/shared/worktrees/branch-management.mjs +1 -5
  266. package/src/runtime/shared/worktrees/contract.mjs +16 -6
  267. package/src/runtime/shared/worktrees/dependencies.mjs +127 -89
  268. package/src/runtime/shared/worktrees/disk.mjs +79 -2
  269. package/src/runtime/shared/worktrees/git.mjs +44 -11
  270. package/src/runtime/shared/worktrees/include.mjs +6 -4
  271. package/src/runtime/shared/worktrees/maintenance.mjs +422 -30
  272. package/src/runtime/shared/worktrees/manager.mjs +772 -75
  273. package/src/runtime/shared/worktrees/settings.mjs +33 -7
  274. package/src/runtime/shared/worktrees/test-support.mjs +5 -1
  275. package/src/runtime/shared/worktrees/worker-snapshots.mjs +82 -0
  276. package/src/runtime/tidy/process.mjs +2 -1
  277. package/src/runtime/web-search/index.mjs +3 -22
  278. package/src/runtime/web-search/lib/web-tools.mjs +2 -2
  279. package/src/runtime/web-search/tool-defs.mjs +17 -26
  280. package/src/session-runtime/agent-profile-changes.mjs +55 -4
  281. package/src/session-runtime/boot/apis.mjs +27 -4
  282. package/src/session-runtime/boot/begin.mjs +2 -0
  283. package/src/session-runtime/boot/config.mjs +10 -1
  284. package/src/session-runtime/boot/facade.mjs +1 -0
  285. package/src/session-runtime/boot/providers.mjs +70 -3
  286. package/src/session-runtime/boot/session.mjs +2 -0
  287. package/src/session-runtime/boot/tools.mjs +69 -25
  288. package/src/session-runtime/channel-config-api.mjs +1 -0
  289. package/src/session-runtime/config-change-effects.mjs +179 -0
  290. package/src/session-runtime/config-lifecycle/config-writers.mjs +1 -0
  291. package/src/session-runtime/config-lifecycle.mjs +160 -24
  292. package/src/session-runtime/conversation-import/discovery.mjs +86 -7
  293. package/src/session-runtime/conversation-import/service.mjs +3 -2
  294. package/src/session-runtime/goal-facade-api.mjs +49 -3
  295. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +3 -0
  296. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -2
  297. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +3 -11
  298. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +18 -4
  299. package/src/session-runtime/internal-tool-executor.mjs +12 -19
  300. package/src/session-runtime/lifecycle/cold-heir.mjs +7 -0
  301. package/src/session-runtime/lifecycle/memory-ingest.mjs +3 -3
  302. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +53 -5
  303. package/src/session-runtime/lifecycle/session-switch.mjs +13 -2
  304. package/src/session-runtime/lifecycle/teardown/session-close.mjs +4 -4
  305. package/src/session-runtime/lifecycle-api.mjs +1 -0
  306. package/src/session-runtime/mcp-glue/server-catalog.mjs +14 -3
  307. package/src/session-runtime/mcp-glue/server-connection.mjs +34 -8
  308. package/src/session-runtime/model-route/set-route.mjs +40 -7
  309. package/src/session-runtime/native-web-search/routes.mjs +3 -3
  310. package/src/session-runtime/provider-auth/refresh.mjs +6 -3
  311. package/src/session-runtime/provider-auth/setup.mjs +1 -1
  312. package/src/session-runtime/provider-readiness.mjs +53 -4
  313. package/src/session-runtime/resource-api.mjs +1 -1
  314. package/src/session-runtime/resource-extension-sync.mjs +4 -8
  315. package/src/session-runtime/resource-mcp-connector-accounts.mjs +5 -1
  316. package/src/session-runtime/resource-mcp-secrets.mjs +3 -3
  317. package/src/session-runtime/resource-plugins-api.mjs +4 -0
  318. package/src/session-runtime/routine-executor.mjs +152 -0
  319. package/src/session-runtime/routine-home-turn.mjs +46 -5
  320. package/src/session-runtime/runtime-facade.mjs +5 -0
  321. package/src/session-runtime/runtime-feature-gates.mjs +2 -11
  322. package/src/session-runtime/schedule-session-run.mjs +1 -0
  323. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +6 -1
  324. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +7 -0
  325. package/src/session-runtime/services/agent-tool/send-flow.mjs +110 -6
  326. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +12 -0
  327. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +5 -0
  328. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +24 -6
  329. package/src/session-runtime/services/agent-tool.mjs +51 -1
  330. package/src/session-runtime/services/channel-admin.mjs +18 -0
  331. package/src/session-runtime/services/hook-bus/command-handler.mjs +7 -11
  332. package/src/session-runtime/services/plugin-admin.mjs +4 -0
  333. package/src/session-runtime/services/seeds.mjs +7 -0
  334. package/src/session-runtime/services/usage-stats-model.mjs +32 -11
  335. package/src/session-runtime/session-create/materialize.mjs +4 -2
  336. package/src/session-runtime/session-create/session-options.mjs +2 -0
  337. package/src/session-runtime/settings-builtin-tools-api.mjs +1 -9
  338. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +1 -1
  339. package/src/session-runtime/setup-tool/executor.mjs +8 -1
  340. package/src/session-runtime/setup-tool/extended-actions.mjs +2 -0
  341. package/src/session-runtime/setup-tool/settings-contract.mjs +28 -0
  342. package/src/session-runtime/setup-tool/tool-defs.mjs +26 -1
  343. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +40 -43
  344. package/src/session-runtime/tool-policy/mode-switch.mjs +1 -1
  345. package/src/session-runtime/tool-policy/policy-stage.mjs +106 -8
  346. package/src/session-runtime/tool-policy/workflow-overlay.mjs +2 -1
  347. package/src/session-runtime/tool-policy-refresh.mjs +182 -5
  348. package/src/session-runtime/tool-surface.mjs +38 -1
  349. package/src/session-runtime/turn/agent-ops.mjs +10 -2
  350. package/src/session-runtime/turn/session-ops.mjs +2 -0
  351. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +5 -0
  352. package/src/session-runtime/turn/turn-run.mjs +1 -1
  353. package/src/session-runtime/webhook-session-run.mjs +2 -0
  354. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +1 -4
  355. package/src/session-runtime/workflow-agents-api/agent-route.mjs +3 -10
  356. package/src/session-runtime/workflow-agents-api/instruction-files.mjs +165 -0
  357. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +5 -4
  358. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +2 -3
  359. package/src/session-runtime/workflow-agents-api.mjs +2 -0
  360. package/src/session-runtime/workspace/project-branches.mjs +1 -1
  361. package/src/session-runtime/workspace/session-view.mjs +1 -1
  362. package/src/session-runtime/workspace/transitions.mjs +20 -4
  363. package/src/session-runtime/workspace/worktree-maintenance.mjs +126 -6
  364. package/src/session-runtime/workspace/worktree-setup.mjs +13 -1
  365. package/src/session-runtime/workspace/worktree-tool.mjs +16 -0
  366. package/src/standalone/daemon-agent-control.mjs +25 -8
  367. package/src/standalone/daemon-desktop-runtime.mjs +6 -2
  368. package/src/standalone/daemon-lag-profiler.mjs +7 -7
  369. package/src/standalone/daemon.mjs +54 -2
  370. package/src/standalone/local-session-runtime.mjs +16 -0
  371. package/src/standalone/session-client/daemon-unavailable.mjs +1 -1
  372. package/src/standalone/session-protocol.mjs +11 -0
  373. package/src/standalone/session-service/agent-route-follow.mjs +23 -20
  374. package/src/standalone/session-service/agent-tree/agent-turns.mjs +18 -6
  375. package/src/standalone/session-service/quota-resume.mjs +9 -0
  376. package/src/standalone/session-service/session-calls/session-action.mjs +51 -1
  377. package/src/standalone/session-service/session-calls/session-turns.mjs +37 -18
  378. package/src/standalone/session-service/session-calls/turn-gate.mjs +34 -0
  379. package/src/standalone/session-service/session-calls.mjs +2 -1
  380. package/src/tui/App.jsx +8 -0
  381. package/src/tui/app/clipboard.mjs +1 -1
  382. package/src/tui/app/create-app-pickers.mjs +2 -0
  383. package/src/tui/app/doctor.mjs +2 -2
  384. package/src/tui/app/panel-signature.mjs +1 -1
  385. package/src/tui/app/settings-picker.mjs +53 -8
  386. package/src/tui/app/shell-layout/row-budgets.mjs +2 -2
  387. package/src/tui/app/slash-commands.mjs +1 -1
  388. package/src/tui/app/slash-dispatch/route-commands.mjs +1 -1
  389. package/src/tui/app/slash-dispatch/session-commands.mjs +1 -1
  390. package/src/tui/app/use-prompt-queue-history.mjs +2 -2
  391. package/src/tui/app/use-transcript-activity.mjs +1 -1
  392. package/src/tui/app/use-transcript-window/geometry.mjs +1 -1
  393. package/src/tui/components/prompt-input/escape-policy.d.mts +0 -1
  394. package/src/tui/components/prompt-input/escape-policy.mjs +1 -1
  395. package/src/tui/components/question-panel-model.mjs +4 -4
  396. package/src/tui/components/tool-execution/standard-card.jsx +3 -4
  397. package/src/tui/components/user-message-skills.mjs +1 -1
  398. package/src/tui/dist/index.mjs +309 -30
  399. package/src/tui/markdown/render-ansi.mjs +1 -1
  400. package/src/tui/session/agent-job-feed/notification-router.mjs +10 -0
  401. package/src/tui/session/agent-job-feed.mjs +2 -0
  402. package/src/tui/session/context-state.mjs +3 -0
  403. package/src/tui/session/quota-resume.mjs +1 -1
  404. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  405. package/src/tui/session/session-api/lifecycle/session-reset.mjs +7 -0
  406. package/src/tui/session/session-api/routes-agent-definitions.mjs +5 -0
  407. package/src/tui/session/session-api/worktree.mjs +13 -1
  408. package/src/tui/session/session-flow/drain.mjs +11 -14
  409. package/src/tui/session/tool-card-results/grouped-result.mjs +2 -2
  410. package/src/tui/session/turn-quiet.mjs +1 -1
  411. package/src/tui/session/turn.mjs +3 -0
  412. package/src/tui/session/worktree-setup-notice.mjs +1 -1
  413. package/src/tui/spinner-meta.mjs +3 -3
  414. package/src/tui/spinner-verbs.mjs +2 -2
  415. package/src/defaults/skills/goal-management/SKILL.md +0 -169
  416. package/src/runtime/agent/orchestrator/runtime-core/goal-tasks.mjs +0 -156
  417. package/src/session-runtime/goal-tasks.mjs +0 -1
@@ -37,7 +37,17 @@ export function crossTurnSignature(name, args) {
37
37
  // they must not reset the escalation ladder's "zero edit" condition. Skill /
38
38
  // recall / agent / task / cwd / load_tool are exploration/meta plumbing.
39
39
  // (tool_search kept as legacy alias for old transcripts.)
40
- const NON_PROGRESS_TOOLS = new Set(['Skill', 'recall', 'agent', 'task', 'cwd', 'session', 'load_tool', 'tool_search']);
40
+ const NON_PROGRESS_TOOLS = new Set([
41
+ 'Skill',
42
+ 'recall',
43
+ 'agent',
44
+ 'task',
45
+ 'cwd',
46
+ 'session',
47
+ 'load_tool',
48
+ 'tool_search',
49
+ 'todo',
50
+ ]);
41
51
 
42
52
  // True when a successfully-executed tool represents real edit/progress. A tool
43
53
  // counts as progress only if its def lacks readOnlyHint (not eager) AND it is
@@ -55,7 +55,7 @@ const tokens = (value) => `${Number(value || 0).toLocaleString('en-US')} tokens`
55
55
  // for any history, so compacting the history cannot help and is not tried.
56
56
  // The wording avoids provider context-overflow phrasing on purpose: this is
57
57
  // not a "conversation too long" refusal and must not be routed as one.
58
- export class ToolSurfaceExceedsContextError extends Error {
58
+ class ToolSurfaceExceedsContextError extends Error {
59
59
  constructor({ sessionId, provider, model, overhead }) {
60
60
  const target = [provider, model].filter(Boolean).join('/') || 'this model';
61
61
  const reserve = overhead.kind === 'configured_reserve';
@@ -129,6 +129,8 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
129
129
  opts,
130
130
  sessionId,
131
131
  onSkillPrompt: (content, message) => state.pendingSkillPrompts.push({ content, message }),
132
+ // Input taken into the turn at any boundary is new work to answer.
133
+ onInjected: () => state.noToolTurn.noteNewInput(),
132
134
  });
133
135
  // Bounded recovery for provider turns that returned no client tool calls
134
136
  // (max-output ladder, refusal retry, provider continuation, empty-turn
@@ -143,6 +145,8 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
143
145
  escalateMaxTokens: () => state.provider?.escalatedMaxTokens?.(state.model, state.opts, state.lastSendTools) ?? null,
144
146
  // Characters the latest send put on screen through the live text stream.
145
147
  streamedChars: () => state.lastStreamedChars,
148
+ // A capped turn was told to reply; it may end without the todo check.
149
+ turnLimited: () => state.turnLimitNoticed === true,
146
150
  });
147
151
  return state;
148
152
  }
@@ -155,6 +159,9 @@ export function finishLoop(state) {
155
159
  // A reaction-only turn is a successful completion, never an empty finish.
156
160
  const terminationReason = classifyTerminationReason(response, { sessionAgent: state.sessionAgent });
157
161
  const reactionCompleted = terminationReason === 'empty' && state.noToolTurn.reactionCompleted;
162
+ // So is an empty end after a kept answer only todo bookkeeping followed:
163
+ // that answer is the turn's report (no-tool-turn.mjs).
164
+ const reportCompleted = terminationReason === 'empty' && state.noToolTurn.reportStands;
158
165
  return {
159
166
  ...response,
160
167
  usage: state.lastUsage || response.usage,
@@ -165,7 +172,7 @@ export function finishLoop(state) {
165
172
  toolCallsTotal: state.toolCallsTotal,
166
173
  providerState: state.providerState,
167
174
  providerStateUpdated: state.providerStateUpdated,
168
- terminationReason: reactionCompleted ? undefined : terminationReason,
175
+ terminationReason: reactionCompleted || reportCompleted ? undefined : terminationReason,
169
176
  ...(reactionCompleted ? { reactionCompleted: true } : {}),
170
177
  providerContinuations: state.noToolTurn.providerContinuations,
171
178
  ...(state.routeUsageHops?.length
@@ -0,0 +1,196 @@
1
+ // End-of-turn completion check for the session's todo list.
2
+ //
3
+ // A turn that ends without a tool call while pending/in-progress items remain
4
+ // gets one hidden reminder naming them, and the loop sends again. Bounded:
5
+ // at most MAX_TODO_REMINDERS per user cycle (reset only by a genuine user
6
+ // prompt, never by an automatic continuation), and silent after a reminder
7
+ // that produced no progress (no tool batch since). Skipped while the turn is
8
+ // genuinely waiting: on the user (an approval denied or a question left
9
+ // unanswered this turn), on an agent, or on a background shell started this
10
+ // user cycle (background-wait.mjs).
11
+ // Cancelled, failed and usage-limited turns never reach this point.
12
+ //
13
+ // With an active Goal whose items are all closed, the same check asks once
14
+ // per cycle for the completion audit: complete the Goal or append the rest.
15
+ import { resolvePluginData } from '../../../../../shared/plugin-paths.mjs';
16
+ import { awaitingBackgroundWork, beginUserCycle } from '../../../../../shared/background-wait.mjs';
17
+ import { readStoredGoalSnapshot } from '../../../runtime-core/goal-storage.mjs';
18
+ import {
19
+ clearTodo,
20
+ openTodoItems,
21
+ todoAllClosed,
22
+ todoPhases,
23
+ unfinishedTodoItems,
24
+ } from '../../../runtime-core/todo-list.mjs';
25
+ import { TODO_TOOL } from '../../../runtime-core/todo-tool-defs.mjs';
26
+ import { HIDDEN_AGENT_NAMES } from '../hidden-agents.mjs';
27
+ import { _isMutationTool, _isShellTool } from '../tool-classify.mjs';
28
+
29
+ const MAX_TODO_REMINDERS = 3;
30
+
31
+ // Sessions whose current turn waits on the user's decision (in memory: the
32
+ // turn itself is in memory, and a new turn clears it).
33
+ const userWaits = new Set();
34
+
35
+ /** The current turn now waits on the user (approval denied, question dismissed). */
36
+ export function noteTodoUserWait(sessionId) {
37
+ if (sessionId) userWaits.add(String(sessionId));
38
+ }
39
+
40
+ function checkState(session) {
41
+ const current = session.todoCheck;
42
+ if (current && typeof current === 'object') return current;
43
+ session.todoCheck = { reminders: 0, awaitingProgress: false, auditAsked: false };
44
+ return session.todoCheck;
45
+ }
46
+
47
+ /** A turn starts: `genuine` (a real user prompt) opens a new reminder cycle. */
48
+ export function beginTodoTurn(session, { genuine = false } = {}) {
49
+ if (!session || typeof session !== 'object') return;
50
+ if (session.id) userWaits.delete(String(session.id));
51
+ if (genuine) {
52
+ session.todoCheck = { reminders: 0, awaitingProgress: false, auditAsked: false };
53
+ beginUserCycle(session.id);
54
+ // A finished list stays on screen until the user moves on, then goes the
55
+ // way a finished Goal does. An active Goal keeps it for its audit.
56
+ if (todoAllClosed(session) && !activeGoal(session.id)) clearTodo(session);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * A delegated agent received a new brief or follow-up (a genuine prompt) with
62
+ * no open list: the hidden reminder trailing that prompt, once per prompt, or
63
+ * null. The tool description alone was read past on short multi-ask briefs.
64
+ */
65
+ export function todoStartReminder(session) {
66
+ if (!session || typeof session !== 'object' || session.owner !== 'agent') return null;
67
+ if (HIDDEN_AGENT_NAMES.has(session.agent)) return null;
68
+ if (!session.tools?.some((tool) => tool?.name === TODO_TOOL.name)) return null;
69
+ if (openTodoItems(session).length) return null;
70
+ // `init` refuses while any item is unfinished (blocked included), so such a
71
+ // list is reconciled or appended to, never re-initialized.
72
+ if (unfinishedTodoItems(session).length) {
73
+ return [
74
+ '<system-reminder>',
75
+ 'The todo list still has blocked items. If this brief adds asks, append them; first done, drop or unblock each blocked item that this brief settles. Do not init over them. Do not mention this reminder.',
76
+ '</system-reminder>',
77
+ ].join('\n');
78
+ }
79
+ return [
80
+ '<system-reminder>',
81
+ 'If this brief has three or more steps or several asks, init the todo list with every ask as its own item in the same response as your first tool call; each item the brief lists is its own todo item, never folded into one. A single step or a plain answer needs none. Do not mention this reminder.',
82
+ '</system-reminder>',
83
+ ].join('\n');
84
+ }
85
+
86
+ /** A tool batch ran: the last reminder produced progress. */
87
+ export function noteTodoProgress(session) {
88
+ if (session?.todoCheck?.awaitingProgress) session.todoCheck.awaitingProgress = false;
89
+ }
90
+
91
+ // Mid-turn nudge: a long turn can finish items without ever touching the list,
92
+ // leaving the progress shown to the user stale. After this many successful
93
+ // mutating tool calls with no todo call (and no earlier nudge), one hidden
94
+ // reminder is appended. Counted per call, not per batch, so a turn of parallel
95
+ // edits ages the list faster than a turn of reads; a batch of three edits is
96
+ // three. What mutates is the work the list tracks: file edits, command
97
+ // execution (shell, as a bash tool is) and mutating git/github requests.
98
+ const STALE_TODO_MUTATIONS = 10;
99
+
100
+ function mutatingCall(call, results) {
101
+ const name = call?.name ?? call?.function?.name;
102
+ const args = call?.arguments ?? call?.function?.arguments;
103
+ if (!_isMutationTool(name, args) && !_isShellTool(name)) return false;
104
+ const result = results.find((message) => message?.toolCallId === call.id);
105
+ // A call that failed or was skipped (a dependency guard after a failed edit) changed nothing.
106
+ return result?.toolKind !== 'error' && result?.toolKind !== 'skipped';
107
+ }
108
+
109
+ /**
110
+ * A tool batch ran with `calls` (and their result messages): the hidden
111
+ * stale-list reminder to append to the transcript, or null. Calls are read in
112
+ * order: a todo call restarts the count, a successful mutating call adds one.
113
+ * Outside the end-of-turn budget (`todoCheck.reminders`) and its progress gate.
114
+ */
115
+ export function staleTodoReminder(session, calls = [], results = [], { sessionAgent = null } = {}) {
116
+ if (!session || typeof session !== 'object' || HIDDEN_AGENT_NAMES.has(sessionAgent)) return null;
117
+ const check = checkState(session);
118
+ for (const call of calls) {
119
+ if ((call?.name ?? call?.function?.name) === TODO_TOOL.name) check.staleMutations = 0;
120
+ else if (mutatingCall(call, results)) check.staleMutations = (check.staleMutations || 0) + 1;
121
+ }
122
+ const phases = todoPhases(session);
123
+ // A finished list needs nothing more; no list at all is nudged once per
124
+ // user cycle (long runs that never made one), an open list repeatedly.
125
+ if (phases.length && !openTodoItems(session).length) {
126
+ check.staleMutations = 0;
127
+ return null;
128
+ }
129
+ if (!phases.length && check.createNudged) return null;
130
+ if ((check.staleMutations || 0) < STALE_TODO_MUTATIONS) return null;
131
+ check.staleMutations = 0;
132
+ if (!phases.length) {
133
+ check.createNudged = true;
134
+ return [
135
+ '<system-reminder>',
136
+ 'No todo list exists for this work yet. If it has three or more steps, init one now with the steps left and keep it current; if it is a single step, ignore this. Keep working; do not mention this reminder.',
137
+ '</system-reminder>',
138
+ ].join('\n');
139
+ }
140
+ return [
141
+ '<system-reminder>',
142
+ 'The todo tool has not been used for a while. Mark finished items done now (one call each, never batched at the end) so the in-progress pointer is on what you are doing. Keep working; do not mention this reminder.',
143
+ ...phases.flatMap((phase) => phase.tasks.map((task) => `- [${task.status}] ${task.content}`)),
144
+ '</system-reminder>',
145
+ ].join('\n');
146
+ }
147
+
148
+ function activeGoal(sessionId) {
149
+ try {
150
+ const goal = readStoredGoalSnapshot({ dataDir: resolvePluginData(), sessionId });
151
+ return goal?.status === 'active' ? goal : null;
152
+ } catch {
153
+ return null;
154
+ }
155
+ }
156
+
157
+ function openItemsReminder(open, count) {
158
+ return [
159
+ '<system-reminder>',
160
+ `You ended the turn with ${open.length} open todo item(s):`,
161
+ ...open.map((task) => `- ${task.content}`),
162
+ 'Continue them, or close each with the todo tool (done, drop, or block with its reason).',
163
+ `(Reminder ${count}/${MAX_TODO_REMINDERS})`,
164
+ '</system-reminder>',
165
+ ].join('\n');
166
+ }
167
+
168
+ const GOAL_AUDIT_REMINDER = [
169
+ '<system-reminder>',
170
+ 'Every todo item is closed but the Goal is still active. Audit each user condition of the Goal objective against current evidence: when all are proven met, call goal with action complete; otherwise append the remaining work to todo and continue.',
171
+ '</system-reminder>',
172
+ ].join('\n');
173
+
174
+ /** The reminder to send before the turn may end, or null to let it end. */
175
+ export function todoCompletionReminder(session, { sessionId = null, sessionAgent = null, turnLimited = false } = {}) {
176
+ if (!session || typeof session !== 'object' || turnLimited) return null;
177
+ if (HIDDEN_AGENT_NAMES.has(sessionAgent)) return null;
178
+ const id = String(sessionId || session.id || '');
179
+ const phases = todoPhases(session);
180
+ if (!phases.length) return null;
181
+ const check = checkState(session);
182
+ if (check.awaitingProgress || userWaits.has(id)) return null;
183
+ const open = openTodoItems(session);
184
+ if (open.length) {
185
+ if (check.reminders >= MAX_TODO_REMINDERS) return null;
186
+ if (awaitingBackgroundWork({ sessionId: id })) return null;
187
+ check.reminders += 1;
188
+ check.awaitingProgress = true;
189
+ return openItemsReminder(open, check.reminders);
190
+ }
191
+ if (check.auditAsked || !id || !activeGoal(id)) return null;
192
+ if (awaitingBackgroundWork({ sessionId: id })) return null;
193
+ check.auditAsked = true;
194
+ check.awaitingProgress = true;
195
+ return GOAL_AUDIT_REMINDER;
196
+ }
@@ -18,9 +18,12 @@ import { resetOutputLimitRecovery, resolveOutputLimit } from './no-tool-turn/out
18
18
  import { resolveRefusal } from './no-tool-turn/refusal.mjs';
19
19
  import { resumeContinuation } from './no-tool-turn/continuation.mjs';
20
20
  import { nudgeEmptyTurn } from './no-tool-turn/empty-nudge.mjs';
21
+ import { noteTodoProgress, staleTodoReminder, todoCompletionReminder } from './no-tool-turn/todo-check.mjs';
21
22
  import { isReactionSuccess, reactionCallIds, takeReactionTarget } from '../../runtime-core/reaction-tool-defs.mjs';
22
23
  import { messageIdOf } from '../message-ids.mjs';
23
24
  import { isReactableMessage } from '../message-reactions.mjs';
25
+ import { joinReports, withEarlierReport } from './turn-report.mjs';
26
+ import { TODO_TOOL } from '../../runtime-core/todo-tool-defs.mjs';
24
27
 
25
28
  // `escalateMaxTokens()` asks the live provider for a higher one-shot max_tokens
26
29
  // (number) or reports none (null/undefined) — the loop stays provider-agnostic.
@@ -32,6 +35,7 @@ export function createNoToolTurnResolver({
32
35
  drainSteering,
33
36
  escalateMaxTokens = () => null,
34
37
  streamedChars = () => 0,
38
+ turnLimited = () => false,
35
39
  }) {
36
40
  const segments = createTurnSegments({ messages, opts });
37
41
  // Per-turn recovery counters shared by the ladders.
@@ -51,6 +55,14 @@ export function createNoToolTurnResolver({
51
55
  // Continuations since the last executed tool batch — bounds the text-only
52
56
  // continuation runaway (see PROVIDER_CONTINUATION_NO_TOOL_LIMIT).
53
57
  continuationsSinceToolBatch: 0,
58
+ // Answers the turn went on past (keepEndedAnswer): those still in the
59
+ // segments (parts before endedPartsEnd), and those a tool batch cleared
60
+ // from them.
61
+ endedPartsEnd: 0,
62
+ carriedAnswers: [],
63
+ // The newest kept answer was followed by nothing but todo bookkeeping:
64
+ // it is the turn's answer, and an empty end after it is no failure.
65
+ reportStands: false,
54
66
  };
55
67
  // The stable id (message-ids.mjs, kept across compaction) of the newest
56
68
  // real user input; runtime reminders and notices are not reactable.
@@ -112,60 +124,108 @@ export function createNoToolTurnResolver({
112
124
  };
113
125
  };
114
126
 
127
+ // An answer the turn goes on past (queued input or a todo reminder arrived
128
+ // after it) is committed like any segment and also kept as part of the
129
+ // turn's report: a tool batch clears the segments, and the caller (an
130
+ // agent's owner) must still receive that answer, not only the short reply
131
+ // that follows it. The answer is every part since the previous one or the
132
+ // last tool batch (an output-limit cut and its resumption included).
133
+ const keepEndedAnswer = (response, hasContent) => {
134
+ if (!segments.commitIntermediate(response) || !hasContent) return false;
135
+ segments.record(response.content, { committed: true });
136
+ state.endedPartsEnd = segments.parts.length;
137
+ return true;
138
+ };
139
+ const endedReport = () =>
140
+ joinReports([...state.carriedAnswers, segments.parts.slice(0, state.endedPartsEnd).join('')]);
141
+ // Answers still in the segments are in the response's own aggregate.
142
+ const finalize = (response) =>
143
+ state.carriedAnswers.length === 0 ? response : withEarlierReport(response, joinReports(state.carriedAnswers));
144
+
145
+ async function resolveTurn(response, iteration) {
146
+ const hasContent = typeof response.content === 'string' && response.content.trim().length > 0;
147
+ const stopReason = response.stopReason ?? response.stop_reason ?? null;
148
+ const isOutputLimitStop = isOutputLimitStopReason(stopReason);
149
+ if (isOutputLimitStop || (hasContent && response.streamCut === true)) {
150
+ return resolveOutputLimit(response, ladder);
151
+ }
152
+ if (stopReason === 'refusal') return resolveRefusal(response, hasContent, ladder);
153
+ // Output-limit stops keep the bounded max-output ladder above and
154
+ // refusals (already returned) keep the bounded refusal retry; both
155
+ // own their own continuation semantics. No lexical/progress-text
156
+ // heuristic is consulted.
157
+ const continuationSignal = isOutputLimitStop ? null : providerContinuationSignal(response);
158
+ if (
159
+ continuationSignal &&
160
+ resumeContinuation(response, { iteration, signal: continuationSignal, hasContent, stopReason }, ladder)
161
+ ) {
162
+ return { action: 'continue', response };
163
+ }
164
+ // Hidden roles are exempt from the empty-turn nudge: their own role
165
+ // rules define a different output contract (pipe-separated chunker
166
+ // output, …) and a text-only terminal turn is the correct shape —
167
+ // nudging them produces a contradictory user message that traps the
168
+ // model in a tool-call-blocked vs contract-required oscillation.
169
+ // A turn that left a reaction on the user's message may end with no
170
+ // text: the reaction is its answer (react tool), never an empty turn.
171
+ // Nor does a turn whose kept answer only todo bookkeeping followed: that
172
+ // answer is its reply.
173
+ if (!hasContent && !HIDDEN_AGENT_NAMES.has(sessionAgent) && !reactionAnswersLatest() && !state.reportStands) {
174
+ return nudgeEmptyTurn(
175
+ response,
176
+ { stopReason, isIncompleteStop: stopReason && INCOMPLETE_STOP_REASONS.has(stopReason) },
177
+ ladder
178
+ );
179
+ }
180
+ // Pending-input rule: queued user input is folded into
181
+ // needs_follow_up before terminal completion. Commit the terminal
182
+ // text first (beforeAppend), then resume.
183
+ if (
184
+ drainSteering('terminal', {
185
+ maxPriority: 'next',
186
+ beforeAppend: () => keepEndedAnswer(response, hasContent),
187
+ })
188
+ ) {
189
+ state.emptyNudgeStreak = 0;
190
+ resetOutputLimitRecovery(state);
191
+ return { action: 'continue', response };
192
+ }
193
+ const reminder = todoCompletionReminder(opts.session, {
194
+ sessionId,
195
+ sessionAgent,
196
+ turnLimited: turnLimited(),
197
+ });
198
+ if (reminder) {
199
+ if (keepEndedAnswer(response, hasContent)) state.reportStands = true;
200
+ messages.push({ role: 'user', content: reminder });
201
+ return { action: 'continue', response };
202
+ }
203
+ return finishTurn(response);
204
+ }
205
+
115
206
  return {
116
207
  // { action: 'continue' | 'break', response }. 'continue' means the
117
208
  // recovery appended to the transcript and the loop must re-send;
118
- // 'break' returns the (possibly aggregated) terminal response.
209
+ // 'break' returns the (possibly aggregated) terminal response, with the
210
+ // ended answers a tool batch cleared from it as its report.
119
211
  async resolve(response, iteration) {
120
- const hasContent = typeof response.content === 'string' && response.content.trim().length > 0;
121
- const stopReason = response.stopReason ?? response.stop_reason ?? null;
122
- const isOutputLimitStop = isOutputLimitStopReason(stopReason);
123
- if (isOutputLimitStop || (hasContent && response.streamCut === true)) {
124
- return resolveOutputLimit(response, ladder);
125
- }
126
- if (stopReason === 'refusal') return resolveRefusal(response, hasContent, ladder);
127
- // Output-limit stops keep the bounded max-output ladder above and
128
- // refusals (already returned) keep the bounded refusal retry; both
129
- // own their own continuation semantics. No lexical/progress-text
130
- // heuristic is consulted.
131
- const continuationSignal = isOutputLimitStop ? null : providerContinuationSignal(response);
132
- if (
133
- continuationSignal &&
134
- resumeContinuation(response, { iteration, signal: continuationSignal, hasContent, stopReason }, ladder)
135
- ) {
136
- return { action: 'continue', response };
137
- }
138
- // Hidden roles are exempt from the empty-turn nudge: their own role
139
- // rules define a different output contract (pipe-separated chunker
140
- // output, …) and a text-only terminal turn is the correct shape —
141
- // nudging them produces a contradictory user message that traps the
142
- // model in a tool-call-blocked vs contract-required oscillation.
143
- // A turn that left a reaction on the user's message may end with no
144
- // text: the reaction is its answer (react tool), never an empty turn.
145
- if (!hasContent && !HIDDEN_AGENT_NAMES.has(sessionAgent) && !reactionAnswersLatest()) {
146
- return nudgeEmptyTurn(
147
- response,
148
- { stopReason, isIncompleteStop: stopReason && INCOMPLETE_STOP_REASONS.has(stopReason) },
149
- ladder
150
- );
151
- }
152
- // Pending-input rule: queued user input is folded into
153
- // needs_follow_up before terminal completion. Commit the terminal
154
- // text first (beforeAppend), then resume.
155
- if (
156
- drainSteering('terminal', {
157
- maxPriority: 'next',
158
- beforeAppend: () => {
159
- if (segments.commitIntermediate(response) && hasContent)
160
- segments.record(response.content, { committed: true });
161
- },
162
- })
163
- ) {
164
- state.emptyNudgeStreak = 0;
165
- resetOutputLimitRecovery(state);
166
- return { action: 'continue', response };
167
- }
168
- return finishTurn(response);
212
+ const outcome = await resolveTurn(response, iteration);
213
+ if (outcome.action !== 'break') return outcome;
214
+ return { ...outcome, response: finalize(outcome.response) };
215
+ },
216
+ // The terminal response of a turn that ended any other way (a capped
217
+ // turn's overrun) gets the same report.
218
+ finalize,
219
+ // Every answer the turn went on past, for a turn that fails after them.
220
+ endedReport,
221
+ // Input the turn took in (steering at any boundary) is new work: its
222
+ // outcome is the turn's, not the kept answer's.
223
+ noteNewInput() {
224
+ state.reportStands = false;
225
+ },
226
+ // An empty end after the kept answer is that answer's turn ending.
227
+ get reportStands() {
228
+ return state.reportStands;
169
229
  },
170
230
  // A turn that produced tool calls is never an empty contract violation,
171
231
  // and it is real progress: a later output-limit cut starts a fresh ladder.
@@ -177,6 +237,12 @@ export function createNoToolTurnResolver({
177
237
  // succeeded marks the turn as having reacted.
178
238
  noteToolBatchCompleted(calls = [], results = []) {
179
239
  state.continuationsSinceToolBatch = 0;
240
+ noteTodoProgress(opts.session);
241
+ const stale = staleTodoReminder(opts.session, calls, results, { sessionAgent });
242
+ if (stale) messages.push({ role: 'user', content: stale, meta: 'hook' });
243
+ if (state.endedPartsEnd > 0) state.carriedAnswers.push(segments.parts.slice(0, state.endedPartsEnd).join(''));
244
+ state.endedPartsEnd = 0;
245
+ if (calls.some((call) => (call?.name ?? call?.function?.name) !== TODO_TOOL.name)) state.reportStands = false;
180
246
  segments.clear();
181
247
  state.reactions.push(...batchReactions(calls, results));
182
248
  },
@@ -14,7 +14,11 @@
14
14
  import { isAgentOwner } from '../../agent-owner.mjs';
15
15
  import { isOffReadonlySurface } from './deferred-call-through.mjs';
16
16
  import { BRIDGE_CALL_TOOL_NAME, bridgeErrorText, isToolBridgeEnabled } from '../../runtime-core/tool-bridge.mjs';
17
- import { sessionAllowlistExcludes, sessionDisallows } from '../../runtime-core/deferred-tool-availability.mjs';
17
+ import {
18
+ sessionAllowlistExcludes,
19
+ sessionDisallows,
20
+ sessionFeatureDenies,
21
+ } from '../../runtime-core/deferred-tool-availability.mjs';
18
22
  import { providerToolExclusionReason } from './provider-tool-exclusions.mjs';
19
23
  import { workflowOverlayDenial } from '../manager/workflow-switch-notice.mjs';
20
24
  import { agentMcpToolPermission, connectorPolicyAgentOf, projectMcpToolDisabled } from '../../mcp/agent-connectors.mjs';
@@ -125,6 +129,9 @@ export function preDispatchDenyForSession(sessionRef, call) {
125
129
  const workflowDenial = workflowOverlayDenial(sessionRef, schemaName);
126
130
  if (workflowDenial) return workflowDenial;
127
131
  if (sessionDisallows(sessionRef, schemaName)) {
132
+ if (sessionFeatureDenies(sessionRef, schemaName)) {
133
+ return `Error: tool "${schemaName}" is disabled in settings by the user. Do not retry unless the user turns it back on.`;
134
+ }
128
135
  return `Error: tool "${name}" is not available in this session.`;
129
136
  }
130
137
  // An agent's connectors: a tool of a server it does not mount, or one set
@@ -43,7 +43,7 @@ export function beginProviderToolExclusions(sessionRef) {
43
43
  if (isSessionRef(sessionRef)) sessions.get(sessionRef)?.current.clear();
44
44
  }
45
45
 
46
- export function excludedToolNoticeText({ name, pointer, reason }) {
46
+ function excludedToolNoticeText({ name, pointer, reason }) {
47
47
  return `Tool "${name}" was excluded from this session's requests: its input schema is invalid at ${pointer || '/'} (${reason}). Fix the tool's schema or disable the tool.`;
48
48
  }
49
49
 
@@ -38,7 +38,7 @@ const HOP_REASONS = {
38
38
  const routeLabel = (route) => displayModelName(route.model, route.provider) || `${route.provider}/${route.model}`;
39
39
 
40
40
  /** The muted transcript line for a hop: `<from> <reason> — switched to <to>.` */
41
- export function routeFallbackNoticeText(from, to, error) {
41
+ function routeFallbackNoticeText(from, to, error) {
42
42
  const reason = HOP_REASONS[providerErrorKind(error).kind] || 'unavailable';
43
43
  return `${routeLabel(from)} ${reason} — switched to ${routeLabel(to)}.`;
44
44
  }
@@ -198,7 +198,8 @@ function injectSteeringEntry(merged, { messages, opts, stage, onSkillPrompt }) {
198
198
  // The drain is synchronous (terminal guards depend on that) and returns whether
199
199
  // anything was injected. `onSkillPrompt` receives each typed entry so the loop
200
200
  // can run its policy-checked skill selection before the next provider snapshot.
201
- export function createSteeringDrain({ messages, opts, sessionId, onSkillPrompt }) {
201
+ // `onInjected` learns of every drain that added input, whatever its stage.
202
+ export function createSteeringDrain({ messages, opts, sessionId, onSkillPrompt, onInjected = () => {} }) {
202
203
  return function drainSteeringIntoMessages(stage = 'mid-turn', options = {}) {
203
204
  if (typeof opts.drainSteering !== 'function') return false;
204
205
  let steerMsgs = [];
@@ -232,6 +233,7 @@ export function createSteeringDrain({ messages, opts, sessionId, onSkillPrompt }
232
233
  totalTextLen += injected.textLength;
233
234
  maxQueueWaitMs = Math.max(maxQueueWaitMs, injected.queueWaitMs);
234
235
  }
236
+ onInjected(stage);
235
237
  if (sessionId) {
236
238
  try {
237
239
  process.stderr.write(
@@ -6,12 +6,11 @@
6
6
  // copy `[mixdog compacted …]` back as literal patch input.
7
7
  //
8
8
  // Mutation bodies (patch / old_string / new_string / content / rewrite) are
9
- // never collapsed after the fact. The sweep that used to do it (deferBodies
10
- // plus a settled-body pass on the next push) rewrote a prefix the provider had
11
- // already cached, and every rewrite re-billed that whole request at the
12
- // uncached rate. Measured over one 89-task run: $1.20 lost to recover $0.16,
13
- // because the collapsed tokens only come back at the cached rate over the
14
- // REMAINING turns, and a task rarely has enough of them left. Bodies now stay
9
+ // never collapsed after the fact. A later sweep would rewrite a prefix the
10
+ // provider has already cached, and every rewrite re-bills that whole request at
11
+ // the uncached rate. Measured over one 89-task run: $1.20 lost to recover
12
+ // $0.16, because the collapsed tokens only come back at the cached rate over
13
+ // the REMAINING turns, and a task rarely has enough of them left. Bodies stay
15
14
  // verbatim in history, so the prefix — and its cache — survives the session.
16
15
  import { createHash } from 'node:crypto';
17
16
 
@@ -16,6 +16,7 @@ import {
16
16
  } from '../../human-gate/gate.mjs';
17
17
  import { applyEditedArgs } from '../../human-gate/requests.mjs';
18
18
  import { effectiveToolCwd } from '../../../tools/builtin/bash-tool/shell-cwd.mjs';
19
+ import { noteTodoUserWait } from '../no-tool-turn/todo-check.mjs';
19
20
 
20
21
  const isArgsObject = (value) => value && typeof value === 'object' && !Array.isArray(value);
21
22
 
@@ -56,7 +57,11 @@ async function askApproval(
56
57
  }
57
58
  const approval = askOutcome.approval;
58
59
  await recordHumanDecision(gate, approval);
59
- if (askOutcome.denial) return { denial: askOutcome.denial };
60
+ if (askOutcome.denial) {
61
+ // The user said no: this turn may end waiting on them (todo-check.mjs).
62
+ noteTodoUserWait(callerSessionId);
63
+ return { denial: askOutcome.denial };
64
+ }
60
65
  if (approval && typeof approval === 'object' && isArgsObject(approval.args)) call.args = approval.args;
61
66
  // The card's edited text replaces the model's before the call runs.
62
67
  if (request?.kind === 'outbound' && approval?.editedArgs) {
@@ -19,7 +19,9 @@ import {
19
19
  } from '../../../tools/builtin.mjs';
20
20
  import { executePatchTool } from '../../../tools/patch.mjs';
21
21
  import { executeInternalTool, isInternalTool } from '../../../internal-tools.mjs';
22
+ import { settingDisabledTool } from '../../../runtime-core/tool-availability.mjs';
22
23
  import { buildSkillsListResponse, viewSkill } from '../tool-helpers.mjs';
24
+ import { executeTodoTool } from '../../../runtime-core/todo-list.mjs';
23
25
  import { isOnDeferredToolSurface } from '../deferred-call-through.mjs';
24
26
  import { _executeCodeGraphToolLazy } from '../../manager/runtime-loaders.mjs';
25
27
  import { requiresReadBody } from '../tool-classify.mjs';
@@ -57,8 +59,8 @@ function dispatchMcp(name, args, { cwd, callerSessionId, sessionRef, executeOpts
57
59
  return formatUnknownBuiltinToolMessage(name, args, 'tool');
58
60
  }
59
61
  // 24h trace data shows ~24% of external MCP calls are cwd-sensitive
60
- // (bash / grep / read / list / glob etc.) but the worker session's
61
- // cwd was previously dropped here. Inject cwd only when the tool's
62
+ // (bash / grep / read / list / glob etc.), so the worker session's
63
+ // cwd must reach them. Inject cwd only when the tool's
62
64
  // inputSchema declares the field — schemas without it would reject
63
65
  // an unknown argument.
64
66
  const needsCwdInjection = cwd && mcpToolHasField(name, 'cwd', mcpScopeId) && (args == null || args.cwd == null);
@@ -120,8 +122,20 @@ function dispatchInternal(
120
122
  */
121
123
  export function dispatchToolCall({ name, args }, ctx) {
122
124
  const { cwd, callerSessionId, sessionRef, executeOpts, toolOpts, completionToolOpts } = ctx;
125
+ const disabled = settingDisabledTool(name, sessionRef?.mcpScopeId);
126
+ if (disabled) {
127
+ return `Error: tool "${disabled}" is disabled in settings by the user; do not retry unless the user turns it back on`;
128
+ }
123
129
  if (name === 'Skill' || name === 'skill_view') return viewSkill(cwd, args?.name, sessionRef);
124
130
  if (name === 'skills_list') return buildSkillsListResponse(cwd);
131
+ // The step list belongs to the calling session itself (Lead or agent).
132
+ if (name === 'todo') {
133
+ try {
134
+ return executeTodoTool(sessionRef, args || {});
135
+ } catch (error) {
136
+ return `Error: ${error?.message || String(error)}`;
137
+ }
138
+ }
125
139
  if (isMcpTool(name)) return dispatchMcp(name, args, ctx);
126
140
  if (name === 'code_graph') {
127
141
  // cwd chain: args.cwd (caller-explicit) → session cwd → undefined (handler throws)