mixdog 0.9.86 → 0.9.87

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 (38) hide show
  1. package/package.json +2 -2
  2. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +4 -2
  3. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +125 -16
  4. package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
  5. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -2
  6. package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +8 -0
  7. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -3
  8. package/src/runtime/agent/orchestrator/tools/token-manifest.json +23 -2
  9. package/src/runtime/memory/lib/memory-action-handlers.mjs +4 -1
  10. package/src/runtime/memory/tool-defs.mjs +19 -3
  11. package/src/session-runtime/lifecycle-api.mjs +15 -0
  12. package/src/session-runtime/provider-auth-api.mjs +8 -0
  13. package/src/session-runtime/provider-usage.mjs +30 -5
  14. package/src/session-runtime/runtime-core.mjs +267 -481
  15. package/src/session-runtime/session-lifecycle.mjs +378 -0
  16. package/src/standalone/agent-shard/shard-pool.mjs +6 -5
  17. package/src/standalone/agent-tool/job-views.mjs +329 -0
  18. package/src/standalone/agent-tool/spawn-flow.mjs +629 -0
  19. package/src/standalone/agent-tool/tag-registry.mjs +340 -0
  20. package/src/standalone/agent-tool.mjs +100 -1151
  21. package/src/standalone/usage-dashboard.mjs +23 -1
  22. package/src/tui/App.jsx +362 -2576
  23. package/src/tui/app/app-view.jsx +504 -0
  24. package/src/tui/app/create-app-pickers.mjs +313 -0
  25. package/src/tui/app/prompt-submit.mjs +501 -0
  26. package/src/tui/app/shell-layout.mjs +563 -0
  27. package/src/tui/app/usage-context-panels.mjs +288 -0
  28. package/src/tui/app/use-copy-selection.mjs +56 -0
  29. package/src/tui/app/use-global-key-input.mjs +151 -0
  30. package/src/tui/app/use-pasted-buffers.mjs +115 -0
  31. package/src/tui/app/use-prompt-draft-flow.mjs +167 -0
  32. package/src/tui/app/use-prompt-hint.mjs +58 -0
  33. package/src/tui/app/use-prompt-queue-history.mjs +100 -0
  34. package/src/tui/app/use-terminal-chrome.mjs +72 -0
  35. package/src/tui/app/use-transcript-activity.mjs +131 -0
  36. package/src/tui/app/use-welcome-prompt-hint.mjs +96 -0
  37. package/src/tui/dist/index.mjs +9802 -9024
  38. package/src/tui/engine/session-api-ext.mjs +23 -0
@@ -1,3 +1,4 @@
1
+ import { createSessionLifecycle } from './session-lifecycle.mjs';
1
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
2
3
  import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
3
4
  import { performance } from 'node:perf_hooks';
@@ -41,7 +42,10 @@ import {
41
42
  setLocalProvider,
42
43
  } from '../standalone/provider-admin.mjs';
43
44
  import { createUsageDashboard } from '../standalone/usage-dashboard.mjs';
