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
@@ -1,22 +1,18 @@
1
1
  /**
2
2
  * goal-mutations.mjs — the durable Goal state transitions: create, the
3
- * model/user status update (complete / blocked), task edits and abandon.
3
+ * model/user status update (complete / blocked) and abandon.
4
4
  * Each takes the shared runtime record (`ctx`) and commits through its store.
5
5
  */
6
6
  import { randomUUID } from 'node:crypto';
7
7
  import { join } from 'node:path';
8
8
 
9
9
  import { compactSessionTitle } from './session-title.mjs';
10
- import { GOAL_TASK_SETTLED } from './goal-tool-defs.mjs';
11
- import { applyGoalTaskChanges, normalizeGoalTasks } from './goal-tasks.mjs';
12
10
  import {
13
11
  GOAL_FILE_VERSION,
14
12
  NO_DEADLINE_WARNING_MS,
15
- activeElapsedMs,
16
13
  assertSessionId,
17
14
  checkpointActiveClock,
18
15
  clearTurnFailures,
19
- goalTimeMode,
20
16
  parseGoalDuration,
21
17
  publicGoal,
22
18
  stopActiveClock,
@@ -33,6 +29,13 @@ export function goalFinished(goal) {
33
29
  return FINISHED_STATUSES.includes(goal.status);
34
30
  }
35
31
 
32
+ /** A Goal that is neither running nor finished (paused, blocked, usage
33
+ * limited, time budget reached): it waits for the user, who may clear or
34
+ * replace it. */
35
+ export function goalStalled(goal) {
36
+ return Boolean(goal) && goal.status !== 'active' && !goalFinished(goal);
37
+ }
38
+
36
39
  /** Reject a write aimed at a Goal the caller no longer holds. */
37
40
  export function assertExpectedGoal(goal, expectedGoalId, what = 'update') {
38
41
  if (clean(expectedGoalId) && clean(expectedGoalId) !== clean(goal.id)) {
@@ -51,32 +54,18 @@ function initialTimeLimitMs(args, defaultTimeLimitMs) {
51
54
  function newGoalRecord(id, args, at, defaultTimeLimitMs) {
52
55
  const startTurn = args.startInCurrentTurn === true ? 1 : 0;
53
56
  const timeLimitMs = initialTimeLimitMs(args, defaultTimeLimitMs);
54
- const initialTasks = Array.isArray(args.tasks)
55
- ? normalizeGoalTasks(
56
- args.tasks.filter((task) => clean(task?.text)),
57
- [],
58
- { strict: true }
59
- )
60
- : [];
61
57
  return {
62
58
  id: randomUUID(),
63
- revision: 1,
64
- objectiveRevision: 1,
65
- tasksObjectiveRevision: 1,
66
59
  sessionId: id,
67
60
  objective: validateObjective(args.objective),
68
61
  title: compactSessionTitle(args.objective),
62
+ objectiveRevision: 0,
69
63
  status: 'active',
70
- tasks: initialTasks,
71
64
  blocker: '',
72
65
  failureReason: '',
73
66
  failureCount: 0,
74
67
  turnCount: startTurn,
75
- lastDropTurn: initialTasks.some((task) => task.status === 'dropped') ? startTurn : -1,
76
- tasksUpdatedAt: initialTasks.length > 0 ? at : null,
77
68
  timeLimitMs,
78
- // A stated budget commits that time; only an explicit cap permits early completion.
79
- timeMode: goalTimeMode(args.timeMode, timeLimitMs > 0 ? 'duration' : 'max'),
80
69
  timeUsedMs: 0,
81
70
  deadlineWarnedMs: NO_DEADLINE_WARNING_MS,
82
71
  warningRevision: 0,
@@ -88,37 +77,8 @@ function newGoalRecord(id, args, at, defaultTimeLimitMs) {
88
77
  };
89
78
  }
90
79
 
91
- // Evidence gates the MODEL's completion claim, never the user's. A user
92
- // completing their own Goal is an act of authority: without this the only
93
- // user-side exit was deleting the Goal, which threw the record away.
94
- // Unfinished rows stay unfinished so the record stays honest.
95
- function assertCompletionEvidence(goal, at) {
96
- if (goal.tasksObjectiveRevision !== goal.objectiveRevision) {
97
- throw new Error(
98
- 'cannot complete Goal: objective changed; read goal status and reconcile the full task list with set_tasks first'
99
- );
100
- }
101
- const tasks = normalizeGoalTasks(goal.tasks || []);
102
- const incomplete = tasks.filter((task) => !GOAL_TASK_SETTLED.includes(task.status));
103
- if (incomplete.length > 0) {
104
- throw new Error(`cannot complete Goal: ${incomplete.length} durable tasks remain incomplete`);
105
- }
106
- const turnCount = Math.max(0, Math.floor(Number(goal.turnCount) || 0));
107
- if (goal.lastDropTurn >= 0 && goal.lastDropTurn === turnCount) {
108
- throw new Error(
109
- 'cannot complete Goal: a task was dropped this turn; only a user scope change retires ' +
110
- 'requested work, so finish that work or let the user confirm the change first'
111
- );
112
- }
113
- if (goal.timeMode === 'duration' && goal.timeLimitMs > 0 && activeElapsedMs(goal, at) < goal.timeLimitMs) {
114
- throw new Error(
115
- 'cannot complete Goal before the requested duration ends; keep working, or the user may explicitly complete the Goal'
116
- );
117
- }
118
- }
119
-
120
80
  export function createGoalMutations(ctx) {
121
- const { root, now, defaultTimeLimitMs, turnGoalIds, turnStartedAt } = ctx;
81
+ const { root, now, defaultTimeLimitMs, turnGoalIds, turnStartedAt, turnObjectiveRevisions } = ctx;
122
82
 
123
83
  // The Goal owning the current turn keeps its open segment; any other stops.
124
84
  const settleActiveClock = (id, goal, at) => {
@@ -140,13 +100,14 @@ export function createGoalMutations(ctx) {
140
100
  const createGoal = async (sessionId, args = {}) => {
141
101
  const id = assertSessionId(sessionId);
142
102
  const record = ctx.readRecord(id);
143
- if (record.goal && !goalFinished(record.goal)) {
103
+ const replaced = goalStalled(record.goal) && args.replace === true;
104
+ if (record.goal && !goalFinished(record.goal) && !replaced) {
144
105
  // Recovery is part of the message on purpose: without it a stopped Goal
145
106
  // (paused/blocked/limited) permanently blocked every later Goal in the
146
107
  // session, because the model owns no way to retire one.
147
108
  throw new Error(
148
109
  `cannot create a new Goal while an unfinished Goal exists (status ${record.goal.status}); ` +
149
- 'resume and finish it, or abandon it if the user redirected away from that objective'
110
+ 'finish it, or ask the user to stop it from the Goal controls'
150
111
  );
151
112
  }
152
113
  const at = now();
@@ -154,17 +115,25 @@ export function createGoalMutations(ctx) {
154
115
  if (args.startInCurrentTurn === true) {
155
116
  goal.lastStartedAt = record.goal ? at : turnStartedAt.get(id) || at;
156
117
  }
118
+ if (replaced) {
119
+ // The user confirmed: the stalled Goal retires into history as stopped.
120
+ record.goal.status = 'stopped';
121
+ record.goal.stoppedAt = at;
122
+ record.goal.archivedAt = at;
123
+ record.goal.updatedAt = at;
124
+ }
157
125
  if (record.goal) await archiveFinishedGoal(id, record.goal);
158
126
  const created = await ctx.commit(id, goal);
159
127
  if (args.startInCurrentTurn === true) {
160
128
  turnGoalIds.set(id, goal.id);
161
129
  turnStartedAt.set(id, goal.lastStartedAt);
130
+ turnObjectiveRevisions.set(id, goal.objectiveRevision);
162
131
  }
163
132
  ctx.scheduleGoalTitle(id, goal);
164
133
  return created;
165
134
  };
166
135
 
167
- const updateGoal = async (sessionId, args = {}, { user = false, expectedGoalId = '' } = {}) => {
136
+ const updateGoal = async (sessionId, args = {}, { expectedGoalId = '' } = {}) => {
168
137
  const id = assertSessionId(sessionId);
169
138
  const goal = ctx.requireGoal(id);
170
139
  assertExpectedGoal(goal, expectedGoalId, 'update');
@@ -180,7 +149,8 @@ export function createGoalMutations(ctx) {
180
149
  throw new Error('a completed Goal cannot change status; edit it or create a new Goal');
181
150
  }
182
151
  if (status === 'complete') {
183
- if (!user) assertCompletionEvidence(goal, at);
152
+ // Completion is the model's claim after its own evidence audit (or
153
+ // the user's act of authority); the runtime never completes by itself.
184
154
  settleActiveClock(id, goal, at);
185
155
  goal.status = 'complete';
186
156
  goal.completedAt = at;
@@ -197,14 +167,6 @@ export function createGoalMutations(ctx) {
197
167
  return ctx.commit(id, goal);
198
168
  };
199
169
 
200
- const setGoalTasks = async (sessionId, args = {}, { expectedGoalId = '', partial = false } = {}) => {
201
- const id = assertSessionId(sessionId);
202
- const goal = ctx.requireGoal(id);
203
- assertExpectedGoal(goal, expectedGoalId, 'task update');
204
- applyGoalTaskChanges(goal, args, { partial, at: now() });
205
- return ctx.commit(id, goal);
206
- };
207
-
208
170
  // Explicit retirement of a superseded Goal. The create guard stays strict so
209
171
  // parallel Goals stay impossible; this is the one way out of it, and it is
210
172
  // deliberately an act the model has to take rather than a silent overwrite.
@@ -224,8 +186,9 @@ export function createGoalMutations(ctx) {
224
186
  if (archive) goal.archivedAt = goal.stoppedAt;
225
187
  await ctx.commit(id, goal);
226
188
  turnGoalIds.delete(id);
189
+ turnObjectiveRevisions.delete(id);
227
190
  return publicGoal(goal, now());
228
191
  };
229
192
 
230
- return { createGoal, updateGoal, setGoalTasks, abandonGoal };
193
+ return { createGoal, updateGoal, abandonGoal };
231
194
  }
@@ -13,6 +13,11 @@ import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
13
13
  import { readStoredGoalSnapshot } from './goal-runtime.mjs';
14
14
  import { goalStateReminder } from './goal-text.mjs';
15
15
  import { clean } from './session-text.mjs';
16
+ import { todoContextBlock } from './todo-list.mjs';
17
+
18
+ // Stalled states the model is told about when they begin. Time-budget
19
+ // exhaustion has its own one-time closeout notice (deadline-reached).
20
+ const STALL_NOTICE_STATUSES = ['paused', 'blocked', 'usage_limited'];
16
21
 
17
22
  function pendingRevision(session) {
18
23
  return Math.max(0, Number(session?.pendingGoalReminder?.revision) || 0);
@@ -53,18 +58,38 @@ export function snapshotPendingGoalReminder(session, { dataDir = null, readGoal
53
58
  } catch {
54
59
  goal = null;
55
60
  }
61
+ if (goal?.status === 'complete') goal = null;
62
+ const marked = clean(session.pendingGoalReminder?.reason);
63
+ // A Goal that left the stalled states is told afresh if it stalls again.
64
+ if (!goal || goal.status === 'active') delete session.goalStalledNotice;
65
+ // A stalled Goal is told once, when it stalls; compaction does not
66
+ // re-inject it. Only a Goal that keeps running needs its state restored.
67
+ if (marked === 'compaction' && goal && goal.status !== 'active') goal = null;
68
+ // Compaction drops the todo tool results too: the open list rides once.
69
+ const todoBlock =
70
+ revision && marked === 'compaction'
71
+ ? todoContextBlock(session, 'Context was compacted; your todo list (exact wordings):')
72
+ : '';
56
73
  // Nothing left to remind about (no Goal, or it already finished): drop the
57
74
  // marker instead of re-reading the same file on every later turn.
58
- if (!goal || goal.status === 'complete') {
75
+ if (!goal && !todoBlock) {
59
76
  clearPendingGoalReminder(session);
60
77
  return null;
61
78
  }
62
79
  if (!revision) {
63
- if (!['paused', 'blocked'].includes(goal.status)) return null;
80
+ if (!STALL_NOTICE_STATUSES.includes(goal.status)) return null;
81
+ // One notice per stall: a later pause, block or limit of the same Goal
82
+ // (a turn ran in between) is a new stall.
83
+ const stall = `${goal.id}:${goal.status}:${Math.max(0, Number(goal.turnCount) || 0)}`;
84
+ if (session.goalStalledNotice === stall) return null;
85
+ session.goalStalledNotice = stall;
64
86
  revision = markPendingGoalReminder(session, goal.status).revision;
65
87
  }
66
88
  const reason = clean(session.pendingGoalReminder?.reason);
67
- const content = goalStateReminder(goal, { reason });
89
+ const goalContent = goal ? goalStateReminder(goal, { reason }) : '';
90
+ const content = [goalContent, todoBlock ? `<system-reminder>\n${todoBlock}\n</system-reminder>` : '']
91
+ .filter(Boolean)
92
+ .join('\n\n');
68
93
  if (!content) {
69
94
  clearPendingGoalReminder(session);
70
95
  return null;
@@ -29,12 +29,16 @@ export {
29
29
  MAX_GOAL_TIME_LIMIT_MS,
30
30
  parseGoalDuration,
31
31
  };
32
- export { GOAL_STATUS_VALUES } from './goal-state.mjs';
33
32
  export { listStoredActiveGoalSessionIds, readStoredGoalSnapshot } from './goal-storage.mjs';
34
33
 
35
34
  export function createGoalRuntime({
36
35
  dataDir,
37
- now = () => Date.now(),
36
+ now: injectedNow,
37
+ // Suspend evidence needs a monotonic clock paired with the wall clock. An
38
+ // injected `now` has no real pair, so a test supplies its own or none. A test
39
+ // that fakes `Date` must pass `monotonicNow: null` (undefined takes the default).
40
+ monotonicNow = injectedNow ? null : () => performance.now(),
41
+ suspendDetectable,
38
42
  defaultTimeLimitMs = DEFAULT_GOAL_TIME_LIMIT_MS,
39
43
  completedGoalTtlMs = DEFAULT_COMPLETED_GOAL_TTL_MS,
40
44
  deadlineWarningMs = DEFAULT_GOAL_DEADLINE_WARNING_MS,
@@ -42,6 +46,7 @@ export function createGoalRuntime({
42
46
  writeGoalRecord,
43
47
  onStorageError = reportGoalStorageError,
44
48
  } = {}) {
49
+ const now = injectedNow ?? (() => Date.now());
45
50
  const ctx = {
46
51
  dataDir,
47
52
  root: goalsRoot(dataDir),
@@ -55,15 +60,15 @@ export function createGoalRuntime({
55
60
  mutationChains: new Map(),
56
61
  turnGoalIds: new Map(),
57
62
  turnStartedAt: new Map(),
63
+ // Session -> the objective revision the current turn has seen (turn start,
64
+ // a status read, or a refusal that showed it). In memory like the turn.
65
+ turnObjectiveRevisions: new Map(),
58
66
  titleJobs: new Map(),
59
- observedGoals: new Map(),
60
- // Session -> the Goal identity, turn, and revision already answered with
61
- // no progress: the one settled-duration review, or an automatic turn that
62
- // called no tool. In memory on purpose: a restart is a new chance.
67
+ // Session -> the Goal identity and turn an automatic turn answered with no
68
+ // tool call. In memory on purpose: a restart is a new chance.
63
69
  idleReviewTurns: new Map(),
64
- // Session -> { goalId, revision, quietTurns }: which continuation tier this
65
- // context still owes, from the rules and state already delivered into it.
66
- // In memory on purpose: a restart is a fresh context that needs them again.
70
+ // Session -> id of the Goal whose full continuation rules this context
71
+ // already holds. In memory on purpose: a restart is a fresh context.
67
72
  continuationTiers: new Map(),
68
73
  // Session -> Goal id whose turn ended in a stop (failed, cancelled, usage
69
74
  // limit) the record could not save. The record still reads active, so
@@ -74,11 +79,14 @@ export function createGoalRuntime({
74
79
  Object.assign(ctx, createGoalStore(ctx));
75
80
  ctx.deadlines = createGoalDeadlines({
76
81
  now,
77
- readRecord: ctx.readRecord,
82
+ readRecord: ctx.readStoredRecord,
78
83
  withMutation: ctx.withMutation,
79
84
  commit: ctx.commit,
85
+ stampLive: ctx.stampLive,
80
86
  onStorageError,
81
87
  deadlineWarningMs,
88
+ monotonicNow,
89
+ ...(suspendDetectable !== undefined ? { suspendDetectable } : {}),
82
90
  });
83
91
  ctx.scheduleGoalTitle = createGoalTitleScheduler(ctx);
84
92
  Object.assign(ctx, createGoalMutations(ctx));
@@ -109,6 +117,23 @@ export function createGoalRuntime({
109
117
  return withMutation(sessionId, () => ctx.control(sessionId, args));
110
118
  },
111
119
  executeTool: (name, args = {}, context = {}) => executeGoalTool(ctx, name, args, context),
120
+ // Tasks a legacy record still carries for the session's todo list to
121
+ // adopt; [] once adopted (clearLegacyTasks).
122
+ legacyTasks(sessionId) {
123
+ try {
124
+ return ctx.readRecord(sessionId).goal?.legacyTasks || [];
125
+ } catch {
126
+ return [];
127
+ }
128
+ },
129
+ clearLegacyTasks(sessionId) {
130
+ return withMutation(sessionId, async (id) => {
131
+ const goal = ctx.readRecord(id).goal;
132
+ if (!goal?.legacyTasks?.length) return null;
133
+ goal.legacyTasks = [];
134
+ return ctx.commit(id, goal);
135
+ });
136
+ },
112
137
  ...createGoalTurnLifecycle(ctx),
113
138
  close() {
114
139
  ctx.closed = true;
@@ -117,7 +142,7 @@ export function createGoalRuntime({
117
142
  deadlines.close();
118
143
  ctx.turnGoalIds.clear();
119
144
  ctx.turnStartedAt.clear();
120
- ctx.observedGoals.clear();
145
+ ctx.turnObjectiveRevisions.clear();
121
146
  ctx.idleReviewTurns.clear();
122
147
  ctx.continuationTiers.clear();
123
148
  ctx.unsavedStops.clear();
@@ -2,7 +2,6 @@ import { randomUUID } from 'node:crypto';
2
2
  import { clean } from '../../../shared/clean.mjs';
3
3
  import { compactSessionTitle } from './session-title.mjs';
4
4
  import { MAX_GOAL_TIME_LIMIT_MS } from './goal-tool-defs.mjs';
5
- import { goalTaskProgress, normalizeGoalTasks } from './goal-tasks.mjs';
6
5
  import { SESSION_ID_PATTERN } from './session-id.mjs';
7
6
 
8
7
  export const DEFAULT_GOAL_TIME_LIMIT_MS = 0;
@@ -10,6 +9,9 @@ export const DEFAULT_COMPLETED_GOAL_TTL_MS = 24 * 60 * 60 * 1000;
10
9
  export const DEFAULT_GOAL_DEADLINE_WARNING_MS = Object.freeze([10 * 60_000, 5 * 60_000]);
11
10
  export const NO_DEADLINE_WARNING_MS = Number.MAX_SAFE_INTEGER;
12
11
  export const GOAL_FILE_VERSION = 1;
12
+ // A live runtime re-stamps an active Goal's `liveAt` this often; a longer gap
13
+ // means nothing was running it (app closed, PC asleep).
14
+ export const GOAL_LIVE_HEARTBEAT_MS = 60_000;
13
15
  export const SESSION_ID = SESSION_ID_PATTERN;
14
16
  export const GOAL_STATUS_VALUES = Object.freeze([
15
17
  'active',
@@ -23,14 +25,6 @@ export const GOAL_STATUS_VALUES = Object.freeze([
23
25
 
24
26
  const MAX_OBJECTIVE_LENGTH = 4_000;
25
27
  const MAX_GOAL_BLOCKER_LENGTH = 1_000;
26
- const ACTIVE_AGENT_STATUSES = new Set([
27
- 'connecting',
28
- 'requesting',
29
- 'streaming',
30
- 'tool_running',
31
- 'running',
32
- 'cancelling',
33
- ]);
34
28
 
35
29
  export function assertSessionId(value) {
36
30
  const sessionId = clean(value);
@@ -56,12 +50,6 @@ export function validateGoalBlocker(value) {
56
50
  return blocker;
57
51
  }
58
52
 
59
- export function goalTimeMode(value, fallback = 'max') {
60
- if (value == null || value === '') return fallback;
61
- if (!['max', 'duration'].includes(value)) throw new Error('goal timeMode must be max or duration');
62
- return value;
63
- }
64
-
65
53
  export function parseGoalDuration(value) {
66
54
  if (typeof value === 'number') {
67
55
  if (!Number.isFinite(value) || value <= 0) throw new Error('goal duration must be positive');
@@ -100,12 +88,10 @@ export function parseUserCommand(command) {
100
88
 
101
89
  let objective = value;
102
90
  let duration = null;
103
- let timeMode = null;
104
- const modeMatch = objective.match(/(?:^|\s)--time-mode(?:=|\s+)([^\s]+)/i);
105
- if (modeMatch) {
106
- timeMode = goalTimeMode(modeMatch[1]);
107
- objective = objective.replace(modeMatch[0], ' ').trim();
108
- }
91
+ // The user's confirmation that a new Goal replaces a stalled one, naming it:
92
+ // `--replace=<goal id>`.
93
+ const replaceMatch = objective.match(/(?:^|\s)--replace(?:=(\S+))?(?=\s|$)/i);
94
+ if (replaceMatch) objective = objective.replace(replaceMatch[0], ' ').trim();
109
95
  const equalsMatch = objective.match(/(?:^|\s)--time=([^\s]+)/i);
110
96
  if (equalsMatch) {
111
97
  duration = equalsMatch[1];
@@ -117,7 +103,12 @@ export function parseUserCommand(command) {
117
103
  objective = objective.replace(spacedMatch[0], ' ').trim();
118
104
  }
119
105
  }
120
- return { action: 'create', objective, duration, timeMode };
106
+ return {
107
+ action: 'create',
108
+ objective,
109
+ duration,
110
+ ...(replaceMatch ? { replace: true, ...(replaceMatch[1] ? { expectedGoalId: replaceMatch[1] } : {}) } : {}),
111
+ };
121
112
  }
122
113
 
123
114
  function storedTimeLimitMs(value) {
@@ -125,11 +116,19 @@ function storedTimeLimitMs(value) {
125
116
  return Number.isFinite(stored) && stored > 0 ? Math.min(MAX_GOAL_TIME_LIMIT_MS, Math.max(60_000, stored)) : 0;
126
117
  }
127
118
 
128
- // Dropped work must survive the turn that removed it before completion.
129
- function storedLastDropTurn(value) {
130
- const dropped =
131
- value.revision != null || value.lastDropTurn !== 0 || (value.tasks || []).some((task) => task.status === 'dropped');
132
- return Number.isInteger(value.lastDropTurn) && dropped ? value.lastDropTurn : -1;
119
+ // Tasks a record written before the todo list carried: kept on the record
120
+ // only until the session's todo list adopts them (todo-list.mjs).
121
+ function storedLegacyTasks(value) {
122
+ const tasks = value.legacyTasks ?? value.tasks ?? value.criteria;
123
+ return Array.isArray(tasks) && tasks.length
124
+ ? tasks
125
+ .filter((task) => task && typeof task === 'object' && clean(task.text ?? task.content))
126
+ .map((task) => ({
127
+ text: clean(task.text ?? task.content),
128
+ // A criterion written before task statuses carried `satisfied`.
129
+ status: clean(task.status) || (task.satisfied === true ? 'completed' : 'pending'),
130
+ }))
131
+ : [];
133
132
  }
134
133
 
135
134
  export function normalizeStoredGoal(value, sessionId, resumedAt = Date.now()) {
@@ -139,31 +138,35 @@ export function normalizeStoredGoal(value, sessionId, resumedAt = Date.now()) {
139
138
  if (!GOAL_STATUS_VALUES.includes(status)) throw new Error(`invalid stored Goal status: ${status}`);
140
139
  return {
141
140
  id: clean(value.id) || randomUUID(),
142
- revision: Math.max(1, Math.floor(Number(value.revision) || 1)),
143
- objectiveRevision: Math.max(1, Math.floor(Number(value.objectiveRevision) || 1)),
144
- tasksObjectiveRevision: Math.max(1, Math.floor(Number(value.tasksObjectiveRevision) || 1)),
145
141
  sessionId,
146
142
  objective: validateObjective(value.objective),
147
143
  title: compactSessionTitle(value.title || value.objective),
144
+ // Advances on every objective edit, so a settlement can tell whether the
145
+ // turn it answers saw the current wording.
146
+ objectiveRevision: Math.max(0, Math.floor(Number(value.objectiveRevision) || 0)),
148
147
  status,
149
- tasks: normalizeGoalTasks(value.tasks ?? value.criteria ?? []),
148
+ legacyTasks: storedLegacyTasks(value),
150
149
  blocker: clean(value.blocker),
151
150
  pauseReason: ['waiting', 'cancelled'].includes(value.pauseReason) ? value.pauseReason : 'user',
152
151
  failureReason: clean(value.failureReason),
153
152
  failureCount: Math.max(0, Math.floor(Number(value.failureCount) || 0)),
154
153
  // Observations report progress; they never decide when work is complete.
155
154
  turnCount: Math.max(0, Math.floor(Number(value.turnCount) || 0)),
156
- lastDropTurn: storedLastDropTurn(value),
157
- tasksUpdatedAt: Number(value.tasksUpdatedAt) > 0 ? Number(value.tasksUpdatedAt) : null,
158
155
  timeLimitMs: storedTimeLimitMs(value),
159
- // Unversioned durations retain their original full-period commitment.
160
- timeMode: goalTimeMode(value.timeMode, 'duration'),
161
- timeUsedMs: Math.max(0, Number(value.timeUsedMs) || 0),
156
+ // Time is an upper bound only: a legacy `duration` record reads as a cap.
157
+ // A record exhausted by an older build may carry wall-clock time past its
158
+ // cap; the cap is what was spent.
159
+ timeUsedMs: cappedTimeUsedMs(
160
+ status === 'duration_reached' ? storedTimeLimitMs(value) : 0,
161
+ Math.max(0, Number(value.timeUsedMs) || 0)
162
+ ),
162
163
  createdAt: Math.max(0, Number(value.createdAt) || Date.now()),
163
164
  updatedAt: Math.max(0, Number(value.updatedAt) || Date.now()),
164
165
  // Preserve the open segment across task writes, eviction, and restart.
165
166
  lastStartedAt:
166
167
  status === 'active' ? Math.max(0, Number(value.lastStartedAt) || Number(resumedAt) || Date.now()) : null,
168
+ // The last moment a live runtime was running this Goal's open segment.
169
+ liveAt: Math.max(0, Number(value.liveAt) || 0),
167
170
  completedAt: Number(value.completedAt) > 0 ? Number(value.completedAt) : null,
168
171
  stoppedAt: Number(value.stoppedAt) > 0 ? Number(value.stoppedAt) : null,
169
172
  archivedAt: Number(value.archivedAt) > 0 ? Number(value.archivedAt) : null,
@@ -172,6 +175,51 @@ export function normalizeStoredGoal(value, sessionId, resumedAt = Date.now()) {
172
175
  };
173
176
  }
174
177
 
178
+ function cappedTimeUsedMs(limitMs, usedMs) {
179
+ return limitMs > 0 ? Math.min(limitMs, usedMs) : usedMs;
180
+ }
181
+
182
+ /** Drop the part of an active Goal's open segment that no live runtime ran
183
+ * (the app was closed, the PC slept): the segment start moves forward by the
184
+ * unobserved gap. Returns whether anything was dropped. */
185
+ export function discardUnobservedTime(goal, at, toleranceMs = 0) {
186
+ if (goal?.status !== 'active' || !(Number(goal.lastStartedAt) > 0)) return false;
187
+ const seenAt = Math.max(Number(goal.lastStartedAt), Number(goal.liveAt) || 0);
188
+ const gap = at - seenAt;
189
+ // The tolerance covers heartbeat jitter inside a live runtime only; a load
190
+ // after downtime passes none, so no closed-app interval is ever credited.
191
+ if (gap <= toleranceMs) return false;
192
+ goal.lastStartedAt = Number(goal.lastStartedAt) + gap;
193
+ goal.liveAt = at;
194
+ return true;
195
+ }
196
+
197
+ /** Drop `suspendedMs` — time a live runtime provably did not run (the OS was
198
+ * suspended) — from an active Goal's open segment, never more than the gap
199
+ * since it was last seen. Returns whether anything was dropped. */
200
+ export function discardSuspendedTime(goal, at, suspendedMs) {
201
+ if (goal?.status !== 'active' || !(Number(goal.lastStartedAt) > 0)) return false;
202
+ const seenAt = Math.max(Number(goal.lastStartedAt), Number(goal.liveAt) || 0);
203
+ const dropped = Math.min(Math.floor(Number(suspendedMs) || 0), Math.max(0, at - seenAt));
204
+ if (!(dropped > 0)) return false;
205
+ goal.lastStartedAt = Number(goal.lastStartedAt) + dropped;
206
+ goal.liveAt = at;
207
+ return true;
208
+ }
209
+
210
+ /** The Goal as a reader without a runtime sees it. Such a reader cannot know
211
+ * whether anything is running the Goal, so it uses no clock: the open
212
+ * segment counts only up to `liveAt`, the last moment a runtime persisted as
213
+ * having run it (at most one heartbeat behind). That makes the usage a pure
214
+ * function of the record — never above what the runtime has stored, never
215
+ * charging downtime, never moving backward between reads. */
216
+ export function freezeUnobservedSegment(goal) {
217
+ if (goal?.status !== 'active' || !(Number(goal.lastStartedAt) > 0)) return;
218
+ const observedMs = Math.max(0, (Number(goal.liveAt) || 0) - Number(goal.lastStartedAt));
219
+ goal.timeUsedMs = Math.max(0, Number(goal.timeUsedMs) || 0) + observedMs;
220
+ goal.lastStartedAt = null;
221
+ }
222
+
175
223
  export function activeElapsedMs(goal, now = Date.now()) {
176
224
  const committed = Math.max(0, Number(goal?.timeUsedMs) || 0);
177
225
  if (goal?.status !== 'active' || !(Number(goal?.lastStartedAt) > 0)) return committed;
@@ -186,28 +234,23 @@ export function normalizeDeadlineWarnedMs(value) {
186
234
 
187
235
  export function publicGoal(goal, now = Date.now()) {
188
236
  if (!goal) return null;
189
- const timeUsedMs = activeElapsedMs(goal, now);
190
237
  const hasTimeLimit = Number(goal.timeLimitMs) > 0;
238
+ // Never above the cap: a read that lands after the deadline but before the
239
+ // expiry commit reports the cap, not the overshoot.
240
+ const timeUsedMs = cappedTimeUsedMs(Number(goal.timeLimitMs) || 0, activeElapsedMs(goal, now));
191
241
  const remainingMs = hasTimeLimit ? Math.max(0, Number(goal.timeLimitMs) - timeUsedMs) : null;
192
- const tasks = normalizeGoalTasks(goal.tasks || []);
193
242
  return {
194
243
  id: goal.id,
195
- revision: goal.revision,
196
- // Warning delivery does not invalidate the actionable task revision.
244
+ // Advances once per delivered deadline warning (deadline-delivery.mjs).
197
245
  warningRevision: Math.max(0, Math.floor(Number(goal.warningRevision) || 0)),
198
- needsTaskReview: goal.tasksObjectiveRevision !== goal.objectiveRevision,
199
246
  sessionId: goal.sessionId,
200
247
  objective: goal.objective,
201
248
  title: goal.title || compactSessionTitle(goal.objective),
202
249
  status: goal.status,
203
- tasks,
204
250
  turnCount: Math.max(0, Math.floor(Number(goal.turnCount) || 0)),
205
- tasksUpdatedAt: Number(goal.tasksUpdatedAt) > 0 ? Number(goal.tasksUpdatedAt) : null,
206
- ...goalTaskProgress(tasks),
207
251
  blocker: goal.blocker || '',
208
252
  pauseReason: goal.pauseReason || 'user',
209
253
  timeLimitMs: goal.timeLimitMs,
210
- timeMode: goal.timeMode,
211
254
  timeUsedMs,
212
255
  remainingMs,
213
256
  deadlineAt: hasTimeLimit && goal.status === 'active' && goal.lastStartedAt ? now + remainingMs : null,
@@ -310,14 +353,3 @@ export function resumeGoalState(goal, at, added = null) {
310
353
  export function startActiveClock(goal, now = Date.now()) {
311
354
  if (goal.status === 'active' && !goal.lastStartedAt) goal.lastStartedAt = now;
312
355
  }
313
-
314
- export function runningAgentWork(agentStatus) {
315
- const jobs = Array.isArray(agentStatus?.agentJobs) ? agentStatus.agentJobs : [];
316
- if (jobs.some((job) => clean(job?.status).toLowerCase() === 'running')) return true;
317
- const workers = Array.isArray(agentStatus?.agentWorkers) ? agentStatus.agentWorkers : [];
318
- return workers.some((worker) => {
319
- const status = clean(worker?.status).toLowerCase();
320
- const stage = clean(worker?.stage || worker?.worker_stage).toLowerCase();
321
- return ACTIVE_AGENT_STATUSES.has(status) || ACTIVE_AGENT_STATUSES.has(stage);
322
- });
323
- }
@@ -15,6 +15,7 @@ import {
15
15
  SESSION_ID,
16
16
  assertSessionId,
17
17
  completedGoalExpired,
18
+ freezeUnobservedSegment,
18
19
  normalizeStoredGoal,
19
20
  publicGoal,
20
21
  } from './goal-state.mjs';
@@ -40,7 +41,9 @@ export function deleteStoredGoalFile(dataDir, sessionId) {
40
41
  function readStoredGoalFile(dataDir, sessionId, at = Date.now()) {
41
42
  const id = assertSessionId(sessionId);
42
43
  try {
43
- return readGoalRecordFile(goalFilePath(dataDir, id), id, normalizeStoredGoal, at).goal;
44
+ const goal = readGoalRecordFile(goalFilePath(dataDir, id), id, normalizeStoredGoal, at).goal;
45
+ freezeUnobservedSegment(goal);
46
+ return goal;
44
47
  } catch (error) {
45
48
  reportGoalStorageError(error);
46
49
  return null;