mixdog 0.9.95 → 0.9.97

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 (282) hide show
  1. package/LICENSES/Apache-2.0.txt +201 -0
  2. package/LICENSES/MIT.txt +56 -0
  3. package/LICENSES/codex-NOTICE.txt +6 -0
  4. package/NOTICE.md +46 -9
  5. package/README.md +0 -17
  6. package/package.json +16 -12
  7. package/scripts/.tmp-coverage-report.mjs +96 -0
  8. package/scripts/build-tui.mjs +17 -1
  9. package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
  10. package/scripts/fixtures/patch-replay-corpus.json +98 -0
  11. package/src/defaults/mixdog-config.template.json +5 -8
  12. package/src/defaults/skills/setup/SKILL.md +1 -1
  13. package/src/lib/keychain-cjs.cjs +1 -1
  14. package/src/lib/mixdog-debug.cjs +2 -3
  15. package/src/rules/agent/30-explorer.md +7 -4
  16. package/src/rules/lead/01-general.md +1 -2
  17. package/src/rules/lead/lead-tool.md +0 -3
  18. package/src/rules/shared/01-tool.md +21 -29
  19. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
  21. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
  22. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
  23. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
  24. package/src/runtime/agent/orchestrator/config.mjs +246 -67
  25. package/src/runtime/agent/orchestrator/mcp/client.mjs +104 -41
  26. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
  27. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
  28. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
  29. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
  30. package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
  31. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
  32. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
  33. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
  34. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
  35. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
  36. package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
  37. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
  38. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
  39. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
  40. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
  41. package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
  42. package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
  43. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +36 -10
  44. package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -33
  46. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
  47. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
  48. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
  49. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +21 -29
  50. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +7 -10
  51. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +45 -25
  52. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
  53. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
  54. package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
  55. package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
  56. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
  57. package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
  58. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
  59. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
  60. package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
  61. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
  62. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
  63. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
  64. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +71 -42
  65. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +4 -33
  66. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
  67. package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
  69. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
  70. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +116 -26
  71. package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
  72. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
  73. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
  74. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
  75. package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
  76. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
  77. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +12 -26
  78. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +5 -5
  79. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -33
  80. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +42 -45
  81. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +27 -9
  82. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
  83. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
  84. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
  85. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
  86. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
  87. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
  88. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
  89. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
  90. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +142 -26
  91. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
  92. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +47 -19
  93. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -2
  94. package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
  95. package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
  96. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
  97. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +94 -20
  99. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
  100. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
  101. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +12 -11
  102. package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +63 -9
  104. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
  105. package/src/runtime/channels/lib/config.mjs +34 -34
  106. package/src/runtime/channels/lib/inbound-handler.mjs +8 -8
  107. package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
  108. package/src/runtime/channels/lib/memory-client.mjs +31 -157
  109. package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
  110. package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
  111. package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
  112. package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
  113. package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
  114. package/src/runtime/channels/lib/scheduler.mjs +2 -2
  115. package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
  116. package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
  117. package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
  118. package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
  119. package/src/runtime/channels/lib/worker-main.mjs +38 -38
  120. package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
  121. package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
  122. package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
  123. package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
  124. package/src/runtime/channels/tool-defs.mjs +1 -1
  125. package/src/runtime/memory/index.mjs +76 -248
  126. package/src/runtime/memory/lib/agent-ipc.mjs +146 -81
  127. package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
  128. package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
  129. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +13 -34
  130. package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
  131. package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
  132. package/src/runtime/memory/lib/http-router.mjs +9 -6
  133. package/src/runtime/memory/lib/ko-morph.mjs +3 -20
  134. package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
  135. package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
  136. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
  137. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
  138. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
  139. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
  140. package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
  141. package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
  142. package/src/runtime/memory/lib/memory-log.mjs +1 -1
  143. package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
  144. package/src/runtime/memory/lib/memory.mjs +1 -1
  145. package/src/runtime/memory/lib/pg/process.mjs +32 -9
  146. package/src/runtime/memory/lib/session-ingest.mjs +1 -1
  147. package/src/runtime/memory/tool-defs.mjs +1 -5
  148. package/src/runtime/search/index.mjs +2 -2
  149. package/src/runtime/search/tool-defs.mjs +2 -2
  150. package/src/runtime/shared/abort-race.mjs +70 -0
  151. package/src/runtime/shared/agent-route-config.mjs +119 -0
  152. package/src/runtime/shared/atomic-file.mjs +80 -4
  153. package/src/runtime/shared/channel-notification-routing.mjs +2 -2
  154. package/src/runtime/shared/child-guardian.mjs +204 -108
  155. package/src/runtime/shared/child-spawn-gate.mjs +139 -72
  156. package/src/runtime/shared/config.mjs +120 -47
  157. package/src/runtime/shared/llm/index.mjs +1 -1
  158. package/src/runtime/shared/owner-fair-gate.mjs +134 -0
  159. package/src/runtime/shared/resource-admission.mjs +150 -100
  160. package/src/runtime/shared/stream-progress.mjs +6 -0
  161. package/src/runtime/shared/tool-execution-owner.mjs +12 -0
  162. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  163. package/src/runtime/shared/tool-surface.mjs +91 -27
  164. package/src/runtime/shared/tool-workload-gates.mjs +29 -0
  165. package/src/runtime/shared/turn-snapshot.mjs +47 -6
  166. package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
  167. package/src/session-runtime/channel-config-api.mjs +5 -5
  168. package/src/session-runtime/config-helpers.mjs +39 -37
  169. package/src/session-runtime/config-lifecycle.mjs +27 -27
  170. package/src/session-runtime/lifecycle-api.mjs +22 -3
  171. package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
  172. package/src/session-runtime/model-route-api.mjs +7 -5
  173. package/src/session-runtime/prewarm.mjs +1 -1
  174. package/src/session-runtime/provider-auth-api.mjs +0 -7
  175. package/src/session-runtime/provider-models.mjs +1 -1
  176. package/src/session-runtime/remote-control.mjs +6 -7
  177. package/src/session-runtime/runtime-core.mjs +63 -39
  178. package/src/session-runtime/runtime-tunables.mjs +4 -4
  179. package/src/session-runtime/self-update.mjs +1 -1
  180. package/src/session-runtime/session-lifecycle.mjs +45 -17
  181. package/src/session-runtime/session-title.mjs +1 -1
  182. package/src/session-runtime/session-turn-api.mjs +96 -24
  183. package/src/session-runtime/settings-api.mjs +12 -24
  184. package/src/session-runtime/tool-defs.mjs +3 -3
  185. package/src/session-runtime/workflow-agents-api.mjs +55 -81
  186. package/src/session-runtime/workflow.mjs +14 -24
  187. package/src/standalone/agent-dispatch-broker.mjs +198 -0
  188. package/src/standalone/agent-tool/helpers.mjs +2 -2
  189. package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
  190. package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
  191. package/src/standalone/agent-tool/tool-def.mjs +0 -14
  192. package/src/standalone/channel-admin.mjs +53 -64
  193. package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +33 -19
  194. package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +127 -46
  195. package/src/standalone/channel-worker.mjs +124 -647
  196. package/src/standalone/{backend-daemon.mjs → daemon.mjs} +270 -154
  197. package/src/standalone/explore-tool.mjs +187 -70
  198. package/src/standalone/fair-call-scheduler.mjs +264 -0
  199. package/src/standalone/memory-runtime-proxy.mjs +58 -1
  200. package/src/standalone/session-client.mjs +1134 -0
  201. package/src/standalone/session-protocol.mjs +192 -0
  202. package/src/standalone/session-runtime-pool.mjs +393 -0
  203. package/src/standalone/session-runtime-record.mjs +20 -0
  204. package/src/standalone/session-runtime-worker.mjs +203 -0
  205. package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +481 -224
  206. package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +390 -121
  207. package/src/standalone/session-wire.mjs +55 -0
  208. package/src/tui/App.jsx +34 -21
  209. package/src/tui/app/app-format.mjs +1 -1
  210. package/src/tui/app/app-view.jsx +5 -2
  211. package/src/tui/app/channel-pickers.mjs +41 -41
  212. package/src/tui/app/core-memory-picker.mjs +19 -19
  213. package/src/tui/app/doctor.mjs +5 -11
  214. package/src/tui/app/input-parsers.mjs +2 -2
  215. package/src/tui/app/message-selector.mjs +103 -0
  216. package/src/tui/app/onboarding-steps.mjs +3 -4
  217. package/src/tui/app/project-picker.mjs +80 -56
  218. package/src/tui/app/prompt-submit.mjs +48 -43
  219. package/src/tui/app/resume-picker.mjs +2 -2
  220. package/src/tui/app/route-pickers.mjs +3 -1
  221. package/src/tui/app/settings-picker.mjs +31 -33
  222. package/src/tui/app/transcript-window.mjs +38 -0
  223. package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
  224. package/src/tui/app/use-prompt-handlers.mjs +86 -21
  225. package/src/tui/app/use-prompt-hint.mjs +3 -2
  226. package/src/tui/app/use-prompt-queue-history.mjs +10 -3
  227. package/src/tui/app/use-transcript-scroll.mjs +10 -3
  228. package/src/tui/app/use-transcript-window.mjs +50 -17
  229. package/src/tui/components/PromptInput.jsx +71 -38
  230. package/src/tui/components/Spinner.jsx +1 -1
  231. package/src/tui/components/ToolExecution.jsx +1 -1
  232. package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
  233. package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
  234. package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
  235. package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
  236. package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
  237. package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
  238. package/src/tui/dist/index.mjs +1031 -1676
  239. package/src/tui/hooks/useSession.mjs +16 -0
  240. package/src/tui/index.jsx +12 -13
  241. package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
  242. package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
  243. package/src/tui/{engine → session}/boot-profile.mjs +2 -2
  244. package/src/tui/{engine → session}/context-state.mjs +3 -3
  245. package/src/tui/{engine → session}/labels.mjs +2 -2
  246. package/src/tui/{engine → session}/live-share.mjs +38 -4
  247. package/src/tui/{engine → session}/notice-text.mjs +2 -2
  248. package/src/tui/{engine → session}/notification-plan.mjs +3 -3
  249. package/src/tui/session/oauth-flows.mjs +140 -0
  250. package/src/tui/{engine → session}/prompt-history.mjs +3 -3
  251. package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
  252. package/src/tui/{engine → session}/render-timing.mjs +2 -2
  253. package/src/tui/{engine → session}/session-api-ext.mjs +30 -20
  254. package/src/tui/{engine → session}/session-api.mjs +110 -93
  255. package/src/tui/{engine → session}/session-flow.mjs +8 -2
  256. package/src/tui/{engine → session}/session-stats.mjs +3 -3
  257. package/src/tui/{engine → session}/tool-approval.mjs +3 -3
  258. package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
  259. package/src/tui/{engine → session}/tool-card-results.mjs +8 -6
  260. package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
  261. package/src/tui/{engine → session}/tool-result-text.mjs +13 -3
  262. package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
  263. package/src/tui/{engine → session}/turn.mjs +38 -176
  264. package/src/tui/{engine-local-session.mjs → session-local.mjs} +103 -103
  265. package/src/tui/session.mjs +22 -0
  266. package/src/tui/spinner-meta.mjs +4 -4
  267. package/src/tui/spinner-verbs.mjs +51 -3
  268. package/src/ui/statusline-segments.mjs +1 -1
  269. package/src/ui/statusline.mjs +1 -1
  270. package/vendor/ink/build/components/App.js +10 -1
  271. package/src/session-runtime/memory-daemon-probe.mjs +0 -61
  272. package/src/standalone/engine-daemon-client.mjs +0 -894
  273. package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
  274. package/src/standalone/engine-daemon-protocol.mjs +0 -33
  275. package/src/tui/engine/turn-watchdog.mjs +0 -92
  276. package/src/tui/engine.mjs +0 -22
  277. package/src/tui/hooks/useEngine.mjs +0 -16
  278. /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
  279. /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
  280. /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
  281. /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
  282. /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
