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
@@ -55,9 +55,7 @@
55
55
  }
56
56
  ],
57
57
  "store": false,
58
- "include": [
59
- "reasoning.encrypted_content"
60
- ],
58
+ "include": ["reasoning.encrypted_content"],
61
59
  "prompt_cache_key": "f0858c98-2db1-4764-9d18-e6e1ec2ea4c7",
62
60
  "tools": [
63
61
  {
@@ -148,9 +146,7 @@
148
146
  }
149
147
  ],
150
148
  "store": false,
151
- "include": [
152
- "reasoning.encrypted_content"
153
- ],
149
+ "include": ["reasoning.encrypted_content"],
154
150
  "prompt_cache_key": "f0858c98-2db1-4764-9d18-e6e1ec2ea4c7",
155
151
  "tools": [
156
152
  {
@@ -278,9 +274,7 @@
278
274
  }
279
275
  ],
280
276
  "store": false,
281
- "include": [
282
- "reasoning.encrypted_content"
283
- ],
277
+ "include": ["reasoning.encrypted_content"],
284
278
  "prompt_cache_key": "f0858c98-2db1-4764-9d18-e6e1ec2ea4c7",
285
279
  "tools": [
286
280
  {
@@ -400,9 +394,7 @@
400
394
  }
401
395
  ],
402
396
  "store": false,
403
- "include": [
404
- "reasoning.encrypted_content"
405
- ],
397
+ "include": ["reasoning.encrypted_content"],
406
398
  "prompt_cache_key": "f0858c98-2db1-4764-9d18-e6e1ec2ea4c7",
407
399
  "instructions": "sys",
408
400
  "tools": [
@@ -513,9 +505,7 @@
513
505
  }
514
506
  ],
515
507
  "store": false,
516
- "include": [
517
- "reasoning.encrypted_content"
518
- ],
508
+ "include": ["reasoning.encrypted_content"],
519
509
  "prompt_cache_key": "f0858c98-2db1-4764-9d18-e6e1ec2ea4c7",
520
510
  "tools": [
521
511
  {
@@ -626,9 +616,7 @@
626
616
  }
627
617
  ],
628
618
  "store": false,
629
- "include": [
630
- "reasoning.encrypted_content"
631
- ],
619
+ "include": ["reasoning.encrypted_content"],
632
620
  "prompt_cache_key": "f0858c98-2db1-4764-9d18-e6e1ec2ea4c7",
633
621
  "tools": [
634
622
  {
@@ -738,9 +726,7 @@
738
726
  }
739
727
  ],
740
728
  "store": false,
741
- "include": [
742
- "reasoning.encrypted_content"
743
- ],
729
+ "include": ["reasoning.encrypted_content"],
744
730
  "prompt_cache_key": "f0858c98-2db1-4764-9d18-e6e1ec2ea4c7",
745
731
  "tools": [
746
732
  {
@@ -865,9 +851,7 @@
865
851
  }
866
852
  ],
867
853
  "store": false,
868
- "include": [
869
- "reasoning.encrypted_content"
870
- ],
854
+ "include": ["reasoning.encrypted_content"],
871
855
  "prompt_cache_key": "f0858c98-2db1-4764-9d18-e6e1ec2ea4c7",
872
856
  "tools": [
873
857
  {
@@ -971,9 +955,7 @@
971
955
  }
972
956
  ],
973
957
  "store": false,
974
- "include": [
975
- "reasoning.encrypted_content"
976
- ],
958
+ "include": ["reasoning.encrypted_content"],
977
959
  "prompt_cache_key": "f0858c98-2db1-4764-9d18-e6e1ec2ea4c7",
978
960
  "tools": [
979
961
  {
@@ -1078,9 +1060,7 @@
1078
1060
  }
1079
1061
  ],
1080
1062
  "store": false,
1081
- "include": [
1082
- "reasoning.encrypted_content"
1083
- ],
1063
+ "include": ["reasoning.encrypted_content"],
1084
1064
  "prompt_cache_key": "f0858c98-2db1-4764-9d18-e6e1ec2ea4c7",
