mixdog 0.9.135 → 0.9.136

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 (121) hide show
  1. package/package.json +3 -1
  2. package/scripts/tool-stress.mjs +8 -1
  3. package/src/agents/maintainer/agent.json +1 -1
  4. package/src/defaults/skills/setup/SKILL.md +8 -43
  5. package/src/help.mjs +0 -2
  6. package/src/rules/lead/01-general.md +0 -4
  7. package/src/rules/shared/01-tool.md +10 -4
  8. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +30 -4
  9. package/src/runtime/agent/orchestrator/agent-trace-timing.test.mjs +57 -0
  10. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +3 -3
  11. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.test.mjs +40 -0
  12. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +7 -0
  13. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +0 -5
  14. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +17 -4
  15. package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +40 -0
  16. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +150 -41
  17. package/src/runtime/agent/orchestrator/tools/builtin/local-search-telemetry.mjs +4 -0
  18. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +34 -0
  19. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +18 -1
  20. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +10 -9
  21. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +124 -18
  22. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-top-k.test.mjs +39 -0
  23. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +7 -1
  24. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +4 -1
  25. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +82 -5
  26. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +2 -2
  27. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  28. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +78 -12
  29. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +34 -3
  30. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +12 -5
  31. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +87 -0
  32. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  33. package/src/runtime/channels/lib/config.mjs +14 -101
  34. package/src/runtime/channels/lib/index-drop-trace.mjs +2 -5
  35. package/src/runtime/channels/lib/owned-runtime.mjs +4 -80
  36. package/src/runtime/channels/lib/scheduler.mjs +75 -18
  37. package/src/runtime/channels/lib/status-snapshot.mjs +5 -68
  38. package/src/runtime/channels/lib/tool-dispatch.mjs +12 -55
  39. package/src/runtime/channels/lib/worker-ipc.mjs +1 -16
  40. package/src/runtime/channels/lib/worker-main.mjs +22 -307
  41. package/src/runtime/media/renditions.mjs +68 -5
  42. package/src/runtime/media/renditions.test.mjs +45 -0
  43. package/src/runtime/media/store.mjs +1 -0
  44. package/src/runtime/memory/index.mjs +4 -3
  45. package/src/runtime/memory/lib/cycle-scheduler.mjs +17 -6
  46. package/src/runtime/memory/lib/memory-cycle-packets.test.mjs +7 -1
  47. package/src/runtime/memory/lib/memory-cycle-requests.mjs +13 -0
  48. package/src/runtime/shared/config.mjs +8 -49
  49. package/src/runtime/shared/schedule-time.mjs +25 -0
  50. package/src/runtime/shared/schedule-time.test.mjs +29 -0
  51. package/src/runtime/shared/schedules-db.mjs +73 -1
  52. package/src/runtime/shared/tool-card-model.mjs +1 -1
  53. package/src/runtime/shared/transcript-writer.mjs +10 -17
  54. package/src/runtime/shared/update-checker.mjs +7 -2
  55. package/src/runtime/shared/update-checker.test.mjs +33 -0
  56. package/src/session-runtime/channel-config-api.mjs +3 -20
  57. package/src/session-runtime/config-lifecycle.mjs +1 -42
  58. package/src/session-runtime/lifecycle-api.mjs +1 -19
  59. package/src/session-runtime/model-route-api.mjs +0 -2
  60. package/src/session-runtime/model-settings-persist.test.mjs +0 -1
  61. package/src/session-runtime/prewarm.mjs +2 -7
  62. package/src/session-runtime/prewarm.test.mjs +0 -1
  63. package/src/session-runtime/runtime-core.mjs +6 -185
  64. package/src/session-runtime/session-lifecycle.mjs +4 -17
  65. package/src/session-runtime/session-transcript.mjs +54 -0
  66. package/src/session-runtime/session-turn-api.mjs +4 -44
  67. package/src/session-runtime/settings-api.mjs +0 -45
  68. package/src/session-runtime/tool-catalog-schema.mjs +1 -1
  69. package/src/standalone/channel-admin.mjs +8 -150
  70. package/src/standalone/daemon.mjs +5 -48
  71. package/src/standalone/seeds.mjs +18 -1
  72. package/src/standalone/session-protocol.mjs +0 -8
  73. package/src/standalone/session-service.mjs +1 -3
  74. package/src/tui/App.jsx +0 -5
  75. package/src/tui/app/app-view.jsx +1 -2
  76. package/src/tui/app/create-app-pickers.mjs +0 -20
  77. package/src/tui/app/doctor.mjs +2 -6
  78. package/src/tui/app/prompt-submit.mjs +2 -49
  79. package/src/tui/app/settings-picker.mjs +22 -96
  80. package/src/tui/app/slash-commands.mjs +0 -2
  81. package/src/tui/app/slash-dispatch.mjs +0 -17
  82. package/src/tui/app/use-prompt-handlers.mjs +4 -1
  83. package/src/tui/components/StatusLine.jsx +4 -4
  84. package/src/tui/dist/index.mjs +1003 -1461
  85. package/src/tui/session/context-state.mjs +0 -1
  86. package/src/tui/session/queue-helpers.mjs +1 -1
  87. package/src/tui/session/queue-helpers.test.mjs +10 -1
  88. package/src/tui/session/session-api-ext.mjs +0 -89
  89. package/src/tui/session/session-api.mjs +0 -2
  90. package/src/tui/session/tool-card-results.mjs +1 -1
  91. package/src/tui/session/tool-result-status.mjs +3 -3
  92. package/src/tui/session/tool-result-status.test.mjs +3 -3
  93. package/src/tui/session-local.mjs +0 -26
  94. package/src/workflows/default/WORKFLOW.md +13 -15
  95. package/src/workflows/solo/WORKFLOW.md +6 -7
  96. package/src/runtime/channels/lib/config-lock.mjs +0 -13
  97. package/src/runtime/channels/lib/drop-trace.mjs +0 -71
  98. package/src/runtime/channels/lib/format.mjs +0 -190
  99. package/src/runtime/channels/lib/inbound-handler.mjs +0 -387
  100. package/src/runtime/channels/lib/inbound-routing.mjs +0 -81
  101. package/src/runtime/channels/lib/interaction-handlers.mjs +0 -70
  102. package/src/runtime/channels/lib/interaction-workflows.mjs +0 -76
  103. package/src/runtime/channels/lib/memory-client.mjs +0 -289
  104. package/src/runtime/channels/lib/output-forwarder.mjs +0 -934
  105. package/src/runtime/channels/lib/session-control.mjs +0 -36
  106. package/src/runtime/channels/lib/session-discovery.mjs +0 -217
  107. package/src/runtime/channels/lib/session-discovery.test.mjs +0 -32
  108. package/src/runtime/channels/lib/telegram-format.mjs +0 -280
  109. package/src/runtime/channels/lib/tool-format.mjs +0 -125
  110. package/src/runtime/channels/lib/transcript-binding.mjs +0 -336
  111. package/src/runtime/channels/lib/transcript-discovery.mjs +0 -208
  112. package/src/runtime/channels/providers/discord-access.mjs +0 -30
  113. package/src/runtime/channels/providers/discord-attachments.mjs +0 -69
  114. package/src/runtime/channels/providers/discord-gateway.mjs +0 -230
  115. package/src/runtime/channels/providers/discord.mjs +0 -730
  116. package/src/runtime/channels/providers/telegram.mjs +0 -598
  117. package/src/runtime/channels/providers/telegram.test.mjs +0 -75
  118. package/src/session-runtime/remote-control.mjs +0 -126
  119. package/src/session-runtime/remote-transcript.mjs +0 -134
  120. package/src/session-runtime/remote-transition-queue.mjs +0 -14
  121. package/src/tui/app/channel-pickers.mjs +0 -384
