mixdog 0.9.96 → 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 (170) hide show
  1. package/README.md +0 -17
  2. package/package.json +8 -7
  3. package/scripts/build-tui.mjs +6 -6
  4. package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
  5. package/src/defaults/skills/setup/SKILL.md +1 -1
  6. package/src/lib/keychain-cjs.cjs +1 -1
  7. package/src/lib/mixdog-debug.cjs +2 -3
  8. package/src/rules/lead/01-general.md +1 -2
  9. package/src/rules/lead/lead-tool.md +0 -3
  10. package/src/rules/shared/01-tool.md +11 -10
  11. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
  12. package/src/runtime/agent/orchestrator/mcp/client.mjs +81 -33
  13. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
  14. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
  15. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +8 -6
  16. package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
  17. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -4
  18. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +20 -2
  19. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +4 -3
  20. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +40 -25
  21. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
  22. package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
  23. package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
  24. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
  25. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
  26. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +59 -42
  27. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +3 -2
  28. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +12 -22
  29. package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
  30. package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
  31. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +2 -0
  32. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -4
  33. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -30
  34. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +41 -22
  35. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +6 -2
  36. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +55 -11
  37. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
  38. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +55 -18
  39. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -0
  40. package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
  41. package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +2 -2
  42. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
  43. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +67 -21
  44. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +2 -1
  45. package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
  46. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -1
  47. package/src/runtime/channels/lib/config.mjs +33 -33
  48. package/src/runtime/channels/lib/inbound-handler.mjs +7 -7
  49. package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
  50. package/src/runtime/channels/lib/memory-client.mjs +31 -157
  51. package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
  52. package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
  53. package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
  54. package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
  55. package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
  56. package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
  57. package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
  58. package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
  59. package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
  60. package/src/runtime/channels/lib/worker-main.mjs +38 -38
  61. package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
  62. package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
  63. package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
  64. package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
  65. package/src/runtime/channels/tool-defs.mjs +1 -1
  66. package/src/runtime/memory/index.mjs +72 -211
  67. package/src/runtime/memory/lib/agent-ipc.mjs +9 -8
  68. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +2 -2
  69. package/src/runtime/memory/lib/http-router.mjs +1 -6
  70. package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
  71. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
  72. package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
  73. package/src/runtime/memory/lib/memory-log.mjs +1 -1
  74. package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
  75. package/src/runtime/memory/lib/memory.mjs +1 -1
  76. package/src/runtime/memory/lib/session-ingest.mjs +1 -1
  77. package/src/runtime/search/index.mjs +2 -2
  78. package/src/runtime/shared/abort-race.mjs +70 -0
  79. package/src/runtime/shared/child-spawn-gate.mjs +101 -63
  80. package/src/runtime/shared/config.mjs +11 -24
  81. package/src/runtime/shared/llm/index.mjs +1 -1
  82. package/src/runtime/shared/owner-fair-gate.mjs +134 -0
  83. package/src/runtime/shared/resource-admission.mjs +6 -5
  84. package/src/runtime/shared/tool-execution-owner.mjs +12 -0
  85. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  86. package/src/runtime/shared/tool-surface.mjs +91 -22
  87. package/src/runtime/shared/tool-workload-gates.mjs +29 -0
  88. package/src/runtime/shared/turn-snapshot.mjs +17 -2
  89. package/src/session-runtime/channel-config-api.mjs +5 -5
  90. package/src/session-runtime/config-lifecycle.mjs +27 -27
  91. package/src/session-runtime/lifecycle-api.mjs +22 -3
  92. package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
  93. package/src/session-runtime/prewarm.mjs +1 -1
  94. package/src/session-runtime/provider-models.mjs +1 -1
  95. package/src/session-runtime/remote-control.mjs +6 -7
  96. package/src/session-runtime/runtime-core.mjs +52 -28
  97. package/src/session-runtime/self-update.mjs +1 -1
  98. package/src/session-runtime/session-lifecycle.mjs +36 -15
  99. package/src/session-runtime/session-title.mjs +1 -1
  100. package/src/session-runtime/session-turn-api.mjs +55 -15
  101. package/src/session-runtime/settings-api.mjs +7 -7
  102. package/src/standalone/agent-dispatch-broker.mjs +1 -1
  103. package/src/standalone/channel-admin.mjs +42 -53
  104. package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +16 -18
  105. package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +45 -46
  106. package/src/standalone/channel-worker.mjs +124 -647
  107. package/src/standalone/{backend-daemon.mjs → daemon.mjs} +187 -132
  108. package/src/standalone/memory-runtime-proxy.mjs +58 -1
  109. package/src/standalone/{engine-daemon-client.mjs → session-client.mjs} +151 -86
  110. package/src/standalone/session-protocol.mjs +8 -4
  111. package/src/standalone/session-runtime-pool.mjs +393 -0
  112. package/src/standalone/session-runtime-record.mjs +20 -0
  113. package/src/standalone/session-runtime-worker.mjs +203 -0
  114. package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +170 -157
  115. package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +139 -61
  116. package/src/standalone/session-wire.mjs +55 -0
  117. package/src/tui/App.jsx +4 -4
  118. package/src/tui/app/app-format.mjs +1 -1
  119. package/src/tui/app/channel-pickers.mjs +41 -41
  120. package/src/tui/app/input-parsers.mjs +2 -2
  121. package/src/tui/app/onboarding-steps.mjs +2 -2
  122. package/src/tui/app/project-picker.mjs +8 -8
  123. package/src/tui/app/prompt-submit.mjs +8 -8
  124. package/src/tui/app/settings-picker.mjs +25 -25
  125. package/src/tui/app/use-prompt-queue-history.mjs +1 -1
  126. package/src/tui/app/use-transcript-scroll.mjs +1 -1
  127. package/src/tui/components/ToolExecution.jsx +1 -1
  128. package/src/tui/components/prompt-input/restore-policy.mjs +1 -1
  129. package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
  130. package/src/tui/dist/index.mjs +154 -127
  131. package/src/tui/hooks/useSession.mjs +16 -0
  132. package/src/tui/index.jsx +11 -11
  133. package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
  134. package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
  135. package/src/tui/{engine → session}/boot-profile.mjs +2 -2
  136. package/src/tui/{engine → session}/context-state.mjs +3 -3
  137. package/src/tui/{engine → session}/labels.mjs +2 -2
  138. package/src/tui/{engine → session}/live-share.mjs +7 -7
  139. package/src/tui/{engine → session}/notice-text.mjs +2 -2
  140. package/src/tui/{engine → session}/notification-plan.mjs +3 -3
  141. package/src/tui/session/oauth-flows.mjs +140 -0
  142. package/src/tui/{engine → session}/prompt-history.mjs +3 -3
  143. package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
  144. package/src/tui/{engine → session}/render-timing.mjs +2 -2
  145. package/src/tui/{engine → session}/session-api-ext.mjs +30 -17
  146. package/src/tui/{engine → session}/session-api.mjs +7 -60
  147. package/src/tui/{engine → session}/session-flow.mjs +1 -1
  148. package/src/tui/{engine → session}/session-stats.mjs +3 -3
  149. package/src/tui/{engine → session}/tool-approval.mjs +3 -3
  150. package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
  151. package/src/tui/{engine → session}/tool-card-results.mjs +4 -4
  152. package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
  153. package/src/tui/{engine → session}/tool-result-text.mjs +3 -3
  154. package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
  155. package/src/tui/{engine → session}/turn.mjs +29 -107
  156. package/src/tui/{engine-local-session.mjs → session-local.mjs} +59 -75
  157. package/src/tui/session.mjs +22 -0
  158. package/src/ui/statusline-segments.mjs +1 -1
  159. package/src/ui/statusline.mjs +1 -1
  160. package/vendor/ink/build/components/App.js +10 -1
  161. package/src/session-runtime/memory-daemon-probe.mjs +0 -61
  162. package/src/standalone/engine-daemon-protocol.mjs +0 -32
  163. package/src/tui/engine/turn-watchdog.mjs +0 -92
  164. package/src/tui/engine.mjs +0 -22
  165. package/src/tui/hooks/useEngine.mjs +0 -16
  166. /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
  167. /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
  168. /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
  169. /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
  170. /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
