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
@@ -2,7 +2,7 @@ import { __mixdogMemoryLog } from './memory-log.mjs';
2
2
 
3
3
  import { embedTexts, getEmbeddingModelId, holdEmbeddingWarm } from './embedding-provider.mjs';
4
4
  import { onnxRuntimeSupported } from '../../shared/onnx-runtime-support.mjs';
5
- import { embeddingToSql } from './memory.mjs';
5
+ import { embeddingToSql, ROOT_EMBED_SWEEP_META_KEY } from './memory.mjs';
6
6
  import { createHash } from 'node:crypto';
7
7
  import { pruneEmbeddingCache, resolveEmbeddingCacheMaxRows } from './embedding-cache-retention.mjs';
8
8
  import { throwIfAborted, markStoreFault } from './memory-cycle-shared.mjs';
@@ -524,6 +524,18 @@ async function syncRawBatchEmbeddings(db, ids, options = {}) {
524
524
  return writtenIds;
525
525
  }
526
526
 
527
+ // A chunk root is also the chunk's first message: cycle1 promotes that raw row
528
+ // in place, and every other member keeps its own content vector. Embedding only
529
+ // element/summary left the first message's own words with no vector, so its
530
+ // details were reachable only through the lossy summary. The summary leads so a
531
+ // long message truncated at the model window still keeps it.
532
+ export function rootEmbedText(row) {
533
+ const head = [row?.element, row?.summary].filter(Boolean).join(' — ').trim();
534
+ if (!head) return '';
535
+ const content = isRawEmbeddable(row?.role, row?.content) ? row.content.trim() : '';
536
+ return content ? `${head}\n\n${content}` : head;
537
+ }
538
+
527
539
  // Batch-embed all ids in one DB round-trip for fetching, one API call per unique text,
528
540
  // then one VALUES UPDATE for all results.
529
541
  // Returns the list of ids that were actually UPDATEd with a fresh embedding.
@@ -535,9 +547,10 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
535
547
  throwIfAborted(signal);
536
548
  if (!onnxRuntimeSupported()) return [];
537
549
  const rows = (
538
- await db.query(`SELECT id, element, summary FROM memory.entries WHERE id = ANY($1::bigint[]) AND is_root = 1`, [
539
- ids,
540
- ])
550
+ await db.query(
551
+ `SELECT id, element, summary, content, role FROM memory.entries WHERE id = ANY($1::bigint[]) AND is_root = 1`,
552
+ [ids]
553
+ )
541
554
  ).rows;
542
555
  throwIfAborted(signal);
543
556
  if (rows.length === 0) return [];
@@ -546,8 +559,9 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
546
559
  throwIfAborted(signal);
547
560
  const expected = Number(dimsRow?.value ?? 0);
548
561
 
549
- const texts = rows.map((row) => [row.element, row.summary].filter(Boolean).join(' — ').trim());
550
- const vectors = await cachedEmbedTextBatch(db, texts.filter(Boolean), { signal });
562
+ const texts = rows.map(rootEmbedText);
563
+ const embedBatch = options.embedBatch ?? cachedEmbedTextBatch;
564
+ const vectors = await embedBatch(db, texts.filter(Boolean), { signal });
551
565
  throwIfAborted(signal);
552
566
  // Re-map: only non-empty texts were passed to cachedEmbedTextBatch
553
567
  let vecIdx = 0;
@@ -562,28 +576,29 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
562
576
  __mixdogMemoryLog(`[embed] dim mismatch (id=${row.id} got=${vector.length} expected=${expected})\n`);
563
577
  continue;
564
578
  }
565
- updates.push({ id: Number(row.id), element: row.element, summary: row.summary, vector });
579
+ updates.push({ id: Number(row.id), element: row.element, summary: row.summary, content: row.content, vector });
566
580
  }
567
581
 
568
582
  if (updates.length === 0) return [];
569
583
 
570
584
  // The VALUES update is one SQL batch; do not split it with an abort checkpoint.
571
585
  const valClauses = updates.map((_u, i) => {
572
- const base = i * 4;
573
- return `($${base + 1}::bigint, $${base + 2}::halfvec, $${base + 3}::text, $${base + 4}::text)`;
586
+ const base = i * 5;
587
+ return `($${base + 1}::bigint, $${base + 2}::halfvec, $${base + 3}::text, $${base + 4}::text, $${base + 5}::text)`;
574
588
  });
575
589
  const params = [];