@@ -1,17 +1,18 @@
1
- // Stub channels daemon for the flip smoke: same lifecycle contract as
2
- // src/standalone/channel-daemon.mjs (pid-verified singleton claim, HTTP+SSE
1
+ // Stub channel service for the flip smoke: same lifecycle contract as
2
+ // src/standalone/daemon.mjs (pid-verified singleton claim, HTTP+SSE
3
3
  // transport, discovery file, ready handshake, client-grace self-shutdown) but
4
4
  // with a STUB runtime instead of worker-main — no Discord token needed. The
5
5
  // real channel-worker.mjs spawn-or-attach path forks THIS via
6
- // MIXDOG_CHANNEL_DAEMON_ENTRY, so the flip is exercised end to end.
6
+ // MIXDOG_DAEMON_ENTRY, so the flip is exercised end to end.
7
7
  process.env.MIXDOG_WORKER_MODE = process.env.MIXDOG_WORKER_MODE || '1';
8
8
 
9
9
  import os from 'node:os';
10
10
  import path from 'node:path';
11
- import { mkdirSync, writeFileSync } from 'node:fs';
11
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
12
+ import { writeJsonAtomicSync } from '../src/runtime/shared/atomic-file.mjs';
12
13
  import { claimSingletonOwner, releaseSingletonOwner } from '../src/runtime/shared/singleton-owner.mjs';