1085
1065
  "tools": [
1086
1066
  {
@@ -129,6 +129,36 @@ export function builtinFeatureActive(configLike, id) {
129
129
  return false;
130
130
  }
131
131
 
132
+ /** Every tool a settings gate of featureDisallowedToolsFor can deny (the
133
+ * headless `load_tool` rule derives from these and is not a setting). */
134
+ export const FEATURE_GATED_TOOL_NAMES = Object.freeze([
135
+ 'web_search',
136
+ 'web_fetch',
137
+ 'memory',
138
+ 'recall',
139
+ 'git',
140
+ 'github',
141
+ 'browser',
142
+ 'browser_devtools',
143
+ 'computer',
144
+ 'terminal',
145
+ 'office',
146
+ 'media',
147
+ 'tidy',
148
+ 'visualize',
149
+ 'explore',
150
+ 'react',
151
+ ]);
152
+
153
+ /** Gated by desktop bridge presence, not a setting. */
154
+ const BRIDGE_GATED_TOOL_NAMES = Object.freeze(['browser', 'browser_devtools', 'computer', 'terminal']);
155
+
156
+ /** The tools a user setting turns off (react answers for itself: it gates
157
+ * non-model callers too). */
158
+ export const SETTING_GATED_TOOL_NAMES = Object.freeze(
159
+ FEATURE_GATED_TOOL_NAMES.filter((name) => !BRIDGE_GATED_TOOL_NAMES.includes(name) && name !== 'react')
160
+ );
161
+
132
162
  /** The session-surface exclusion list. Session build, the empty-session policy
133
163
  * refresh, and the deferred tool catalog all consume THIS list, so an
134
164
  * uninstalled or disabled feature never reaches a session's tool surface.
@@ -348,17 +348,6 @@ export function resolveAutoClearIdleMs(config, provider) {
348
348
  return autoClearIdleMsForProvider(provider, raw.providerIdleMs);
349
349
  }
350
350
 
351
- /** The idle auto-clear gate's settings for a thread on `provider`
352
- * (idle-auto-clear-gate.mjs): `{ enabled, idleMs, minContextPercent }`. */
353
- function autoClearGateSettings(config, provider) {
354
- const normalized = normalizeAutoClearConfig(config?.autoClear);
355
- return {
356
- enabled: normalized.enabled,
357
- idleMs: resolveAutoClearIdleMs(config, provider),
358
- minContextPercent: normalized.minContextPercent,
359
- };
360
- }
361
-
362
351
  export function normalizeSystemShellConfig(value = {}) {
363
352
  const raw = value && typeof value === 'object' ? value : {};
364
353
  const command = clean(raw.command);
@@ -14,6 +14,14 @@ export function sessionDisallows(session, name) {
14
14
  );
15
15
  }
16
16
 
17
+ /** A settings gate denies `name` to the session (as last reconciled, tool-availability.mjs). */
18
+ export function sessionFeatureDenies(session, name) {
19
+ return (
20
+ Array.isArray(session?.featureDeniedTools) &&
21
+ session.featureDeniedTools.some((denied) => lowerName(denied) === lowerName(name))
22
+ );
23
+ }
24
+
17
25
  /**
18
26
  * The session's own tool policy permits `name`: discovery (load_tool by name
19
27
  * or query) shows, selects and describes only what execution would run.
@@ -2,12 +2,15 @@ import { clean } from './session-text.mjs';
2
2
  import { parseMcpToolName } from '../../../shared/tool-primitives.mjs';
3
3
  import { mcpServerLines } from '../context/deferred-tools.mjs';
4
4
 
5
+ // A delta names tools; its description is a hint, the schema arrives on load.
6
+ export const DELTA_DESCRIPTION_MAX = 80;
7
+
5
8
  function compactDescription(value) {
6
- return String(value || '')
9
+ const text = String(value || '')
7
10
  .replace(/\s+/g, ' ')
8
11
  .replace(/[<>]/g, '')
9
- .trim()
10
- .slice(0, 240);
12
+ .trim();
13
+ return text.length > DELTA_DESCRIPTION_MAX ? `${text.slice(0, DELTA_DESCRIPTION_MAX - 1).trimEnd()}…` : text;
11
14
  }
12
15
 
13
16
  function addedEntry(entry) {
@@ -168,6 +171,17 @@ export function snapshotPendingDeferredToolDelta(session) {
168
171
  };
169
172
  }
170
173
 
174
+ /** Drop pending additions the prompt now lists itself (`listed`). */
175
+ export function dropPendingDeferredToolAdds(session, listed) {
176
+ const pending = session?.pendingDeferredToolDelta;
177
+ if (!pending || !Array.isArray(pending.added)) return;
178
+ const added = pending.added.filter((entry) => !listed.has(clean(entry?.name)));
179
+ if (added.length === pending.added.length) return;
180
+ if (!added.length && !(pending.removed || []).length) delete session.pendingDeferredToolDelta;
181
+ else session.pendingDeferredToolDelta = { ...pending, added };
182
+ session.updatedAt = Date.now();
183
+ }
184
+
171
185
  export function acknowledgePendingDeferredToolDelta(session, revision) {
172
186
  if (!session || typeof session !== 'object') return false;
173
187
  const current = normalizedDelta(session.pendingDeferredToolDelta);
@@ -5,21 +5,20 @@
5
5
  */
6
6
  import { durationLabel } from './goal-text.mjs';
7
7
  import { compactSessionTitle } from './session-title.mjs';
8
- import { applyGoalTaskChanges, optionalGoalTaskChanges } from './goal-tasks.mjs';
9
8
  import {
10
9
  NO_DEADLINE_WARNING_MS,
11
10
  activateGoal,
12
11
  activeElapsedMs,
13
12
  assertSessionId,
14
13
  clearTurnFailures,
15
- goalTimeMode,
16
14
  parseGoalDuration,
17
15
  parseUserCommand,
16
+ publicGoal,
18
17
  resumeGoalState,
19
18
  stopActiveClock,
20
19
  validateObjective,
21
20
  } from './goal-state.mjs';
22
- import { assertExpectedGoal, goalFinished } from './goal-mutations.mjs';
21
+ import { assertExpectedGoal, goalFinished, goalStalled } from './goal-mutations.mjs';
23
22
  import { clean } from '../../../shared/clean.mjs';
24
23
 
25
24
  /** "12m remaining" for a budgeted Goal, "3m elapsed" for an open-ended one. */
@@ -31,6 +30,9 @@ function goalTimingLabel(goal) {
31
30
 
32
31
  const reply = (action, goal, message) => ({ ok: true, action, goal, message });
33
32
 
33
+ /** `code` of the create answer that asks the user to confirm replacing a stalled Goal. */
34
+ export const GOAL_REPLACE_REQUIRED = 'goal-replace-required';
35
+
34
36
  const GOAL_CONTROL_ACTIONS = {
35
37
  async stop(ctx, { id, expectedGoalId, action }) {
36
38
  const goal = await ctx.abandonGoal(id, { expectedGoalId, archive: true });
@@ -52,34 +54,27 @@ const GOAL_CONTROL_ACTIONS = {
52
54
  throw new Error('a completed Goal cannot be resumed; edit it or create a new Goal');
53
55
  }
54
56
  const added = args.duration ? parseGoalDuration(args.duration) : null;
55
- const timeMode = goalTimeMode(args.timeMode, goal.timeMode);
56
- // Empty optional fields from frozen provider schemas still mean a plain
57
- // resume. Supplied changes validate before activation and share its write.
58
- const taskChanges = optionalGoalTaskChanges(args);
59
- const hasTaskChanges = [taskChanges.updates, taskChanges.tasks].some(
60
- (value) => value != null && (!Array.isArray(value) || value.length > 0)
61
- );
62
- if (hasTaskChanges) applyGoalTaskChanges(goal, taskChanges, { partial: true, at });
63
57
  resumeGoalState(goal, at, added);
64
- goal.timeMode = timeMode;
65
58
  const resumed = await ctx.commit(id, goal);
66
59
  return reply(action, resumed, `Goal resumed · ${resumed.objective} · ${goalTimingLabel(resumed)}`);
67
60
  },
68
61
  async edit(ctx, { id, goal, args, at, action }) {
69
- // Tasks survive an objective edit. The desktop "Edit goal" button drafts
70
- // the CURRENT objective, so wiping the list meant refining one word threw
71
- // away every completed row; re-aligning a stale list is set_tasks' job.
62
+ // The session's todo list is untouched by an objective edit; the
63
+ // objective-updated reminder asks the model to re-align it.
72
64
  const objective = validateObjective(args.objective);
73
65
  let timeLimitMs = goal.timeLimitMs;
74
- if (args.timeLimitMs != null) timeLimitMs = args.timeLimitMs === 0 ? 0 : parseGoalDuration(args.timeLimitMs);
75
- else if (args.duration != null) timeLimitMs = parseGoalDuration(args.duration);
76
- const timeMode = goalTimeMode(args.timeMode, goal.timeMode);
77
- if (objective !== goal.objective) goal.objectiveRevision += 1;
66
+ if (args.time_limit_minutes != null && args.time_limit_minutes !== '') {
67
+ const minutes = Number(args.time_limit_minutes);
68
+ if (!Number.isFinite(minutes) || minutes < 0) throw new Error('goal time_limit_minutes must be 0 or more');
69
+ timeLimitMs = minutes === 0 ? 0 : parseGoalDuration(minutes * 60_000);
70
+ } else if (args.timeLimitMs != null) {
71
+ timeLimitMs = args.timeLimitMs === 0 ? 0 : parseGoalDuration(args.timeLimitMs);
72
+ } else if (args.duration != null) timeLimitMs = parseGoalDuration(args.duration);
73
+ if (objective !== goal.objective) goal.objectiveRevision = (Number(goal.objectiveRevision) || 0) + 1;
78
74
  goal.objective = objective;
79
75
  goal.title = compactSessionTitle(goal.objective);
80
76
  if (timeLimitMs !== goal.timeLimitMs) goal.deadlineWarnedMs = NO_DEADLINE_WARNING_MS;
81
77
  goal.timeLimitMs = timeLimitMs;
82
- goal.timeMode = timeMode;
83
78
  if (goal.status === 'complete') activateGoal(goal, at);
84
79
  else clearTurnFailures(goal);
85
80
  goal.updatedAt = at;
@@ -106,13 +101,19 @@ const GOAL_CONTROL_ACTIONS = {
106
101
  return reply(action, timed, `Goal duration · ${durationLabel(limit)}`);
107
102
  },
108
103
  async complete(ctx, { id, action }) {
109
- const goal = await ctx.updateGoal(id, { status: 'complete' }, { user: true });
104
+ const goal = await ctx.updateGoal(id, { status: 'complete' });
110
105
  return reply(action, goal, `Goal complete · ${goal.objective} · ${durationLabel(goal.timeUsedMs)} elapsed`);
111
106
  },
112
- async dismiss(ctx, { id, goal, at, action }) {
107
+ async dismiss(ctx, { id, goal, at, action, expectedGoalId }) {
108
+ // A stalled Goal waits for the user and nothing else retires it: dismissing
109
+ // it is the stop-and-archive the Stop control confirms.
110
+ if (goalStalled(goal)) {
111
+ await ctx.abandonGoal(id, { expectedGoalId, archive: true });
112
+ return reply(action, null, `Goal dismissed · ${goal.objective}`);
113
+ }
113
114
  // The user's acknowledgement of finished work: the same archive a new
114
115
  // prompt applies, so the chrome lets go while the record stays.
115
- if (!goalFinished(goal)) throw new Error('only a finished Goal can be dismissed; stop or complete it first');
116
+ if (!goalFinished(goal)) throw new Error('only a finished or stalled Goal can be dismissed; stop it first');
116
117
  if (!goal.archivedAt) {
117
118
  goal.archivedAt = at;
118
119
  goal.updatedAt = at;
@@ -129,11 +130,33 @@ export async function runGoalControl(ctx, sessionId, rawArgs = {}) {
129
130
  else if (rawArgs?.command != null) args = { ...rawArgs, ...parseUserCommand(rawArgs.command) };
130
131
  const action = clean(args?.action || 'get').toLowerCase();
131
132
  if (action === 'create') {
133
+ const existing = ctx.readRecord(id).goal;
134
+ // Replacing a stalled Goal is the user's call: ask once, and let the same
135
+ // request come back with `replace` once they agree.
136
+ if (goalStalled(existing) && args.replace !== true) {
137
+ return {
138
+ ok: false,
139
+ action,
140
+ code: GOAL_REPLACE_REQUIRED,
141
+ goal: publicGoal(existing, ctx.now()),
142
+ message:
143
+ `A ${existing.status} Goal already exists · ${existing.objective}. ` +
144
+ `Run /goal --replace=${existing.id} followed by the new objective to replace it.`,
145
+ };
146
+ }
147
+ // The confirmation names the Goal the user saw; it runs inside the
148
+ // mutation queue, so a Goal that changed since refuses instead of retiring.
149
+ if (args.replace === true && existing) {
150
+ if (goalStalled(existing) && !clean(args.expectedGoalId)) {
151
+ throw new Error(`a replace must name the stalled Goal to retire (--replace=${existing.id})`);
152
+ }
153
+ if (clean(args.expectedGoalId)) assertExpectedGoal(existing, args.expectedGoalId, 'replace');
154
+ }
132
155
  const goal = await ctx.createGoal(id, {
133
156
  objective: args.objective,
134
157
  duration: args.duration,
135
158
  timeLimitMs: args.timeLimitMs,
136
- timeMode: args.timeMode,
159
+ replace: args.replace === true,
137
160
  });
138
161
  return reply(action, goal, `Goal active · ${goal.objective} · ${goalTimingLabel(goal)}`);
139
162
  }
@@ -153,9 +176,6 @@ export async function runGoalControl(ctx, sessionId, rawArgs = {}) {
153
176
  const goal = ctx.requireGoal(id);
154
177
  const expectedGoalId = clean(args.expectedGoalId);
155
178
  assertExpectedGoal(goal, expectedGoalId, 'update');
156
- if (args.revision != null && args.revision !== goal.revision) {
157
- throw new Error('Goal changed while editing; reopen the editor before saving');
158
- }
159
179
  const at = ctx.now();
160
180
  if (goal.status === 'stopped' && action !== 'dismiss') {
161
181
  throw new Error('a stopped Goal cannot be changed; create a new Goal');
@@ -1,4 +1,14 @@
1
- import { activeElapsedMs, assertSessionId, normalizeDeadlineWarnedMs, stopActiveClock } from './goal-state.mjs';
1
+ import {
2
+ GOAL_LIVE_HEARTBEAT_MS,
3
+ activeElapsedMs,
4
+ assertSessionId,
5
+ discardSuspendedTime,
6
+ normalizeDeadlineWarnedMs,
7
+ stopActiveClock,
8
+ } from './goal-state.mjs';
9
+
10
+ // Wall time may outrun the monotonic clock by a little without any suspend.
11
+ const MIN_SUSPEND_EVIDENCE_MS = 5_000;
2
12
 
3
13
  function normalizeDeadlineWarningMs(value) {
4
14
  const thresholds = (Array.isArray(value) ? value : [value])
@@ -9,10 +19,52 @@ function normalizeDeadlineWarningMs(value) {
9
19
 
10
20
  // Timers only observe crossings. Every durable transition joins the same
11
21
  // mutation queue as user edits and re-checks the current committed record.
12
- export function createGoalDeadlines({ now, readRecord, withMutation, commit, onStorageError, deadlineWarningMs }) {
22
+ export function createGoalDeadlines({
23
+ now,
24
+ readRecord,
25
+ withMutation,
26
+ commit,
27
+ stampLive,
28
+ onStorageError,
29
+ deadlineWarningMs,
30
+ monotonicNow = null,
31
+ // libuv's clock (Node's performance.now) stops during suspend on Linux and
32
+ // macOS (CLOCK_MONOTONIC, mach_absolute_time) but typically keeps counting
33
+ // on Windows (QueryPerformanceCounter), so there it proves nothing.
34
+ suspendDetectable = process.platform !== 'win32',
35
+ }) {
13
36
  const warningThresholdsMs = normalizeDeadlineWarningMs(deadlineWarningMs);
37
+ // Session -> wall and monotonic readings at the last moment the Goal was
38
+ // known to be running. Wall time that outran the monotonic clock since then
39
+ // is suspend: positive evidence. A stall of an awake process moves both
40
+ // clocks, so it is never dropped; without evidence time is counted, because
41
+ // overcounting is safer than a Goal running past its cap.
42
+ const anchors = new Map();
43
+ // Only a Goal that is still running keeps an anchor: an inactive one has its
44
+ // entry retired by armDeadline, and recording it again would leak.
45
+ const observe = (id, at, goal) => {
46
+ if (goal?.status !== 'active') return;
47
+ const mono = suspendDetectable ? monotonicNow?.() : null;
48
+ if (mono == null) anchors.delete(id);
49
+ else anchors.set(id, { wall: at, mono });
50
+ };
51
+ const suspendedMs = (id, at) => {
52
+ const anchor = anchors.get(id);
53
+ if (!anchor) return 0;
54
+ const mono = monotonicNow?.();
55
+ // No reading now: this interval has no evidence and is counted. The anchor
56
+ // goes too, or a later valid reading would discard time already counted.
57
+ if (mono == null) {
58
+ anchors.delete(id);
59
+ return 0;
60
+ }
61
+ const suspended = at - anchor.wall - (mono - anchor.mono);
62
+ return suspended > MIN_SUSPEND_EVIDENCE_MS ? suspended : 0;
63
+ };
64
+ const dropSuspended = (id, goal, at) => discardSuspendedTime(goal, at, suspendedMs(id, at));
14
65
  const deadlineTimers = new Map();
15
66
  const warningTimers = new Map();
67
+ const heartbeatTimers = new Map();
16
68
  const expiryPending = new Set();
17
69
  const warningPending = new Set();
18
70
  let closed = false;
@@ -30,25 +82,70 @@ export function createGoalDeadlines({ now, readRecord, withMutation, commit, onS
30
82
  }
31
83
  };
32
84
 
85
+ // The heartbeat has its own lifetime: re-arming the deadline (every snapshot
86
+ // read does) must not restart its interval, or reads under a minute apart
87
+ // would keep it from ever firing.
88
+ const stopHeartbeat = (sessionId) => {
89
+ const heartbeat = heartbeatTimers.get(sessionId);
90
+ if (heartbeat) {
91
+ clearInterval(heartbeat);
92
+ heartbeatTimers.delete(sessionId);
93
+ }
94
+ };
95
+
96
+ // An active Goal's clock runs only while a live runtime holds it. The beat
97
+ // re-stamps the record so a later load can tell how long nothing ran it; a
98
+ // beat that finds a long silence (the PC slept) drops that stretch from the
99
+ // open segment and republishes. The stamp alone is a silent write.
100
+ const beat = (sessionId) => {
101
+ const id = assertSessionId(sessionId);
102
+ void withMutation(id, async () => {
103
+ if (closed) return;
104
+ const goal = readRecord(id).goal;
105
+ if (goal?.status !== 'active' || !goal.lastStartedAt) return;
106
+ const at = now();
107
+ if (dropSuspended(id, goal, at)) await commit(id, goal);
108
+ else {
109
+ goal.liveAt = at;
110
+ await stampLive(id, goal);
111
+ }
112
+ observe(id, at, goal);
113
+ }).catch(onStorageError);
114
+ };
115
+
33
116
  const expiredProjection = (goal, at) => {
34
117
  if (goal?.status !== 'active' || !(goal.timeLimitMs > 0) || activeElapsedMs(goal, at) < goal.timeLimitMs)
35
118
  return false;
36
119
  stopActiveClock(goal, at);
37
120
  goal.status = 'duration_reached';
38
- goal.timeUsedMs = Math.max(goal.timeUsedMs, goal.timeLimitMs);
121
+ // The cap is what was spent; a late observation must not report more.
122
+ goal.timeUsedMs = goal.timeLimitMs;
39
123
  goal.updatedAt = at;
40
124
  return true;
41
125
  };
42
126
 
127
+ // Every path that reads an open segment against the clock (expiry, warnings,
128
+ // the next timer) first drops the suspended stretch, as the heartbeat does;
129
+ // otherwise a read after the PC slept would charge the sleep to the budget.
130
+ // `goal` is a working copy. Returns whether it changed.
131
+ const settleProjection = (id, goal, at) => {
132
+ const dropped = dropSuspended(id, goal, at);
133
+ return expiredProjection(goal, at) || dropped;
134
+ };
135
+
43
136
  const limitIfExpired = (sessionId) => {
44
137
  const id = assertSessionId(sessionId);
45
138
  const goal = readRecord(id).goal;
46
- if (expiredProjection(goal, now()) && !closed && !expiryPending.has(id)) {
139
+ if (settleProjection(id, goal, now()) && !closed && !expiryPending.has(id)) {
47
140
  expiryPending.add(id);
48
141
  void withMutation(id, async () => {
49
142
  if (closed) return;
50
143
  const current = readRecord(id).goal;
51
- if (expiredProjection(current, now())) await commit(id, current);
144
+ const at = now();
145
+ if (settleProjection(id, current, at)) {
146
+ await commit(id, current);
147
+ observe(id, at, current);
148
+ }
52
149
  })
53
150
  .catch(onStorageError)
54
151
  .finally(() => expiryPending.delete(id));
@@ -78,8 +175,9 @@ export function createGoalDeadlines({ now, readRecord, withMutation, commit, onS
78
175
  return delay;
79
176
  };
80
177
 
81
- const warningState = (goal, at) => {
178
+ const warningState = (id, goal, at) => {
82
179
  if (goal?.status !== 'active' || !goal.lastStartedAt || !(Number(goal.timeLimitMs) > 0)) return null;
180
+ dropSuspended(id, goal, at);
83
181
  const remainingMs = Math.max(0, Number(goal.timeLimitMs) - activeElapsedMs(goal, at));
84
182
  if (remainingMs <= 0) return null;
85
183
  return {
@@ -92,14 +190,14 @@ export function createGoalDeadlines({ now, readRecord, withMutation, commit, onS
92
190
  const deliverDeadlineWarning = (sessionId, thresholdMs = 0) => {
93
191
  const id = assertSessionId(sessionId);
94
192
  if (closed || warningPending.has(id)) return;
95
- const pending = warningState(readRecord(id).goal, now());
193
+ const pending = warningState(id, readRecord(id).goal, now());
96
194
  const target = thresholdMs || pending?.crossedMs || 0;
97
195
  if (!pending || !target || pending.crossedMs !== target) return;
98
196
  warningPending.add(id);
99
197
  void withMutation(id, async () => {
100
198
  if (closed) return;
101
199
  const current = readRecord(id).goal;
102
- const state = warningState(current, now());
200
+ const state = warningState(id, current, now());
103
201
  if (!state?.crossedMs || state.crossedMs !== target) return;
104
202
  current.deadlineWarnedMs = Math.min(normalizeDeadlineWarnedMs(current.deadlineWarnedMs), target);
105
203
  current.warningRevision = Math.max(0, Math.floor(Number(current.warningRevision) || 0)) + 1;
@@ -123,14 +221,26 @@ export function createGoalDeadlines({ now, readRecord, withMutation, commit, onS
123
221
  if (closed) return;
124
222
  clearDeadline(sessionId);
125
223
  const goal = readRecord(sessionId).goal;
224
+ if (goal?.status === 'active' && goal.lastStartedAt) {
225
+ if (!anchors.has(sessionId)) observe(sessionId, now(), goal);
226
+ if (!heartbeatTimers.has(sessionId)) {
227
+ const timer = setInterval(() => beat(sessionId), GOAL_LIVE_HEARTBEAT_MS);
228
+ timer.unref?.();
229
+ heartbeatTimers.set(sessionId, timer);
230
+ }
231
+ } else {
232
+ stopHeartbeat(sessionId);
233
+ anchors.delete(sessionId);
234
+ }
126
235
  if (goal?.status !== 'active' || !goal.lastStartedAt || !(Number(goal.timeLimitMs) > 0)) return;
127
236
  const at = now();
237
+ dropSuspended(sessionId, goal, at);
128
238
  const remainingMs = Math.max(0, goal.timeLimitMs - activeElapsedMs(goal, at));
129
239
  if (remainingMs <= 0) {
130
240
  queueMicrotask(() => reportingErrors(() => limitIfExpired(sessionId)));
131
241
  return;
132
242
  }
133
- const warnings = warningState(goal, at);
243
+ const warnings = warningState(sessionId, goal, at);
134
244
  if (warnings?.crossedMs) {
135
245
  const thresholdMs = warnings.crossedMs;
136
246
  queueMicrotask(() => reportingErrors(() => deliverDeadlineWarning(sessionId, thresholdMs)));
@@ -146,7 +256,19 @@ export function createGoalDeadlines({ now, readRecord, withMutation, commit, onS
146
256
  }
147
257
  const timer = setTimeout(() => {
148
258
  deadlineTimers.delete(sessionId);
149
- reportingErrors(() => limitIfExpired(sessionId));
259
+ // A timer that fires after the PC slept must not expire the Goal on
260
+ // time nobody ran: settle the suspension first, then decide.
261
+ reportingErrors(() => {
262
+ void withMutation(sessionId, async () => {
263
+ if (closed) return;
264
+ const current = readRecord(sessionId).goal;
265
+ const at = now();
266
+ if (current && settleProjection(sessionId, current, at)) {
267
+ await commit(sessionId, current);
268
+ observe(sessionId, at, current);
269
+ }
270
+ }).catch(onStorageError);
271
+ });
150
272
  }, remainingMs);
151
273
  timer.unref?.();
152
274
  deadlineTimers.set(sessionId, timer);
@@ -156,14 +278,23 @@ export function createGoalDeadlines({ now, readRecord, withMutation, commit, onS
156
278
  armDeadline,
157
279
  clearDeadline,
158
280
  limitIfExpired,
281
+ // A write is about to stamp `liveAt`: charge the pending suspend to the
282
+ // working copy first, so the stamp never hides it, and `refreshAnchor`
283
+ // starts the next evidence window once the write has landed.
284
+ settleSuspended: dropSuspended,
285
+ refreshAnchor: observe,
286
+ anchorCount: () => anchors.size,
159
287
  close() {
160
288
  closed = true;
161
289
  for (const timer of deadlineTimers.values()) clearTimeout(timer);
162
290
  deadlineTimers.clear();
163
291
  for (const timer of warningTimers.values()) clearTimeout(timer);
164
292
  warningTimers.clear();
293
+ for (const timer of heartbeatTimers.values()) clearInterval(timer);
294
+ heartbeatTimers.clear();
165
295
  warningPending.clear();
166
296
  expiryPending.clear();
297
+ anchors.clear();
167
298
  },
168
299
  };
169
300
  }