576
590
  for (const u of updates) {
577
- params.push(u.id, embeddingToSql(u.vector), u.element, u.summary);
591
+ params.push(u.id, embeddingToSql(u.vector), u.element, u.summary, u.content);
578
592
  }
579
593
  const res = await db.query(
580
594
  `UPDATE memory.entries AS e
581
595
  SET embedding = t.vector
582
- FROM (VALUES ${valClauses.join(',')}) AS t(id, vector, element, summary)
596
+ FROM (VALUES ${valClauses.join(',')}) AS t(id, vector, element, summary, content)
583
597
  WHERE e.id = t.id AND e.is_root = 1
584
- AND e.embedding IS NULL
598
+ AND e.embedding IS ${options.replace ? 'NOT ' : ''}NULL
585
599
  AND e.element IS NOT DISTINCT FROM t.element
586
600
  AND e.summary IS NOT DISTINCT FROM t.summary
601
+ AND e.content IS NOT DISTINCT FROM t.content
587
602
  RETURNING e.id`,
588
603
  params
589
604
  );
@@ -596,7 +611,7 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
596
611
  const curRows = missedIds.length
597
612
  ? (
598
613
  await db.query(
599
- `SELECT id, embedding IS NOT NULL AS has_embedding, element, summary
614
+ `SELECT id, embedding IS NOT NULL AS has_embedding, element, summary, content
600
615
  FROM memory.entries WHERE id = ANY($1::bigint[]) AND is_root = 1`,
601
616
  [missedIds]
602
617
  )
@@ -610,7 +625,8 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
610
625
  if (!cur) continue;
611
626
  const textUnchanged =
612
627
  (cur.element === u.element || (cur.element == null && u.element == null)) &&
613
- (cur.summary === u.summary || (cur.summary == null && u.summary == null));
628
+ (cur.summary === u.summary || (cur.summary == null && u.summary == null)) &&
629
+ (cur.content === u.content || (cur.content == null && u.content == null));
614
630
  if (!textUnchanged) {
615
631
  staleSkips++;
616
632
  continue;
@@ -631,6 +647,59 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
631
647
  return writtenIds;
632
648
  }
633
649
 
650
+ // One paced step of the root-text sweep (startRootEmbedSweep in memory.mjs).
651
+ // Existing vectors stay searchable until replaced: the step claims the next
652
+ // roots below the stored cursor that still HAVE a vector, embeds their current
653
+ // text, replaces each vector only where the text is unchanged, and advances the
654
+ // cursor in the same transaction. The meta row lock serializes concurrent
655
+ // sweepers; any failure rolls back both vectors and cursor. Roots without a
656
+ // vector are left to the ordinary flush, which already uses the new text.
657
+ const ROOT_SWEEP_BATCH = 8;
658
+ export async function sweepRootEmbedText(db, options = {}) {
659
+ const { batchSize = ROOT_SWEEP_BATCH, signal, embedBatch } = options;
660
+ throwIfAborted(signal);
661
+ if (!onnxRuntimeSupported()) return { claimed: 0, replaced: 0, done: true };
662
+ await ensureEmbCacheTable(db);
663
+ const client = await db._pool.connect();
664
+ let committed = false;
665
+ try {
666
+ await client.query('BEGIN');
667
+ const state = await client.query(`SELECT value FROM memory.meta WHERE key = $1 FOR UPDATE`, [
668
+ ROOT_EMBED_SWEEP_META_KEY,
669
+ ]);
670
+ const value = state.rows[0]?.value;
671
+ const cursor = Number(value?.cursor);
672
+ if (!value || value.done === true || !Number.isFinite(cursor)) {
673
+ await client.query('COMMIT');
674
+ committed = true;
675
+ return { claimed: 0, replaced: 0, done: true };
676
+ }
677
+ const claim = await client.query(
678
+ `SELECT id FROM memory.entries
679
+ WHERE is_root = 1 AND id < $1 AND embedding IS NOT NULL
680
+ AND (element IS NOT NULL OR summary IS NOT NULL)
681
+ ORDER BY id DESC
682
+ LIMIT $2
683
+ FOR UPDATE`,
684
+ [cursor, batchSize]
685
+ );
686
+ const ids = claim.rows.map((r) => Number(r.id));
687
+ throwIfAborted(signal);
688
+ const written = ids.length ? await syncBatchEmbeddings(client, ids, { signal, replace: true, embedBatch }) : [];
689
+ const done = ids.length < batchSize;
690
+ await client.query(
691
+ `UPDATE memory.meta SET value = jsonb_build_object('cursor', $2::bigint, 'done', $3::boolean) WHERE key = $1`,
692
+ [ROOT_EMBED_SWEEP_META_KEY, ids.length ? Math.min(...ids) : cursor, done]
693
+ );
694
+ await client.query('COMMIT');
695
+ committed = true;
696
+ return { claimed: ids.length, replaced: written.length, done };
697
+ } finally {
698
+ if (!committed) await client.query('ROLLBACK').catch(() => {});
699
+ client.release();
700
+ }
701
+ }
702
+
634
703
  // Explicit maintenance must propagate partial failures and caller cancellation,
635
704
  // including callers sharing a flush that was started without their signal.
636
705
  export async function flushSearchEmbeddings(db, options = {}) {
@@ -1,7 +1,11 @@
1
1
  // Process lock for the standalone memory MCP entry.
2
2
  // A direct entry (including the isolated daemon child) owns this lock.
3
3
  import fs from 'node:fs';
4
- import { execFileSync } from 'node:child_process';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { killProcessTreesSync } from '../../agent/orchestrator/mcp/child-tree.mjs';
6
+
7
+ // Windows: the tree kill's whole pass (a table read under load included).
8
+ const PREVIOUS_SERVER_KILL_TIMEOUT_MS = 10_000;
5
9
 
6
10
  export function parsePositivePid(value) {
7
11
  const pid = Number(value);
@@ -18,42 +22,158 @@ export function isPidAliveLocal(pid) {
18
22
  }
19
23
  }
20
24
 
21
- function killPreviousServer(pid, log = () => {}) {
25
+ const POSIX_ENV = () => ({ ...process.env, LC_ALL: 'C', LANG: 'C' });
26
+
27
+ // When a process was created, as epoch-ms bounds { low, high } around the true
28
+ // time, or null when it cannot be read. Linux: /proc/<pid>/stat starttime
29
+ // (clock ticks since boot) against /proc/uptime, both read at `now`; the
30
+ // margin covers one tick, uptime's centiseconds and the read skew.
31
+ export function linuxCreatedBounds(statText, uptimeText, clockTicks, now) {
32
+ const ticks = linuxStartTicks(statText);
33
+ const startTicks = Number(ticks);
34
+ const uptime = Number(String(uptimeText || '').split(' ')[0]);
35
+ if (ticks === null || !Number.isFinite(uptime) || !(clockTicks > 0)) return null;
36
+ const created = now - (uptime - startTicks / clockTicks) * 1000;
37
+ const margin = 1000 / clockTicks + 20;
38
+ return { low: created - margin, high: created + margin };
39
+ }
40
+
41
+ // Elsewhere: `ps -o lstart=` under the C locale, whole seconds.
42
+ export function lstartCreatedBounds(text) {
43
+ const started = Date.parse(String(text || '').trim());
44
+ return Number.isFinite(started) ? { low: started, high: started + 1000 } : null;
45
+ }
46
+
47
+ // /proc/<pid>/stat's starttime field (clock ticks since boot), as its digits.
48
+ function linuxStartTicks(statText) {
49
+ const stat = String(statText || '');
50
+ if (!stat.includes(')')) return null;
51
+ const ticks = stat.slice(stat.lastIndexOf(')') + 2).split(' ')[19];
52
+ return /^\d+$/.test(ticks || '') ? ticks : null;
53
+ }
54
+
55
+ /** A process's start token, as recorded in the lock and compared on
56
+ * recovery, or null when it cannot be read: Linux `/proc/<pid>/stat`
57
+ * starttime, elsewhere `ps -o lstart=` under the C locale. The two sources
58
+ * never mix (each is prefixed), and both are read from the same host. */
59
+ export function linuxStartToken(statText) {
60
+ const ticks = linuxStartTicks(statText);
61
+ return ticks ? `proc:${ticks}` : null;
62
+ }
63
+ export function lstartStartToken(text) {
64
+ const started = String(text || '').trim();
65
+ return Number.isFinite(Date.parse(started)) ? `lstart:${started}` : null;
66
+ }
67
+
68
+ function posixStartToken(pid) {
69
+ try {
70
+ if (process.platform === 'linux') return linuxStartToken(fs.readFileSync(`/proc/${pid}/stat`, 'utf8'));
71
+ const r = spawnSync('ps', ['-o', 'lstart=', '-p', String(pid)], {
72
+ encoding: 'utf8',
73
+ env: POSIX_ENV(),
74
+ timeout: 5000,
75
+ });
76
+ return lstartStartToken(r.stdout);
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ function posixCreatedBounds(pid) {
83
+ const env = POSIX_ENV();
84
+ try {
85
+ if (process.platform === 'linux') {
86
+ const ticks = Number(spawnSync('getconf', ['CLK_TCK'], { encoding: 'utf8', env, timeout: 5000 }).stdout);
87
+ const now = Date.now();
88
+ return linuxCreatedBounds(
89
+ fs.readFileSync(`/proc/${pid}/stat`, 'utf8'),
90
+ fs.readFileSync('/proc/uptime', 'utf8'),
91
+ ticks,
92
+ now
93
+ );
94
+ }
95
+ const r = spawnSync('ps', ['-o', 'lstart=', '-p', String(pid)], { encoding: 'utf8', env, timeout: 5000 });
96
+ return lstartCreatedBounds(r.stdout);
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ /** Who holds a lock's pid, from the start token the writer recorded and the
103
+ * pid's current one: 'writer' (the same token: the server that wrote the
104
+ * lock), 'other' (a different one: the writer is gone and its pid reused) or
105
+ * 'unproven' (the current token cannot be read). */
106
+ export function lockHolderTokenVerdict(recordedToken, currentToken) {
107
+ if (!currentToken) return 'unproven';
108
+ return currentToken === recordedToken ? 'writer' : 'other';
109
+ }
110
+
111
+ /** The legacy verdict, for a lock written without a start token: from the
112
+ * holder's creation bounds, 'writer' (created before the lock was written),
113
+ * 'other' (created after) or 'unproven' (unreadable, or too close to the
114
+ * write to tell). Only 'writer' may be signalled, and only 'other' or a dead
115
+ * pid lets the lock be recovered. */
116
+ export function lockHolderVerdict(bounds, lockWrittenAt) {
117
+ if (!bounds) return 'unproven';
118
+ if (bounds.high <= lockWrittenAt) return 'writer';
119
+ if (bounds.low > lockWrittenAt) return 'other';
120
+ return 'unproven';
121
+ }
122
+
123
+ /** The lock's text: the writer's pid, then (POSIX) its start token on the
124
+ * next line. */
125
+ export function formatLock(pid, token) {
126
+ return token ? `${pid}\n${token}` : String(pid);
127
+ }
128
+ export function parseLock(text) {
129
+ const [pidLine = '', token = ''] = String(text || '').split(/\r?\n/);
130
+ return { pid: Number(pidLine.trim()), token: token.trim() || null };
131
+ }
132
+
133
+ // The lock names its writer only while that writer lives. A POSIX writer
134
+ // recorded its start token; the pid's holder is the writer exactly when its
135
+ // token is still the same. Accepted: lstart has whole seconds, so a writer
136
+ // that died within its first second, its pid reused in that same second,
137
+ // would pass as the writer; sequential pid allocation makes that impractical,
138
+ // and treating same-second tokens as unproven would block every restart.
139
+ // Windows compares the holder's creation time against
140
+ // the lock's write time inside the shared tree kill: its creation times are
141
+ // sub-millisecond, and a pid is only reused after the writer died, after the
142
+ // write. A lock without a token (an older writer) keeps the creation-bounds
143
+ // verdict. A pid taken over since is never signalled; one whose identity
144
+ // cannot be proven is neither signalled nor evicted (fail closed).
145
+ function killPreviousServer(pid, recordedToken, lockWrittenAt, log = () => {}) {
22
146
  if (pid <= 0 || pid === process.pid) return false;
23
147
  if (process.platform === 'win32') {
24
- try {
25
- execFileSync('taskkill', ['/F', '/T', '/PID', String(pid)], {
26
- encoding: 'utf8',
27
- timeout: 5000,
28
- windowsHide: true,
29
- });
148
+ const status = killProcessTreesSync([pid], {
149
+ aliveAt: lockWrittenAt,
150
+ timeoutMs: PREVIOUS_SERVER_KILL_TIMEOUT_MS,
151
+ });
152
+ if (status === 0) {
30
153
  log(`[memory-service] Killed previous server PID ${pid}\n`);
31
154
  return true;
32
- } catch (e) {
33
- // Exit code 128 = process not found; treat stale lock as already-dead = success.
34
- // Status 128 reliably means "process not found" regardless of locale; no text match needed.
35
- // Status 1 with English text match handles edge cases on some Windows versions.
36
- const notFoundText =
37
- /not found|no running instance/i.test(e.stdout || '') ||
38
- /not found|no running instance/i.test(e.stderr || '') ||
39
- /not found|no running instance/i.test(e.message || '');
40
- const alreadyDead = e.status === 128 || (e.status === 1 && notFoundText);
41
- if (alreadyDead) {
42
- log(`[memory-service] PID ${pid} already dead (stale lock), proceeding\n`);
43
- return true;
44
- }
45
- log(`[memory-service] taskkill failed for PID ${pid}: ${e.message}\n`);
46
- return false;
47
155
  }
156
+ if (status === 128) {
157
+ log(`[memory-service] PID ${pid} is not the lock's writer any more (stale lock), proceeding\n`);
158
+ return true;
159
+ }
160
+ log(`[memory-service] Killing previous server PID ${pid} failed (status ${status})\n`);
161
+ return false;
48
162
  } else {
49
- // Pre-flight: if the process is already gone, treat stale lock as success.
50
- try {
51
- process.kill(pid, 0);
52
- } catch (e) {
53
- if (e.code === 'ESRCH') {
54
- log(`[memory-service] PID ${pid} already dead (stale lock), proceeding\n`);
55
- return true;
56
- }
163
+ const verdict = !isPidAliveLocal(pid)
164
+ ? 'gone'
165
+ : recordedToken
166
+ ? lockHolderTokenVerdict(recordedToken, posixStartToken(pid))
167
+ : lockHolderVerdict(posixCreatedBounds(pid), lockWrittenAt);
168
+ if (verdict === 'gone' || verdict === 'other' || (verdict === 'unproven' && !isPidAliveLocal(pid))) {
169
+ log(`[memory-service] PID ${pid} is not the lock's writer any more (stale lock), proceeding\n`);
170
+ return true;
171
+ }
172
+ if (verdict === 'unproven') {
173
+ log(
174
+ `[memory-service] PID ${pid} may still be the lock's writer and cannot be proven otherwise; leaving it running\n`
175
+ );
176
+ return false;
57
177
  }
58
178
  try {
59
179
  process.kill(pid, 'SIGTERM');
@@ -86,9 +206,10 @@ export function acquireLock(lockFile, log = () => {}) {
86
206
  if (process.env.MIXDOG_MULTI_INSTANCE === '1') return;
87
207
  try {
88
208
  if (fs.existsSync(lockFile)) {
89
- const lockedPid = Number(fs.readFileSync(lockFile, 'utf8').trim());
209
+ const lockWrittenAt = fs.statSync(lockFile).mtimeMs;
210
+ const { pid: lockedPid, token } = parseLock(fs.readFileSync(lockFile, 'utf8'));
90
211
  if (lockedPid > 0 && lockedPid !== process.pid) {
91
- const killed = killPreviousServer(lockedPid, log);
212
+ const killed = killPreviousServer(lockedPid, token, lockWrittenAt, log);
92
213
  if (!killed) {
93
214
  log(`[memory-service] Could not kill previous server PID ${lockedPid}, aborting\n`);
94
215
  process.exit(1);
@@ -98,9 +219,12 @@ export function acquireLock(lockFile, log = () => {}) {
98
219
  } catch {}
99
220
  }
100
221
  }
222
+ // Read before the lock exists: an unreadable token leaves a pid-only lock,
223
+ // which a successor judges by the legacy verdict.
224
+ const token = process.platform === 'win32' ? null : posixStartToken(process.pid);
101
225
  const fd = fs.openSync(lockFile, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
102
226
  try {
103
- fs.writeSync(fd, String(process.pid));
227
+ fs.writeSync(fd, formatLock(process.pid, token));
104
228
  } finally {
105
229
  fs.closeSync(fd);
106
230
  }
@@ -116,7 +240,6 @@ export function acquireLock(lockFile, log = () => {}) {
116
240
 
117
241
  export function releaseLock(lockFile) {
118
242
  try {
119
- const content = fs.readFileSync(lockFile, 'utf8').trim();
120
- if (Number(content) === process.pid) fs.unlinkSync(lockFile);
243
+ if (parseLock(fs.readFileSync(lockFile, 'utf8')).pid === process.pid) fs.unlinkSync(lockFile);
121
244
  } catch {}
122
245
  }
@@ -130,7 +130,12 @@ export function mergeRecallConceptTokens(text, conceptTokens, limit = 24) {
130
130
  const entityConcepts = concepts.filter(isEntityConcept);
131
131
  const rankingEntityConcepts = entityConcepts.filter((token) => !isStructuralTimeToken(token));
132
132
  const useEntityConcepts = rankingEntityConcepts.some(isIdentifierToken) || rankingEntityConcepts.length > 2;
133
- const rankingConcepts = useEntityConcepts ? entityConcepts : concepts;
133
+ // Entity-heavy queries rank their entities first, but the Korean content
134
+ // words stay: dropping them left a mixed query ("웹 검색 원인 Claude Codex
135
+ // Hermes 비교") matching only on product names that half the history shares.
136
+ const rankingConcepts = useEntityConcepts
137
+ ? [...entityConcepts, ...concepts.filter((token) => !isEntityConcept(token))]
138
+ : concepts;
134
139
  const source =
135
140
  concepts.length > 0
136
141
  ? [...raw.filter((token) => isIdentifierToken(token) || isConceptCompound(token, concepts)), ...rankingConcepts]
@@ -309,6 +309,22 @@ async function removeReviewAndPromotionCycleState(db) {
309
309
  }
310
310
  }
311
311
 
312
+ // Root vectors now cover the chunk's first message as well as its summary
313
+ // (memory-embed.mjs rootEmbedText). Vectors written under the older text stay
314
+ // in place and are replaced by a paced background sweep (sweepRootEmbedText)
315
+ // that walks roots below this starting cursor, newest first. One statement, so
316
+ // the start is atomic; ON CONFLICT keeps an existing sweep's progress.
317
+ export const ROOT_EMBED_SWEEP_META_KEY = 'embedding.root_text_v2';
318
+ export async function startRootEmbedSweep(db) {
319
+ await db.query(
320
+ `INSERT INTO meta(key, value)
321
+ SELECT $1, jsonb_build_object('cursor', COALESCE(MAX(id), 0) + 1, 'done', false)
322
+ FROM entries WHERE is_root = 1
323
+ ON CONFLICT (key) DO NOTHING`,
324
+ [ROOT_EMBED_SWEEP_META_KEY]
325
+ );
326
+ }
327
+
312
328
  export async function ensureCurrentSchemaExtensions(db, dims, embeddingIdentity = null) {
313
329
  await removeAttachmentPlaceholderRows(db);
314
330
  await removeRuntimeNotificationRowsOnce(db);
@@ -413,6 +429,7 @@ export async function openDatabase(dataDir, dims, embeddingIdentity = null) {
413
429
  }
414
430
  await ensureCurrentSchemaExtensions(db, Number(dims), embeddingIdentity);
415
431
  await validateEmbeddingDims(db, Number(dims));
432
+ await withSchemaBootstrapLock(pool, () => startRootEmbedSweep(db));
416
433
 
417
434
  dbs.set(key, db);
418
435
  return db;
@@ -400,7 +400,7 @@ async function startAndWaitReady({ pgctl, startArgs, env, pgdataDir, port }) {
400
400
  // so the postmaster is already reparented OUT of the Node process tree —
401
401
  // detached only isolated the short-lived pg_ctl. Shutdown/reuse target
402
402
  // pgdata/postmaster.pid (pg_ctl stop -D, tryReusePgInstance), not a
403
- // Node-tree taskkill, so `windowsHide` alone is correct on all platforms.
403
+ // Node-tree kill, so `windowsHide` alone is correct on all platforms.
404
404
  const child = spawn(pgctl, startArgs, { env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
405
405
  child.unref?.();
406
406
  const run = { stdout: '', stderr: '', closed: false, exitCode: null };
@@ -20,7 +20,7 @@ import { __mixdogMemoryLog } from '../memory-log.mjs';
20
20
  */
21
21
 
22
22
  import { createServer } from 'node:net';
23
- import { existsSync, unlinkSync, readFileSync, writeFileSync, renameSync, statSync, mkdirSync } from 'node:fs';
23
+ import { existsSync, unlinkSync, readFileSync, renameSync, statSync, mkdirSync } from 'node:fs';
24
24
  import { join, resolve } from 'node:path';
25
25
  import {
26
26
  discoveryPath as _pgDiscoveryPath,
@@ -28,6 +28,7 @@ import {
28
28
  writeServiceAdvert as _writePgServiceAdvert,
29
29
  } from '../../../shared/service-discovery.mjs';
30
30
  import { withFileLockSync } from '../../../shared/atomic-file.mjs';
31
+ import { withFileLock } from '../../../shared/file-lock.mjs';
31
32
  import { isPidAlive } from '../../../shared/pid-liveness.mjs';
32
33
  import { envFlag } from '../../../shared/env.mjs';
33
34
  import { createSharedAbortable } from '../../../shared/abort-race.mjs';
@@ -64,92 +65,11 @@ let _ownedExitStopInstalled = false;
64
65
  const PG_PORT_MIN = 55432;
65
66
  const PG_PORT_MAX = 55632;
66
67
  const PG_LOG_MAX_BYTES = 10 * 1024 * 1024; // 10 MB
67
- const SPAWN_LOCK_NAME = 'pg-spawn.lock';
68
- const LOCK_WAIT_MS = 30_000;
69
- const LOCK_POLL_MS = 100;
70
- const LOCK_WARN_MS = 5_000;
71
- const LOCK_WAIT_CODES = new Set(['EEXIST', 'EPERM', 'EACCES', 'EBUSY']);
72
68
 
73
69
  function attachOnlyMode() {
74
70
  return envFlag('MIXDOG_PG_ATTACH_ONLY') || envFlag('MIXDOG_MEMORY_SECONDARY');
75
71
  }
76
72
 
77
- // ── File lock (O_EXCL pattern from dispatch-persist.mjs / run-mcp.mjs) ───────
78
-
79
- /**
80
- * Acquire `${dataDir}/pg-spawn.lock`.
81
- * Returns the lock-file path on success, or null on timeout (caller proceeds
82
- * best-effort — worst case: two supervisors race initdb on the same pgdata,
83
- * but the second will fail pg_ctl start gracefully and healthcheck will
84
- * converge on the first's port).
85
- */
86
- async function acquireSpawnLock(dataDir, blockedProbe = null) {
87
- const lp = join(dataDir, SPAWN_LOCK_NAME);
88
- const deadline = Date.now() + LOCK_WAIT_MS;
89
- const body = JSON.stringify({ pid: process.pid, startedAt: Date.now() });
90
- let warned = false;
91
- for (;;) {
92
- try {
93
- // 'wx' = O_CREAT | O_EXCL — atomic, fails with EEXIST if held.
94
- writeFileSync(lp, body, { flag: 'wx' });
95
- return { lockPath: lp, reuse: null };
96
- } catch (err) {
97
- if (!LOCK_WAIT_CODES.has(err?.code)) {
98
- __mixdogMemoryLog(`[supervisor-pg] spawn lock error: ${err?.code || err?.message}\n`);
99
- return { lockPath: null, reuse: null };
100
- }
101
- if (blockedProbe) {
102
- try {
103
- const reuse = await blockedProbe();
104
- if (reuse) return { lockPath: null, reuse };
105
- } catch {}
106
- }
107
- // Dead-holder detection: same as run-mcp.mjs acquireLock().
108
- try {
109
- const holder = JSON.parse(readFileSync(lp, 'utf8'));
110
- if (holder?.pid) {
111
- try {
112
- process.kill(holder.pid, 0);
113
- } catch (ke) {
114
- if (ke.code === 'ESRCH') {
115
- try {
116
- unlinkSync(lp);
117
- } catch {}
118
- continue; // retry immediately after removing stale lock
119
- }
120
- }
121
- }
122
- const ageMs = Date.now() - Number(holder?.startedAt || 0);
123
- if (!warned && ageMs >= LOCK_WARN_MS) {
124
- warned = true;
125
- __mixdogMemoryLog(
126
- `[supervisor-pg] waiting for spawn lock holder pid=${holder?.pid || 'unknown'} ageMs=${ageMs}\n`
127
- );
128
- }
129
- } catch {
130
- /* unreadable — fall through and wait */
131
- }
132
- if (Date.now() >= deadline) {
133
- if (blockedProbe) {
134
- try {
135
- const reuse = await blockedProbe();
136
- if (reuse) return { lockPath: null, reuse };
137
- } catch {}
138
- }
139
- throw new Error(`[supervisor-pg] spawn-lock acquire timeout (concurrent supervisor risk)`);
140
- }
141
- await new Promise((r) => setTimeout(r, LOCK_POLL_MS));
142
- }
143
- }
144
- }
145
-
146
- function releaseSpawnLock(lp) {
147
- if (!lp) return;
148
- try {
149
- unlinkSync(lp);
150
- } catch {}
151
- }
152
-
153
73
  // ── Port allocation ──────────────────────────────────────────────────────────
154
74
 
155
75
  function _probePort(port) {
@@ -602,18 +522,10 @@ async function _doEnsure(dataDir, signal) {
602
522
  throw new Error('secondary memory runtime requires an existing PG instance');
603
523
  }
604
524
 
605
- // ── Acquire spawn lock to serialize initdb races ─────────────────────────
606
- const acquired = await acquireSpawnLock(dataDir, () =>
607
- tryReusePgInstance({
608
- pgdata,
609
- runtimeDir,
610
- healthcheckPg,
611
- source: 'lock-wait',
612
- })
613
- );
614
- if (acquired?.reuse) return acquired.reuse;
615
- const lp = acquired?.lockPath ?? null;
616
- try {
525
+ // ── Spawn lock serializes initdb races (see withFileLock below) ──────────
526
+ let lockEntered = false;
527
+ const spawnUnderLock = async () => {
528
+ lockEntered = true;
617
529
  // ── Reuse path: another supervisor already started PG ─────────────────
618
530
  const lockedReuse = await tryReusePgInstance({
619
531
  pgdata,
@@ -701,8 +613,16 @@ async function _doEnsure(dataDir, signal) {
701
613
  // ── Allocate a fresh port and spawn ───────────────────────────────────
702
614
  const port = await allocatePort();
703
615
  return await _startFresh(dataDir, pgdata, port, runtimeDir);
704
- } finally {
705
- releaseSpawnLock(lp);
616
+ };
617
+ try {
618
+ return await withFileLock(join(dataDir, 'pg-spawn.lock'), spawnUnderLock, { timeoutMs: 30_000 });
619
+ } catch (err) {
620
+ // Only an acquisition timeout (spawnUnderLock never ran) falls back to
621
+ // reuse; failures from inside the locked region propagate untouched.
622
+ if (lockEntered || err?.code !== 'ELOCKTIMEOUT') throw err;
623
+ const reuse = await tryReusePgInstance({ pgdata, runtimeDir, healthcheckPg, source: 'lock-wait' });
624
+ if (reuse) return reuse;
625
+ throw new Error('[supervisor-pg] spawn-lock acquire timeout (concurrent supervisor risk)', { cause: err });
706
626
  }
707
627
  }
708
628
 
@@ -119,6 +119,11 @@ exact AS (
119
119
  WHERE false
120
120
  ),`;
121
121
  }
122
+ // Each row is lowercased once (OFFSET 0 keeps the subquery from being
123
+ // inlined into the per-term condition) and every term is a literal strpos
124
+ // on that text. Per-term ILIKE re-lowercased the whole content for every
125
+ // term, so a natural-language query's 10+ terms multiplied the scan cost.
126
+ // Terms arrive lowercased from buildExactTerms.
122
127
  return `
123
128
  exact_matches AS (
124
129
  SELECT ee.id,
@@ -126,10 +131,14 @@ exact_matches AS (
126
131
  q.term,
127
132
  COUNT(*) OVER (PARTITION BY q.term)::float8 AS term_df
128
133
  FROM entries ee
134
+ CROSS JOIN LATERAL (
135
+ SELECT lower(ee.content) AS content, lower(ee.element) AS element, lower(ee.summary) AS summary
136
+ OFFSET 0
137
+ ) lc
129
138
  JOIN LATERAL unnest($${exactTermsParam}::text[]) AS q(term) ON (
130
- ee.content ILIKE '%' || q.term || '%'
131
- OR ${recallSubstringPredicate('ee.element', 'q.term')}
132
- OR ${recallSubstringPredicate('ee.summary', 'q.term')}
139
+ strpos(lc.content, q.term) > 0
140
+ OR strpos(lc.element, q.term) > 0
141
+ OR strpos(lc.summary, q.term) > 0
133
142
  )
134
143
  WHERE true
135
144
  ${filters.exactFilterClause}
@@ -17,7 +17,7 @@ import { renderPdfPages } from '../pdf/pdf-render.mjs';
17
17
  const PX_TO_PT = 0.75;
18
18
 
19
19
  /** Artifacts kept beside the document: its HTML and the browser's print of it. */
20
- export function docxHtmlArtifacts(target) {
20
+ function docxHtmlArtifacts(target) {
21
21
  return {
22
22
  source: `${target}.mixdog-source.html`,
23
23
  print: `${target}.mixdog-html.pdf`,
@@ -34,7 +34,7 @@ const PPTX_AUTHOR_NEEDS_SOURCE =
34
34
  'author requires script: HTML slides or a pptxgenjs script (without one it re-authors from <deck>.pptx.mixdog-source.html, and none is there). Load the `pptx` Skill first (Skill name:"pptx"): it carries the authoring workflow, the HTML contract, the composition grammar, and the device kit, then call author again with path and script.';
35
35
 
36
36
  // HTML opens with markup (a doctype, a comment, an element); a pptxgenjs script never does.
37
- export function isHtmlSource(source) {
37
+ function isHtmlSource(source) {
38
38
  return /^\s*</.test(String(source || ''));
39
39
  }
40
40