@@ -0,0 +1,33 @@
1
+ import assert from 'node:assert/strict';
2
+ import {
3
+ mkdtempSync,
4
+ readFileSync,
5
+ readdirSync,
6
+ rmSync,
7
+ } from 'node:fs';
8
+ import { tmpdir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ import { test } from 'node:test';
11
+
12
+ test('update checks publish one parseable atomic cache file', async () => {
13
+ const root = mkdtempSync(join(tmpdir(), 'mixdog-update-cache-'));
14
+ const previousFetch = globalThis.fetch;
15
+ globalThis.fetch = async () => ({
16
+ ok: true,
17
+ json: async () => ({ version: '999.0.0' }),
18
+ });
19
+ try {
20
+ const checker = await import(`./update-checker.mjs?test=${Date.now()}`);
21
+ const result = await checker.checkLatestVersion({ force: true, dataDir: root });
22
+ assert.equal(result.latestVersion, '999.0.0');
23
+ const names = readdirSync(root);
24
+ assert.deepEqual(names, ['update-check-cache.json']);
25
+ assert.deepEqual(
26
+ JSON.parse(readFileSync(join(root, 'update-check-cache.json'), 'utf8')),
27
+ { latestVersion: '999.0.0', lastCheckedAt: result.lastCheckedAt },
28
+ );
29
+ } finally {
30
+ globalThis.fetch = previousFetch;
31
+ rmSync(root, { recursive: true, force: true });
32
+ }
33
+ });
@@ -3,7 +3,6 @@ import {
3
3
  deleteSchedule,
4
4
  deleteWebhook,
5
5
  getWebhookSecret,
6
- setChannelAsync,
7
6
  saveSchedule,
8
7
  saveWebhook,
9
8
  setScheduleEnabled,
@@ -13,38 +12,22 @@ import {
13
12
  import { getSchedule } from '../runtime/shared/schedules-db.mjs';
14
13
  import { runScheduleSession } from '../runtime/shared/schedule-session-run.mjs';
15
14
 
16
- // Channel/webhook/schedule config surface. Extracted verbatim from the runtime
15
+ // Webhook/schedule config surface. Extracted verbatim from the runtime
17
16
  // API object; the mutating admin helpers are imported directly here and the
18
- // runtime injects only the closure-owned callbacks (channel provider flush, channel
19
- // worker handle, soft reload).
17
+ // runtime injects only the closure-owned callbacks (channel worker handle,
18
+ // soft reload).
20
19
  export function createChannelConfigApi({
21
- flushChannelProviderSave,
22
20
  channels,
23
21
  reloadChannelsSoon,
24
22
  ensureAutomationRuntime = () => {},
25
- awaitKeychainPrewarm = async () => {},
26
23
  }) {
27
24
  return {
28
25
  async getChannelSetup() {
29
- // Flush a pending debounced channel provider switch first so setup readers
30
- // (Settings → Channel Setting, remote toggles) never observe the
31
- // previous provider during the 150ms debounce window.
32
- try { await flushChannelProviderSave(); } catch {}
33
- // Bot tokens live in the OS keychain. Reading them before the batch
34
- // prewarm lands costs one SYNCHRONOUS PowerShell DPAPI decrypt each
35
- // (measured 971ms + 579ms on a cold desktop boot, blocking the whole
36
- // event loop); waiting for the prewarm serves both from its cache.
37
- try { await awaitKeychainPrewarm(); } catch {}
38
26
  return channelSetup();
39
27
  },
40
28
  getChannelWorkerStatus() {
41
29
  return channels.status();
42
30
  },
43
- async setChannel(entry) {
44
- const result = await setChannelAsync(entry);
45
- reloadChannelsSoon();
46
- return result;
47
- },
48
31
  async setWebhookConfig(patch) {
49
32
  const result = await setWebhookConfigAsync(patch);
50
33
  reloadChannelsSoon();
@@ -11,7 +11,6 @@
11
11
  // same-tick readers see fresh state, and DEBOUNCE the disk write so a burst of
12
12
  // toggles collapses into one persist. Three independent debounce channels:
13
13
  // - config save (cfgMod.saveConfig, agent-section serialize)
14
- // - channel provider save (channel-admin setChannelProvider, file-locked RMW)
15
14
  // - outputStyle (sharedCfgMod.updateConfig whole-root RMW — cfgMod.saveConfig
16
15
  // only serializes agent-section fields, so a top-level
17
16
  // outputStyle would never reach disk via that path)
@@ -30,7 +29,6 @@ export function createConfigLifecycle({
30
29
  // shared modules / helpers
31
30
  cfgMod,
32
31
  sharedCfgMod,
33
- setChannelProviderAsync,
34
32
  setConfiguredShell,
35
33
  normalizeSystemShellConfig,
36
34
  normalizeSearchRouteConfig,
@@ -188,42 +186,6 @@ export function createConfigLifecycle({
188
186
  return adopted;
189
187
  }
190
188
 
191
- // --- debounced channel provider switch ----------------------------------------------
192
- let pendingChannelProvider = null;
193
- let channelProviderSaveTimer = null;
194
- let channelProviderFlushInFlight = null;
195
-
196
- async function runChannelProviderFlushAsync() {
197
- while (pendingChannelProvider !== null) {
198
- const name = pendingChannelProvider;
199
- try {
200
- await setChannelProviderAsync(name);
201
- } catch (err) {
202
- process.stderr.write(`[channels] async setChannelProvider failed: ${err?.message || err}\n`);
203
- if (pendingChannelProvider === name) pendingChannelProvider = null;
204
- break;
205
- }
206
- if (pendingChannelProvider === name) pendingChannelProvider = null;
207
- }
208
- }
209
-
210
- function flushChannelProviderSaveAsync() {
211
- if (channelProviderSaveTimer) { clearTimeout(channelProviderSaveTimer); channelProviderSaveTimer = null; }
212
- const start = () => runChannelProviderFlushAsync();
213
- const p = channelProviderFlushInFlight ? channelProviderFlushInFlight.then(start, start) : start();
214
- channelProviderFlushInFlight = p;
215
- const clear = () => { if (channelProviderFlushInFlight === p) channelProviderFlushInFlight = null; };
216
- p.then(clear, clear);
217
- return p;
218
- }
219
-
220
- function scheduleChannelProviderSave(name) {
221
- pendingChannelProvider = name;
222
- if (channelProviderSaveTimer) clearTimeout(channelProviderSaveTimer);
223
- channelProviderSaveTimer = setTimeout(() => { flushChannelProviderSaveAsync(); }, CONFIG_SAVE_DEBOUNCE_MS);
224
- channelProviderSaveTimer.unref?.();
225
- }
226
-
227
189
  // --- debounced skills.disabled persist -------------------------------------
228
190
  // In-memory skills state is adopted synchronously by setDisabledSkills; the
229
191
  // heavy in-lock file RMW (cfgMod.patchSkillsDisabled) is deferred here so a
@@ -326,11 +288,10 @@ export function createConfigLifecycle({
326
288
  async function flushAllConfigSavesAsync() {
327
289
  await Promise.all([
328
290
  flushConfigSaveAsync(),
329
- flushChannelProviderSaveAsync(),
330
291
  flushOutputStyleSaveAsync(),
331
292
  ]);
332
293
  // The shared config layer also tracks writes started directly by channel,
333
- // webhook, voice, Discord access, and future async RMW callers.
294
+ // webhook, voice, and future async RMW callers.
334
295
  await sharedCfgMod.pendingConfigWrites();
335
296
  }
336
297
 
@@ -420,8 +381,6 @@ export function createConfigLifecycle({
420
381
  // Every lifecycle flush uses the async lock path. A synchronous waiter on
421
382
  // the same lock would block the event loop needed by an in-flight async
422
383
  // writer, so callers must await this before a dependent read/start.
423
- flushChannelProviderSave: flushChannelProviderSaveAsync,
424
- scheduleChannelProviderSave,
425
384
  flushSkillsSave,
426
385
  scheduleSkillsSave,
427
386
  flushOutputStyleSave,
@@ -53,8 +53,7 @@ export function createLifecycleApi(deps) {
53
53
  invalidateContextStatusCache, invalidatePreSessionToolSurface,
54
54
  applyResolvedCwd, resolveRoute, applyDeferredToolSurface, getStandaloneTools,
55
55
  beginRoutePreparation, clearRoutePreparation,
56
- pushTranscriptRebind, scheduleRemoteIntentRestore,
57
- notificationListeners, clearRuntimeNotifications, remoteStateListeners,
56
+ notificationListeners, clearRuntimeNotifications,
58
57
  disposeSessionTitles, abortActiveTurns, getReservedSessionId,
59
58
  } = deps;
60
59
  const closeSurfaceSession = (session, reason, options) => {
@@ -321,7 +320,6 @@ export function createLifecycleApi(deps) {
321
320
  invalidateContextStatusCache();
322
321
  if (typeof clearRuntimeNotifications === 'function') clearRuntimeNotifications();
323
322
  else notificationListeners?.clear?.();
324
- remoteStateListeners?.clear?.();
325
323
  const shellJobsStop = teardownReapsWork && globalThis.__mixdogShellJobsRuntimeLoaded === true
326
324
  ? import('../runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs')
327
325
  .then((mod) => mod?.shutdownShellJobs?.(reason, {
@@ -431,7 +429,6 @@ export function createLifecycleApi(deps) {
431
429
  invalidateContextStatusCache();
432
430
  invalidatePreSessionToolSurface();
433
431
  await createCurrentSession();
434
- pushTranscriptRebind?.();
435
432
  return true;
436
433
  },
437
434
  async switchContext({ cwd, desktopSession: nextDesktopSession, forResume = false } = {}) {
@@ -489,10 +486,6 @@ export function createLifecycleApi(deps) {
489
486
  }
490
487
  invalidateContextStatusCache();
491
488
  await createCurrentSession();
492
- // New session.id => the worker's binding (and persisted status) now point
493
- // at the previous session's transcript. Push the current transcript so
494
- // outbound forwarding repoints immediately (best-effort, remote-gated).
495
- pushTranscriptRebind?.();
496
489
  return getSession().id;
497
490
  },
498
491
  prefetchSession(id) {
@@ -572,17 +565,6 @@ export function createLifecycleApi(deps) {
572
565
  invalidatePreSessionToolSurface();
573
566
  invalidateContextStatusCache();
574
567
  setSessionNeedsCwdRefresh(false);
575
- // Session swapped to the resumed one: repoint the worker to the current
576
- // transcript instead of waiting for the next inbound steal.
577
- pushTranscriptRebind?.();
578
- // Daemon-boot restore resumes the pinned session AFTER the runtime's
579
- // one-shot boot probe may have fired with no session (it returns without
580
- // re-arming). Re-check now that the session identity is final; the probe
581
- // self-guards (intent match, one-shot, remote already on), so this is a
582
- // no-op for every non-pinned resume. Without it the runtime stays
583
- // non-remote after a restart: tool_use rows are never mirrored and the
584
- // channel shows text without tool markers (user report).
585
- scheduleRemoteIntentRestore?.(0);
586
568
  return {
587
569
  id: resumed.id,
588
570
  messages: resumed.messages || [],
@@ -61,7 +61,6 @@ export function createModelRouteApi(deps) {
61
61
  refreshStatuslineUsageSnapshot, scheduleStatuslineUsageRefresh,
62
62
  invalidateContextStatusCache, invalidateProviderCaches,
63
63
  createCurrentSession, invalidatePreSessionToolSurface,
64
- pushTranscriptRebind,
65
64
  collectSearchProviderModels,
66
65
  } = deps;
67
66
  function persistAdoptedModelSettings(route) {
@@ -212,7 +211,6 @@ export function createModelRouteApi(deps) {
212
211
  setSession(null);
213
212
  invalidatePreSessionToolSurface?.();
214
213
  await createCurrentSession('model-switch-empty');
215
- pushTranscriptRebind?.();
216
214
  invalidateContextStatusCache();
217
215
  return getRoute();
218
216
  }
@@ -63,7 +63,6 @@ function stubRouteApi({ persistLeadRoute, saveConfigAndAdopt, cfgMod }) {
63
63
  invalidateProviderCaches: () => {},
64
64
  createCurrentSession: async () => {},
65
65
  invalidatePreSessionToolSurface: () => {},
66
- pushTranscriptRebind: () => {},
67
66
  collectSearchProviderModels: async () => [],
68
67
  });
69
68
  }
@@ -13,8 +13,6 @@ export function createPrewarmSchedulers({
13
13
  getActiveTurnCount,
14
14
  getSessionCreatePromise,
15
15
  getSession,
16
- isRemoteEnabled,
17
- channelsEnabled,
18
16
  hasActiveAutomation,
19
17
  getCodeGraphModule,
20
18
  createCurrentSession,
@@ -194,15 +192,12 @@ export function createPrewarmSchedulers({
194
192
  if (isCloseRequested()) return;
195
193
  // Channels-module and remote toggles gate MESSAGING; automation
196
194
  // (enabled schedules/webhooks) keeps the worker boot alive — its
197
- // channel provider runs headless when messaging is off or unconfigured.
195
+ // channel worker runs headless: only active automation boots it.
198
196
  const automation = await hasActiveAutomation().catch(() => false);
199
- if (!channelsEnabled() && !automation) {
197
+ if (!automation) {
200
198
  bootProfile('channels:start-disabled');
201
199
  return;
202
200
  }
203
- // A deferred start may straddle a stopRemote(); re-check before booting so
204
- // a turned-off session neither starts channels nor keeps rescheduling.
205
- if (!isRemoteEnabled() && !automation) return;
206
201
  if (isCloseRequested()) return;
207
202
  if (getActiveTurnCount() > 0 || getSessionCreatePromise()) {
208
203
  bootProfile('channels:start-deferred', { reason: getActiveTurnCount() > 0 ? 'turn-active' : 'session-create' });
@@ -16,7 +16,6 @@ test('first-visible code graph prewarm overlaps an active turn', async () => {
16
16
  getActiveTurnCount: () => 1,
17
17
  getSessionCreatePromise: () => null,
18
18
  getSession: () => null,
19
- isRemoteEnabled: () => false,
20
19
  channelsEnabled: () => false,
21
20
  hasActiveAutomation: () => false,
22
21
  getCodeGraphModule: async () => ({
@@ -14,12 +14,10 @@ import { isAgentOwner } from '../runtime/agent/orchestrator/agent-owner.mjs';
14
14
  import { createStandaloneChannelWorker } from '../standalone/channel-worker.mjs';
15
15
  import { createStandaloneHookBus } from '../standalone/hook-bus.mjs';
16
16
  import { getStandaloneMemoryRuntime } from '../standalone/memory-runtime-proxy.mjs';
17
- import { createRemoteTransitionQueue } from './remote-transition-queue.mjs';
18
17
  import { writeLastSessionCwd } from '../runtime/shared/user-cwd.mjs';
19
18
  import { cancelBackgroundTasks } from '../runtime/shared/background-tasks.mjs';
20
19
  import { createTranscriptWriter } from '../runtime/shared/transcript-writer.mjs';
21
20
  import { mixdogHome } from '../runtime/shared/plugin-paths.mjs';
22
- import { remoteIntentMatchesSession } from '../runtime/shared/remote-intent.mjs';
23
21
  import { checkLatestVersion, localPackageVersion, isDevInstall } from '../runtime/shared/update-checker.mjs';
24
22
  import { spawnStagedInstall, runStagedInstall, isStagedComplete } from '../runtime/shared/staged-update.mjs';
25
23
  import {
@@ -59,19 +57,11 @@ import {
59
57
  toResponsesCustomTool,
60
58
  } from '../runtime/agent/orchestrator/providers/custom-tool-wire.mjs';
61
59
  import {
62
- channelSetup,
63
60
  deleteSchedule,
64
61
  deleteWebhook,
65
- forgetDiscordToken,
66
- forgetTelegramToken,
67
62
  hasActiveAutomation,
68
- setChannel,
69
- saveDiscordToken,
70
- saveTelegramToken,
71
63
  saveSchedule,
72
64
  saveWebhook,
73
- setChannelProvider,
74
- setChannelProviderAsync,
75
65
  setScheduleEnabled,
76
66
  setWebhookEnabled,
77
67
  setWebhookConfig,
@@ -252,8 +242,6 @@ import { createModelRouteApi } from './model-route-api.mjs';
252
242
  import { createWorkflowAgentsApi } from './workflow-agents-api.mjs';
253
243
  import { createSelfUpdateController } from './self-update.mjs';
254
244
  import { createSkillsApi } from './skills-api.mjs';
255
- import { createRemoteTranscript } from './remote-transcript.mjs';
256
- import { createRemoteControl } from './remote-control.mjs';
257
245
  import { createNotificationBus } from './notification-bus.mjs';
258
246
  import { createToolSurface } from './tool-surface.mjs';
259
247
  import { createToolPolicyRefresh } from './tool-policy-refresh.mjs';
@@ -344,14 +332,6 @@ export async function createMixdogSessionRuntime({
344
332
  // (session-lifecycle) instead of the Lead session shape.
345
333
  rt.agentSessionSpec = agentSession && typeof agentSession === 'object' ? agentSession : null;
346
334
  bootProfile('session-runtime:start', { provider, model, toolMode, cwd });
347
- rt.remoteEnabled = remote === true;
348
- // SESSION-SCOPED Remote is manual-only. Explicit ON selects/overrides; no
349
- // registration, turn, session death, or process death transfers ownership.
350
- rt.remoteSessionId = null;
351
- // Desktop/TUI Remote toggles are desired-state transitions. Serialize the
352
- // daemon deactivate/detach and reconnect/activate chains so an OFF that is
353
- // still stopping can never tear down the worker created by the following ON.
354
- const remoteTransitions = createRemoteTransitionQueue();
355
335
  // Last assistant text handed to the transcript writer (via onAssistantText),
356
336
  // so the post-turn final-content append can skip an exact duplicate.
357
337
  rt._lastAppendedAssistant = '';
@@ -620,14 +600,6 @@ export async function createMixdogSessionRuntime({
620
600
  rt.codeGraphFirstTurnPrewarmDone = false;
621
601
  const modelMetaByRoute = new Map();
622
602
  const notificationListeners = new Set();
623
- // Remote seat listeners (TUI): fired when remote mode flips outside a direct
624
- // user action — currently only the superseded (seat stolen) path.
625
- const remoteStateListeners = new Set();
626
- function emitRemoteStateChange(enabled, reason = '') {
627
- for (const listener of [...remoteStateListeners]) {
628
- try { listener({ enabled: enabled === true, reason: String(reason || '') }); } catch {}
629
- }
630
- }
631
603
  const providerModelCaches = {
632
604
  providerModelsCache: { models: null, at: 0 },
633
605
  providerModelsPromise: null,
@@ -866,16 +838,6 @@ export async function createMixdogSessionRuntime({
866
838
  // session-pinned channel-link restore for exactly the session that owns it.
867
839
  getSessionId: () => rt.session?.id || rt.reservedSessionId || null,
868
840
  onNotify: (msg) => {
869
- // Single-holder remote: the worker reports it lost the bridge seat to a
870
- // newer remote session. Drop remote mode entirely on this session (no
871
- // handover, no retry) and tell UI listeners so the indicator updates.
872
- if (msg?.method === 'notifications/mixdog/remote') {
873
- if (msg?.params?.state === 'superseded' && rt.remoteEnabled) {
874
- stopRemote('superseded-by-newer-remote-session');
875
- emitRemoteStateChange(false, 'superseded');
876
- }
877
- return;
878
- }
879
841
  if (msg?.method !== 'notifications/claude/channel') return;
880
842
  const params = msg?.params && typeof msg.params === 'object' ? msg.params : {};
881
843
  const meta = params.meta && typeof params.meta === 'object' ? params.meta : {};
@@ -1056,8 +1018,6 @@ export async function createMixdogSessionRuntime({
1056
1018
  adoptConfig,
1057
1019
  saveConfigAndAdopt,
1058
1020
  flushConfigSave,
1059
- flushChannelProviderSave,
1060
- scheduleChannelProviderSave,
1061
1021
  scheduleSkillsSave,
1062
1022
  flushSkillsSave,
1063
1023
  flushOutputStyleSave,
@@ -1077,8 +1037,6 @@ export async function createMixdogSessionRuntime({
1077
1037
  getRoute: () => rt.route,
1078
1038
  cfgMod,
1079
1039
  sharedCfgMod,
1080
- setChannelProvider,
1081
- setChannelProviderAsync,
1082
1040
  setConfiguredShell,
1083
1041
  normalizeSystemShellConfig,
1084
1042
  normalizeSearchRouteConfig,
@@ -1318,47 +1276,6 @@ export async function createMixdogSessionRuntime({
1318
1276
  agentTool,
1319
1277
  });
1320
1278
  const ensureSessionTranscriptWriter = () => remoteTranscript.ensureSessionTranscriptWriter();
1321
- const ensureRemoteTranscriptWriter = () => remoteTranscript.ensureRemoteTranscriptWriter();
1322
- const pushTranscriptRebind = () => remoteTranscript.pushTranscriptRebind();
1323
- const flushPendingTranscriptRebind = () => remoteTranscript.flushPendingTranscriptRebind();
1324
-
1325
- // Remote (channel relay) claim/release/stop live in remote-control.mjs; the
1326
- // facade injects the mutable session + remote state they mutate.
1327
- const remoteControl = createRemoteControl({
1328
- getSession: () => rt.session,
1329
- isRemoteEnabled: () => rt.remoteEnabled,
1330
- setRemoteEnabled: (next) => { rt.remoteEnabled = next; },
1331
- getRemoteSessionId: () => rt.remoteSessionId,
1332
- setRemoteSessionId: (next) => { rt.remoteSessionId = next; },
1333
- isCloseRequested: () => rt.closeRequested,
1334
- clearChannelStartTimer: () => {
1335
- if (prewarmTimers.channelStartTimer) {
1336
- clearTimeout(prewarmTimers.channelStartTimer);
1337
- prewarmTimers.channelStartTimer = null;
1338
- }
1339
- },
1340
- remoteTransitions,
1341
- channels,
1342
- channelsEnabled,
1343
- hasActiveAutomation,
1344
- flushChannelProviderSave,
1345
- invokeChannelStart,
1346
- createCurrentSession,
1347
- ensureSessionTranscriptWriter,
1348
- ensureRemoteTranscriptWriter,
1349
- getTranscriptPath: () => remoteTranscript.transcriptWriter?.transcriptPath || null,
1350
- emitRemoteStateChange,
1351
- getMemoryModule,
1352
- bootProfile,
1353
- envFlag,
1354
- });
1355
- const startRemote = (options) => remoteControl.startRemote(options);
1356
- const releaseRemote = (reason) => remoteControl.releaseRemote(reason);
1357
- const stopRemote = (reason) => remoteControl.stopRemote(reason);
1358
-
1359
- function isRemoteEnabled() {
1360
- return rt.remoteEnabled;
1361
- }
1362
1279
 
1363
1280
  function withTeardownDeadline(promise, ms, fallback = false) {
1364
1281
  let timer = null;
@@ -1391,61 +1308,18 @@ export async function createMixdogSessionRuntime({
1391
1308
  scheduleModelCatalogWarmup();
1392
1309
  scheduleStatuslineUsageWarmup();
1393
1310
  }
1394
- // Channels are opt-in: only boot the worker when this session started in (or
1395
- // was toggled into) remote mode. Non-remote sessions never contend for the
1396
- // channel; see startRemote()/stopRemote() and the `/remote` toggle.
1397
- // A manual `mixdog --remote` request is deferred past the TUI's
1398
- // first frame. startRemote() front-loads memory initialization
1399
- // (PG + forced ONNX embed warmup in the child), eager session create, and
1400
- // the channel-worker fork — and running it inline here interleaves that
1401
- // CPU/disk load with engine boot, visibly delaying the first ink frame by
1402
- // seconds. The deferred timer reuses prewarmTimers.channelStartTimer so an
1403
- // early /remote (startRemote clears it), stopRemote(), and close() all
1404
- // cancel it through the existing clearTimeout paths. Runtime /remote calls
1405
- // still start immediately (user-initiated, UI already painted).
1406
- // Reconnect restore: a session that owned the channel link before the app or
1407
- // daemon restarted reclaims it by itself. The durable intent used to be
1408
- // consumed only by the daemon's register-time restore, which never ran for a
1409
- // non-remote, automation-free session — so the link looked silently dropped
1410
- // until the user toggled Remote by hand.
1411
- let remoteIntentRestoreTimer = null;
1412
- function scheduleRemoteIntentRestore(delayMs = remoteAutoStartDelayMs) {
1413
- if (rt.agentSessionSpec || rt.remoteIntentRestoreDone) return;
1414
- if (rt.closeRequested || rt.remoteEnabled || remoteIntentRestoreTimer) return;
1415
- remoteIntentRestoreTimer = setTimeout(() => {
1416
- remoteIntentRestoreTimer = null;
1417
- if (rt.remoteIntentRestoreDone || rt.closeRequested || rt.remoteEnabled) return;
1418
- const sessionId = rt.session?.id || rt.reservedSessionId || null;
1419
- if (!sessionId || !remoteIntentMatchesSession(sessionId, rt.currentCwd)) return;
1420
- rt.remoteIntentRestoreDone = true;
1421
- bootProfile('channels:remote-intent-restore', { sessionId });
1422
- void startRemote();
1423
- }, delayMs);
1424
- remoteIntentRestoreTimer?.unref?.();
1425
- }
1426
-
1427
1311
  if (rt.agentSessionSpec) {
1428
1312
  // Worker sessions never run channels or schedule automation probes.
1429
- } else if (rt.remoteEnabled) {
1430
- bootProfile('channels:manual-start-deferred', { delayMs: remoteAutoStartDelayMs });
1431
- prewarmTimers.channelStartTimer = setTimeout(() => {
1432
- prewarmTimers.channelStartTimer = null;
1433
- if (rt.closeRequested || !rt.remoteEnabled) return;
1434
- startRemote();
1435
- }, remoteAutoStartDelayMs);
1436
- prewarmTimers.channelStartTimer.unref?.();
1437
1313
  } else {
1438
- scheduleRemoteIntentRestore();
1439
1314
  // Automation decoupling (user decision): enabled schedules/webhooks boot
1440
- // the worker on their own — no remote flag, no remote.autoStart, and no
1441
- // messaging provider. A later explicit Remote ON promotes the same
1442
- // daemon without restarting schedules or webhooks.
1315
+ // the worker on their own — no messaging provider. The worker runs
1316
+ // headless (scheduler/webhooks/voice only).
1443
1317
  prewarmTimers.channelStartTimer = setTimeout(() => {
1444
1318
  prewarmTimers.channelStartTimer = null;
1445
- if (rt.closeRequested || rt.remoteEnabled) return;
1319
+ if (rt.closeRequested) return;
1446
1320
  void hasActiveAutomation()
1447
1321
  .then((active) => {
1448
- if (!active || rt.closeRequested || rt.remoteEnabled) return;
1322
+ if (!active || rt.closeRequested) return;
1449
1323
  bootProfile('channels:automation-autostart');
1450
1324
  void invokeChannelStart();
1451
1325
  })
@@ -1455,8 +1329,8 @@ export async function createMixdogSessionRuntime({
1455
1329
  }
1456
1330
 
1457
1331
  // Pure settings-delegate methods (onboarding status/skip, autoClear, profile,
1458
- // compaction, recap/memory, channels, systemShell, update settings, channel
1459
- // token save/forget, setChannelProvider). Extracted to session-runtime/settings-api.mjs
1332
+ // compaction, recap/memory, channels, systemShell, update settings).
1333
+ // Extracted to session-runtime/settings-api.mjs
1460
1334
  // and SPREAD into the API object below so the external surface is unchanged.
1461
1335
  const { refreshEmptySessionToolPolicy } = createToolPolicyRefresh({
1462
1336
  getSession: () => rt.session,
@@ -1475,10 +1349,8 @@ export async function createMixdogSessionRuntime({
1475
1349
  getConfig: () => rt.config,
1476
1350
  getRoute: () => rt.route,
1477
1351
  getSession: () => rt.session,
1478
- getRemoteEnabled: () => rt.remoteEnabled,
1479
1352
  adoptConfig,
1480
1353
  saveConfigAndAdopt,
1481
- scheduleChannelProviderSave,
1482
1354
  scheduleSkillsSave,
1483
1355
  cfgMod,
1484
1356
  hasOwn,
@@ -1519,21 +1391,14 @@ export async function createMixdogSessionRuntime({
1519
1391
  runUpdateNowInternal: (...a) => runUpdateNowInternal(...a),
1520
1392
  reloadChannelsSoon: (...a) => reloadChannelsSoon(...a),
1521
1393
  ONBOARDING_VERSION,
1522
- saveDiscordToken,
1523
- forgetDiscordToken,
1524
- saveTelegramToken,
1525
- forgetTelegramToken,
1526
- setChannelProvider,
1527
1394
  });
1528
1395
 
1529
1396
  const channelConfigApi = createChannelConfigApi({
1530
- flushChannelProviderSave,
1531
1397
  channels,
1532
1398
  reloadChannelsSoon,
1533
1399
  // Automation saved mid-session boots the worker (claim-if-vacant) even
1534
1400
  // though the boot-time autostart window has already passed.
1535
1401
  ensureAutomationRuntime: () => scheduleChannelStart(0),
1536
- awaitKeychainPrewarm,
1537
1402
  });
1538
1403
  const providerAuthApi = createProviderAuthApi({
1539
1404
  cfgMod,
@@ -1582,17 +1447,10 @@ export async function createMixdogSessionRuntime({
1582
1447
  statusRoutes,
1583
1448
  channels,
1584
1449
  agentTool,
1585
- pushTranscriptRebind,
1586
- // Post-resume remote-intent recheck (hoisted declaration below). The boot
1587
- // probe can fire before the daemon-driven resume has established
1588
- // rt.session/cwd and then never re-arms; a completed resume is the first
1589
- // moment the pinned session can actually reclaim its channel link.
1590
- scheduleRemoteIntentRestore,
1591
1450
  mcpClient,
1592
1451
  warmupTimers,
1593
1452
  prewarmTimers,
1594
1453
  flushConfigSave,
1595
- flushChannelProviderSave,
1596
1454
  flushOutputStyleSave,
1597
1455
  flushAllConfigSavesAsync,
1598
1456
  withTeardownDeadline,
@@ -1666,7 +1524,6 @@ export async function createMixdogSessionRuntime({
1666
1524
  invalidateProviderCaches,
1667
1525
  createCurrentSession,
1668
1526
  invalidatePreSessionToolSurface,
1669
- pushTranscriptRebind,
1670
1527
  collectSearchProviderModels,
1671
1528
  });
1672
1529
  const workflowAgentsApi = createWorkflowAgentsApi({
@@ -1713,10 +1570,8 @@ export async function createMixdogSessionRuntime({
1713
1570
  getCodeGraphFirstTurnPrewarmDone: () => rt.codeGraphFirstTurnPrewarmDone,
1714
1571
  setCodeGraphFirstTurnPrewarmDone: (v) => { rt.codeGraphFirstTurnPrewarmDone = v; },
1715
1572
  codeGraphPrewarmLazy,
1716
- getRemoteEnabled: () => rt.remoteEnabled,
1717
1573
  getCloseRequested: () => rt.closeRequested,
1718
1574
  getTranscriptWriter: () => remoteTranscript.transcriptWriter,
1719
- getTwKey: () => remoteTranscript.transcriptKey,
1720
1575
  getLastAppendedAssistant: () => rt._lastAppendedAssistant,
1721
1576
  setLastAppendedAssistant: (v) => { rt._lastAppendedAssistant = v; },
1722
1577
  scheduleCodeGraphPrewarm,
@@ -1725,11 +1580,6 @@ export async function createMixdogSessionRuntime({
1725
1580
  refreshSessionForCwdIfNeeded,
1726
1581
  createCurrentSession,
1727
1582
  ensureSessionTranscriptWriter,
1728
- ensureRemoteTranscriptWriter,
1729
- pushTranscriptRebind,
1730
- flushPendingTranscriptRebind,
1731
- channelsEnabled,
1732
- invokeChannelStart,
1733
1583
  channels,
1734
1584
  hooks,
1735
1585
  hookCommonPayload,
@@ -1748,7 +1598,6 @@ export async function createMixdogSessionRuntime({
1748
1598
  resolveCwdPath,
1749
1599
  agentStatusState,
1750
1600
  notificationListeners,
1751
- remoteStateListeners,
1752
1601
  awaitInitialMcpConnect,
1753
1602
  mcpTurnGraceMs,
1754
1603
  awaitRoutePreparation: () => routePreparation.wait(),
@@ -1793,10 +1642,6 @@ export async function createMixdogSessionRuntime({
1793
1642
  }
1794
1643
  rt.reservedSessionId = id;
1795
1644
  bindRuntimeNotificationSession(id);
1796
- // First moment this runtime knows WHICH session it is: re-check the
1797
- // channel-link intent, since the boot probe may have run before the
1798
- // daemon addressed this session.
1799
- scheduleRemoteIntentRestore();
1800
1645
  // Reservation is the earliest safe point to prepare keychain, memory,
1801
1646
  // provider metadata, hooks, and the provider transport. This starts no
1802
1647
  // model response and therefore incurs no inference/token usage. The first
@@ -1889,30 +1734,6 @@ export async function createMixdogSessionRuntime({
1889
1734
  renameSessionTitle(sessionId, title) {
1890
1735
  return mgr.updateSessionManualTitle(sessionId, title);
1891
1736
  },
1892
- startRemote() {
1893
- return startRemote();
1894
- },
1895
- stopRemote(reason) {
1896
- return stopRemote(reason);
1897
- },
1898
- releaseRemote(reason) {
1899
- return releaseRemote(reason);
1900
- },
1901
- isRemoteEnabled() {
1902
- return isRemoteEnabled();
1903
- },
1904
- // Session-scoped remote: the owning session id (null when off/unassigned).
1905
- getRemoteSessionId() {
1906
- return rt.remoteSessionId;
1907
- },
1908
- // Subscribe to non-user-initiated remote flips (seat superseded). Returns
1909
- // an unsubscribe function. TUI uses this to sync its Remote indicator and
1910
- // show a "remote taken over" notice.
1911
- onRemoteStateChange(listener) {
1912
- if (typeof listener !== 'function') return () => {};
1913
- remoteStateListeners.add(listener);
1914
- return () => remoteStateListeners.delete(listener);
1915
- },
1916
1737
  get clientHostPid() {
1917
1738
  return rt.session?.clientHostPid || process.pid;
1918
1739
  },