13
14
  import { safeIpcSend } from '../src/runtime/shared/safe-ipc-send.mjs';
14
- import { createChannelDaemonTransport } from '../src/standalone/channel-daemon-transport.mjs';
15
+ import { createChannelTransport } from '../src/standalone/channel-transport.mjs';
15
16
 
16
17
  function runtimeRoot() {
17
18
  return process.env.MIXDOG_RUNTIME_ROOT
@@ -20,8 +21,8 @@ function runtimeRoot() {
20
21
  }
21
22
  const RUNTIME_ROOT = runtimeRoot();
22
23
  const DATA_DIR = process.env.MIXDOG_DATA_DIR ? path.resolve(process.env.MIXDOG_DATA_DIR) : RUNTIME_ROOT;
23
- const DISCOVERY_PATH = path.join(RUNTIME_ROOT, 'channel-daemon.json');
24
- const OWNER_PATH = path.join(DATA_DIR, 'channel-daemon-owner.json');
24
+ const DISCOVERY_PATH = path.join(RUNTIME_ROOT, 'daemon.json');
25
+ const OWNER_PATH = path.join(DATA_DIR, 'daemon-owner.json');
25
26
 
26
27
  function log(line) { if (process.env.DAEMON_SMOKE_VERBOSE) process.stderr.write(`[stub-daemon] ${line}\n`); }
27
28
 
@@ -31,23 +32,24 @@ async function shutdown(reason, code = 0) {
31
32
  if (shuttingDown) return;
32
33
  shuttingDown = true;
33
34
  try { await transport?.stop?.(); } catch {}
35
+ try { rmSync(DISCOVERY_PATH, { force: true }); } catch {}
34
36
  try { releaseSingletonOwner(OWNER_PATH, process.pid); } catch {}
35
37
  process.exit(code);
36
38
  }
37
39
 
38
40
  async function main() {
39
41
  try { mkdirSync(RUNTIME_ROOT, { recursive: true }); } catch {}
40
- const claim = claimSingletonOwner(OWNER_PATH, { kind: 'channel-runtime-daemon', pid: process.pid, meta: { cwd: process.cwd() } });
42
+ const claim = claimSingletonOwner(OWNER_PATH, { kind: 'mixdog-daemon', pid: process.pid, meta: { cwd: process.cwd() } });
41
43
  if (!claim.owned) { process.exit(0); } // race loser → spawner attaches to winner
42
44
  process.on('exit', () => { try { releaseSingletonOwner(OWNER_PATH, process.pid); } catch {} });
43
45
 
44
46
  // Optional env dump for the tool smoke: proves daemonEnv() spawned us with
45
- // the daemon-mode flags (MIXDOG_CHANNEL_DAEMON=1, MIXDOG_CLI_OWNED=0).
47
+ // the host-mode flags (MIXDOG_DAEMON_HOST=1, MIXDOG_CLI_OWNED=0).
46
48
  if (process.env.SMOKE_CHANNEL_ENV_OUT) {
47
49
  try {
48
50
  writeFileSync(process.env.SMOKE_CHANNEL_ENV_OUT, JSON.stringify({
49
51
  cliOwned: process.env.MIXDOG_CLI_OWNED,
50
- daemon: process.env.MIXDOG_CHANNEL_DAEMON,
52
+ host: process.env.MIXDOG_DAEMON_HOST,
51
53
  }));
52
54
  } catch { /* smoke-only, best-effort */ }
53
55
  }
@@ -61,15 +63,19 @@ async function main() {
61
63
  return { ok: true, name, args, leadPid: ctx.leadPid };
62
64
  };
63
65
 
64
- transport = createChannelDaemonTransport({
66
+ transport = createChannelTransport({
65
67
  handleCall,
66
- discoveryPath: DISCOVERY_PATH,
67
68
  clientGraceMs: 250,
68
69
  sweepMs: 1000,
69
70
  log,
70
71
  onClientsEmpty: () => { void shutdown('no live clients'); },
71
72
  });
72
73
  const { port, token } = await transport.start();
74
+ writeJsonAtomicSync(DISCOVERY_PATH, {
75
+ pid: process.pid,
76
+ startedAt: Date.now(),
77
+ endpoints: { channel: { port, token } },
78
+ }, { compact: true });
73
79
  safeIpcSend(process, { type: 'ready', port, token });
74
80
  log(`ready port=${port} pid=${process.pid}`);
75
81
  }
@@ -0,0 +1,98 @@
1
+ [
2
+ {
3
+ "id": "eof-marker-midfile",
4
+ "note": "observed: a mid-file hunk carried *** End of File; the EOF-anchored seek missed and every recovery tier was gated off, so a byte-perfect context failed",
5
+ "expect": "applied",
6
+ "expect_content": { "a.txt": "one\nTWO\nthree\nfour\nfive\n" },
7
+ "file_snapshots": { "a.txt": "one\ntwo\nthree\nfour\nfive\n" },
8
+ "args": { "patch": "*** Begin Patch\n*** Update File: a.txt\n@@\n one\n-two\n+TWO\n three\n*** End of File\n*** End Patch\n" }
9
+ },
10
+ {
11
+ "id": "deletion-line-one-char-off",
12
+ "note": "observed: `first divergent line` hints whose expected/actual differ by <= 1 character (retyped from memory) on a deletion line",
13
+ "expect": "applied",
14
+ "expect_content": { "src.js": "alpha\nconst total = count + 2;\nbeta\ngamma\n" },
15
+ "file_snapshots": { "src.js": "alpha\nconst total = count + 1;\nbeta\ngamma\n" },
16
+ "args": { "patch": "*** Begin Patch\n*** Update File: src.js\n@@\n alpha\n-const total = cout + 1;\n+const total = count + 2;\n beta\n*** End Patch\n" }
17
+ },
18
+ {
19
+ "id": "stale-outer-context-line",
20
+ "note": "observed: one unrelated context line copied just outside the real edit",
21
+ "expect": "applied",
22
+ "expect_content": { "tools.js": "const tools = {\n shell_step: {\n command: 'node test.js',\n },\n};\n" },
23
+ "file_snapshots": { "tools.js": "const tools = {\n shell_step: {\n command: 'node test.js',\n cwd: root,\n timeout: 30000,\n },\n};\n" },
24
+ "args": { "patch": "*** Begin Patch\n*** Update File: tools.js\n@@\n stale outer context\n shell_step: {\n command: 'node test.js',\n- cwd: root,\n- timeout: 30000,\n },\n*** End Patch\n" }
25
+ },
26
+ {
27
+ "id": "context-retyped-from-memory",
28
+ "note": "observed (dominant stale-context shape): the surrounding context was retyped from memory while the edited line itself is current and unique",
29
+ "expect": "applied",
30
+ "expect_content": { "cfg.js": "l1\nl2\nl3\nconst flag = false;\nl5\nl6\nl7\n" },
31
+ "file_snapshots": { "cfg.js": "l1\nl2\nl3\nconst flag = true;\nl5\nl6\nl7\n" },
32
+ "args": { "patch": "*** Begin Patch\n*** Update File: cfg.js\n@@\n remembered header\n another stale line\n-const flag = true;\n+const flag = false;\n stale trailer\n*** End Patch\n" }
33
+ },
34
+ {
35
+ "id": "retyped-context-with-duplicate-core",
36
+ "note": "guard: the same rescue must refuse when the edited line occurs more than once",
37
+ "expect": "rejected",
38
+ "expect_error": "context not found",
39
+ "file_snapshots": { "dupcore.js": "a\nconst flag = true;\nb\nc\nconst flag = true;\nd\n" },
40
+ "args": { "patch": "*** Begin Patch\n*** Update File: dupcore.js\n@@\n stale one\n-const flag = true;\n+const flag = false;\n stale two\n*** End Patch\n" }
41
+ },
42
+ {
43
+ "id": "decomposed-unicode-context",
44
+ "note": "observed: context authored in decomposed Unicode against composed on-disk text",
45
+ "expect": "applied",
46
+ "expect_content": { "label.js": "head\nconst label = \"tea\";\ntail\n" },
47
+ "file_snapshots": { "label.js": "head\nconst label = \"caf\u00e9\";\ntail\n" },
48
+ "args": { "patch": "*** Begin Patch\n*** Update File: label.js\n@@\n head\n-const label = \"cafe\u0301\";\n+const label = \"tea\";\n tail\n*** End Patch\n" }
49
+ },
50
+ {
51
+ "id": "stacked-at-anchors",
52
+ "note": "V4A anchor chain (@@ class + @@ def): consecutive headers must narrow ONE hunk, not silently resolve against the first occurrence",
53
+ "expect": "applied",
54
+ "expect_content": { "dup.py": "class A:\n def run():\n return 1\n\nclass B:\n def run():\n return 2\n" },
55
+ "file_snapshots": { "dup.py": "class A:\n def run():\n return 1\n\nclass B:\n def run():\n return 1\n" },
56
+ "args": { "patch": "*** Begin Patch\n*** Update File: dup.py\n@@ class B:\n@@ def run():\n- return 1\n+ return 2\n*** End Patch\n" }
57
+ },
58
+ {
59
+ "id": "divergent-line-far-off",
60
+ "note": "observed (dominant class, 65 of 91 hinted misses): the quoted context is genuinely different content — must stay a hard miss with a divergence hint",
61
+ "expect": "rejected",
62
+ "expect_error": "first divergent line|context not found",
63
+ "file_snapshots": { "app.js": "head\nawait engine.startWork();\ntail\n" },
64
+ "args": { "patch": "*** Begin Patch\n*** Update File: app.js\n@@\n head\n-unsubscribe();\n+cleanup();\n tail\n*** End Patch\n" }
65
+ },
66
+ {
67
+ "id": "shifted-context-first-line-only",
68
+ "note": "observed (62 misses): the first old line exists but the block does not — must reject and point at the nearest line",
69
+ "expect": "rejected",
70
+ "expect_error": "nearest line|context not found",
71
+ "file_snapshots": { "shift.js": "open();\nmiddle();\nclose();\n" },
72
+ "args": { "patch": "*** Begin Patch\n*** Update File: shift.js\n@@\n open();\n-gone();\n-also gone();\n+replacement();\n close();\n*** End Patch\n" }
73
+ },
74
+ {
75
+ "id": "near-miss-in-two-places",
76
+ "note": "guard: a near-miss context that fits two windows must never be applied to a guessed one",
77
+ "expect": "rejected",
78
+ "expect_error": "context not found",
79
+ "file_snapshots": { "dup.js": "alpha\nvalue = 1;\nbeta\nalpha\nvalue = 1;\nbeta\n" },
80
+ "args": { "patch": "*** Begin Patch\n*** Update File: dup.js\n@@\n alpha\n-value = 7;\n+value = 2;\n beta\n*** End Patch\n" }
81
+ },
82
+ {
83
+ "id": "compacted-history-placeholder",
84
+ "note": "observed (39 rows): the patch argument was replaced by the history-compaction marker",
85
+ "expect": "rejected",
86
+ "expect_error": "compacted-history placeholder",
87
+ "file_snapshots": { "a.txt": "one\ntwo\n" },
88
+ "args": { "patch": "[mixdog compacted patch: 15983 chars, sha256:9ae407d696803e2a; already applied to a.txt - do not copy]" }
89
+ },
90
+ {
91
+ "id": "missing-patch-argument",
92
+ "note": "observed (783 rows, single harness burst): apply_patch called with no patch argument — must be a stated contract error, never a runtime type crash",
93
+ "expect": "rejected",
94
+ "expect_error": "\"patch\" is required",
95
+ "file_snapshots": {},
96
+ "args": { "base_path": null }
97
+ }
98
+ ]
@@ -1,15 +1,12 @@
1
1
  {
2
2
  "outputStyle": "default",
3
- "mcpServers": {},
4
- "channels": {
5
- "promptInjection": {
6
- "mode": "hook",
7
- "targetPath": ""
8
- }
3
+ "agent": {
4
+ "mcpServers": {},
5
+ "profile": { "title": "", "language": "system" },
6
+ "recap": { "enabled": true }
9
7
  },
8
+ "channels": {},
10
9
  "memory": {
11
- "enabled": true,
12
- "user": { "title": "" },
13
10
  "cycle1": { "interval": "10m" },
14
11
  "cycle2": { "interval": "1h" }
15
12
  }
@@ -132,7 +132,7 @@ apply it, and verify the result.
132
132
  ### Discord / Telegram / voice channels
133
133
 
134
134
  1. Check `/channels` (or `/setting` → Channel) and runtime channel status.
135
- 2. Update the requested backend, credential, or main channel/chat setting.
135
+ 2. Update the requested provider, credential, or main channel/chat setting.
136
136
  3. Restart/reconnect only when the channel implementation requires it.
137
137
  4. Verify channel presence and a non-secret status signal.
138
138
 
@@ -6,7 +6,7 @@ const fs = require('fs');
6
6
  const { resolvePluginData } = require('./plugin-paths.cjs');
7
7
 
8
8
  const SERVICE = 'mixdog';
9
- // Shared bound for every synchronous keychain backend (DPAPI/PowerShell on
9
+ // Shared bound for every synchronous keychain provider (DPAPI/PowerShell on
10
10
  // Windows, security(1) on macOS). A single env override keeps them consistent.
11
11
  const KEYCHAIN_TIMEOUT_MS = Number(process.env.MIXDOG_KEYCHAIN_TIMEOUT_MS || 15000);
12
12
  const POWERSHELL_TIMEOUT_MS = KEYCHAIN_TIMEOUT_MS;
@@ -102,8 +102,7 @@ const CANONICAL_PLUGIN_LOG_NAMES = new Set([
102
102
  'webhook.log',
103
103
  'perf.log',
104
104
  'tool-events.log',
105
- 'memory-runtime-proxy.log',
106
- 'channels-worker-standalone.log',
105
+ 'daemon.log',
107
106
  'mixdog-tui.stderr.log',
108
107
  ]);
109
108
 
@@ -183,7 +182,7 @@ function rotateBoundedLog(filePath, maxBytes, keepBytes) {
183
182
  }
184
183
 
185
184
  // Shared bound for unbounded per-writer plugin logs (tool-events,
186
- // memory-runtime-proxy, channels-worker-standalone). Keep a tail so recent
185
+ // memory runtime and daemon logs). Keep a tail so recent
187
186
  // context survives rotation while the file stays under the 10 MB cap.
188
187
  const PLUGIN_LOG_MAX_BYTES = 10 * 1024 * 1024;
189
188
  const PLUGIN_LOG_KEEP_BYTES = 2 * 1024 * 1024;
@@ -32,11 +32,14 @@ or concept synonym; never a prose phrase. Spaces and non-ASCII are allowed
32
32
  only in verbatim quoted error/log literals. Translate other non-English
33
33
  queries to English identifiers.
34
34
 
35
- Scope is session cwd; `path` may be omitted. For unverified `src` paths, use
35
+ Scope is every `<roots><root>…</root></roots>` entry when supplied, otherwise
36
+ session cwd. Search every supplied root in the turn-1 batch: grep/glob batch
37
+ `path[]`, while find uses one sibling call per root. Never silently fall back to
38
+ cwd or omit a supplied root. A find result is relative to its exact root; prefix
39
+ that root when returning a path outside cwd. For unverified `src` paths, use
36
40
  `find` first; never guess or invent directories or pair `path:"."` with guessed
37
- `src/**`. Scoped grep/glob may use only an exact find-returned path, no earlier
38
- than turn 2. After zero hits, change tokens or scope, never wording or guessed
39
- paths.
41
+ `src/**`. Scoped grep/glob may use only a supplied root or an exact find-returned
42
+ path. After zero hits, change tokens or scope, never wording or guessed paths.
40
43
 
41
44
  An anchor is a `path:line` containing a query token or synonym, including a
42
45
  code_graph hit. Generic terms without query specificity are zero. Never
@@ -7,12 +7,11 @@
7
7
  - Destructive/hard-to-reverse action needs explicit confirmation and explicit
8
8
  validated target paths — never `~`, a root, or unresolved variables/globs;
9
9
  report material deletions with recoverability.
10
- - Act proactively; ask only for decisions.
10
+ - Ask only for decisions.
11
11
  - Build only what the task requires; trust internal and framework guarantees.
12
12
  - Mid-task input: a replacement supersedes current work, an addition folds
13
13
  into it, a status question gets a brief answer while work continues; after
14
14
  context compaction continue from the summary — never restart or redo
15
15
  finished work.
16
- - When blocked, exhaust safe in-scope checks once and report the blocker.
17
16
  - Your final message ends the turn: answer only when the work is done. After a
18
17
  failed tool call, fix and re-run it, or state plainly that it is unresolved.
@@ -1,6 +1,3 @@
1
1
  # Lead Tools
2
2
 
3
- - Write-role agents self-verify with `shell`. Lead uses `shell` only for git,
4
- benches, and cross-scope verification no retrieval tool can produce;
5
- inspection stays on `read`/`grep`/`glob`/`list`/`find`/`code_graph`.
6
3
  - Use the current project/workspace unless the request or tool requires another.
@@ -1,31 +1,23 @@
1
1
  # Tool Use
2
2
 
3
- - Gather every known facet — environment, capability, artifact, failure — in
4
- one bounded first message, one shortest route each: unknown coordinates→
5
- `explore` first and alone, its anchors routing the next batch (roles without
6
- it: `find`); a known path or anchor skips it; partial path/name→`find`;
7
- verified root+wildcard→`glob`; text/code→`grep`; symbol/relation→`code_graph`;
8
- known file/span→`read`, not `grep`; verified directory→`list`; known
9
- edit→`apply_patch`; web/current→`search`; only what none of them reach
10
- (process/env, git, build/run/test)→`shell`, never to cat/ls/find/grep.
11
- - Fewest turns, not fewest calls: always send independent calls together in
12
- one message, merged per tool — one `shell` chain (`&&`/`;`), one `read`
13
- region array, one `apply_patch` with every determined edit proved by its
14
- `post_shell` in the same turn; a later turn only for unseen output.
15
- - Verified paths: project root, session cwd, user-provided, tool-returned.
16
- `find` first for guessed fragments; on ENOENT find the basename; retry
17
- `EXPLORATION_FAILED` once with changed tokens.
18
- - Never re-fetch what a tool already returned: every span this session emitted —
19
- `path:line` hits, read regions, an `apply_patch` post-patch body — is final for
20
- its range, so read only uncovered lines and stop once evidence covers the
21
- deliverable; only zero/error justifies new scope, each call narrow
22
- (`head_limit`, regions).
23
- - Verify in proportion to risk: one decisive probe rides the edit call. A pass
24
- is final; on failure fix and rerun what failed. Diagnostics non-fatal; report
25
- verified vs assumed.
26
- - `apply_patch` is the primary edit tool: once path and content are known it
27
- joins the current batch — all files and hunks in one call, never one turn per
28
- file (the format contract lives in its tool description).
29
- - After a background task starts or reports, end the turn — its notification
30
- resumes the work. Never poll or block; wait only for what the turn cannot
31
- proceed without. Long commands whose output the next step ignores go async.
3
+ - Unknown coordinates → one `explore` call with every unknown facet, sent
4
+ alone. Then batch every anchored retrieval needed to determine the complete
5
+ edit: partial path/name→`find`; exact directory entries→`list`; wildcard→
6
+ `glob`; text/regex-anchored source blocks→`grep`; anchorless known file/range→
7
+ `read`; symbol/relation→`code_graph`; web/current→`search`; returned URL body→
8
+ `web_fetch`; prior work→`recall`; durable compact English memory→`memory`;
9
+ explicit project change→`cwd`; explicit user-requested conversation reset→
10
+ `session_manage`; process/env, git, build/run/test→`shell`. Call `shell` only
11
+ when the task actually requires one of those operations; do not treat it as
12
+ a routine investigation or completion step. Never use shell equivalents for
13
+ file discovery or content retrieval.
14
+ - Use verified paths (cwd/project/user/tool); explicit paths may be outside cwd;
15
+ guessed fragments use `find`. Maximize fan-out across routed retrieval tools
16
+ and fetch all information needed in one batch; never re-fetch an unchanged
17
+ span.
18
+ - Once the edit is determined, finish in one assistant turn with one
19
+ `apply_patch` for all edits. If final verification actually requires `shell`,
20
+ use one verification chain after the edit; otherwise finish without it.
21
+ - After a call returns a background `task_id`, end the turn; its completion
22
+ notification resumes work. Never poll; use task control only for recovery or
23
+ a required blocking result.
@@ -36,6 +36,7 @@ import {
36
36
  abortAgentProgressWatchdog,
37
37
  agentWatchdogPolicyActive,
38
38
  evaluateAgentWatchdogAbort,
39
+ partialHandoffTextFromSession,
39
40
  resolveAgentWatchdogPolicy,
40
41
  resolveHandoffMessageStartIndex,
41
42
  watchdogPartialHandoffFromError,
@@ -65,6 +66,17 @@ function formatCompactElapsedSeconds(ms) {
65
66
  return `${Math.max(1, Math.ceil(value / 1000))}s`;
66
67
  }
67
68
 
69
+ // True when an abort explicitly opted into partial salvage — the error object
70
+ // or the abort reason carries `salvagePartial: true`. A DEADLINE-driven caller
71
+ // (explore hard timeout) sets it so the anchors the sub-agent already produced
72
+ // are returned instead of discarded; user cancellation (ESC) never sets it and
73
+ // keeps the throw-everything behaviour.
74
+ function salvagePartialRequested(error, signal) {
75
+ if (error && typeof error === 'object' && error.salvagePartial === true) return true;
76
+ const reason = signal?.reason;
77
+ return !!(reason && typeof reason === 'object' && reason.salvagePartial === true);
78
+ }
79
+
68
80
  function agentCompactEventLabel(event = {}) {
69
81
  const status = String(event.status || '').toLowerCase();
70
82
  const reactive = String(event.trigger || '').toLowerCase() === 'reactive';
@@ -229,7 +241,7 @@ export function makeAgentDispatch(opts = {}) {
229
241
  }
230
242
  const agent = opts.agent;
231
243
 
232
- return async function agentDispatch({ prompt, preset: presetArg, sourceName: sourceNameArg, parentSignal: callParentSignal, idleTimeoutMs: callIdleTimeoutMs, cwd: callCwd }) {
244
+ return async function agentDispatch({ prompt, preset: presetArg, sourceName: sourceNameArg, parentSignal: callParentSignal, idleTimeoutMs: callIdleTimeoutMs, cwd: callCwd, sessionId: callSessionId }) {
233
245
  if (typeof prompt !== 'string' || !prompt) {
234
246
  throw new Error(`[agent-dispatch] prompt required for agent "${agent}"`);
235
247
  }
@@ -243,6 +255,7 @@ export function makeAgentDispatch(opts = {}) {
243
255
  lease = await admission.acquire('agent', {
244
256
  signal: admissionAbortLink.signal,
245
257
  label: agent,
258
+ ownerKey: callSessionId || opts.ownerSessionId || opts.parentSessionId || opts.sessionId || null,
246
259
  });
247
260
  } catch (error) {
248
261
  admissionAbortLink.dispose();
@@ -470,7 +483,10 @@ export function makeAgentDispatch(opts = {}) {
470
483
  try { closeSession(session.id, 'ephemeral-done'); } catch { /* ignore */ }
471
484
  return out;
472
485
  } catch (err) {
473
- const partial = watchdogPartialHandoffFromError(err, getSession(session.id), _handoffMsgStart);
486
+ const partial = watchdogPartialHandoffFromError(err, getSession(session.id), _handoffMsgStart)
487
+ ?? (salvagePartialRequested(err, _abortLink?.signal)
488
+ ? partialHandoffTextFromSession(getSession(session.id), _handoffMsgStart)
489
+ : null);
474
490
  if (partial) {
475
491
  terminalStatus = 'idle';
476
492
  try { closeSession(session.id, 'ephemeral-done'); } catch { /* ignore */ }
@@ -7,10 +7,26 @@
7
7
  import { appendAgentTrace } from '../agent-trace-io.mjs';
8
8
  import { getHiddenAgent } from '../internal-agents.mjs';
9
9
  import {
10
+ PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
11
+ PROVIDER_WS_SEMANTIC_IDLE_TIMEOUT_MS,
12
+ STALL_TICK_MS,
10
13
  resolveAgentStallThresholds,
11
14
  resolveAgentToolThresholdSeconds,
12
15
  } from '../stall-policy.mjs';
13
16
 
17
+ // Ordering guarantee, stated in stall-policy.mjs: the provider layer — which
18
+ // can retry in place or fall back to non-streaming — must fire STRICTLY before
19
+ // the agent watchdog's terminal abort. Role abort budgets (worker/reviewer
20
+ // 300s, explore 240s) sat at or BELOW the provider semantic-idle window
21
+ // (300s), inverting that order: the watchdog aborted the shared signal first,
22
+ // so the provider's recovery never ran and the `agent_stall` failure — which
23
+ // the classifier calls retryable — died on throwIfAborted instead. Hold the
24
+ // role-derived idle budget at one watchdog tick above the provider window.
25
+ const PROVIDER_RECOVERY_FLOOR_MS = Math.max(
26
+ PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
27
+ PROVIDER_WS_SEMANTIC_IDLE_TIMEOUT_MS,
28
+ ) + STALL_TICK_MS;
29
+
14
30
  const WATCHDOG_ABORT_RE = /^agent (?:first (?:transport|semantic response|response) stale|task stale|tool running stale)\s*\(/;
15
31
 
16
32
  /**
@@ -123,6 +139,16 @@ export function watchdogPartialHandoffFromError(error, session, messageStartInde
123
139
  return text.trim() ? text : null;
124
140
  }
125
141
 
142
+ // Salvage path for NON-watchdog aborts that explicitly opt in (the abort error
143
+ // / abort reason carries `salvagePartial: true` — e.g. the explore wall-clock
144
+ // hard timeout). Same collection rule as the watchdog handoff: only assistant
145
+ // text appended during this run. Plain user cancellation never opts in, so ESC
146
+ // still discards the run.
147
+ export function partialHandoffTextFromSession(session, messageStartIndex = 0) {
148
+ const text = collectSessionAssistantHandoffText(session, messageStartIndex);
149
+ return text.trim() ? text : null;
150
+ }
151
+
126
152
  function resolveWatchdogAbortElapsedMs({ error, snapshot, policy, now, anchorTs, lastProgressAt }) {
127
153
  if (snapshot && policy) {
128
154
  if (snapshot.waitingForFirstActivity) {
@@ -223,7 +249,8 @@ export function resolveAgentWatchdogPolicy(agent, overrides = {}) {
223
249
  idleStaleMs = Math.floor(overrides.idleTimeoutMs);
224
250
  } else if (getHiddenAgent(agent)) {
225
251
  const { abort } = resolveAgentStallThresholds(agent);
226
- idleStaleMs = abort * 1000;
252
+ // Role budget, floored so the provider recovery window always wins.
253
+ idleStaleMs = Math.max(abort * 1000, PROVIDER_RECOVERY_FLOOR_MS);
227
254
  } else {
228
255
  // Part B: the primary mid-stream stall catch is now the provider-level
229
256
  // SEMANTIC idle abort (~120s, ping-immune). This public-agent idle is a
@@ -238,6 +265,9 @@ export function resolveAgentWatchdogPolicy(agent, overrides = {}) {
238
265
  idleStaleMs = backstopMs > 0
239
266
  ? Math.min(DEFAULT_STALE_TIMEOUT_MS, backstopMs)
240
267
  : DEFAULT_STALE_TIMEOUT_MS;
268
+ // Same floor for the public backstop: a workflow role (worker 300s,
269
+ // explore 240s) must not undercut the provider window either.
270
+ idleStaleMs = Math.max(idleStaleMs, PROVIDER_RECOVERY_FLOOR_MS);
241
271
  }
242
272
 
243
273
  const idleSec = idleStaleMs / 1000;
@@ -1,16 +1,10 @@
1
- import { isKnownProvider } from '../../../../standalone/provider-admin.mjs';
1
+ import { configuredAgentRouteCandidates } from '../../../shared/agent-route-config.mjs';
2
2
  import { loadConfig } from '../config.mjs';
3
3
  import { getHiddenAgent } from '../internal-agents.mjs';
4
4
 
5
- const DEFAULT_AGENT_ROUTE_PROVIDER = 'anthropic-oauth';
6
-
7
- function normalizeMaintenanceCandidate(candidate, config) {
5
+ function normalizeMaintenanceCandidate(candidate) {
8
6
  if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) return candidate || null;
9
- const configuredProvider = String(config?.defaultProvider || '').trim();
10
- const fallbackProvider = isKnownProvider(configuredProvider)
11
- ? configuredProvider
12
- : DEFAULT_AGENT_ROUTE_PROVIDER;
13
- const provider = String(candidate.provider || fallbackProvider).trim();
7
+ const provider = String(candidate.provider || '').trim();
14
8
  const model = String(candidate.model || '').trim();
15
9
  if (!provider || !model) return null;
16
10
  return {
@@ -34,20 +28,15 @@ export function resolveMaintenanceRoute({ preset, optsPreset, agent, config: cfg
34
28
  if (hidden) {
35
29
  try {
36
30
  const config = cfgIn || loadConfig({ secrets: false });
37
- const maint = config?.maintenance || {};
38
31
  const key = hidden.maintKey || hidden.slot;
39
32
  const role = key === 'explore' ? 'explore' : (key === 'memory' ? 'maintainer' : '');
40
- const workflowSlot = key === 'explore' ? 'explorer' : (key === 'memory' ? 'memory' : '');
41
- if (!role) return maint[key] ?? null;
33
+ if (!role) return config?.maintenance?.[key] ?? null;
42
34
  const candidates = [
43
- role ? config?.agents?.[role] : null,
44
- key === 'memory' ? config?.agents?.maintenance : null,
45
- workflowSlot ? config?.workflowRoutes?.[workflowSlot] : null,
46
- maint[key],
35
+ ...configuredAgentRouteCandidates(config, role),
47
36
  role ? config?.default : null,
48
37
  ];
49
38
  for (const candidate of candidates) {
50
- const route = normalizeMaintenanceCandidate(candidate, config);
39
+ const route = normalizeMaintenanceCandidate(candidate);
51
40
  if (route) return route;
52
41
  }
53
42
  return null;
@@ -31,7 +31,16 @@ function compactRecoveredErrorMessage(value) {
31
31
  : message;
32
32
  }
33
33
 
34
- function traceAgentLoop({ sessionId, iteration, sendMs, messageCount, bodyBytesEst, agent = null }) {
34
+ function traceAgentLoop({
35
+ sessionId,
36
+ iteration,
37
+ sendMs,
38
+ preSendMs,
39
+ toolResumeMs,
40
+ messageCount,
41
+ bodyBytesEst,
42
+ agent = null,
43
+ }) {
35
44
  // Two emit modes, no behavior change either way:
36
45
  // VERBOSE=1 → full loop row incl. body_bytes_est (payload serialized).
37
46
  // TIMING=1 → lightweight send-latency attribution for high-fanout
@@ -45,6 +54,8 @@ function traceAgentLoop({ sessionId, iteration, sendMs, messageCount, bodyBytesE
45
54
  kind: 'loop',
46
55
  agent: agent || null,
47
56
  send_ms: sendMs,
57
+ pre_send_ms: preSendMs ?? null,
58
+ tool_resume_ms: toolResumeMs ?? null,
48
59
  message_count: messageCount,
49
60
  body_bytes_est: bodyBytesEst ?? null,
50
61
  });
@@ -9,7 +9,8 @@ import { isDiagnosticIOEnabled } from '../../../lib/mixdog-debug.cjs';
9
9
  const WARNED_KEYS = new Set();
10
10
 
11
11
  // ---------------------------------------------------------------------------
12
- // In-memory buffer + HTTP flush to memory-service /admin/trace-record
12
+ // In-memory buffer. Product sessions flush directly into the daemon-hosted
13
+ // memory module; the standalone path can still use its local HTTP service.
13
14
  // ---------------------------------------------------------------------------
14
15
  let _buffer = [];
15
16
  const _BUFFER_MAX = 2000;
@@ -225,21 +226,11 @@ try {
225
226
  function _resolveServiceUrl() {
226
227
  if (_serviceUrl) return _serviceUrl;
227
228
  try {
228
- // Prefer the single-writer discovery advert (discovery/memory.json),
229
- // pid-validated; fall back to legacy active-instance.json memory_port.
229
+ // Standalone memory HTTP uses its pid-validated discovery advert.
230
230
  const advertPort = readServicePort('memory', { requirePid: false });
231
231
  if (advertPort) { _serviceAdvertPort = advertPort; _serviceUrl = `http://127.0.0.1:${advertPort}`; return _serviceUrl; }
232
232
  _serviceAdvertPort = null;
233
- const runtimeRoot = process.env.MIXDOG_RUNTIME_ROOT
234
- ? join(process.env.MIXDOG_RUNTIME_ROOT)
235
- : join(os.tmpdir(), 'mixdog');
236
- const activeFile = join(runtimeRoot, 'active-instance.json');
237
- if (!existsSync(activeFile)) return null;
238
- const active = JSON.parse(readFileSync(activeFile, 'utf-8'));
239
- const port = Number(active && active.memory_port);
240
- if (!Number.isFinite(port) || port <= 0) return null;
241
- _serviceUrl = `http://127.0.0.1:${port}`;
242
- return _serviceUrl;
233
+ return null;
243
234
  } catch {
244
235
  return null;
245
236
  }