44
- import { fetchOAuthUsageSnapshot } from '../runtime/agent/orchestrator/providers/oauth-usage.mjs';
45
+ import {
46
+ consumeOpenAICodexResetCredit,
47
+ fetchOAuthUsageSnapshot,
48
+ } from '../runtime/agent/orchestrator/providers/oauth-usage.mjs';
45
49
  import {
46
50
  getModelMetadataSync,
47
51
  warmCatalogsInBackground,
@@ -320,19 +324,22 @@ export async function createMixdogSessionRuntime({
320
324
  remote = false,
321
325
  desktopSession: initialDesktopSession = null,
322
326
  } = {}) {
323
- let desktopSession = initialDesktopSession;
327
+ // Shared mutable runtime state, promoted from closure `let`s so extracted
328
+ // modules can read/write live values through one reference.
329
+ const rt = {};
330
+ rt.desktopSession = initialDesktopSession;
324
331
  bootProfile('session-runtime:start', { provider, model, toolMode, cwd });
325
- let remoteEnabled = remote === true;
332
+ rt.remoteEnabled = remote === true;
326
333
  // SESSION-SCOPED Remote is manual-only. Explicit ON selects/overrides; no
327
334
  // registration, turn, session death, or process death transfers ownership.
328
- let remoteSessionId = null;
335
+ rt.remoteSessionId = null;
329
336
  // Desktop/TUI Remote toggles are desired-state transitions. Serialize the
330
337
  // daemon deactivate/detach and reconnect/activate chains so an OFF that is
331
338
  // still stopping can never tear down the worker created by the following ON.
332
339
  const remoteTransitions = createRemoteTransitionQueue();
333
340
  // Last assistant text handed to the transcript writer (via onAssistantText),
334
341
  // so the post-turn final-content append can skip an exact duplicate.
335
- let _lastAppendedAssistant = '';
342
+ rt._lastAppendedAssistant = '';
336
343
  process.env.MIXDOG_QUIET_SESSION_LOG ??= '1';
337
344
  const standaloneStartedAt = performance.now();
338
345
  ensureStandaloneEnvironment({
@@ -341,11 +348,11 @@ export async function createMixdogSessionRuntime({
341
348
  });
342
349
  bootProfile('standalone-env:ready', { ms: (performance.now() - standaloneStartedAt).toFixed(1) });
343
350
  const keychainPrewarmPromise = keychain.prewarmSecrets();
344
- let keychainPrewarmWaitDone = false;
345
- let keychainPrewarmWaitPromise = null;
351
+ rt.keychainPrewarmWaitDone = false;
352
+ rt.keychainPrewarmWaitPromise = null;
346
353
  function awaitKeychainPrewarm() {
347
- if (keychainPrewarmWaitDone) return Promise.resolve();
348
- keychainPrewarmWaitPromise ??= (async () => {
354
+ if (rt.keychainPrewarmWaitDone) return Promise.resolve();
355
+ rt.keychainPrewarmWaitPromise ??= (async () => {
349
356
  let timeoutId;
350
357
  const deadline = new Promise((resolveDeadline) => {
351
358
  timeoutId = setTimeout(resolveDeadline, KEYCHAIN_PREWARM_WAIT_MS);
@@ -355,13 +362,13 @@ export async function createMixdogSessionRuntime({
355
362
  await Promise.race([keychainPrewarmPromise, deadline]);
356
363
  } finally {
357
364
  clearTimeout(timeoutId);
358
- keychainPrewarmWaitDone = true;
365
+ rt.keychainPrewarmWaitDone = true;
359
366
  }
360
367
  // Invoked here: the assignment used to store the async FUNCTION, so every
361
368
  // `await awaitKeychainPrewarm()` resolved instantly (awaiting a function is
362
369
  // a no-op) and callers silently skipped the wait they asked for.
363
370
  })();
364
- return keychainPrewarmWaitPromise;
371
+ return rt.keychainPrewarmWaitPromise;
365
372
  }
366
373
  const routePreparation = createRoutePreparationGate({
367
374
  onError: (error) => bootProfile('route-preparation:failed', {
@@ -413,22 +420,22 @@ export async function createMixdogSessionRuntime({
413
420
  dataDir: pluginDataDir,
414
421
  cwd,
415
422
  });
416
- let memoryModPromise = null;
417
- let searchModPromise = null;
418
- let codeGraphModPromise = null;
423
+ rt.memoryModPromise = null;
424
+ rt.searchModPromise = null;
425
+ rt.codeGraphModPromise = null;
419
426
 
420
427
  // Memory module is always-on. `memoryEnabled()` is kept as a thin alias that
421
428
  // now always returns true (callers/compaction helpers still reference it);
422
429
  // the user-facing toggle is `recap` (background cycles only), read via
423
430
  // recapEnabled(config).
424
431
  const memoryEnabled = () => true;
425
- const recapEnabledFn = () => recapEnabled(config, true);
426
- const channelsEnabled = () => moduleEnabled(config, 'channels', true);
432
+ const recapEnabledFn = () => recapEnabled(rt.config, true);
433
+ const channelsEnabled = () => moduleEnabled(rt.config, 'channels', true);
427
434
 
428
435
  async function getMemoryModule() {
429
436
  const startedAt = performance.now();
430
- memoryModPromise ??= Promise.resolve(memoryRuntime);
431
- const mod = await memoryModPromise;
437
+ rt.memoryModPromise ??= Promise.resolve(memoryRuntime);
438
+ const mod = await rt.memoryModPromise;
432
439
  if (typeof mod?.init === 'function') {
433
440
  await mod.init();
434
441
  }
@@ -438,16 +445,16 @@ export async function createMixdogSessionRuntime({
438
445
 
439
446
  async function getSearchModule() {
440
447
  const startedAt = performance.now();
441
- searchModPromise ??= import(SEARCH_RUNTIME);
442
- const mod = await searchModPromise;
448
+ rt.searchModPromise ??= import(SEARCH_RUNTIME);
449
+ const mod = await rt.searchModPromise;
443
450
  bootProfile('search-runtime:ready', { ms: (performance.now() - startedAt).toFixed(1) });
444
451
  return mod;
445
452
  }
446
453
 
447
454
  async function getCodeGraphModule() {
448
455
  const startedAt = performance.now();
449
- codeGraphModPromise ??= import(CODE_GRAPH_RUNTIME);
450
- const mod = await codeGraphModPromise;
456
+ rt.codeGraphModPromise ??= import(CODE_GRAPH_RUNTIME);
457
+ const mod = await rt.codeGraphModPromise;
451
458
  bootProfile('code-graph-runtime:ready', { ms: (performance.now() - startedAt).toFixed(1) });
452
459
  return mod;
453
460
  }
@@ -456,7 +463,7 @@ export async function createMixdogSessionRuntime({
456
463
  const leadRoute = normalizeWorkflowRoute(routeLike);
457
464
  if (!leadRoute) return null;
458
465
 
459
- const nextConfig = { ...(config || {}) };
466
+ const nextConfig = { ...(rt.config || {}) };
460
467
  nextConfig.presets = upsertWorkflowPreset(nextConfig.presets, 'lead', leadRoute);
461
468
  nextConfig.workflowRoutes = {
462
469
  ...(nextConfig.workflowRoutes || {}),
@@ -483,21 +490,21 @@ export async function createMixdogSessionRuntime({
483
490
  }
484
491
 
485
492
  const configStartedAt = performance.now();
486
- let config = cfgMod.loadConfig({ secrets: false });
487
- setConfiguredShell(normalizeSystemShellConfig(config.shell).command);
488
- let configHasSecrets = false;
489
- let route = resolveRoute(config, { provider, model });
490
- let searchRoute = normalizeSearchRouteConfig(config.searchRoute);
493
+ rt.config = cfgMod.loadConfig({ secrets: false });
494
+ setConfiguredShell(normalizeSystemShellConfig(rt.config.shell).command);
495
+ rt.configHasSecrets = false;
496
+ rt.route = resolveRoute(rt.config, { provider, model });
497
+ rt.searchRoute = normalizeSearchRouteConfig(rt.config.searchRoute);
491
498
  bootProfile('config:ready', { ms: (performance.now() - configStartedAt).toFixed(1) });
492
- let mode = normalizeToolMode(toolMode);
493
- let session = null;
494
- let sessionCreatePromise = null;
495
- let currentCwd = cwd;
496
- let sessionNeedsCwdRefresh = false;
499
+ rt.mode = normalizeToolMode(toolMode);
500
+ rt.session = null;
501
+ rt.sessionCreatePromise = null;
502
+ rt.currentCwd = cwd;
503
+ rt.sessionNeedsCwdRefresh = false;
497
504
  // session_manage tool: reset request scheduled by the model mid-turn,
498
505
  // consumed by the TUI engine at turn end ('clear' | 'compact_clear').
499
- let pendingSessionReset = null;
500
- let closeRequested = false;
506
+ rt.pendingSessionReset = null;
507
+ rt.closeRequested = false;
501
508
  const warmupTimers = {
502
509
  providerSetupWarmupTimer: null,
503
510
  providerWarmupTimer: null,
@@ -518,8 +525,8 @@ export async function createMixdogSessionRuntime({
518
525
  codeGraphPrewarmQueuedCwd: '',
519
526
  channelStartPromise: null,
520
527
  };
521
- let activeTurnCount = 0;
522
- let firstTurnCompleted = false;
528
+ rt.activeTurnCount = 0;
529
+ rt.firstTurnCompleted = false;
523
530
  function hookTranscriptPath(sessionId) {
524
531
  const id = clean(sessionId);
525
532
  if (!id || !/^[A-Za-z0-9_-]+$/.test(id)) return null;
@@ -527,15 +534,15 @@ export async function createMixdogSessionRuntime({
527
534
  return join(dataDir, 'sessions', `${id}.json`);
528
535
  }
529
536
  function hookEffortPayload() {
530
- const level = clean(route.effectiveEffort || route.effort);
537
+ const level = clean(rt.route.effectiveEffort || rt.route.effort);
531
538
  return level ? { level: level.toLowerCase() } : undefined;
532
539
  }
533
540
  function hookCommonPayload(extra = {}) {
534
- const sid = clean(extra.session_id || extra.sessionId || session?.id);
541
+ const sid = clean(extra.session_id || extra.sessionId || rt.session?.id);
535
542
  return {
536
543
  ...(sid ? { session_id: sid, transcript_path: hookTranscriptPath(sid) } : {}),
537
- cwd: currentCwd,
538
- permission_mode: session?.permissionMode || 'default',
544
+ cwd: rt.currentCwd,
545
+ permission_mode: rt.session?.permissionMode || 'default',
539
546
  ...(hookEffortPayload() ? { effort: hookEffortPayload() } : {}),
540
547
  ...extra,
541
548
  };
@@ -560,7 +567,7 @@ export async function createMixdogSessionRuntime({
560
567
  modelCatalogWarmupEnabled,
561
568
  codeGraphPrewarmLazy,
562
569
  } = readRuntimeTunables();
563
- let codeGraphFirstTurnPrewarmDone = false;
570
+ rt.codeGraphFirstTurnPrewarmDone = false;
564
571
  const modelMetaByRoute = new Map();
565
572
  const notificationListeners = new Set();
566
573
  // Remote seat listeners (TUI): fired when remote mode flips outside a direct
@@ -585,11 +592,11 @@ export async function createMixdogSessionRuntime({
585
592
  providerSetupPromise: null,
586
593
  };
587
594
  const providerInitPromises = new Map();
588
- let startupProviderCatalogRefreshStarted = false;
595
+ rt.startupProviderCatalogRefreshStarted = false;
589
596
  // True while the boot-time provider-catalog refresh is in flight: warming a
590
597
  // model cache it is about to invalidate only burns the load twice.
591
- let startupProviderCatalogRefreshPending = false;
592
- let lastProjectMcpKey = null;
598
+ rt.startupProviderCatalogRefreshPending = false;
599
+ rt.lastProjectMcpKey = null;
593
600
  // MCP connect state, owned here so teardown/reconnect paths still observe it;
594
601
  // the mcp-glue factory mutates this object in place (see createMcpGlue).
595
602
  const mcpState = {
@@ -608,15 +615,15 @@ export async function createMixdogSessionRuntime({
608
615
  normalizeMcpServerInput,
609
616
  } = createMcpGlue({
610
617
  mcpClient,
611
- getConfig: () => config,
612
- getCurrentCwd: () => currentCwd,
613
- getDesktopSession: () => desktopSession,
614
- setDesktopSession: (v) => { desktopSession = v; },
618
+ getConfig: () => rt.config,
619
+ getCurrentCwd: () => rt.currentCwd,
620
+ getDesktopSession: () => rt.desktopSession,
621
+ setDesktopSession: (v) => { rt.desktopSession = v; },
615
622
  state: mcpState,
616
623
  });
617
624
  const hooksStartedAt = performance.now();
618
625
  const hooks = createStandaloneHookBus({ dataDir: cfgMod.getPluginData() });
619
- hooks.emit('runtime:start', { cwd: currentCwd, provider: route.provider, model: route.model, toolMode: mode });
626
+ hooks.emit('runtime:start', { cwd: rt.currentCwd, provider: rt.route.provider, model: rt.route.model, toolMode: rt.mode });
620
627
  bootProfile('hooks:ready', { ms: (performance.now() - hooksStartedAt).toFixed(1) });
621
628
 
622
629
  // Self-update: registry check + background staging live in self-update.mjs;
@@ -624,7 +631,7 @@ export async function createMixdogSessionRuntime({
624
631
  // boot check is deferred past this constructor so a hanging registry request
625
632
  // can never delay session boot.
626
633
  const selfUpdate = createSelfUpdateController({
627
- getConfig: () => config,
634
+ getConfig: () => rt.config,
628
635
  getDataDir: () => cfgMod.getPluginData?.() || STANDALONE_DATA_DIR,
629
636
  emitNotification: (...a) => emitRuntimeNotification(...a),
630
637
  });
@@ -647,7 +654,7 @@ export async function createMixdogSessionRuntime({
647
654
  skillContent,
648
655
  skillToolContent,
649
656
  addProjectSkill,
650
- } = createSkillsApi({ contextMod, getCwd: () => currentCwd });
657
+ } = createSkillsApi({ contextMod, getCwd: () => rt.currentCwd });
651
658
 
652
659
  // cwd resolution/apply + plugins-status + core-memory context. Extracted to
653
660
  // session-runtime/cwd-plugins.mjs; the facade keeps ownership of the mutable
@@ -661,17 +668,17 @@ export async function createMixdogSessionRuntime({
661
668
  pluginsStatus,
662
669
  loadCoreMemoryContext,
663
670
  } = createCwdPlugins({
664
- getCurrentCwd: () => currentCwd,
665
- setCurrentCwd: (next) => { currentCwd = next; },
666
- getConfig: () => config,
667
- getSession: () => session,
668
- getRoute: () => route,
669
- getLastProjectMcpKey: () => lastProjectMcpKey,
670
- setLastProjectMcpKey: (next) => { lastProjectMcpKey = next; },
671
+ getCurrentCwd: () => rt.currentCwd,
672
+ setCurrentCwd: (next) => { rt.currentCwd = next; },
673
+ getConfig: () => rt.config,
674
+ getSession: () => rt.session,
675
+ getRoute: () => rt.route,
676
+ getLastProjectMcpKey: () => rt.lastProjectMcpKey,
677
+ setLastProjectMcpKey: (next) => { rt.lastProjectMcpKey = next; },
671
678
  isCodeGraphPrewarmLazy: () => codeGraphPrewarmLazy,
672
- isCodeGraphFirstTurnPrewarmDone: () => codeGraphFirstTurnPrewarmDone,
679
+ isCodeGraphFirstTurnPrewarmDone: () => rt.codeGraphFirstTurnPrewarmDone,
673
680
  getCodeGraphPrewarmDelayMs: () => codeGraphPrewarmDelayMs,
674
- setSessionNeedsCwdRefresh: (next) => { sessionNeedsCwdRefresh = next; },
681
+ setSessionNeedsCwdRefresh: (next) => { rt.sessionNeedsCwdRefresh = next; },
675
682
  connectConfiguredMcp,
676
683
  invalidatePreSessionToolSurface: (...a) => invalidatePreSessionToolSurface(...a),
677
684
  scheduleCodeGraphPrewarm: (...a) => scheduleCodeGraphPrewarm(...a),
@@ -703,7 +710,7 @@ export async function createMixdogSessionRuntime({
703
710
  dataDir: cfgMod.getPluginData(),
704
711
  cwd,
705
712
  awaitKeychainPrewarm,
706
- isKeychainPrewarmReady: () => keychainPrewarmWaitDone,
713
+ isKeychainPrewarmReady: () => rt.keychainPrewarmWaitDone,
707
714
  // SubagentStart/SubagentStop: bridge internal worker spawn/finish to the
708
715
  // standard hook bus. agent_type is passed top-level via hookCommonPayload
709
716
  // (added to hook-bus buildEventPayload passthrough). Best-effort.
@@ -720,7 +727,7 @@ export async function createMixdogSessionRuntime({
720
727
  bootProfile('agent:ready', { ms: (performance.now() - agentToolStartedAt).toFixed(1) });
721
728
  const agentStatusState = () => {
722
729
  try {
723
- const status = agentTool.getStatus?.({ clientHostPid: session?.clientHostPid || process.pid }) || {};
730
+ const status = agentTool.getStatus?.({ clientHostPid: rt.session?.clientHostPid || process.pid }) || {};
724
731
  return {
725
732
  agentWorkers: Array.isArray(status.workers) ? status.workers : [],
726
733
  agentJobs: Array.isArray(status.jobs) ? status.jobs : [],
@@ -741,7 +748,7 @@ export async function createMixdogSessionRuntime({
741
748
  // newer remote session. Drop remote mode entirely on this session (no
742
749
  // handover, no retry) and tell UI listeners so the indicator updates.
743
750
  if (msg?.method === 'notifications/mixdog/remote') {
744
- if (msg?.params?.state === 'superseded' && remoteEnabled) {
751
+ if (msg?.params?.state === 'superseded' && rt.remoteEnabled) {
745
752
  stopRemote('superseded-by-newer-remote-session');
746
753
  emitRemoteStateChange(false, 'superseded');
747
754
  }
@@ -753,8 +760,8 @@ export async function createMixdogSessionRuntime({
753
760
  const content = channelNotificationModelContent(params);
754
761
  if (!content) return;
755
762
  const { handled } = emitRuntimeNotification(content, meta);
756
- if (!handled && session?.id && shouldMirrorChannelNotificationToPending(meta)) {
757
- try { mgr.enqueuePendingMessage(session.id, content); } catch {}
763
+ if (!handled && rt.session?.id && shouldMirrorChannelNotificationToPending(meta)) {
764
+ try { mgr.enqueuePendingMessage(rt.session.id, content); } catch {}
758
765
  }
759
766
  },
760
767
  });
@@ -790,12 +797,12 @@ export async function createMixdogSessionRuntime({
790
797
  applyPreSessionToolSelection,
791
798
  } = createToolSurface({
792
799
  mgr,
793
- mode,
800
+ mode: rt.mode,
794
801
  standaloneTools,
795
802
  agentToolNames,
796
- getSession: () => session,
797
- getRoute: () => route,
798
- getConfig: () => config,
803
+ getSession: () => rt.session,
804
+ getRoute: () => rt.route,
805
+ getConfig: () => rt.config,
799
806
  cfgMod,
800
807
  loadWorkflowPack,
801
808
  activeWorkflowId,
@@ -803,20 +810,20 @@ export async function createMixdogSessionRuntime({
803
810
  });
804
811
 
805
812
  const { contextStatus: computeContextStatus, invalidateContextStatusCache } = createContextStatus({
806
- getSession: () => session,
807
- getRoute: () => route,
808
- getCurrentCwd: () => currentCwd,
809
- getMode: () => mode,
813
+ getSession: () => rt.session,
814
+ getRoute: () => rt.route,
815
+ getCurrentCwd: () => rt.currentCwd,
816
+ getMode: () => rt.mode,
810
817
  });
811
818
  internalTools.setInternalToolsProvider({
812
819
  tools: [...standaloneTools, ...searchRuntimeTools.filter((tool) => tool?.public === false)],
813
820
  executor: async (name, args, callerCtx = {}) => {
814
- const callerCwd = callerCtx?.callerCwd || currentCwd;
821
+ const callerCwd = callerCtx?.callerCwd || rt.currentCwd;
815
822
  if (name === 'search' || name === 'web_fetch' || name === 'local_fetch' || name === 'image_fetch') {
816
823
  return dispatchSearchRuntimeTool(name, args, callerCtx, {
817
824
  getSearchModule,
818
- getCurrentCwd: () => currentCwd,
819
- getSession: () => session,
825
+ getCurrentCwd: () => rt.currentCwd,
826
+ getSession: () => rt.session,
820
827
  notifyFnForSession,
821
828
  runNativeWebSearch,
822
829
  });
@@ -827,7 +834,7 @@ export async function createMixdogSessionRuntime({
827
834
  return await memoryMod.handleToolCall(
828
835
  name,
829
836
  memoryToolArgsForCaller(args, callerCwd),
830
- callerCtx?.signal || session?.controller?.signal || null,
837
+ callerCtx?.signal || rt.session?.controller?.signal || null,
831
838
  );
832
839
  }
833
840
  if (name === 'code_graph') {
@@ -836,20 +843,20 @@ export async function createMixdogSessionRuntime({
836
843
  return await codeGraphMod.executeCodeGraphTool(name, args || {}, args?.cwd || callerCwd);
837
844
  }
838
845
  if (name === 'tool_search' || name === 'load_tool') {
839
- return renderToolSearch(args, activeToolSurface(), mode, { mcpStatus });
846
+ return renderToolSearch(args, activeToolSurface(), rt.mode, { mcpStatus });
840
847
  }
841
848
  if (name === 'explore') {
842
- const callerSessionId = callerCtx?.callerSessionId || session?.id || null;
849
+ const callerSessionId = callerCtx?.callerSessionId || rt.session?.id || null;
843
850
  return await runExplore(args || {}, {
844
851
  callerCwd: args?.cwd ? resolveCwdPath(args.cwd) : callerCwd,
845
852
  callerSessionId,
846
853
  routingSessionId: callerSessionId,
847
- clientHostPid: callerCtx?.clientHostPid || session?.clientHostPid || process.pid,
854
+ clientHostPid: callerCtx?.clientHostPid || rt.session?.clientHostPid || process.pid,
848
855
  notifyFn: notifyFnForSession(callerSessionId),
849
856
  // Cascade caller cancellation (ESC / owner abort) into the explore
850
857
  // fan-out so every child dispatch tears down immediately — same
851
858
  // signal the adjacent native search already forwards.
852
- signal: callerCtx?.signal || session?.controller?.signal,
859
+ signal: callerCtx?.signal || rt.session?.controller?.signal,
853
860
  });
854
861
  }
855
862
  if (name === 'cwd') {
@@ -859,17 +866,17 @@ export async function createMixdogSessionRuntime({
859
866
  } else if (action !== 'get') {
860
867
  throw new Error(`cwd: unknown action "${action}"`);
861
868
  }
862
- return JSON.stringify({ cwd: currentCwd, sessionId: session?.id || null }, null, 2);
869
+ return JSON.stringify({ cwd: rt.currentCwd, sessionId: rt.session?.id || null }, null, 2);
863
870
  }
864
871
  if (name === 'session_manage') {
865
872
  // Lead/owner sessions only: an agent worker resetting its own
866
873
  // transcript mid-task would corrupt the delegation contract, and it
867
874
  // must never reach the owner conversation either.
868
875
  const callerSessionId = callerCtx?.callerSessionId || null;
869
- if (callerSessionId && session?.id && callerSessionId !== session.id) {
876
+ if (callerSessionId && rt.session?.id && callerSessionId !== rt.session.id) {
870
877
  throw new Error('session_manage: only the lead session may reset the conversation');
871
878
  }
872
- if (!session?.id) throw new Error('session_manage: no active session');
879
+ if (!rt.session?.id) throw new Error('session_manage: no active session');
873
880
  const action = clean(args?.action).toLowerCase();
874
881
  if (action !== 'clear' && action !== 'compact_clear') {
875
882
  throw new Error(`session_manage: unknown action "${action}" (use clear | compact_clear)`);
@@ -879,7 +886,7 @@ export async function createMixdogSessionRuntime({
879
886
  // boundary the idle auto-clear uses).
880
887
  // Pin to the current session id so a resume/new-session between
881
888
  // scheduling and consumption can never clear the wrong conversation.
882
- pendingSessionReset = { action, sessionId: session.id };
889
+ rt.pendingSessionReset = { action, sessionId: rt.session.id };
883
890
  return action === 'clear'
884
891
  ? 'Session reset scheduled: full clear will run when this turn ends. All prior context will be gone.'
885
892
  : 'Session reset scheduled: the conversation will be summarized (compact) and cleared when this turn ends; key context carries forward in the summary.';
@@ -888,12 +895,12 @@ export async function createMixdogSessionRuntime({
888
895
  return skillToolContent(args?.name);
889
896
  }
890
897
  if (name === 'agent') {
891
- const callerSessionId = callerCtx?.callerSessionId || session?.id || null;
898
+ const callerSessionId = callerCtx?.callerSessionId || rt.session?.id || null;
892
899
  return await agentTool.execute(args, {
893
900
  callerCwd,
894
901
  invocationSource: 'model-tool',
895
902
  callerSessionId,
896
- clientHostPid: callerCtx?.clientHostPid || session?.clientHostPid || process.pid,
903
+ clientHostPid: callerCtx?.clientHostPid || rt.session?.clientHostPid || process.pid,
897
904
  signal: callerCtx?.signal,
898
905
  notifyFn: notifyFnForSession(callerSessionId),
899
906
  });
@@ -906,7 +913,7 @@ export async function createMixdogSessionRuntime({
906
913
  },
907
914
  });
908
915
  internalTools.markBootReady?.();
909
- try { lastProjectMcpKey = resolve(currentCwd) + '\u0000' + JSON.stringify(readProjectMcpServers(currentCwd)); } catch { lastProjectMcpKey = null; }
916
+ try { rt.lastProjectMcpKey = resolve(rt.currentCwd) + '\u0000' + JSON.stringify(readProjectMcpServers(rt.currentCwd)); } catch { rt.lastProjectMcpKey = null; }
910
917
  void connectConfiguredMcp()
911
918
  .then((status) => bootProfile('mcp:ready', {
912
919
  connected: Number(status?.connectedCount || 0),
@@ -955,13 +962,13 @@ export async function createMixdogSessionRuntime({
955
962
  displayConfig,
956
963
  ensureConfigForRouteProvider,
957
964
  } = createConfigLifecycle({
958
- getConfig: () => config,
959
- setConfig: (next) => { config = next; },
960
- getSearchRoute: () => searchRoute,
961
- setSearchRoute: (next) => { searchRoute = next; },
962
- getConfigHasSecrets: () => configHasSecrets,
963
- setConfigHasSecrets: (next) => { configHasSecrets = next; },
964
- getRoute: () => route,
965
+ getConfig: () => rt.config,
966
+ setConfig: (next) => { rt.config = next; },
967
+ getSearchRoute: () => rt.searchRoute,
968
+ setSearchRoute: (next) => { rt.searchRoute = next; },
969
+ getConfigHasSecrets: () => rt.configHasSecrets,
970
+ setConfigHasSecrets: (next) => { rt.configHasSecrets = next; },
971
+ getRoute: () => rt.route,
965
972
  cfgMod,
966
973
  sharedCfgMod,
967
974
  setBackend,
@@ -976,7 +983,7 @@ export async function createMixdogSessionRuntime({
976
983
  STANDALONE_DATA_DIR,
977
984
  });
978
985
 
979
- async function ensureProvidersReady(providerConfig = config.providers || {}) {
986
+ async function ensureProvidersReady(providerConfig = rt.config.providers || {}) {
980
987
  await awaitKeychainPrewarm();
981
988
  const initKey = providerInitCacheKey(providerConfig);
982
989
  const existing = providerInitPromises.get(initKey);
@@ -996,16 +1003,16 @@ export async function createMixdogSessionRuntime({
996
1003
  if (providerInitPromises.get(initKey) === providerInitPromise) providerInitPromises.delete(initKey);
997
1004
  throw error;
998
1005
  }
999
- if (!startupProviderCatalogRefreshStarted && !closeRequested) {
1000
- startupProviderCatalogRefreshStarted = true;
1001
- startupProviderCatalogRefreshPending = true;
1006
+ if (!rt.startupProviderCatalogRefreshStarted && !rt.closeRequested) {
1007
+ rt.startupProviderCatalogRefreshStarted = true;
1008
+ rt.startupProviderCatalogRefreshPending = true;
1002
1009
  try {
1003
1010
  void Promise.resolve(reg.refreshProviderCatalogsOnStartup())
1004
1011
  .then(() => {
1005
1012
  // Fresh catalog rows invalidate model-derived caches, but the
1006
1013
  // already initialized provider registry remains valid.
1007
1014
  invalidateProviderCaches({ preserveProviderInit: true });
1008
- startupProviderCatalogRefreshPending = false;
1015
+ rt.startupProviderCatalogRefreshPending = false;
1009
1016
  // Secrets-aware: a no-secrets rewarm bumps the load sequence and
1010
1017
  // is never adopted, so it discarded the in-flight authoritative
1011
1018
  // load and left the picker cache empty — every later consumer then
@@ -1014,12 +1021,12 @@ export async function createMixdogSessionRuntime({
1014
1021
  bootProfile('provider-catalogs:refresh-ready');
1015
1022
  })
1016
1023
  .catch((error) => {
1017
- startupProviderCatalogRefreshPending = false;
1024
+ rt.startupProviderCatalogRefreshPending = false;
1018
1025
  bootProfile('provider-catalogs:refresh-failed', { error: error?.message || String(error) });
1019
1026
  });
1020
1027
  bootProfile('provider-catalogs:refresh-started');
1021
1028
  } catch (error) {
1022
- startupProviderCatalogRefreshPending = false;
1029
+ rt.startupProviderCatalogRefreshPending = false;
1023
1030
  bootProfile('provider-catalogs:refresh-failed', { error: error?.message || String(error) });
1024
1031
  }
1025
1032
  }
@@ -1030,11 +1037,11 @@ export async function createMixdogSessionRuntime({
1030
1037
  currentMainSearchModelMeta,
1031
1038
  runNativeWebSearch,
1032
1039
  } = createNativeSearch({
1033
- getRoute: () => route,
1034
- getSearchRoute: () => searchRoute,
1035
- setSearchRoute: (next) => { searchRoute = next; },
1036
- getConfig: () => config,
1037
- getSession: () => session,
1040
+ getRoute: () => rt.route,
1041
+ getSearchRoute: () => rt.searchRoute,
1042
+ setSearchRoute: (next) => { rt.searchRoute = next; },
1043
+ getConfig: () => rt.config,
1044
+ getSession: () => rt.session,
1038
1045
  getReg: () => reg,
1039
1046
  ensureFullConfig,
1040
1047
  awaitKeychainPrewarm,
@@ -1051,23 +1058,25 @@ export async function createMixdogSessionRuntime({
1051
1058
  // cachedProviderSetup(quick) may nudge scheduleProviderSetupWarmup on a cold
1052
1059
  // quick-cache fill. Thread it by reference so the scheduler is reachable once
1053
1060
  // it exists (a pre-scheduler quick fill simply skips the warmup nudge).
1054
- let scheduleProviderSetupWarmupRef = () => {};
1061
+ rt.scheduleProviderSetupWarmupRef = () => {};
1055
1062
  const {
1056
1063
  refreshStatuslineUsageSnapshot,
1057
1064
  cachedProviderSetup,
1058
1065
  hasProviderSetupCached,
1059
1066
  getUsageDashboard,
1067
+ consumeCodexRateLimitResetCredit,
1060
1068
  } = createProviderUsage({
1061
1069
  caches: providerUsageCaches,
1062
- getConfig: () => config,
1070
+ getConfig: () => rt.config,
1063
1071
  getReg: () => reg,
1064
1072
  displayConfig,
1065
1073
  providerSetup,
1066
1074
  createUsageDashboard,
1067
1075
  fetchOAuthUsageSnapshot,
1068
- isCloseRequested: () => closeRequested,
1076
+ consumeOpenAICodexResetCredit,
1077
+ isCloseRequested: () => rt.closeRequested,
1069
1078
  getProviderSetupWarmupTimer: () => warmupTimers.providerSetupWarmupTimer,
1070
- scheduleProviderSetupWarmup: (delayMs) => scheduleProviderSetupWarmupRef(delayMs),
1079
+ scheduleProviderSetupWarmup: (delayMs) => rt.scheduleProviderSetupWarmupRef(delayMs),
1071
1080
  });
1072
1081
 
1073
1082
  // Holder filled after createQuickModelRows resolves; provider-models and
@@ -1079,7 +1088,7 @@ export async function createMixdogSessionRuntime({
1079
1088
  // lookupModelMeta may fire scheduleProviderModelWarmup on a cache miss. Thread
1080
1089
  // it by reference so the scheduler is called once it exists (miss handling is
1081
1090
  // best-effort; a pre-scheduler miss simply skips the warmup nudge).
1082
- let scheduleProviderModelWarmupRef = () => {};
1091
+ rt.scheduleProviderModelWarmupRef = () => {};
1083
1092
  const {
1084
1093
  modelMetaKey,
1085
1094
  lookupModelMeta,
@@ -1092,8 +1101,8 @@ export async function createMixdogSessionRuntime({
1092
1101
  } = createProviderModels({
1093
1102
  caches: providerModelCaches,
1094
1103
  modelMetaByRoute,
1095
- getRoute: () => route,
1096
- getConfig: () => config,
1104
+ getRoute: () => rt.route,
1105
+ getConfig: () => rt.config,
1097
1106
  getReg: () => reg,
1098
1107
  searchCapableFor,
1099
1108
  sortProviderModelsRaw,
@@ -1104,7 +1113,7 @@ export async function createMixdogSessionRuntime({
1104
1113
  awaitKeychainPrewarm,
1105
1114
  ensureProvidersReady,
1106
1115
  bootProfile,
1107
- scheduleProviderModelWarmup: () => scheduleProviderModelWarmupRef(),
1116
+ scheduleProviderModelWarmup: () => rt.scheduleProviderModelWarmupRef(),
1108
1117
  quickHelpers: providerModelQuickHelpers,
1109
1118
  });
1110
1119
 
@@ -1115,8 +1124,8 @@ export async function createMixdogSessionRuntime({
1115
1124
  searchModelsFromRows,
1116
1125
  searchRowsWithDefault,
1117
1126
  } = createQuickModelRows({
1118
- getRoute: () => route,
1119
- getSearchRoute: () => searchRoute,
1127
+ getRoute: () => rt.route,
1128
+ getSearchRoute: () => rt.searchRoute,
1120
1129
  displayConfig,
1121
1130
  providerModelCacheRow,
1122
1131
  providerModelsFromCacheRows,
@@ -1137,295 +1146,71 @@ export async function createMixdogSessionRuntime({
1137
1146
  searchRowsWithDefault,
1138
1147
  });
1139
1148
 
1140
- async function resolveMissingRouteModelForFirstTurn() {
1141
- if (routeHasModel()) return route;
1142
- const models = await collectProviderModels();
1143
- const picked = models[0] || null;
1144
- if (!picked) {
1145
- throw new Error('No provider models available. Open /providers to sign in, then /model to choose a model.');
1146
- }
1147
- route = {
1148
- ...route,
1149
- provider: picked.provider,
1150
- model: picked.id,
1151
- preset: null,
1152
- };
1153
- return route;
1154
- }
1155
-
1156
- async function refreshRouteEffort(modelMetaOverride = null, expectedRoute = null) {
1157
- const targetRoute = expectedRoute || route;
1158
- await ensureProvidersReady(ensureProviderEnabled(config, targetRoute.provider));
1159
- const modelMeta = modelMetaOverride || await lookupModelMeta(targetRoute.provider, targetRoute.model);
1160
- // A rapid second resume/model change can replace the route while provider
1161
- // metadata is loading. Never let the older completion overwrite it.
1162
- if (expectedRoute && route !== expectedRoute) return null;
1163
- const requested = hasOwn(targetRoute, 'effort')
1164
- ? targetRoute.effort
1165
- : (targetRoute.preset?.effort || null);
1166
- const effectiveEffort = coerceEffortFor(targetRoute.provider, modelMeta, requested);
1167
- const fastCapable = fastCapableFor(targetRoute.provider, modelMeta);
1168
- // Carry the catalog display name onto the route so the statusline shows a
1169
- // human label (e.g. "Claude Fable 5") for preset-less direct models instead
1170
- // of the raw id. `name` is only trusted when it differs from the raw model
1171
- // id (some providers echo the id as `name`), so it can't clobber a better
1172
- // already-resolved label. Falls back to existing route.modelDisplay, then unset.
1173
- const metaName = clean(modelMeta?.name);
1174
- const modelDisplay = clean(modelMeta?.display) || clean(modelMeta?.displayName)
1175
- || (metaName && metaName !== clean(targetRoute.model) ? metaName : '')
1176
- || clean(targetRoute.modelDisplay);
1177
- route = {
1178
- ...targetRoute,
1179
- fast: fastCapable ? targetRoute.fast === true : false,
1180
- fastCapable,
1181
- effectiveEffort,
1182
- effortOptions: effortItemsFor(route.provider, modelMeta, effectiveEffort),
1183
- ...(modelDisplay ? { modelDisplay } : {}),
1184
- };
1185
- return route;
1186
- }
1187
-
1188
- function routeHasModel() {
1189
- return !!clean(route?.model);
1190
- }
1191
-
1192
- function requireModelRoute() {
1193
- if (routeHasModel()) return;
1194
- throw new Error('No model configured. Open /providers to sign in, then /model to choose a model.');
1195
- }
1196
-
1197
- async function recreateCurrentSessionIfReady() {
1198
- if (!routeHasModel()) {
1199
- session = null;
1200
- return null;
1201
- }
1202
- return await createCurrentSession();
1203
- }
1204
-
1205
- async function createCurrentSession(reason = 'demand') {
1206
- if (sessionCreatePromise) return await sessionCreatePromise;
1207
- if (session?.id && !sessionNeedsCwdRefresh) {
1208
- const liveSession = mgr.getSession(session.id);
1209
- if (liveSession && liveSession.closed !== true && liveSession.status !== 'closed') {
1210
- session = liveSession;
1211
- return session;
1212
- }
1213
- session = null;
1214
- }
1215
-
1216
- const startedAt = performance.now();
1217
- bootProfile('session:create:start', { mode, reason });
1218
- const promise = (async () => {
1219
- // Demand-only: this starts only after the user submits (unless an
1220
- // explicitly enabled prewarm caller asks for a session). Core-memory
1221
- // startup does not depend on keychain/provider readiness, so overlap the
1222
- // two cold paths instead of paying their bounded waits serially.
1223
- const coreMemoryContextPromise = loadCoreMemoryContext();
1224
- await awaitKeychainPrewarm();
1225
- ensureConfigForRouteProvider();
1226
- await resolveMissingRouteModelForFirstTurn();
1227
- requireModelRoute();
1228
- bootProfile('session:create:route-ready', { ms: (performance.now() - startedAt).toFixed(1) });
1229
- // Route effort waits on provider readiness while the already-started
1230
- // memory load continues independently.
1231
- const [, coreMemoryContext] = await Promise.all([
1232
- refreshRouteEffort(),
1233
- coreMemoryContextPromise,
1234
- ]);
1235
- bootProfile('session:create:effort-ready', { ms: (performance.now() - startedAt).toFixed(1) });
1236
- const providerImpl = reg.getProvider(route.provider);
1237
- if (!providerImpl) {
1238
- throw new Error(`Provider "${route.provider}" is not configured.`);
1239
- }
1240
- bootProfile('session:create:provider-ready', { ms: (performance.now() - startedAt).toFixed(1) });
1241
- if (closeRequested) throw new Error('runtime is closing');
1242
- const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
1243
- // Load the active WORKFLOW.md pack once for both summary + context block.
1244
- const { summary: workflow, context: workflowContext } = activeWorkflowContext(config, dataDir);
1245
- const sessionOpts = {
1246
- provider: route.provider,
1247
- model: route.model,
1248
- preset: route.preset || undefined,
1249
- tools: toolSpecForMode(mode),
1250
- owner: 'cli',
1251
- agent: 'lead',
1252
- lane: 'cli',
1253
- sourceType: 'lead',
1254
- sourceName: 'main',
1255
- clientHostPid: process.pid,
1256
- disallowedTools: LEAD_DISALLOWED_TOOLS,
1257
- cwd: currentCwd,
1258
- ...(desktopSession && typeof desktopSession === 'object' ? { desktopSession } : {}),
1259
- coreMemoryContext,
1260
- workflow,
1261
- workflowContext,
1262
- fast: route.fast === true,
1263
- compaction: config.compaction && typeof config.compaction === 'object'
1264
- ? normalizeCompactionConfig(config.compaction, { memoryEnabled: memoryEnabled() })
1265
- : undefined,
1266
- };
1267
- if (hasOwn(route, 'effort') || route.effectiveEffort) {
1268
- sessionOpts.effort = route.effectiveEffort || null;
1269
- }
1270
- session = mgr.createSession(sessionOpts);
1271
- sessionNeedsCwdRefresh = false;
1272
- attachSessionHooks(session, { hooks, hookCommonPayload, getCwd: () => currentCwd });
1273
- // Every-create MCP fold (NO blocking): seed the INITIAL provider-visible
1274
- // surface (and native BP1 manifest) from MCP servers connected at create
1275
- // time. There is no await — a boot connect still mid-handshake is caught on
1276
- // the first user turn by refreshInitialDeferredMcpSurface (session-turn-api),
1277
- // which re-folds the live registry into the first-turn surface before the
1278
- // prompt renders. This fold keeps recreate paths (cwd change with MCP
1279
- // already connected) seeding their manifest instead of re-announcing late.
1280
- let connectedMcpTools = [];
1281
- try { connectedMcpTools = mcpClient.getMcpTools?.() || []; }
1282
- catch { connectedMcpTools = []; }
1283
- applyDeferredToolSurface(
1284
- session,
1285
- deferredSurfaceModeForLead(mode),
1286
- connectedMcpTools.length ? [...modelStandaloneTools(), ...connectedMcpTools] : modelStandaloneTools(),
1287
- { provider: route.provider },
1288
- );
1289
- // Session-local one-shot: mark this FRESH session eligible for the
1290
- // first-turn deferred-surface refresh (session-turn-api). A resumed
1291
- // session (prior transcript) is NEVER marked, so its already-baked BP1 is
1292
- // never rebuilt or re-announced — the gate is per-session, not the
1293
- // process-wide firstTurnCompleted.
1294
- session.deferredInitialRefreshPending = !/resume/i.test(String(reason || ''));
1295
- applyPreSessionToolSelection();
1296
- writeStatuslineRoute(statusRoutes, session, route);
1297
- hooks.emit('session:create', { sessionId: session.id, provider: route.provider, model: route.model, toolMode: mode, cwd: currentCwd });
1298
- // SessionStart: bridge to the standard project hook bus. Best-effort;
1299
- // a hook error must never break session creation. additionalContext is
1300
- // injected before the first user turn as a system-reminder context pair.
1301
- try {
1302
- const startSource = /resume/i.test(String(reason || ''))
1303
- ? 'resume'
1304
- : (/clear/i.test(String(reason || '')) ? 'clear' : 'startup');
1305
- const startDispatch = await hooks.dispatch('SessionStart', hookCommonPayload({ session_id: session.id, source: startSource, model: route.model }));
1306
- const startContext = Array.isArray(startDispatch?.additionalContext)
1307
- ? startDispatch.additionalContext.join('\n\n')
1308
- : String(startDispatch?.additionalContext || '');
1309
- if (startContext.trim()) {
1310
- session.messages.push({ role: 'user', content: `<system-reminder>\n# SessionStart Hook Context\n${startContext.trim()}\n</system-reminder>` });
1311
- session.messages.push({ role: 'assistant', content: '.' });
1312
- session.updatedAt = Date.now();
1313
- }
1314
- } catch { /* best-effort: never break session create */ }
1315
- bootProfile('session:create:ready', {
1316
- ms: (performance.now() - startedAt).toFixed(1),
1317
- reason,
1318
- tools: Array.isArray(session.tools) ? session.tools.length : 0,
1319
- catalog: Array.isArray(session.deferredToolCatalog) ? session.deferredToolCatalog.length : 0,
1320
- });
1321
- // A rebind push may have been deferred (e.g. 'acquired' landed before this
1322
- // session existed). The writer is now bindable — flush it exactly once.
1323
- flushPendingTranscriptRebind();
1324
- return session;
1325
- })();
1326
-
1327
- sessionCreatePromise = promise;
1328
- try {
1329
- return await promise;
1330
- } finally {
1331
- if (sessionCreatePromise === promise) sessionCreatePromise = null;
1332
- }
1333
- }
1334
-
1149
+ // Route resolution + createCurrentSession: session-lifecycle.mjs.
1335
1150
  const {
1151
+ resolveMissingRouteModelForFirstTurn,
1336
1152
  scheduleProviderWarmup,
1337
1153
  scheduleProviderSetupWarmup,
1338
1154
  scheduleProviderModelWarmup,
1339
1155
  scheduleModelCatalogWarmup,
1340
1156
  scheduleStatuslineUsageWarmup,
1341
1157
  scheduleStatuslineUsageRefresh,
1342
- } = createWarmupSchedulers({
1343
- timers: warmupTimers,
1344
- bootProfile,
1345
- getRoute: () => route,
1346
- getConfig: () => config,
1347
- isCloseRequested: () => closeRequested,
1348
- getActiveTurnCount: () => activeTurnCount,
1349
- getSessionCreatePromise: () => sessionCreatePromise,
1350
- getProviderModelsCache: () => providerModelCaches.providerModelsCache,
1351
- getProviderModelsPromise: () => providerModelCaches.providerModelsPromise,
1352
- reloadFullConfig,
1353
- ensureConfigForRouteProvider,
1354
- awaitKeychainPrewarm,
1355
- ensureProvidersReady,
1356
- ensureProviderEnabled,
1357
- refreshStatuslineUsageSnapshot,
1358
- warmProviderModelCache,
1359
- cachedProviderSetup,
1360
- warmCatalogsInBackground,
1361
- isFirstTurnCompleted: () => firstTurnCompleted,
1362
- isCatalogRefreshPending: () => startupProviderCatalogRefreshPending,
1363
- envFlag,
1364
- delays: {
1365
- providerWarmupDelayMs,
1366
- providerSetupWarmupDelayMs,
1367
- providerModelWarmupDelayMs,
1368
- modelCatalogWarmupDelayMs,
1369
- statuslineUsageWarmupDelayMs,
1370
- statuslineUsageRefreshDelayMs,
1371
- backgroundBusyRetryMs,
1372
- },
1373
- flags: {
1374
- providerWarmupEnabled,
1375
- modelPrefetchEnabled,
1376
- modelCatalogWarmupEnabled,
1377
- },
1378
- });
1379
- scheduleProviderModelWarmupRef = scheduleProviderModelWarmup;
1380
- scheduleProviderSetupWarmupRef = scheduleProviderSetupWarmup;
1381
-
1382
- const {
1383
1158
  scheduleCodeGraphPrewarm,
1384
1159
  scheduleLeadSessionPrewarm,
1385
1160
  scheduleToolRuntimeWarmup,
1386
1161
  invokeChannelStart,
1387
1162
  scheduleChannelStart,
1388
- } = createPrewarmSchedulers({
1389
- timers: prewarmTimers,
1390
- bootProfile,
1391
- getCurrentCwd: () => currentCwd,
1392
- isCloseRequested: () => closeRequested,
1393
- getActiveTurnCount: () => activeTurnCount,
1394
- getSessionCreatePromise: () => sessionCreatePromise,
1395
- getSession: () => session,
1396
- isRemoteEnabled: () => remoteEnabled,
1397
- channelsEnabled,
1398
- hasActiveAutomation,
1399
- getCodeGraphModule,
1163
+ refreshRouteEffort,
1164
+ routeHasModel,
1165
+ requireModelRoute,
1166
+ recreateCurrentSessionIfReady,
1400
1167
  createCurrentSession,
1401
- channels,
1402
- envFlag,
1403
- delays: {
1404
- codeGraphPrewarmDelayMs,
1405
- channelStartDelayMs,
1406
- sessionPrewarmDelayMs,
1407
- backgroundBusyRetryMs,
1408
- },
1409
- flags: {
1410
- codeGraphPrewarmEnabled,
1411
- sessionPrewarmEnabled,
1412
- },
1413
- state: prewarmState,
1414
- });
1415
-
1416
- // Remote transcript binding + worker rebind pushes live in
1417
- // remote-transcript.mjs; the facade injects the mutable session/cwd/remote
1418
- // state it needs.
1419
- const remoteTranscript = createRemoteTranscript({
1420
- getSession: () => session,
1421
- getCwd: () => currentCwd,
1422
- isRemoteEnabled: () => remoteEnabled,
1423
- getRemoteSessionId: () => remoteSessionId,
1424
- setRemoteSessionId: (next) => { remoteSessionId = next; },
1425
- isCloseRequested: () => closeRequested,
1168
+ remoteTranscript,
1169
+ } = createSessionLifecycle({
1170
+ rt,
1171
+ collectProviderModels,
1172
+ ensureProvidersReady,
1173
+ lookupModelMeta,
1174
+ mgr,
1175
+ loadCoreMemoryContext,
1176
+ awaitKeychainPrewarm,
1177
+ ensureConfigForRouteProvider,
1178
+ reg,
1179
+ cfgMod,
1180
+ activeWorkflowContext,
1181
+ memoryEnabled,
1182
+ hooks,
1183
+ hookCommonPayload,
1184
+ mcpClient,
1185
+ modelStandaloneTools,
1186
+ applyPreSessionToolSelection,
1187
+ statusRoutes,
1188
+ warmupTimers,
1189
+ providerModelCaches,
1190
+ reloadFullConfig,
1191
+ refreshStatuslineUsageSnapshot,
1192
+ warmProviderModelCache,
1193
+ cachedProviderSetup,
1194
+ providerWarmupDelayMs,
1195
+ providerSetupWarmupDelayMs,
1196
+ providerModelWarmupDelayMs,
1197
+ modelCatalogWarmupDelayMs,
1198
+ statuslineUsageWarmupDelayMs,
1199
+ statuslineUsageRefreshDelayMs,
1200
+ backgroundBusyRetryMs,
1201
+ providerWarmupEnabled,
1202
+ modelPrefetchEnabled,
1203
+ modelCatalogWarmupEnabled,
1204
+ prewarmTimers,
1426
1205
  channelsEnabled,
1206
+ getCodeGraphModule,
1427
1207
  channels,
1428
- bootProfile,
1208
+ codeGraphPrewarmDelayMs,
1209
+ channelStartDelayMs,
1210
+ sessionPrewarmDelayMs,
1211
+ codeGraphPrewarmEnabled,
1212
+ sessionPrewarmEnabled,
1213
+ prewarmState,
1429
1214
  });
1430
1215
  const ensureRemoteTranscriptWriter = () => remoteTranscript.ensureRemoteTranscriptWriter();
1431
1216
  const pushTranscriptRebind = () => remoteTranscript.pushTranscriptRebind();
@@ -1434,12 +1219,12 @@ export async function createMixdogSessionRuntime({
1434
1219
  // Remote (channel relay) claim/release/stop live in remote-control.mjs; the
1435
1220
  // facade injects the mutable session + remote state they mutate.
1436
1221
  const remoteControl = createRemoteControl({
1437
- getSession: () => session,
1438
- isRemoteEnabled: () => remoteEnabled,
1439
- setRemoteEnabled: (next) => { remoteEnabled = next; },
1440
- getRemoteSessionId: () => remoteSessionId,
1441
- setRemoteSessionId: (next) => { remoteSessionId = next; },
1442
- isCloseRequested: () => closeRequested,
1222
+ getSession: () => rt.session,
1223
+ isRemoteEnabled: () => rt.remoteEnabled,
1224
+ setRemoteEnabled: (next) => { rt.remoteEnabled = next; },
1225
+ getRemoteSessionId: () => rt.remoteSessionId,
1226
+ setRemoteSessionId: (next) => { rt.remoteSessionId = next; },
1227
+ isCloseRequested: () => rt.closeRequested,
1443
1228
  clearChannelStartTimer: () => {
1444
1229
  if (prewarmTimers.channelStartTimer) {
1445
1230
  clearTimeout(prewarmTimers.channelStartTimer);
@@ -1465,7 +1250,7 @@ export async function createMixdogSessionRuntime({
1465
1250
  const stopRemote = (reason) => remoteControl.stopRemote(reason);
1466
1251
 
1467
1252
  function isRemoteEnabled() {
1468
- return remoteEnabled;
1253
+ return rt.remoteEnabled;
1469
1254
  }
1470
1255
 
1471
1256
  function withTeardownDeadline(promise, ms, fallback = false) {
@@ -1516,11 +1301,11 @@ export async function createMixdogSessionRuntime({
1516
1301
  // early /remote (startRemote clears it), stopRemote(), and close() all
1517
1302
  // cancel it through the existing clearTimeout paths. Runtime /remote calls
1518
1303
  // still start immediately (user-initiated, UI already painted).
1519
- if (remoteEnabled) {
1304
+ if (rt.remoteEnabled) {
1520
1305
  bootProfile('channels:manual-start-deferred', { delayMs: remoteAutoStartDelayMs });
1521
1306
  prewarmTimers.channelStartTimer = setTimeout(() => {
1522
1307
  prewarmTimers.channelStartTimer = null;
1523
- if (closeRequested || !remoteEnabled) return;
1308
+ if (rt.closeRequested || !rt.remoteEnabled) return;
1524
1309
  startRemote();
1525
1310
  }, remoteAutoStartDelayMs);
1526
1311
  prewarmTimers.channelStartTimer.unref?.();
@@ -1531,10 +1316,10 @@ export async function createMixdogSessionRuntime({
1531
1316
  // daemon without restarting schedules or webhooks.
1532
1317
  prewarmTimers.channelStartTimer = setTimeout(() => {
1533
1318
  prewarmTimers.channelStartTimer = null;
1534
- if (closeRequested || remoteEnabled) return;
1319
+ if (rt.closeRequested || rt.remoteEnabled) return;
1535
1320
  void hasActiveAutomation()
1536
1321
  .then((active) => {
1537
- if (!active || closeRequested || remoteEnabled) return;
1322
+ if (!active || rt.closeRequested || rt.remoteEnabled) return;
1538
1323
  bootProfile('channels:automation-autostart');
1539
1324
  void invokeChannelStart();
1540
1325
  })
@@ -1548,10 +1333,10 @@ export async function createMixdogSessionRuntime({
1548
1333
  // token save/forget, setBackend). Extracted to session-runtime/settings-api.mjs
1549
1334
  // and SPREAD into the API object below so the external surface is unchanged.
1550
1335
  const settingsApi = createSettingsApi({
1551
- getConfig: () => config,
1552
- getRoute: () => route,
1553
- getSession: () => session,
1554
- getRemoteEnabled: () => remoteEnabled,
1336
+ getConfig: () => rt.config,
1337
+ getRoute: () => rt.route,
1338
+ getSession: () => rt.session,
1339
+ getRemoteEnabled: () => rt.remoteEnabled,
1555
1340
  adoptConfig,
1556
1341
  saveConfigAndAdopt,
1557
1342
  scheduleBackendSave,
@@ -1610,35 +1395,36 @@ export async function createMixdogSessionRuntime({
1610
1395
  });
1611
1396
  const providerAuthApi = createProviderAuthApi({
1612
1397
  cfgMod,
1613
- getConfig: () => config,
1398
+ getConfig: () => rt.config,
1614
1399
  saveConfigAndAdopt,
1615
1400
  displayConfig,
1616
1401
  reloadFullConfig,
1617
1402
  awaitKeychainPrewarm,
1618
- isKeychainPrewarmReady: () => keychainPrewarmWaitDone,
1403
+ isKeychainPrewarmReady: () => rt.keychainPrewarmWaitDone,
1619
1404
  hasProviderSetupCached,
1620
1405
  invalidateProviderCaches,
1621
1406
  warmProviderModelCache,
1622
- refreshProviderCatalogs: () => ensureProvidersReady(config.providers || {}).then(() => reg.refreshCatalogs()),
1407
+ refreshProviderCatalogs: () => ensureProvidersReady(rt.config.providers || {}).then(() => reg.refreshCatalogs()),
1623
1408
  cachedProviderSetup,
1624
1409
  getUsageDashboard,
1410
+ consumeCodexRateLimitResetCredit,
1625
1411
  collectProviderModels,
1626
1412
  });
1627
1413
  const mediaApi = createMediaApi();
1628
1414
  const lifecycleApi = createLifecycleApi({
1629
- getSession: () => session,
1630
- setSession: (v) => { session = v; },
1631
- getRoute: () => route,
1632
- setRoute: (v) => { route = v; },
1633
- getConfig: () => config,
1634
- getMode: () => mode,
1635
- getCurrentCwd: () => currentCwd,
1636
- getDesktopSession: () => desktopSession,
1637
- setDesktopSession: (v) => { desktopSession = v; },
1638
- setCloseRequested: (v) => { closeRequested = v; },
1639
- getMemoryModPromise: () => memoryModPromise,
1640
- setMemoryModPromise: (v) => { memoryModPromise = v; },
1641
- setSessionNeedsCwdRefresh: (v) => { sessionNeedsCwdRefresh = v; },
1415
+ getSession: () => rt.session,
1416
+ setSession: (v) => { rt.session = v; },
1417
+ getRoute: () => rt.route,
1418
+ setRoute: (v) => { rt.route = v; },
1419
+ getConfig: () => rt.config,
1420
+ getMode: () => rt.mode,
1421
+ getCurrentCwd: () => rt.currentCwd,
1422
+ getDesktopSession: () => rt.desktopSession,
1423
+ setDesktopSession: (v) => { rt.desktopSession = v; },
1424
+ setCloseRequested: (v) => { rt.closeRequested = v; },
1425
+ getMemoryModPromise: () => rt.memoryModPromise,
1426
+ setMemoryModPromise: (v) => { rt.memoryModPromise = v; },
1427
+ setSessionNeedsCwdRefresh: (v) => { rt.sessionNeedsCwdRefresh = v; },
1642
1428
  hooks,
1643
1429
  hookCommonPayload,
1644
1430
  mgr,
@@ -1667,12 +1453,12 @@ export async function createMixdogSessionRuntime({
1667
1453
  // Live getter: cwd-refresh session rebuilds must re-evaluate the
1668
1454
  // workflow's agent-tool gate, not reuse the boot-time array.
1669
1455
  getStandaloneTools: modelStandaloneTools,
1670
- desktopSession,
1456
+ desktopSession: rt.desktopSession,
1671
1457
  });
1672
1458
  const resourceApi = createResourceApi({
1673
- getConfig: () => config,
1674
- getSession: () => session,
1675
- getCurrentCwd: () => currentCwd,
1459
+ getConfig: () => rt.config,
1460
+ getSession: () => rt.session,
1461
+ getCurrentCwd: () => rt.currentCwd,
1676
1462
  cfgMod,
1677
1463
  mgr,
1678
1464
  hooks,
@@ -1690,17 +1476,17 @@ export async function createMixdogSessionRuntime({
1690
1476
  getMemoryModule,
1691
1477
  reloadFullConfig,
1692
1478
  awaitKeychainPrewarm,
1693
- getActiveTurnCount: () => activeTurnCount,
1479
+ getActiveTurnCount: () => rt.activeTurnCount,
1694
1480
  });
1695
1481
  const modelRouteApi = createModelRouteApi({
1696
- getConfig: () => config,
1697
- getRoute: () => route,
1698
- setRouteState: (v) => { route = v; },
1699
- getSession: () => session,
1700
- setSession: (v) => { session = v; },
1701
- getConfigHasSecrets: () => configHasSecrets,
1702
- getSearchRouteState: () => searchRoute,
1703
- setSearchRouteState: (v) => { searchRoute = v; },
1482
+ getConfig: () => rt.config,
1483
+ getRoute: () => rt.route,
1484
+ setRouteState: (v) => { rt.route = v; },
1485
+ getSession: () => rt.session,
1486
+ setSession: (v) => { rt.session = v; },
1487
+ getConfigHasSecrets: () => rt.configHasSecrets,
1488
+ getSearchRouteState: () => rt.searchRoute,
1489
+ setSearchRouteState: (v) => { rt.searchRoute = v; },
1704
1490
  cfgMod,
1705
1491
  reg,
1706
1492
  mgr,
@@ -1725,12 +1511,12 @@ export async function createMixdogSessionRuntime({
1725
1511
  collectSearchProviderModels,
1726
1512
  });
1727
1513
  const workflowAgentsApi = createWorkflowAgentsApi({
1728
- getConfig: () => config,
1729
- getRoute: () => route,
1730
- setRouteState: (v) => { route = v; },
1731
- getSession: () => session,
1732
- setSession: (v) => { session = v; },
1733
- getConfigHasSecrets: () => configHasSecrets,
1514
+ getConfig: () => rt.config,
1515
+ getRoute: () => rt.route,
1516
+ setRouteState: (v) => { rt.route = v; },
1517
+ getSession: () => rt.session,
1518
+ setSession: (v) => { rt.session = v; },
1519
+ getConfigHasSecrets: () => rt.configHasSecrets,
1734
1520
  cfgMod,
1735
1521
  reg,
1736
1522
  mgr,
@@ -1756,26 +1542,26 @@ export async function createMixdogSessionRuntime({
1756
1542
  invalidateContextStatusCache,
1757
1543
  });
1758
1544
  const sessionTurnApi = createSessionTurnApi({
1759
- getSession: () => session,
1760
- setSession: (v) => { session = v; },
1761
- getCurrentCwd: () => currentCwd,
1762
- getMode: () => mode,
1763
- setMode: (v) => { mode = v; },
1764
- getActiveTurnCount: () => activeTurnCount,
1765
- setActiveTurnCount: (v) => { activeTurnCount = v; },
1766
- isFirstTurnCompleted: () => firstTurnCompleted,
1767
- setFirstTurnCompleted: (v) => { firstTurnCompleted = v; },
1768
- getCodeGraphFirstTurnPrewarmDone: () => codeGraphFirstTurnPrewarmDone,
1769
- setCodeGraphFirstTurnPrewarmDone: (v) => { codeGraphFirstTurnPrewarmDone = v; },
1545
+ getSession: () => rt.session,
1546
+ setSession: (v) => { rt.session = v; },
1547
+ getCurrentCwd: () => rt.currentCwd,
1548
+ getMode: () => rt.mode,
1549
+ setMode: (v) => { rt.mode = v; },
1550
+ getActiveTurnCount: () => rt.activeTurnCount,
1551
+ setActiveTurnCount: (v) => { rt.activeTurnCount = v; },
1552
+ isFirstTurnCompleted: () => rt.firstTurnCompleted,
1553
+ setFirstTurnCompleted: (v) => { rt.firstTurnCompleted = v; },
1554
+ getCodeGraphFirstTurnPrewarmDone: () => rt.codeGraphFirstTurnPrewarmDone,
1555
+ setCodeGraphFirstTurnPrewarmDone: (v) => { rt.codeGraphFirstTurnPrewarmDone = v; },
1770
1556
  codeGraphPrewarmLazy,
1771
- getRemoteEnabled: () => remoteEnabled,
1772
- getCloseRequested: () => closeRequested,
1773
- getPendingSessionReset: () => pendingSessionReset,
1774
- setPendingSessionReset: (v) => { pendingSessionReset = v; },
1557
+ getRemoteEnabled: () => rt.remoteEnabled,
1558
+ getCloseRequested: () => rt.closeRequested,
1559
+ getPendingSessionReset: () => rt.pendingSessionReset,
1560
+ setPendingSessionReset: (v) => { rt.pendingSessionReset = v; },
1775
1561
  getTranscriptWriter: () => remoteTranscript.transcriptWriter,
1776
1562
  getTwKey: () => remoteTranscript.transcriptKey,
1777
- getLastAppendedAssistant: () => _lastAppendedAssistant,
1778
- setLastAppendedAssistant: (v) => { _lastAppendedAssistant = v; },
1563
+ getLastAppendedAssistant: () => rt._lastAppendedAssistant,
1564
+ setLastAppendedAssistant: (v) => { rt._lastAppendedAssistant = v; },
1779
1565
  scheduleCodeGraphPrewarm,
1780
1566
  refreshSessionForCwdIfNeeded,
1781
1567
  createCurrentSession,
@@ -1813,55 +1599,55 @@ export async function createMixdogSessionRuntime({
1813
1599
  ...mediaApi,
1814
1600
  // Turn-scoped attributed review: child apply_patch diffs for this Lead turn.
1815
1601
  // Lead patches are read from its transcript by the renderer.
1816
- getTurnReviewDiff: () => getTurnSnapshotReviewDiff(currentCwd, session?.id),
1602
+ getTurnReviewDiff: () => getTurnSnapshotReviewDiff(rt.currentCwd, rt.session?.id),
1817
1603
  get id() {
1818
- return session?.id || null;
1604
+ return rt.session?.id || null;
1819
1605
  },
1820
1606
  get provider() {
1821
- return route.provider;
1607
+ return rt.route.provider;
1822
1608
  },
1823
1609
  get model() {
1824
- return route.model;
1610
+ return rt.route.model;
1825
1611
  },
1826
1612
  get effort() {
1827
- return route.effectiveEffort || route.effort || route.preset?.effort || null;
1613
+ return rt.route.effectiveEffort || rt.route.effort || rt.route.preset?.effort || null;
1828
1614
  },
1829
1615
  get fast() {
1830
- return route.fast === true;
1616
+ return rt.route.fast === true;
1831
1617
  },
1832
1618
  get fastCapable() {
1833
- return route.fastCapable === true;
1619
+ return rt.route.fastCapable === true;
1834
1620
  },
1835
1621
  get effortOptions() {
1836
- return route.effortOptions || [];
1622
+ return rt.route.effortOptions || [];
1837
1623
  },
1838
1624
  get contextWindow() {
1839
- return session?.contextWindow || null;
1625
+ return rt.session?.contextWindow || null;
1840
1626
  },
1841
1627
  get rawContextWindow() {
1842
- return session?.rawContextWindow || session?.contextWindow || null;
1628
+ return rt.session?.rawContextWindow || rt.session?.contextWindow || null;
1843
1629
  },
1844
1630
  get effectiveContextWindowPercent() {
1845
- return session?.effectiveContextWindowPercent || null;
1631
+ return rt.session?.effectiveContextWindowPercent || null;
1846
1632
  },
1847
1633
  get toolMode() {
1848
- return mode;
1634
+ return rt.mode;
1849
1635
  },
1850
1636
  get autoClear() {
1851
1637
  return this.getAutoClear();
1852
1638
  },
1853
1639
  get systemShell() {
1854
- return normalizeSystemShellConfig(config.shell);
1640
+ return normalizeSystemShellConfig(rt.config.shell);
1855
1641
  },
1856
1642
  get searchRoute() {
1857
- searchRoute = normalizeSearchRouteConfig(config.searchRoute) || normalizeSearchRouteConfig(searchRoute);
1858
- return searchRoute;
1643
+ rt.searchRoute = normalizeSearchRouteConfig(rt.config.searchRoute) || normalizeSearchRouteConfig(rt.searchRoute);
1644
+ return rt.searchRoute;
1859
1645
  },
1860
1646
  get workflow() {
1861
1647
  const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
1862
- const active = activeWorkflowSummary(config, dataDir);
1863
- if (session?.workflow && typeof session.workflow === 'object') {
1864
- const current = workflowSummary(session.workflow);
1648
+ const active = activeWorkflowSummary(rt.config, dataDir);
1649
+ if (rt.session?.workflow && typeof rt.session.workflow === 'object') {
1650
+ const current = workflowSummary(rt.session.workflow);
1865
1651
  return current?.id && active?.id && current.id !== active.id
1866
1652
  ? { ...active, currentSession: current, appliedToCurrentSession: false }
1867
1653
  : active;
@@ -1872,10 +1658,10 @@ export async function createMixdogSessionRuntime({
1872
1658
  return getOutputStyleStatusCached().current;
1873
1659
  },
1874
1660
  get cwd() {
1875
- return currentCwd;
1661
+ return rt.currentCwd;
1876
1662
  },
1877
1663
  get session() {
1878
- return session;
1664
+ return rt.session;
1879
1665
  },
1880
1666
  contextStatus() {
1881
1667
  // Prefer the in-flight working transcript while a turn is running so the
@@ -1900,7 +1686,7 @@ export async function createMixdogSessionRuntime({
1900
1686
  },
1901
1687
  // Session-scoped remote: the owning session id (null when off/unassigned).
1902
1688
  getRemoteSessionId() {
1903
- return remoteSessionId;
1689
+ return rt.remoteSessionId;
1904
1690
  },
1905
1691
  // Back-compat alias for an explicit manual ON in the current session.
1906
1692
  claimRemoteForCurrentSession() {
@@ -1915,7 +1701,7 @@ export async function createMixdogSessionRuntime({
1915
1701
  return () => remoteStateListeners.delete(listener);
1916
1702
  },
1917
1703
  get clientHostPid() {
1918
- return session?.clientHostPid || process.pid;
1704
+ return rt.session?.clientHostPid || process.pid;
1919
1705
  },
1920
1706
  ...lifecycleApi,
1921
1707
  ...resourceApi,