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,4 +1,4 @@
1
- // Machine-global channels daemon — HTTP + SSE transport (server side).
1
+ // Machine-global channel front door — HTTP + SSE transport (server side).
2
2
  //
3
3
  // Replaces the per-TUI fork + node-IPC (`{type:'call'|'notify'}`) plumbing with
4
4
  // ONE local HTTP server that many TUIs attach to. Design mirrors the memory
@@ -8,16 +8,17 @@
8
8
  // (targeted routing, never broadcast — see routeNotify below).
9
9
  //
10
10
  // This module owns ONLY the transport (sockets, client registry, notify
11
- // routing, discovery file, lifecycle). The channels runtime (tool dispatch,
12
- // Discord backend, transcript bind/steal) is injected via `handleCall` so the
11
+ // routing and lifecycle). The channels runtime (tool dispatch,
12
+ // Discord provider, transcript bind/steal) is injected via `handleCall` so the
13
13
  // same transport is exercised by the real daemon entry AND the smoke harness
14
14
  // (stub runtime, no Discord token).
15
15
  import http from 'node:http';
16
16
  import { randomUUID } from 'node:crypto';
17
17
  import { readFileSync, rmSync } from 'node:fs';
18
- import { basename, dirname, join } from 'node:path';
18
+ import { basename } from 'node:path';
19
19
  import { writeJsonAtomicSync } from '../runtime/shared/atomic-file.mjs';
20
20
  import { readBody, sendJson, sendError } from '../runtime/memory/lib/http-wire.mjs';
21
+ import { createFairCallScheduler } from './fair-call-scheduler.mjs';
21
22
 