@@ -1,5 +1,5 @@
1
1
  import * as fs from "fs";
2
- import { loadConfig, createBackend } from "./config.mjs";
2
+ import { loadConfig, createProvider } from "./config.mjs";
3
3
  import { WebhookServer } from "./webhook.mjs";
4
4
  import { EventPipeline } from "./event-pipeline.mjs";
5
5
  import { startSnapshotWriter, stopSnapshotWriter } from "./status-snapshot.mjs";
@@ -12,16 +12,16 @@ import {
12
12
  clearActiveInstance,
13
13
  } from "./runtime-paths.mjs";
14
14
  // Owned-runtime lifecycle extracted from channels/index.mjs (behavior-
15
- // preserving): bridge-ownership claim/refresh/loss, backend connect/disconnect,
15
+ // preserving): bridge-ownership claim/refresh/loss, provider connect/disconnect,
16
16
  // scheduler + webhook/event runtime, owner heartbeat gating, and config
17
- // hot-reload. Owns its own in-flight flags + timers; shares config / backend /
17
+ // hot-reload. Owns its own in-flight flags + timers; shares config / provider /
18
18
  // bridgeRuntimeConnected / webhookServer / eventPipeline with the worker via
19
19
  // get/set so file-level reference semantics are preserved.
20
20
  export function createOwnedRuntime({
21
21
  getConfig,
22
22
  setConfig,
23
- getBackend,
24
- setBackend,
23
+ getProvider,
24
+ setProvider,
25
25
  getBridgeRuntimeConnected,
26
26
  setBridgeRuntimeConnected,
27
27
  getWebhookServer,
@@ -50,16 +50,16 @@ export function createOwnedRuntime({
50
50
  let bridgeOwnershipRefreshInFlight = null;
51
51
  let _memoryDrainTimer = null;
52
52
  // Automation (scheduler + webhook server + relay tunnel) can outlive a
53
- // failed/absent messaging backend: it is tracked separately so teardown and
53
+ // failed/absent messaging provider: it is tracked separately so teardown and
54
54
  // reload know it is running even while bridgeRuntimeConnected stays false.
55
55
  let automationRunning = false;
56
56
  let automationStartPromise = null;
57
57
  // Promise that resolves when the current startOwnedRuntime() run fully
58
58
  // settles (bridgeRuntimeStarting -> false). reloadRuntimeConfig awaits this
59
- // before issuing a restart so a backend swap that lands mid-start is not
59
+ // before issuing a restart so a provider swap that lands mid-start is not
60
60
  // dropped by startOwnedRuntime's in-flight guard (lost-restart race).
61
61
  let _inFlightStart = null;
62
- // Daemon model: the machine-global channels daemon (singleton-owner lock in
62
+ // Daemon model: the machine-global service (singleton-owner lock in
63
63
  // src/standalone) guarantees exactly one runtime per machine, so this process
64
64
  // is the unconditional bridge owner. The OS seat lock is retired — no takeover
65
65
  // handler, no vacant-reacquire poll, no cross-process ownership-loss detection.
@@ -158,7 +158,7 @@ async function startAutomationRuntime() {
158
158
  startSnapshotWriter(scheduler);
159
159
  syncOwnedWebhookAndEventRuntime();
160
160
  automationRunning = true;
161
- process.stderr.write('mixdog: automation runtime up without messaging backend\n');
161
+ process.stderr.write('mixdog: automation runtime up without messaging provider\n');
162
162
  })();
163
163
  automationStartPromise = run;
164
164
  try {
@@ -175,8 +175,8 @@ let _ownedRuntimeSelfHealing = false;
175
175
  // activation/reload recovery only.
176
176
  async function selfHealOwnedRuntime(options = {}) {
177
177
  const shouldRestoreBinding = options.restoreBinding === true;
178
- const shouldResetBackend = options.resetBackend === true;
179
- if (!shouldRestoreBinding && !shouldResetBackend) return;
178
+ const shouldResetProvider = options.resetProvider === true;
179
+ if (!shouldRestoreBinding && !shouldResetProvider) return;
180
180
  if (_ownedRuntimeSelfHealing) return;
181
181
  _ownedRuntimeSelfHealing = true;
182
182
  try {
@@ -185,9 +185,9 @@ async function selfHealOwnedRuntime(options = {}) {
185
185
  process.stderr.write(`mixdog: self-heal bindPersistedTranscriptIfAny failed (non-fatal): ${e instanceof Error ? e.message : String(e)}\n`);
186
186
  });
187
187
  }
188
- if (shouldResetBackend && typeof getBackend()?._resetClient === "function") {
189
- await getBackend()._resetClient().catch((e) => {
190
- process.stderr.write(`mixdog: self-heal getBackend() reset failed (non-fatal): ${e instanceof Error ? e.message : String(e)}\n`);
188
+ if (shouldResetProvider && typeof getProvider()?._resetClient === "function") {
189
+ await getProvider()._resetClient().catch((e) => {
190
+ process.stderr.write(`mixdog: self-heal getProvider() reset failed (non-fatal): ${e instanceof Error ? e.message : String(e)}\n`);
191
191
  });
192
192
  }
193
193
  // Do NOT nudge forwardNewText() here. bindPersistedTranscriptIfAny() above
@@ -217,28 +217,28 @@ async function startOwnedRuntime(options = {}) {
217
217
  _settleStart = null;
218
218
  done?.();
219
219
  };
220
- // Capture the getBackend() instance that THIS start operation will connect. A
221
- // reloadRuntimeConfig() hot-swap can replace the global `getBackend()` while this
220
+ // Capture the getProvider() instance that THIS start operation will connect. A
221
+ // reloadRuntimeConfig() hot-swap can replace the global `getProvider()` while this
222
222
  // start is still awaiting connect(); using the captured instance for both
223
223
  // connect() and the bail-path disconnect() guarantees we tear down the
224
- // getBackend() WE started (not the freshly-swapped one), closing the
225
- // both-backends-live window.
226
- const startingBackend = getBackend();
224
+ // getProvider() WE started (not the freshly-swapped one), closing the
225
+ // both-providers-live window.
226
+ const startingProvider = getProvider();
227
227
  // Daemon model: this runtime is the singleton bridge owner (enforced by the
228
228
  // standalone daemon's singleton-owner lock), so there is no seat to claim and
229
229
  // no cross-process contender to back off from. Just advertise metadata
230
- // (memory_port/pg_*/channelId/... preserved inside refreshActiveInstance);
230
+ // (pg_*/channelId/... preserved inside refreshActiveInstance);
231
231
  // active-instance.json is a pure advert. Wrapped so any throw resets
232
232
  // bridgeRuntimeStarting.
233
233
  try {
234
- refreshActiveInstance(instanceId, { backendReady: false });
234
+ refreshActiveInstance(instanceId, { providerReady: false });
235
235
  } catch (e) {
236
236
  settleInFlightStart();
237
237
  process.stderr.write(`mixdog: pre-connect metadata advert aborted (${e instanceof Error ? e.message : String(e)})\n`);
238
238
  return;
239
239
  }
240
240
  // Periodic buffer drain: replays memory-buffer/entry-*/ingest-*.json once the
241
- // memory service publishes its port. Idempotent + reentrancy-guarded inside
241
+ // daemon-hosted memory runtime is ready. Idempotent + reentrancy-guarded inside
242
242
  // drainBuffer(); unref'd so it never holds the worker open.
243
243
  if (!_memoryDrainTimer) {
244
244
  _memoryDrainTimer = setInterval(() => { void memoryDrainBuffer().catch(() => {}); }, 5e3);
@@ -249,11 +249,11 @@ async function startOwnedRuntime(options = {}) {
249
249
  // webhook/binding launches below would revive owner state after stop).
250
250
  // Idempotent: stop's sync teardown already ran; re-running disconnect +
251
251
  // teardown is safe and covers both the pre-connected window (stop could
252
- // not disconnect an in-flight getBackend()) and the post-connected window
252
+ // not disconnect an in-flight getProvider()) and the post-connected window
253
253
  // (stop did disconnect; redo to be defensive).
254
254
  const bailIfStopRequested = async () => {
255
255
  if (!_ownedRuntimeStopRequested) return false;
256
- try { await startingBackend.disconnect(); } catch {}
256
+ try { await startingProvider.disconnect(); } catch {}
257
257
  try { releaseOwnedChannelLocks(instanceId); } catch {}
258
258
  try { clearActiveInstance(instanceId); } catch {}
259
259
  setBridgeRuntimeConnected(false);
@@ -266,20 +266,20 @@ async function startOwnedRuntime(options = {}) {
266
266
  process.stderr.write(`mixdog: bindPersistedTranscriptIfAny failed (non-fatal): ${e instanceof Error ? e.message : String(e)}\n`);
267
267
  })
268
268
  : null;
269
- // Await getBackend().connect() so callers (and bindingReady) only resolve after
269
+ // Await getProvider().connect() so callers (and bindingReady) only resolve after
270
270
  // the Discord binding is real. Previously this was fire-and-forget and
271
271
  // refreshBridgeOwnership returned immediately, letting bindingReady fire
272
- // before getBackend() listeners were attached.
272
+ // before getProvider() listeners were attached.
273
273
  try {
274
- await startingBackend.connect();
274
+ await startingProvider.connect();
275
275
  if (await bailIfStopRequested()) {
276
276
  cancelPendingTranscriptRearm();
277
277
  try { forwarder.stopWatch(); } catch {}
278
278
  if (bindPersistedTranscriptTask) await bindPersistedTranscriptTask;
279
279
  return;
280
280
  }
281
- // Advertise backend readiness (metadata advert).
282
- try { refreshActiveInstance(instanceId, { backendReady: true }); } catch {}
281
+ // Advertise provider readiness (metadata advert).
282
+ try { refreshActiveInstance(instanceId, { providerReady: true }); } catch {}
283
283
  setBridgeRuntimeConnected(true);
284
284
  // Fresh confirmed connection — tell the parent to flip remote ON. Reached
285
285
  // ONLY on a not-connected -> connected transition (the top-of-fn
@@ -327,18 +327,18 @@ async function startOwnedRuntime(options = {}) {
327
327
  });
328
328
  }
329
329
  }
330
- process.stderr.write(`mixdog: running with ${getBackend().name} getBackend()\n`);
330
+ process.stderr.write(`mixdog: running with ${getProvider().name} getProvider()\n`);
331
331
  logOwnership(`active owner lead=${TERMINAL_LEAD_PID} pid=${process.pid}`);
332
332
  } catch (e) {
333
- process.stderr.write(`mixdog: getBackend() connect failed (non-fatal, cycle1/MCP still up): ${e instanceof Error ? e.message : String(e)}\n`);
333
+ process.stderr.write(`mixdog: getProvider() connect failed (non-fatal, cycle1/MCP still up): ${e instanceof Error ? e.message : String(e)}\n`);
334
334
  cancelPendingTranscriptRearm();
335
335
  try { forwarder.stopWatch(); } catch {}
336
336
  if (bindPersistedTranscriptTask) await bindPersistedTranscriptTask;
337
337
  // Roll back partial owner-side state advertised before connect() ran:
338
- // disconnect the backend WE started (a post-connect startup step may have
338
+ // disconnect the provider WE started (a post-connect startup step may have
339
339
  // thrown while the gateway is live), then release the channel locks + clear
340
340
  // the active-instance advert.
341
- try { await startingBackend.disconnect(); } catch {}
341
+ try { await startingProvider.disconnect(); } catch {}
342
342
  try { releaseOwnedChannelLocks(instanceId); } catch {}
343
343
  try { clearActiveInstance(instanceId); } catch {}
344
344
  if (_memoryDrainTimer) { clearInterval(_memoryDrainTimer); _memoryDrainTimer = null; }
@@ -370,13 +370,13 @@ async function stopOwnedRuntime(reason) {
370
370
  // (startWatch is not owner-gated), leaking a live watcher past shutdown.
371
371
  cancelPendingTranscriptRearm();
372
372
  // startOwnedRuntime() advertises owner HTTP/heartbeat/active-instance and
373
- // claims channel locks BEFORE awaiting getBackend().connect(). If shutdown lands
373
+ // claims channel locks BEFORE awaiting getProvider().connect(). If shutdown lands
374
374
  // during that window (bridgeRuntimeStarting=true, getBridgeRuntimeConnected()
375
375
  // still false) we still need to tear that partial state down — otherwise
376
376
  // the port stays bound and active-instance.json stays stale.
377
377
  if (!getBridgeRuntimeConnected() && !bridgeRuntimeStarting && !automationRunning && !automationStartPromise) return;
378
378
  // If a start is in flight (bridgeRuntimeStarting=true), signal the in-flight
379
- // startOwnedRuntime() to abort right after its getBackend().connect() resolves.
379
+ // startOwnedRuntime() to abort right after its getProvider().connect() resolves.
380
380
  // Without this the in-flight start re-marks connected and re-launches
381
381
  // scheduler/webhook/heartbeat after we tear them down here.
382
382
  if (bridgeRuntimeStarting || automationStartPromise) _ownedRuntimeStopRequested = true;
@@ -398,15 +398,15 @@ async function stopOwnedRuntime(reason) {
398
398
  releaseOwnedChannelLocks(instanceId);
399
399
  clearActiveInstance(instanceId);
400
400
  try {
401
- // Only disconnect the getBackend() when connect() actually completed; calling
401
+ // Only disconnect the getProvider() when connect() actually completed; calling
402
402
  // disconnect() mid-connect races the connect promise.
403
403
  if (wasConnected) {
404
404
  // Drain in-flight outbound sends before disconnecting so a handoff
405
405
  // (owned=false observed → ownership lost) never cuts off a reply
406
406
  // mid-delivery. Bounded inside drainPendingSends so a wedged send can
407
407
  // not stall teardown — we still disconnect promptly.
408
- try { await getBackend().drainPendingSends?.(); } catch {}
409
- await getBackend().disconnect();
408
+ try { await getProvider().drainPendingSends?.(); } catch {}
409
+ await getProvider().disconnect();
410
410
  }
411
411
  } finally {
412
412
  setBridgeRuntimeConnected(false);
@@ -442,7 +442,7 @@ function notifyRemoteAcquired() {
442
442
  sendNotifyToParent('notifications/mixdog/remote', { state: 'acquired' });
443
443
  }
444
444
  async function refreshBridgeOwnership(options = {}) {
445
- // Coalesce concurrent callers onto the in-flight refresh so getBackend() tool
445
+ // Coalesce concurrent callers onto the in-flight refresh so getProvider() tool
446
446
  // calls landing during normal login wait for the same connect attempt
447
447
  // instead of returning early and observing spurious auto-connect failure.
448
448
  if (bridgeOwnershipRefreshInFlight) return bridgeOwnershipRefreshInFlight;
@@ -465,8 +465,8 @@ async function refreshBridgeOwnership(options = {}) {
465
465
  }
466
466
 
467
467
  async function reloadRuntimeConfig() {
468
- const previousBackend = getBackend();
469
- const previousBackendName = previousBackend?.name || "";
468
+ const previousProvider = getProvider();
469
+ const previousProviderName = previousProvider?.name || "";
470
470
  // File-watch/tool-triggered reloads must bypass the short keychain hit cache:
471
471
  // another process may have just saved or rotated the channel credential.
472
472
  setConfig(await loadConfig({ freshSecrets: true }));
@@ -478,37 +478,37 @@ async function reloadRuntimeConfig() {
478
478
  // The scheduler must be RE-ARMED by the reload whenever it is supposed to
479
479
  // be running. `restart: false` only destroys the cron/one-shot bindings and
480
480
  // hands lifecycle back to the caller — but on an automation-only install
481
- // (no messaging backend) startAutomationRuntime() is already past its
481
+ // (no messaging provider) startAutomationRuntime() is already past its
482
482
  // `automationRunning` guard, so nobody would ever call scheduler.start()
483
483
  // again and every saved/edited schedule stayed silently disarmed until the
484
484
  // daemon restarted.
485
485
  { restart: automationRunning || getBridgeRuntimeConnected() }
486
486
  );
487
- const nextBackend = createBackend(getConfig());
488
- const backendTypeChanged = (nextBackend?.name || "") !== previousBackendName;
489
- const credentialsChanged = !backendTypeChanged
490
- && String(nextBackend?.token || "") !== String(previousBackend?.token || "");
491
- const backendChanged = backendTypeChanged || credentialsChanged;
492
- if (backendChanged) {
487
+ const nextProvider = createProvider(getConfig());
488
+ const providerTypeChanged = (nextProvider?.name || "") !== previousProviderName;
489
+ const credentialsChanged = !providerTypeChanged
490
+ && String(nextProvider?.token || "") !== String(previousProvider?.token || "");
491
+ const providerChanged = providerTypeChanged || credentialsChanged;
492
+ if (providerChanged) {
493
493
  const shouldRestart = getBridgeRuntimeConnected() || bridgeRuntimeStarting;
494
- if (shouldRestart) await stopOwnedRuntime("getBackend() getConfig() changed");
494
+ if (shouldRestart) await stopOwnedRuntime("getProvider() getConfig() changed");
495
495
  // A start that was in flight when stopOwnedRuntime landed was signalled to
496
- // bail (and disconnects the OLD backend it was connecting). Wait for it to
496
+ // bail (and disconnects the OLD provider it was connecting). Wait for it to
497
497
  // FULLY settle before issuing the fresh start below — otherwise
498
498
  // startOwnedRuntime's `if (bridgeRuntimeStarting) return` guard would drop
499
- // the restart and the NEW backend would never connect (lost-restart race).
499
+ // the restart and the NEW provider would never connect (lost-restart race).
500
500
  if (_inFlightStart) { try { await _inFlightStart; } catch {} }
501
- setBackend(nextBackend);
502
- // The persisted routing channelId belongs to the OLD getBackend() (e.g. a
501
+ setProvider(nextProvider);
502
+ // The persisted routing channelId belongs to the OLD getProvider() (e.g. a
503
503
  // Discord snowflake) and is meaningless for the new one — sending to it
504
504
  // would 400 "chat not found". There is no id mapping between platforms, so
505
505
  // CLEAR the stale binding: drop the forwarder's context + watcher and wipe
506
- // status.channelId/transcriptPath. The next inbound from the new getBackend()
506
+ // status.channelId/transcriptPath. The next inbound from the new getProvider()
507
507
  // rebinds the correct chat via applyTranscriptBinding(). Only done on
508
- // backendChanged — same-getBackend() reloads keep their binding untouched.
508
+ // providerChanged — same-getProvider() reloads keep their binding untouched.
509
509
  // (active-instance is cleared by stopOwnedRuntime on the restart path; we
510
510
  // don't re-advertise here to avoid resurrecting a just-cleared entry.)
511
- if (backendTypeChanged) {
511
+ if (providerTypeChanged) {
512
512
  try { forwarder.stopWatch(); } catch {}
513
513
  forwarder.channelId = "";
514
514
  forwarder.transcriptPath = "";
@@ -520,11 +520,11 @@ async function reloadRuntimeConfig() {
520
520
  } catch {}
521
521
  }
522
522
  // stopOwnedRuntime above tore the owned runtime down; a same-session reload
523
- // reconnects the NEW backend here. The in-flight start (if any) has already
523
+ // reconnects the NEW provider here. The in-flight start (if any) has already
524
524
  // settled above, so this start is not dropped by the in-flight guard.
525
- if (shouldRestart) refreshBridgeOwnershipSafe({ restoreBinding: !backendTypeChanged });
526
- } else if (nextBackend !== previousBackend) {
527
- try { await nextBackend.disconnect?.(); } catch {}
525
+ if (shouldRestart) refreshBridgeOwnershipSafe({ restoreBinding: !providerTypeChanged });
526
+ } else if (nextProvider !== previousProvider) {
527
+ try { await nextProvider.disconnect?.(); } catch {}
528
528
  }
529
529
  if (getBridgeRuntimeConnected() || automationRunning) {
530
530
  syncOwnedWebhookAndEventRuntime({ reload: true });
@@ -1,5 +1,5 @@
1
1
  // Bridge ownership snapshot + ownership logging. Under the machine-global
2
- // channels daemon (singleton-owner lock in src/standalone) there is exactly one
2
+ // unified daemon (singleton-owner lock in src/standalone) has exactly one
3
3
  // runtime per machine, so this process is the unconditional bridge owner — the
4
4
  // OS seat lock and its file heartbeat / last-wins CAS are retired.
5
5
  // active-instance.json is now a pure metadata advert; it is no longer read to
@@ -1,9 +1,9 @@
1
1
  import { recordFetchedMessages } from "./status-snapshot.mjs";
2
2
 
3
- // Backend-tool dispatch helpers. Extracted verbatim from channels/index.mjs
4
- // (behavior-preserving). Bound to live getters so runtime config/backend
3
+ // Provider-tool dispatch helpers. Extracted verbatim from channels/index.mjs
4
+ // (behavior-preserving). Bound to live getters so runtime config/provider
5
5
  // reloads keep the original file-level reference semantics.
6
- function createBackendDispatch({ getConfig, getBackend, scheduler }) {
6
+ function createProviderDispatch({ getConfig, getProvider, scheduler }) {
7
7
  async function dispatchReply(args) {
8
8
  const config = getConfig();
9
9
  const channelId = String(args?.chat_id || args?.channel_id || args?.channel || config.channelId || '').trim();
@@ -29,7 +29,7 @@ function createBackendDispatch({ getConfig, getBackend, scheduler }) {
29
29
  let ids;
30
30
  // Pre-send activity bump keeps idle gating consistent during the await.
31
31
  scheduler.noteActivity();
32
- const sendResult = await getBackend().sendMessage(channelId, message, sendOpts);
32
+ const sendResult = await getProvider().sendMessage(channelId, message, sendOpts);
33
33
  scheduler.noteActivity();
34
34
  ids = sendResult.sentIds;
35
35
  const text = ids.length === 1 ? `sent (id: ${ids[0]})` : `sent ${ids.length} parts (ids: ${ids.join(", ")})`;
@@ -43,7 +43,7 @@ function createBackendDispatch({ getConfig, getBackend, scheduler }) {
43
43
  const channelId = args.channel || config.channelId || "";
44
44
  const limit = args.limit ?? 20;
45
45
  let msgs;
46
- msgs = await getBackend().fetchMessages(channelId, limit);
46
+ msgs = await getProvider().fetchMessages(channelId, limit);
47
47
  recordFetchedMessages(channelId, channelId, msgs);
48
48
  const text = msgs.length === 0 ? "(no messages)" : msgs.map((m) => {
49
49
  const atts = m.attachmentCount > 0 ? ` +${m.attachmentCount}att` : "";
@@ -58,4 +58,4 @@ function createBackendDispatch({ getConfig, getBackend, scheduler }) {
58
58
  };
59
59
  }
60
60
 
61
- export { createBackendDispatch };
61
+ export { createProviderDispatch };
@@ -189,9 +189,8 @@ function buildActiveInstanceState(instanceId, meta) {
189
189
  ...meta?.channelId ? { channelId: meta.channelId } : {},
190
190
  ...meta?.transcriptPath ? { transcriptPath: meta.transcriptPath } : {},
191
191
  ...meta?.httpPort ? { httpPort: meta.httpPort } : {},
192
- ...meta?.memory_port ? { memory_port: meta.memory_port } : {},
193
192
  ...gatewayMeta,
194
- ...typeof meta?.backendReady === "boolean" ? { backendReady: meta.backendReady } : {}
193
+ ...typeof meta?.providerReady === "boolean" ? { providerReady: meta.providerReady } : {}
195
194
  };
196
195
  }
197
196
  function refreshActiveInstance(instanceId, meta, options) {
@@ -208,7 +207,7 @@ function refreshActiveInstance(instanceId, meta, options) {
208
207
  // check ownership OUTSIDE this lock, so a newer session can claim the
209
208
  // seat between that check and this locked update. Without this re-read,
210
209
  // the tick would overwrite the newer owner's instanceId (TOCTOU
211
- // re-steal -> ownership ping-pong / double backend connections).
210
+ // re-steal -> ownership ping-pong / double provider connections).
212
211
  // Returning undefined aborts the update with no write. Explicit claims
213
212
  // (boot, claimBridgeOwnership) omit the option and stay last-wins.
214
213
  if (options?.onlyIfOwned && (!prev?.instanceId || prev.instanceId !== instanceId)) {
@@ -249,9 +248,8 @@ function refreshActiveInstance(instanceId, meta, options) {
249
248
  ...meta?.channelId ? { channelId: meta.channelId } : {},
250
249
  ...meta?.transcriptPath ? { transcriptPath: meta.transcriptPath } : {},
251
250
  ...meta?.httpPort ? { httpPort: meta.httpPort } : {},
252
- ...meta?.memory_port ? { memory_port: meta.memory_port } : {},
253
251
  ...gatewayMeta,
254
- ...typeof meta?.backendReady === "boolean" ? { backendReady: meta.backendReady } : {},
252
+ ...typeof meta?.providerReady === "boolean" ? { providerReady: meta.providerReady } : {},
255
253
  };
256
254
  if (typeof meta?.transcriptPath === "string" && meta.transcriptPath) {
257
255
  const outgoing = prevForPreserve?.transcriptPath;
@@ -267,34 +265,10 @@ function refreshActiveInstance(instanceId, meta, options) {
267
265
  delete next.pinned;
268
266
  // I1: pg_* spreads FIRST so newFields above win on conflict.
269
267
  // prev.pg_port='A', meta.httpPort-adjacent pg_port='B' → result.pg_port='B'.
270
- // memory_port: preserve when the advertising memory worker is still alive
271
- // (sync process.kill(pid,0); ESRCH=dead). Same server_pid restart still
272
- // preserves; a live owner from another session must not be dropped on handoff.
273
268
  const preservedExtra = Object.fromEntries(
274
269
  Object.entries(prevForPreserve ?? {}).filter(([k]) => k.startsWith('pg_'))
275
270
  );
276
- const prevMemoryServerPid = parsePositivePid(prevForPreserve?.memory_server_pid);
277
- const prevMemoryOwnerAlive = (() => {
278
- if (prevMemoryServerPid === null) return false;
279
- try {
280
- process.kill(prevMemoryServerPid, 0);
281
- return true;
282
- } catch (e) {
283
- if (e && e.code === "ESRCH") return false;
284
- return true;
285
- }
286
- })();
287
- const sameMemoryAdvertiser =
288
- prevMemoryServerPid !== null &&
289
- identity.server_pid !== null &&
290
- prevMemoryServerPid === identity.server_pid;
291
- if (sameMemoryAdvertiser || prevMemoryOwnerAlive) {
292
- if (prevForPreserve && Object.prototype.hasOwnProperty.call(prevForPreserve, 'memory_port')) {
293
- preservedExtra.memory_port = prevForPreserve.memory_port;
294
- preservedExtra.memory_server_pid = prevMemoryServerPid;
295
- }
296
- }
297
- // gateway_port follows the same preservation rule as memory_port: the
271
+ // gateway_port is preserved while the gateway owner is alive: the
298
272
  // gateway child advertises itself independently, and active-owner
299
273
  // heartbeats must not erase that discovery record while its owning
300
274
  // server-main process is still alive.
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Usage (from channels/index.mjs):
11
11
  * import { startSnapshotWriter } from './lib/status-snapshot.mjs';
12
- * startSnapshotWriter(scheduler, backend, webhookServer);
12
+ * startSnapshotWriter(scheduler, provider, webhookServer);
13
13
  */
14
14
 
15
15
  import * as fs from 'fs';
@@ -38,8 +38,8 @@ const _discordUnread = new Map();
38
38
  const DISCORD_UNREAD_MAX_CHANNELS = 500;
39
39
 
40
40
  /**
41
- * Called whenever the backend observes messages for a channelId.
42
- * `messages` is the array returned by backend.fetchMessages().
41
+ * Called whenever the provider observes messages for a channelId.
42
+ * `messages` is the array returned by provider.fetchMessages().
43
43
  * We record the most-recently-seen message id and count messages
44
44
  * received since the last call as "unread since last fetch".
45
45
  * Pass { markRead: true } for live gateway messages that were already
@@ -10,7 +10,7 @@ import { OutputForwarder } from "./output-forwarder.mjs";
10
10
  // call time — matching the original in-file closure semantics.
11
11
  function createToolDispatch({
12
12
  getForwarder,
13
- BACKEND_TOOLS,
13
+ PROVIDER_TOOLS,
14
14
  isChannelsDegraded,
15
15
  dispatchReply,
16
16
  dispatchFetch,
@@ -152,11 +152,11 @@ function createToolDispatch({
152
152
  _lastForwardMs = now;
153
153
  await forwarder.forwardNewText();
154
154
  }
155
- if (BACKEND_TOOLS.has(toolName) && !getBridgeRuntimeConnected()) {
156
- // Remote-owner startup: ensure this owner's backend is connected.
155
+ if (PROVIDER_TOOLS.has(toolName) && !getBridgeRuntimeConnected()) {
156
+ // Remote-owner startup: ensure this owner's provider is connected.
157
157
  for (let i = 0; i < 2 && !getBridgeRuntimeConnected(); i++) {
158
158
  try {
159
- // Auto-connect this owner's backend (daemon singleton — no seat claim).
159
+ // Auto-connect this owner's provider (daemon singleton — no seat claim).
160
160
  await refreshBridgeOwnership();
161
161
  } catch {
162
162
  }
@@ -78,7 +78,7 @@ if (!isWorkerMode) {
78
78
  // Publish owner identity immediately so the SessionStart shim's
79
79
  // owner_lead_alive() sees a live owner and uses the full connect budget
80
80
  // instead of the 5s no-owner grace (fixes missing recap/core on restart).
81
- // backendReady intentionally omitted — readiness stays gated until connect.
81
+ // providerReady intentionally omitted — readiness stays gated until connect.
82
82
  try {
83
83
  refreshActiveInstance(instanceId);
84
84
  } catch (e) {
@@ -12,7 +12,7 @@ export function runWorkerIpc({
12
12
  clearServerPid,
13
13
  instanceId,
14
14
  statusState,
15
- getBackend,
15
+ getProvider,
16
16
  getConfig,
17
17
  pendingPermRequests,
18
18
  refreshToolExecConsumerMarker,
@@ -72,14 +72,14 @@ export function runWorkerIpc({
72
72
  // Silent-to-agent lifecycle forward — parent (server.mjs) asks the
73
73
  // channels worker to post status pings to the active bridge Discord
74
74
  // channel without the Lead-notify hop. Best-effort: unknown channel or
75
- // getBackend() failure is swallowed; lifecycle pings are non-critical.
75
+ // getProvider() failure is swallowed; lifecycle pings are non-critical.
76
76
  if (msg && msg.type === 'forward_to_discord') {
77
77
  try {
78
78
  const target = msg.channelId
79
79
  || (statusState?.read?.().channelId)
80
80
  || null;
81
- if (target && getBackend()?.sendMessage && typeof msg.content === 'string' && msg.content) {
82
- await getBackend().sendMessage(target, msg.content).catch(() => {});
81
+ if (target && getProvider()?.sendMessage && typeof msg.content === 'string' && msg.content) {
82
+ await getProvider().sendMessage(target, msg.content).catch(() => {});
83
83
  }
84
84
  } catch { /* best-effort */ }
85
85
  return;
@@ -87,7 +87,7 @@ export function runWorkerIpc({
87
87
  // Host permission request → Discord Allow/Deny prompt.
88
88
  // Parent (server.mjs) receives notifications/claude/channel/permission_request
89
89
  // from the MCP host and forwards the params here. We post a buttoned message;
90
- // button clicks are handled in getBackend().onInteraction and sent back to CC as
90
+ // button clicks are handled in getProvider().onInteraction and sent back to CC as
91
91
  // notifications/claude/channel/permission via sendNotifyToParent.
92
92
  if (msg && msg.type === 'permission_request_inbound') {
93
93
  try {
@@ -110,7 +110,7 @@ export function runWorkerIpc({
110
110
  const target = (statusState?.read?.().channelId)
111
111
  || getConfig()?.channelId
112
112
  || null;
113
- if (!target || !getBackend()?.sendMessage) {
113
+ if (!target || !getProvider()?.sendMessage) {
114
114
  process.stderr.write(`mixdog channels: permission_request dropped, no target channel (request_id=${request_id})\n`);
115
115
  return;
116
116
  }
@@ -128,7 +128,7 @@ export function runWorkerIpc({
128
128
  }];
129
129
  let sentIds = null;
130
130
  try {
131
- const sendResult = await getBackend().sendMessage(target, content, { components });
131
+ const sendResult = await getProvider().sendMessage(target, content, { components });
132
132
  sentIds = sendResult?.sentIds;
133
133
  } catch (err) {
134
134
  process.stderr.write(`mixdog channels: permission_request Discord send failed: ${err && err.message || err}\n`);