22
23
  function parsePid(value) {
23
24
  const n = Number(value);
@@ -35,9 +36,8 @@ const ACTIVATE_TOOL = 'activate_channel_bridge';
35
36
  const REBIND_TOOL = 'rebind_current_transcript';
36
37
  const BINDING_TOOLS = new Set([ACTIVATE_TOOL, REBIND_TOOL]);
37
38
 
38
- export function createChannelDaemonTransport({
39
+ export function createChannelTransport({
39
40
  handleCall,
40
- discoveryPath,
41
41
  serverToken = randomUUID(),
42
42
  log = () => {},
43
43
  clientGraceMs = 10_000,
@@ -49,13 +49,12 @@ export function createChannelDaemonTransport({
49
49
  remoteStatePath = null,
50
50
  remoteIntentPath = null,
51
51
  onClientRegistered = null,
52
+ agentBroker = null,
52
53
  } = {}) {
53
54
  if (typeof handleCall !== 'function') throw new Error('handleCall is required');
54
55
 
55
- const resolvedRemoteStatePath = remoteStatePath
56
- || (discoveryPath ? join(dirname(discoveryPath), 'channel-remote-state.json') : null);
57
- const resolvedRemoteIntentPath = remoteIntentPath
58
- || (discoveryPath ? join(dirname(discoveryPath), 'channel-remote-intent.json') : null);
56
+ const resolvedRemoteStatePath = remoteStatePath;
57
+ const resolvedRemoteIntentPath = remoteIntentPath;
59
58
  // token -> { token, leadPid, cwd, sse, lastSeen, registeredAt }
60
59
  const clients = new Map();
61
60
  // The routing pointer is set ONLY by an explicit manual Remote ON. Client
@@ -81,6 +80,23 @@ export function createChannelDaemonTransport({
81
80
  // retry never double-runs a non-idempotent tool (e.g. reply). Short TTL.
82
81
  const callCache = new Map();
83
82
  const CALL_CACHE_TTL_MS = 60_000;
83
+ const configuredLaneLimit = (name) => {
84
+ const parsed = Math.floor(Number(process.env[name]));
85
+ return Number.isFinite(parsed) && parsed >= 1 ? parsed : Infinity;
86
+ };
87
+ const CALL_QUEUE_MAX = Math.max(256, Number(process.env.MIXDOG_CHANNEL_CALL_QUEUE) || 256);
88
+ const channelCalls = createFairCallScheduler({
89
+ name: 'channel call',
90
+ activeMax: configuredLaneLimit('MIXDOG_CHANNEL_ACTIVE_CALLS'),
91
+ queueMax: CALL_QUEUE_MAX,
92
+ minOwnerQueue: Math.max(8, Math.floor(CALL_QUEUE_MAX / 16)),
93
+ });
94
+ const channelControlCalls = createFairCallScheduler({
95
+ name: 'channel control call',
96
+ activeMax: configuredLaneLimit('MIXDOG_CHANNEL_CONTROL_RESERVE'),
97
+ queueMax: Math.max(16, Math.min(64, CALL_QUEUE_MAX)),
98
+ minOwnerQueue: 4,
99
+ });
84
100
  // Reconnect register replay: a server may commit replacement just before its
85
101
  // HTTP response is lost. The retry supplies this stable id and receives the
86
102
  // already-created fresh token instead of creating an orphan replacement.
@@ -91,6 +107,7 @@ export function createChannelDaemonTransport({
91
107
  let sweepTimer = null;
92
108
  let everHadClient = false;
93
109
  let closed = false;
110
+ let drainingReason = '';
94
111
 
95
112
  function nowMs() { return Date.now(); }
96
113
 
@@ -251,7 +268,7 @@ export function createChannelDaemonTransport({
251
268
  for (const [token, c] of clients) {
252
269
  // A client is dead when its lead pid is gone OR its SSE stream closed and
253
270
  // it has not re-registered within a grace window. pid death is the
254
- // authoritative signal (mirrors memory daemon pruneDeadClients).
271
+ // authoritative signal for client liveness.
255
272
  if (!isPidAlive(c.leadPid)) {
256
273
  dropClient(token, 'pid dead');
257
274
  }
@@ -275,7 +292,7 @@ export function createChannelDaemonTransport({
275
292
  if (pointerToken === token) pointerToken = null;
276
293
  log(`client ${token} (lead=${c.leadPid}) removed: ${reason}`);
277
294
  // Owner removal always means Remote OFF. Never auto-select or rebind a
278
- // survivor; explicitly deactivate the shared backend if the owner vanished
295
+ // survivor; explicitly deactivate the shared provider if the owner vanished
279
296
  // without first sending its normal active:false command.
280
297
  if (wasPointer) {
281
298
  remoteAcquired = false;
@@ -298,6 +315,14 @@ export function createChannelDaemonTransport({
298
315
  maybeArmGrace('client removed');
299
316
  }
300
317
 
318
+ function beginDrain(reason = 'daemon replacement') {
319
+ if (drainingReason) return false;
320
+ drainingReason = String(reason || 'daemon replacement');
321
+ cancelGrace();
322
+ for (const token of [...clients.keys()]) dropClient(token, drainingReason);
323
+ return true;
324
+ }
325
+
301
326
  function removeRegistrationReplay(registrationId, replay = registrationReplays.get(registrationId)) {
302
327
  if (!replay || registrationReplays.get(registrationId) !== replay) return;
303
328
  registrationReplays.delete(registrationId);
@@ -406,7 +431,7 @@ export function createChannelDaemonTransport({
406
431
  }
407
432
 
408
433
  // Manual ON is the only ownership transition. It may temporarily point at
409
- // the claimant before backend activation so a synchronous acquired notify is
434
+ // the claimant before provider activation so a synchronous acquired notify is
410
435
  // routed correctly; the displaced owner is notified only after activation
411
436
  // succeeds.
412
437
  function movePointer(newToken, reason, { notifyDisplaced = true } = {}) {
@@ -489,10 +514,10 @@ export function createChannelDaemonTransport({
489
514
  }
490
515
  }
491
516
 
492
- function registerClient({ leadPid, cwd, reattach = false, replaceToken = null, registrationId = null }) {
517
+ function registerClient({ leadPid, cwd, passive = false, replaceToken = null, registrationId = null }) {
493
518
  const pid = parsePid(leadPid) ?? 0;
494
519
  const replacementToken = replaceToken ? String(replaceToken) : null;
495
- const replayId = reattach && registrationId ? String(registrationId).slice(0, 200) : null;
520
+ const replayId = passive && registrationId ? String(registrationId).slice(0, 200) : null;
496
521
  const existingReplay = replayId ? registrationReplays.get(replayId) : null;
497
522
  if (existingReplay) {
498
523
  if (existingReplay.leadPid === pid && existingReplay.cwd === (cwd || null) &&
@@ -522,9 +547,9 @@ export function createChannelDaemonTransport({
522
547
  cancelGrace();
523
548
  startSweep();
524
549
  log(`client registered token=${token} lead=${pid} cwd=${cwd || '-'}`);
525
- // The unified backend starts the channels runtime (automation, webhooks,
526
- // messaging backend) only once a CHANNELS client is actually present — an
527
- // engine-only daemon must not run tunnels nobody asked for.
550
+ // The unified daemon starts the channels runtime (automation, webhooks,
551
+ // messaging provider) only once a CHANNELS client is actually present — an
552
+ // session runtime-only daemon must not run tunnels nobody asked for.
528
553
  if (typeof onClientRegistered === 'function') {
529
554
  try { onClientRegistered({ token, leadPid: pid, cwd: cwd || null }); } catch {}
530
555
  }
@@ -543,7 +568,7 @@ export function createChannelDaemonTransport({
543
568
  // not the pointer: retaining a non-owner old token would let it later call,
544
569
  // steal ownership, or accumulate a buffered frame after the fresh client
545
570
  // has gone away. Token replacement never crosses leadPid boundaries.
546
- const replaced = reattach && replacementToken ? clients.get(replacementToken) : null;
571
+ const replaced = passive && replacementToken ? clients.get(replacementToken) : null;
547
572
  if (replaced && replaced.leadPid === pid) {
548
573
  const fresh = clients.get(token);
549
574
  const replacedWasPointer = pointerToken === replaced.token;
@@ -615,19 +640,33 @@ export function createChannelDaemonTransport({
615
640
  const pathName = url.pathname;
616
641
  try {
617
642
  if (req.method === 'GET' && pathName === '/health') {
618
- sendJson(res, { status: 'ok', pid: process.pid, clients: clients.size, ...getStatus() });
643
+ sendJson(res, {
644
+ status: 'ok',
645
+ pid: process.pid,
646
+ clients: clients.size,
647
+ activeCalls: channelCalls.active + channelControlCalls.active,
648
+ queuedCalls: channelCalls.queued + channelControlCalls.queued,
649
+ callOwners: channelCalls.snapshot().owners,
650
+ draining: drainingReason || null,
651
+ ...(agentBroker?.snapshot ? { agentBroker: agentBroker.snapshot() } : {}),
652
+ ...getStatus(),
653
+ });
619
654
  return;
620
655
  }
621
656
  const token = req.headers['x-mixdog-daemon-token'] || url.searchParams.get('server_token');
622
657
  if (token !== serverToken) { sendError(res, 'forbidden', 403); return; }
623
658
 
624
659
  if (req.method === 'POST' && pathName === '/client/register') {
660
+ if (drainingReason) {
661
+ sendError(res, `daemon is draining: ${drainingReason}`, 503);
662
+ return;
663
+ }
625
664
  const body = await readBody(req);
626
665
  const clientToken = registerClient({
627
- leadPid: body.leadPid, cwd: body.cwd, reattach: body.reattach === true,
666
+ leadPid: body.leadPid, cwd: body.cwd, passive: body.passive === true,
628
667
  replaceToken: body.replaceToken, registrationId: body.registrationId,
629
668
  });
630
- const replayId = body.reattach === true && body.registrationId ? String(body.registrationId).slice(0, 200) : null;
669
+ const replayId = body.passive === true && body.registrationId ? String(body.registrationId).slice(0, 200) : null;
631
670
  res.once('finish', () => markRegistrationResponseFinished(replayId, clientToken));
632
671
  sendJson(res, { token: clientToken, pid: process.pid });
633
672
  return;
@@ -649,6 +688,45 @@ export function createChannelDaemonTransport({
649
688
  if (!attachSse(clientToken, res)) { sendError(res, 'unknown client token', 404); return; }
650
689
  return; // stream stays open
651
690
  }
691
+ // Internal memory -> session LLM bridge. It is authenticated with the
692
+ // daemon discovery token but deliberately does NOT register as a channel
693
+ // client: background memory work must not acquire remote ownership or
694
+ // keep the channels client registry alive. The broker itself owns a
695
+ // parallel fair scheduler and per-call cancellation.
696
+ if (req.method === 'POST' && pathName === '/agent/dispatch') {
697
+ if (!agentBroker?.dispatch) { sendError(res, 'agent broker unavailable', 503); return; }
698
+ const body = await readBody(req);
699
+ const callId = String(body.callId || '').trim();
700
+ if (!callId) { sendError(res, 'callId required', 400); return; }
701
+ res.on('close', () => {
702
+ if (res.writableFinished) return;
703
+ try { agentBroker.cancel(callId, 'agent broker client disconnected'); } catch {}
704
+ });
705
+ try {
706
+ const result = await agentBroker.dispatch(body.params || {}, { callId });
707
+ sendJson(res, { ok: true, result });
708
+ } catch (error) {
709
+ sendJson(res, { ok: false, error: error?.message || String(error) }, 200);
710
+ }
711
+ return;
712
+ }
713
+ if (req.method === 'POST' && pathName === '/agent/cancel') {
714
+ if (!agentBroker?.cancel) { sendError(res, 'agent broker unavailable', 503); return; }
715
+ const body = await readBody(req);
716
+ const callId = String(body.callId || '').trim();
717
+ if (!callId) { sendError(res, 'callId required', 400); return; }
718
+ const cancelled = agentBroker.cancelAndWait
719
+ ? await agentBroker.cancelAndWait(
720
+ callId,
721
+ String(body.reason || 'memory agent dispatch canceled'),
722
+ )
723
+ : agentBroker.cancel(callId, String(body.reason || 'memory agent dispatch canceled'));
724
+ sendJson(res, {
725
+ ok: true,
726
+ cancelled,
727
+ });
728
+ return;
729
+ }
652
730
  if (req.method === 'POST' && pathName === '/call') {
653
731
  const body = await readBody(req);
654
732
  const clientToken = body.token || null;
@@ -660,13 +738,21 @@ export function createChannelDaemonTransport({
660
738
  c.lastSeen = nowMs();
661
739
  const name = String(body.name || '');
662
740
  const callId = body.callId ? String(body.callId) : null;
741
+ const addressedSessionId = String(body.args?.sessionId || '').trim()
742
+ || remoteSessionIdFromBinding(name, body.args || {});
743
+ const ownerKey = addressedSessionId
744
+ ? `session:${addressedSessionId}`
745
+ : c.leadPid
746
+ ? `pid:${c.leadPid}`
747
+ : `client:${clientToken}`;
748
+ const cacheKey = callId ? `${ownerKey}\u0000${callId}` : null;
663
749
  let dispatch;
664
- if (callId && callCache.has(callId)) {
750
+ if (cacheKey && callCache.has(cacheKey)) {
665
751
  // Replay of a retried call — dedup to the original run (exactly one
666
752
  // side-effect) instead of dispatching handleCall a second time.
667
- dispatch = callCache.get(callId).promise;
753
+ dispatch = callCache.get(cacheKey).promise;
668
754
  } else {
669
- dispatch = Promise.resolve().then(async () => {
755
+ const run = async () => {
670
756
  const args = body.args || {};
671
757
  const activationCall = name === ACTIVATE_TOOL;
672
758
  const activating = activationCall && args.active === true;
@@ -748,15 +834,20 @@ export function createChannelDaemonTransport({
748
834
  }
749
835
  throw err;
750
836
  }
751
- });
752
- if (callId) {
753
- callCache.set(callId, { promise: dispatch, at: nowMs() });
837
+ };
838
+ const scheduler = BINDING_TOOLS.has(name) ? channelControlCalls : channelCalls;
839
+ dispatch = scheduler.enqueue(ownerKey, run);
840
+ if (cacheKey) {
841
+ const record = { promise: dispatch, at: nowMs() };
842
+ callCache.set(cacheKey, record);
754
843
  // Start the TTL only once the call SETTLES: an in-flight call can
755
844
  // outlive a fixed-from-dispatch TTL (e.g. a slow reply upload past
756
845
  // 60s), and expiring its entry mid-flight would let a transport
757
846
  // retry replay-miss and dispatch a second real side-effect.
758
847
  dispatch.then(() => {}, () => {}).then(() => {
759
- const t = setTimeout(() => callCache.delete(callId), CALL_CACHE_TTL_MS);
848
+ const t = setTimeout(() => {
849
+ if (callCache.get(cacheKey) === record) callCache.delete(cacheKey);
850
+ }, CALL_CACHE_TTL_MS);
760
851
  t.unref?.();
761
852
  });
762
853
  }
@@ -780,20 +871,6 @@ export function createChannelDaemonTransport({
780
871
  }
781
872
  }
782
873
 
783
- function writeDiscovery() {
784
- if (!discoveryPath) return;
785
- try {
786
- writeJsonAtomicSync(discoveryPath, {
787
- pid: process.pid,
788
- port: boundPort,
789
- token: serverToken,
790
- startedAt: Date.now(),
791
- }, { compact: true });
792
- } catch (err) {
793
- log(`discovery write failed: ${err?.message || err}`);
794
- }
795
- }
796
-
797
874
  function start() {
798
875
  return new Promise((resolve, reject) => {
799
876
  server = http.createServer(handleRequest);
@@ -803,7 +880,6 @@ export function createChannelDaemonTransport({
803
880
  server.removeListener('error', reject);
804
881
  boundPort = server.address().port;
805
882
  server.on('error', (err) => log(`server error: ${err?.message || err}`));
806
- writeDiscovery();
807
883
  publishRemoteOwnerState();
808
884
  log(`daemon transport listening on 127.0.0.1:${boundPort} pid=${process.pid}`);
809
885
  resolve({ port: boundPort, token: serverToken });
@@ -813,6 +889,8 @@ export function createChannelDaemonTransport({
813
889
 
814
890
  async function stop() {
815
891
  closed = true;
892
+ channelCalls.close('channel transport is closed');
893
+ channelControlCalls.close('channel transport is closed');
816
894
  cancelGrace();
817
895
  if (sweepTimer) { try { clearInterval(sweepTimer); } catch {} sweepTimer = null; }
818
896
  for (const [token] of clients) dropClient(token, 'transport stop');
@@ -820,7 +898,6 @@ export function createChannelDaemonTransport({
820
898
  pointerToken = null;
821
899
  publishRemoteOwnerState();
822
900
  clearRegistrationReplays();
823
- if (discoveryPath) { try { rmSync(discoveryPath, { force: true }); } catch {} }
824
901
  if (server) {
825
902
  await new Promise((resolve) => { try { server.close(() => resolve()); } catch { resolve(); } });
826
903
  server = null;
@@ -831,11 +908,15 @@ export function createChannelDaemonTransport({
831
908
  start,
832
909
  stop,
833
910
  notify,
911
+ beginDrain,
834
912
  get port() { return boundPort; },
835
913
  get token() { return serverToken; },
836
- // The unified daemon hosts channels AND engines: it may only self-shutdown
914
+ // The unified daemon hosts channels AND session runtimes: it may only self-shutdown
837
915
  // when BOTH sides are empty, so each transport has to expose its liveness.
838
916
  get clientCount() { return clients.size; },
917
+ get activeCount() { return channelCalls.active + channelControlCalls.active; },
918
+ get queuedCount() { return channelCalls.queued + channelControlCalls.queued; },
919
+ get draining() { return Boolean(drainingReason); },
839
920
  _clientsForTest: clients,
840
921
  _registrationReplaysForTest: registrationReplays,
841
922
  _resolveTargetForTest: resolveTarget,