mixdog 0.9.68 → 0.9.70

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 (64) hide show
  1. package/package.json +8 -4
  2. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +1 -6
  3. package/src/runtime/agent/orchestrator/context/collect.mjs +42 -27
  4. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +1 -1
  5. package/src/runtime/agent/orchestrator/providers/gemini.mjs +0 -6
  6. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +161 -0
  7. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +8 -204
  8. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +26 -0
  9. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +25 -13
  10. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -11
  11. package/src/runtime/agent/orchestrator/session/store/listing.mjs +613 -0
  12. package/src/runtime/agent/orchestrator/session/store.mjs +9 -575
  13. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +41 -0
  14. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +154 -0
  15. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +78 -18
  16. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +9 -144
  17. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +7 -3
  18. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +40 -3
  19. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +24 -8
  20. package/src/runtime/channels/lib/owned-runtime.mjs +8 -1
  21. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +6 -2
  22. package/src/runtime/channels/lib/webhook.mjs +13 -1
  23. package/src/runtime/media/adapters/codex-image.mjs +109 -0
  24. package/src/runtime/media/adapters/gemini-image.mjs +68 -0
  25. package/src/runtime/media/adapters/gemini-video.mjs +119 -0
  26. package/src/runtime/media/adapters/xai-media.mjs +116 -0
  27. package/src/runtime/media/auth.mjs +51 -0
  28. package/src/runtime/media/index.mjs +17 -0
  29. package/src/runtime/media/jobs.mjs +174 -0
  30. package/src/runtime/media/lanes.mjs +230 -0
  31. package/src/runtime/media/store.mjs +164 -0
  32. package/src/runtime/media/upstream-error.mjs +39 -0
  33. package/src/session-runtime/channel-config-api.mjs +12 -1
  34. package/src/session-runtime/media-api.mjs +47 -0
  35. package/src/session-runtime/memory-daemon-probe.mjs +61 -0
  36. package/src/session-runtime/model-capabilities.mjs +6 -4
  37. package/src/session-runtime/model-route-api.mjs +4 -1
  38. package/src/session-runtime/notification-bus.mjs +82 -0
  39. package/src/session-runtime/provider-auth-api.mjs +16 -1
  40. package/src/session-runtime/provider-usage.mjs +3 -0
  41. package/src/session-runtime/remote-control.mjs +166 -0
  42. package/src/session-runtime/remote-transcript.mjs +126 -0
  43. package/src/session-runtime/remote-transition-queue.mjs +14 -0
  44. package/src/session-runtime/runtime-core.mjs +160 -706
  45. package/src/session-runtime/runtime-tunables.mjs +57 -0
  46. package/src/session-runtime/self-update.mjs +129 -0
  47. package/src/session-runtime/skills-api.mjs +77 -0
  48. package/src/session-runtime/tool-surface.mjs +83 -0
  49. package/src/session-runtime/warmup-schedulers.mjs +14 -1
  50. package/src/session-runtime/workflow-agents-api.mjs +235 -7
  51. package/src/session-runtime/workflow.mjs +84 -17
  52. package/src/standalone/agent-tool/worker-index.mjs +287 -0
  53. package/src/standalone/agent-tool.mjs +22 -346
  54. package/src/standalone/agent-watchdog-registry.mjs +101 -0
  55. package/src/standalone/channel-worker.mjs +7 -13
  56. package/src/tui/components/StatusLine.jsx +6 -5
  57. package/src/tui/dist/index.mjs +144 -95
  58. package/src/tui/engine/labels.mjs +0 -8
  59. package/src/tui/engine/session-api-ext.mjs +31 -8
  60. package/src/tui/engine/turn-watchdog.mjs +92 -0
  61. package/src/tui/engine/turn.mjs +18 -70
  62. package/src/tui/engine.mjs +39 -1
  63. package/src/workflows/default/WORKFLOW.md +1 -1
  64. package/src/workflows/solo/WORKFLOW.md +1 -1
@@ -6818,10 +6818,10 @@ ${grafted}` : cachedL1;
6818
6818
  }, [sessionId, clientHostPid, provider, model, effort, fast, cwd, statsSignature, contextWindow, displayContextWindow, compactBoundaryTokens, autoCompactTokenLimit, rawContextWindow, resizeEpoch, agentRevision, agentWorkersSignature, agentJobsSignature, activeToolsSignature, refreshTick, themeEpoch]);
6819
6819
  const lines = line ? line.split("\n").slice(0, 2) : [" ", " "];
6820
6820
  const workflowLabel = workflowModeLabel(workflow, remoteEnabled);
6821
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: "100%", height: 3, overflow: "hidden", justifyContent: "flex-start", paddingLeft: 2, backgroundColor: surfaceBackground(), children: [
6821
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: "100%", height: 2, overflow: "hidden", justifyContent: "flex-start", paddingLeft: 2, paddingRight: 2, backgroundColor: surfaceBackground(), children: [
6822
6822
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", width: "100%", overflow: "hidden", children: [
6823
6823
  /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, flexShrink: 1, flexBasis: 0, overflow: "hidden", children: /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", children: lines[0] || " " }) }),
6824
- /* @__PURE__ */ jsx2(Box2, { flexShrink: 0, marginLeft: 1, marginRight: 1, children: /* @__PURE__ */ jsx2(Text2, { color: theme.statusText, wrap: "truncate", children: workflowLabel }) })
6824
+ /* @__PURE__ */ jsx2(Box2, { flexShrink: 0, marginLeft: 1, children: /* @__PURE__ */ jsx2(Text2, { color: theme.statusText, wrap: "truncate", children: workflowLabel }) })
6825
6825
  ] }),
6826
6826
  /* @__PURE__ */ jsx2(Box2, { flexDirection: "row", width: "100%", overflow: "hidden", children: /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, flexShrink: 1, flexBasis: 0, overflow: "hidden", children: /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", children: lines[1] || " " }) }) })
6827
6827
  ] });
@@ -24171,11 +24171,6 @@ function compactEventDetail(event = {}) {
24171
24171
  const parts = [];
24172
24172
  const elapsed = formatElapsedSeconds(Number(event.durationMs ?? event.elapsedMs ?? 0));
24173
24173
  if (elapsed) parts.push(elapsed);
24174
- const type = String(event.compactType || event.type || "").trim();
24175
- if (type && type !== "semantic") parts.push(type);
24176
- const trigger = String(event.trigger || "").toLowerCase();
24177
- if (trigger === "reactive") parts.push("reactive");
24178
- else if (trigger === "manual") parts.push("manual");
24179
24174
  const before = Number(event.pressureTokens ?? event.beforeTokens ?? 0);
24180
24175
  const after = Number(event.afterTokens ?? 0);
24181
24176
  const fmtTok = (n) => {
@@ -26658,6 +26653,93 @@ function createSessionFlow(bag) {
26658
26653
  return { leadSessionId, shouldMirrorSteeringEntry, commitSteeringQueueEntries, makeQueueEntry, removeQueuedEntries, requeueEntriesFront, dequeueQueueBatch, drain, enqueue, drainPendingSteering, restoreLeadSteeringFromDisk, autoClearBeforeSubmit, performSessionClear, restoreQueued, resetStats, clearUiActivityBeforeContextSync, resetTuiForPendingSessionReset, snapshotTuiBeforeSessionReset, restoreTuiAfterFailedSessionReset, commitTuiSessionReset, resetStatsAndSyncContext };
26659
26654
  }
26660
26655
 
26656
+ // src/tui/engine/turn-watchdog.mjs
26657
+ function createTurnWatchdog({
26658
+ runtime,
26659
+ timeoutMs,
26660
+ toolMaxMs,
26661
+ isCurrentTurn,
26662
+ onTrip
26663
+ }) {
26664
+ let timer2 = null;
26665
+ let tripped = false;
26666
+ let lastProgressAt = Date.now();
26667
+ let lastProgressLabel = "start";
26668
+ let deferralCeilingAt = 0;
26669
+ function clear() {
26670
+ if (timer2) {
26671
+ clearTimeout(timer2);
26672
+ timer2 = null;
26673
+ }
26674
+ }
26675
+ function refreshFromRuntimeLiveness() {
26676
+ let liveness;
26677
+ try {
26678
+ liveness = runtime.getTurnLiveness?.();
26679
+ } catch {
26680
+ return false;
26681
+ }
26682
+ if (liveness?.stage !== "tool_running") deferralCeilingAt = 0;
26683
+ const progressAt = Number(liveness?.lastProgressAt);
26684
+ const now = Date.now();
26685
+ if (!liveness || !Number.isFinite(progressAt) || progressAt <= now - timeoutMs) return false;
26686
+ if (liveness.stage === "tool_running") {
26687
+ deferralCeilingAt = 0;
26688
+ const toolStartedAt = Number(liveness.toolStartedAt);
26689
+ if (!Number.isFinite(toolStartedAt) || toolStartedAt <= 0) return false;
26690
+ const toolSelfDeadlineMs = Number(liveness.toolSelfDeadlineMs);
26691
+ const toolCeilingMs = Math.max(
26692
+ Number.isFinite(toolSelfDeadlineMs) && toolSelfDeadlineMs > 0 ? toolSelfDeadlineMs + 6e4 : 0,
26693
+ toolMaxMs
26694
+ );
26695
+ if (now - toolStartedAt >= toolCeilingMs) return false;
26696
+ deferralCeilingAt = toolStartedAt + toolCeilingMs;
26697
+ }
26698
+ lastProgressAt = progressAt;
26699
+ lastProgressLabel = `orchestrator:${String(liveness.stage || "unknown")}`;
26700
+ arm();
26701
+ return true;
26702
+ }
26703
+ function arm() {
26704
+ clear();
26705
+ if (tripped) return;
26706
+ const now = Date.now();
26707
+ const remaining = Math.max(1, timeoutMs - Math.max(0, now - lastProgressAt));
26708
+ const ceilingRemaining = deferralCeilingAt > 0 ? Math.max(1, deferralCeilingAt - now) : Infinity;
26709
+ timer2 = setTimeout(() => {
26710
+ if (!isCurrentTurn() || tripped) return;
26711
+ const firedAt = Date.now();
26712
+ const idleMs = firedAt - lastProgressAt;
26713
+ if (idleMs < timeoutMs) {
26714
+ if (deferralCeilingAt > 0 && firedAt >= deferralCeilingAt) {
26715
+ if (refreshFromRuntimeLiveness()) return;
26716
+ } else {
26717
+ arm();
26718
+ return;
26719
+ }
26720
+ } else if (refreshFromRuntimeLiveness()) return;
26721
+ tripped = true;
26722
+ onTrip({ idleMs, lastProgressLabel });
26723
+ }, Math.min(remaining, ceilingRemaining));
26724
+ timer2.unref?.();
26725
+ }
26726
+ function markProgress(label) {
26727
+ if (!isCurrentTurn()) return false;
26728
+ if (tripped) return false;
26729
+ lastProgressAt = Date.now();
26730
+ lastProgressLabel = String(label || "progress");
26731
+ return true;
26732
+ }
26733
+ return {
26734
+ arm,
26735
+ clear,
26736
+ markProgress,
26737
+ get tripped() {
26738
+ return tripped;
26739
+ }
26740
+ };
26741
+ }
26742
+
26661
26743
  // src/tui/engine/turn.mjs
26662
26744
  function createRunTurn(bag) {
26663
26745
  const {
@@ -26742,76 +26824,15 @@ function createRunTurn(bag) {
26742
26824
  let assistantText = "";
26743
26825
  let currentAssistantId = null;
26744
26826
  tuiDebug2(`runTurn start turn=${turnIndex} pending=${pending.length} timeoutMs=${LEAD_TURN_TIMEOUT_MS2}`);
26745
- let watchdogTripped = false;
26746
- let watchdogTimer = null;
26747
26827
  let watchdogGraceTimer = null;
26748
- let lastProgressAt = startedAt;
26749
- let lastProgressLabel = "start";
26750
- let watchdogDeferralCeilingAt = 0;
26751
26828
  const configuredLeadToolMaxMs = Number(process.env.MIXDOG_LEAD_TOOL_MAX_MS);
26752
26829
  const leadToolMaxMs = Number.isFinite(configuredLeadToolMaxMs) && configuredLeadToolMaxMs > 0 ? configuredLeadToolMaxMs : 30 * 60 * 1e3;
26753
- const clearWatchdog = () => {
26754
- if (watchdogTimer) {
26755
- clearTimeout(watchdogTimer);
26756
- watchdogTimer = null;
26757
- }
26758
- if (watchdogGraceTimer) {
26759
- clearTimeout(watchdogGraceTimer);
26760
- watchdogGraceTimer = null;
26761
- }
26762
- };
26763
- const refreshWatchdogFromRuntimeLiveness = () => {
26764
- let liveness;
26765
- try {
26766
- liveness = runtime.getTurnLiveness?.();
26767
- } catch {
26768
- return false;
26769
- }
26770
- if (liveness?.stage !== "tool_running") watchdogDeferralCeilingAt = 0;
26771
- const progressAt = Number(liveness?.lastProgressAt);
26772
- const now = Date.now();
26773
- if (!liveness || !Number.isFinite(progressAt) || progressAt <= now - LEAD_TURN_TIMEOUT_MS2) return false;
26774
- if (liveness.stage === "tool_running") {
26775
- watchdogDeferralCeilingAt = 0;
26776
- const toolStartedAt = Number(liveness.toolStartedAt);
26777
- if (!Number.isFinite(toolStartedAt) || toolStartedAt <= 0) return false;
26778
- const toolSelfDeadlineMs = Number(liveness.toolSelfDeadlineMs);
26779
- const toolCeilingMs = Math.max(
26780
- Number.isFinite(toolSelfDeadlineMs) && toolSelfDeadlineMs > 0 ? toolSelfDeadlineMs + 6e4 : 0,
26781
- leadToolMaxMs
26782
- );
26783
- if (now - toolStartedAt >= toolCeilingMs) return false;
26784
- watchdogDeferralCeilingAt = toolStartedAt + toolCeilingMs;
26785
- }
26786
- lastProgressAt = progressAt;
26787
- lastProgressLabel = `orchestrator:${String(liveness.stage || "unknown")}`;
26788
- armWatchdog();
26789
- return true;
26790
- };
26791
- const armWatchdog = () => {
26792
- if (watchdogTimer) {
26793
- clearTimeout(watchdogTimer);
26794
- watchdogTimer = null;
26795
- }
26796
- if (watchdogTripped) return;
26797
- const now = Date.now();
26798
- const remaining = Math.max(1, LEAD_TURN_TIMEOUT_MS2 - Math.max(0, now - lastProgressAt));
26799
- const ceilingRemaining = watchdogDeferralCeilingAt > 0 ? Math.max(1, watchdogDeferralCeilingAt - now) : Infinity;
26800
- const delay2 = Math.min(remaining, ceilingRemaining);
26801
- watchdogTimer = setTimeout(() => {
26802
- if (!isCurrentTurn()) return;
26803
- if (watchdogTripped) return;
26804
- const now2 = Date.now();
26805
- const idleMs = now2 - lastProgressAt;
26806
- if (idleMs < LEAD_TURN_TIMEOUT_MS2) {
26807
- if (watchdogDeferralCeilingAt > 0 && now2 >= watchdogDeferralCeilingAt) {
26808
- if (refreshWatchdogFromRuntimeLiveness()) return;
26809
- } else {
26810
- armWatchdog();
26811
- return;
26812
- }
26813
- } else if (refreshWatchdogFromRuntimeLiveness()) return;
26814
- watchdogTripped = true;
26830
+ const watchdog = createTurnWatchdog({
26831
+ runtime,
26832
+ timeoutMs: LEAD_TURN_TIMEOUT_MS2,
26833
+ toolMaxMs: leadToolMaxMs,
26834
+ isCurrentTurn,
26835
+ onTrip: ({ idleMs, lastProgressLabel }) => {
26815
26836
  if (_batchTimer !== null) {
26816
26837
  clearTimeout(_batchTimer);
26817
26838
  _batchTimer = null;
@@ -26851,17 +26872,17 @@ function createRunTurn(bag) {
26851
26872
  flushEmitImmediate?.();
26852
26873
  }, 5e3);
26853
26874
  watchdogGraceTimer.unref?.();
26854
- }, delay2);
26855
- watchdogTimer.unref?.();
26856
- };
26857
- const markTurnProgress = (label) => {
26858
- if (!isCurrentTurn()) return false;
26859
- if (watchdogTripped) return;
26860
- lastProgressAt = Date.now();
26861
- lastProgressLabel = String(label || "progress");
26862
- return true;
26875
+ }
26876
+ });
26877
+ const clearWatchdog = () => {
26878
+ watchdog.clear();
26879
+ if (watchdogGraceTimer) {
26880
+ clearTimeout(watchdogGraceTimer);
26881
+ watchdogGraceTimer = null;
26882
+ }
26863
26883
  };
26864
- armWatchdog();
26884
+ const markTurnProgress = (label) => watchdog.markProgress(label);
26885
+ watchdog.arm();
26865
26886
  let currentAssistantText = "";
26866
26887
  const committedSegments = [];
26867
26888
  let thinkingText = "";
@@ -27607,7 +27628,7 @@ function createRunTurn(bag) {
27607
27628
  flushStreamBatch();
27608
27629
  },
27609
27630
  onReasoningDelta: (chunk) => {
27610
- if (!isCurrentTurn() || watchdogTripped) return;
27631
+ if (!isCurrentTurn() || watchdog.tripped) return;
27611
27632
  if (String(chunk ?? "")) {
27612
27633
  if (!markTurnProgress("reasoning-delta")) return;
27613
27634
  markPromptCommitted();
@@ -27747,7 +27768,7 @@ function createRunTurn(bag) {
27747
27768
  flushEmit?.();
27748
27769
  _publishedThinkingActive = false;
27749
27770
  const finalStatus = cancelled ? "cancelled" : failed ? "failed" : "done";
27750
- tuiDebug2(`runTurn end turn=${turnIndex} status=${finalStatus} elapsedMs=${Date.now() - startedAt}${watchdogTripped ? " watchdogTripped=1" : ""} pending=${pending.length}`);
27771
+ tuiDebug2(`runTurn end turn=${turnIndex} status=${finalStatus} elapsedMs=${Date.now() - startedAt}${watchdog.tripped ? " watchdogTripped=1" : ""} pending=${pending.length}`);
27751
27772
  return finalStatus;
27752
27773
  }
27753
27774
  return runTurn;
@@ -28085,6 +28106,15 @@ function createEngineApiB(bag) {
28085
28106
  listWorkflows: () => {
28086
28107
  return runtime.listWorkflows?.() || [];
28087
28108
  },
28109
+ // Workflow pack editing (desktop Workflows page): direct passthroughs —
28110
+ // the runtime owns validation, user-pack persistence, and delete guards.
28111
+ getWorkflowPack: (workflowId) => runtime.getWorkflowPack?.(workflowId) ?? null,
28112
+ saveWorkflowPack: async (payload) => runtime.saveWorkflowPack?.(payload) ?? null,
28113
+ createWorkflow: async (payload) => runtime.createWorkflow?.(payload) ?? null,
28114
+ deleteWorkflow: async (workflowId) => runtime.deleteWorkflow?.(workflowId) ?? null,
28115
+ getAgentDefinition: (agentId) => runtime.getAgentDefinition?.(agentId) ?? null,
28116
+ saveAgentDefinition: async (payload) => runtime.saveAgentDefinition?.(payload) ?? null,
28117
+ deleteAgentDefinition: async (agentId) => runtime.deleteAgentDefinition?.(agentId) ?? null,
28088
28118
  getOutputStyle: () => {
28089
28119
  return runtime.getOutputStyle?.() || runtime.listOutputStyles?.() || null;
28090
28120
  },
@@ -28122,13 +28152,13 @@ function createEngineApiB(bag) {
28122
28152
  // user decision). Off → on claims for this session; on + this session
28123
28153
  // owns → off; on + ANOTHER session owns → move the relay seat here
28124
28154
  // (last-wins) without restarting the worker.
28125
- toggleRemote: () => {
28155
+ toggleRemote: async () => {
28126
28156
  const enabled = runtime.isRemoteEnabled?.() === true;
28127
28157
  const owner = String(runtime.getRemoteSessionId?.() || "");
28128
28158
  const currentId = String(getState().sessionId || "");
28129
28159
  const workerRunning = runtime.getChannelWorkerStatus?.()?.running === true;
28130
28160
  if (enabled && !workerRunning) {
28131
- runtime.startRemote?.();
28161
+ await runtime.startRemote?.();
28132
28162
  const next2 = runtime.isRemoteEnabled?.() === true;
28133
28163
  set({ remoteEnabled: next2, remoteSessionId: runtime.getRemoteSessionId?.() || null });
28134
28164
  return next2;
@@ -28138,8 +28168,8 @@ function createEngineApiB(bag) {
28138
28168
  set({ remoteEnabled: true, remoteSessionId: runtime.getRemoteSessionId?.() || null });
28139
28169
  return true;
28140
28170
  }
28141
- if (enabled) runtime.releaseRemote?.();
28142
- else runtime.startRemote?.();
28171
+ if (enabled) await runtime.releaseRemote?.();
28172
+ else await runtime.startRemote?.();
28143
28173
  const next = runtime.isRemoteEnabled?.() === true;
28144
28174
  set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
28145
28175
  return next;
@@ -28148,16 +28178,16 @@ function createEngineApiB(bag) {
28148
28178
  // turns remote ON here and steals the bridge seat; the previous holder is
28149
28179
  // superseded and flips itself OFF via onRemoteStateChange. Used by the
28150
28180
  // `/remote` slash command — repeated /remote just re-claims (idempotent).
28151
- claimRemote: () => {
28152
- runtime.startRemote?.();
28181
+ claimRemote: async () => {
28182
+ await runtime.startRemote?.();
28153
28183
  const next = runtime.isRemoteEnabled?.() === true;
28154
28184
  set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
28155
28185
  return next;
28156
28186
  },
28157
28187
  // Idempotent desired-state OFF for desktop controls. Unlike toggleRemote,
28158
28188
  // a delayed/replayed request can never turn remote back on.
28159
- releaseRemote: () => {
28160
- runtime.releaseRemote?.();
28189
+ releaseRemote: async () => {
28190
+ await runtime.releaseRemote?.();
28161
28191
  set({ remoteEnabled: false, remoteSessionId: null });
28162
28192
  return false;
28163
28193
  },
@@ -28372,6 +28402,20 @@ function createEngineApiB(bag) {
28372
28402
  pushNotice("channel saved", "info");
28373
28403
  return result;
28374
28404
  },
28405
+ // Media studio (image/video generation). Reads stay quiet; only the
28406
+ // generation start posts a notice so the TUI shows background work.
28407
+ listMediaLanes: () => runtime.listMediaLanes?.(),
28408
+ listMediaAssets: (options) => runtime.listMediaAssets?.(options),
28409
+ readMediaAsset: (id) => runtime.readMediaAsset?.(id),
28410
+ getMediaJob: (id) => runtime.getMediaJob?.(id),
28411
+ listMediaJobs: () => runtime.listMediaJobs?.(),
28412
+ // No notice on start: the Studio surfaces progress on its own pending tile,
28413
+ // and a toast for a user-initiated generation is pure noise.
28414
+ startMediaJob: (input) => runtime.startMediaJob(input),
28415
+ cancelMediaJob: (id) => runtime.cancelMediaJob?.(id),
28416
+ deleteMediaAsset: (id) => runtime.deleteMediaAsset?.(id),
28417
+ openMediaAsset: (id) => runtime.openMediaAsset?.(id),
28418
+ openMediaFolder: () => runtime.openMediaFolder?.(),
28375
28419
  setWebhookConfig: async (patch) => {
28376
28420
  const result = await runtime.setWebhookConfig(patch);
28377
28421
  pushNotice("webhook config updated", "info");
@@ -30502,6 +30546,11 @@ function createEngineItemMutators({
30502
30546
 
30503
30547
  // src/tui/engine.mjs
30504
30548
  var SESSION_RUNTIME_MODULE = import.meta.url.replace(/\\/g, "/").includes("/tui/dist/") ? "../../mixdog-session-runtime.mjs" : "../mixdog-session-runtime.mjs";
30549
+ var sessionRuntimeModulePromise = null;
30550
+ function importSessionRuntimeModule() {
30551
+ sessionRuntimeModulePromise ??= import(SESSION_RUNTIME_MODULE);
30552
+ return sessionRuntimeModulePromise;
30553
+ }
30505
30554
  var TOOL_APPROVAL_TIMEOUT_MS = (() => {
30506
30555
  const value = Number(process.env.MIXDOG_TOOL_APPROVAL_TIMEOUT_MS);
30507
30556
  return Number.isFinite(value) && value > 0 ? Math.max(1e3, Math.round(value)) : 12e4;
@@ -30526,7 +30575,7 @@ async function createEngineSession({
30526
30575
  process.env.MIXDOG_QUIET_MEMORY_LOG = "1";
30527
30576
  process.env.MIXDOG_PATCH_NATIVE_PREWARM ??= "0";
30528
30577
  const importStartedAt = performance3.now();
30529
- const { createMixdogSessionRuntime } = await import(SESSION_RUNTIME_MODULE);
30578
+ const { createMixdogSessionRuntime } = await importSessionRuntimeModule();
30530
30579
  bootProfile("session-runtime:imported", { ms: (performance3.now() - importStartedAt).toFixed(1) });
30531
30580
  const runtime = await createMixdogSessionRuntime({
30532
30581
  provider: providerName,
@@ -39,17 +39,9 @@ export function compactEventLabel(event = {}) {
39
39
  }
40
40
 
41
41
  export function compactEventDetail(event = {}) {
42
- // Keep the elapsed time as the lead detail, but no longer discard the rest of
43
- // the compact metadata. Surface type/trigger and the boundary/pressure so the
44
- // statusdone marker reflects what actually fired.
45
42
  const parts = [];
46
43
  const elapsed = formatElapsedSeconds(Number(event.durationMs ?? event.elapsedMs ?? 0));
47
44
  if (elapsed) parts.push(elapsed);
48
- const type = String(event.compactType || event.type || '').trim();
49
- if (type && type !== 'semantic') parts.push(type);
50
- const trigger = String(event.trigger || '').toLowerCase();
51
- if (trigger === 'reactive') parts.push('reactive');
52
- else if (trigger === 'manual') parts.push('manual');
53
45
  const before = Number(event.pressureTokens ?? event.beforeTokens ?? 0);
54
46
  const after = Number(event.afterTokens ?? 0);
55
47
  const fmtTok = (n) => {
@@ -368,6 +368,15 @@ export function createEngineApiB(bag) {
368
368
  listWorkflows: () => {
369
369
  return runtime.listWorkflows?.() || [];
370
370
  },
371
+ // Workflow pack editing (desktop Workflows page): direct passthroughs —
372
+ // the runtime owns validation, user-pack persistence, and delete guards.
373
+ getWorkflowPack: (workflowId) => runtime.getWorkflowPack?.(workflowId) ?? null,
374
+ saveWorkflowPack: async (payload) => runtime.saveWorkflowPack?.(payload) ?? null,
375
+ createWorkflow: async (payload) => runtime.createWorkflow?.(payload) ?? null,
376
+ deleteWorkflow: async (workflowId) => runtime.deleteWorkflow?.(workflowId) ?? null,
377
+ getAgentDefinition: (agentId) => runtime.getAgentDefinition?.(agentId) ?? null,
378
+ saveAgentDefinition: async (payload) => runtime.saveAgentDefinition?.(payload) ?? null,
379
+ deleteAgentDefinition: async (agentId) => runtime.deleteAgentDefinition?.(agentId) ?? null,
371
380
  getOutputStyle: () => {
372
381
  return runtime.getOutputStyle?.() || runtime.listOutputStyles?.() || null;
373
382
  },
@@ -407,7 +416,7 @@ export function createEngineApiB(bag) {
407
416
  // user decision). Off → on claims for this session; on + this session
408
417
  // owns → off; on + ANOTHER session owns → move the relay seat here
409
418
  // (last-wins) without restarting the worker.
410
- toggleRemote: () => {
419
+ toggleRemote: async () => {
411
420
  const enabled = runtime.isRemoteEnabled?.() === true;
412
421
  const owner = String(runtime.getRemoteSessionId?.() || '');
413
422
  const currentId = String(getState().sessionId || '');
@@ -417,7 +426,7 @@ export function createEngineApiB(bag) {
417
426
  // no-op into a claim or flip a dead relay "off".
418
427
  const workerRunning = runtime.getChannelWorkerStatus?.()?.running === true;
419
428
  if (enabled && !workerRunning) {
420
- runtime.startRemote?.();
429
+ await runtime.startRemote?.();
421
430
  const next = runtime.isRemoteEnabled?.() === true;
422
431
  set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
423
432
  return next;
@@ -427,8 +436,8 @@ export function createEngineApiB(bag) {
427
436
  set({ remoteEnabled: true, remoteSessionId: runtime.getRemoteSessionId?.() || null });
428
437
  return true;
429
438
  }
430
- if (enabled) runtime.releaseRemote?.();
431
- else runtime.startRemote?.();
439
+ if (enabled) await runtime.releaseRemote?.();
440
+ else await runtime.startRemote?.();
432
441
  const next = runtime.isRemoteEnabled?.() === true;
433
442
  set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
434
443
  return next;
@@ -437,16 +446,16 @@ export function createEngineApiB(bag) {
437
446
  // turns remote ON here and steals the bridge seat; the previous holder is
438
447
  // superseded and flips itself OFF via onRemoteStateChange. Used by the
439
448
  // `/remote` slash command — repeated /remote just re-claims (idempotent).
440
- claimRemote: () => {
441
- runtime.startRemote?.();
449
+ claimRemote: async () => {
450
+ await runtime.startRemote?.();
442
451
  const next = runtime.isRemoteEnabled?.() === true;
443
452
  set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
444
453
  return next;
445
454
  },
446
455
  // Idempotent desired-state OFF for desktop controls. Unlike toggleRemote,
447
456
  // a delayed/replayed request can never turn remote back on.
448
- releaseRemote: () => {
449
- runtime.releaseRemote?.();
457
+ releaseRemote: async () => {
458
+ await runtime.releaseRemote?.();
450
459
  set({ remoteEnabled: false, remoteSessionId: null });
451
460
  return false;
452
461
  },
@@ -664,6 +673,20 @@ export function createEngineApiB(bag) {
664
673
  pushNotice('channel saved', 'info');
665
674
  return result;
666
675
  },
676
+ // Media studio (image/video generation). Reads stay quiet; only the
677
+ // generation start posts a notice so the TUI shows background work.
678
+ listMediaLanes: () => runtime.listMediaLanes?.(),
679
+ listMediaAssets: (options) => runtime.listMediaAssets?.(options),
680
+ readMediaAsset: (id) => runtime.readMediaAsset?.(id),
681
+ getMediaJob: (id) => runtime.getMediaJob?.(id),
682
+ listMediaJobs: () => runtime.listMediaJobs?.(),
683
+ // No notice on start: the Studio surfaces progress on its own pending tile,
684
+ // and a toast for a user-initiated generation is pure noise.
685
+ startMediaJob: (input) => runtime.startMediaJob(input),
686
+ cancelMediaJob: (id) => runtime.cancelMediaJob?.(id),
687
+ deleteMediaAsset: (id) => runtime.deleteMediaAsset?.(id),
688
+ openMediaAsset: (id) => runtime.openMediaAsset?.(id),
689
+ openMediaFolder: () => runtime.openMediaFolder?.(),
667
690
  setWebhookConfig: async (patch) => {
668
691
  const result = await runtime.setWebhookConfig(patch);
669
692
  pushNotice('webhook config updated', 'info');
@@ -0,0 +1,92 @@
1
+ // Lead-turn stall watchdog. A turn is declared stuck only when NOTHING has
2
+ // progressed for the timeout: local deltas mark progress directly, and before
3
+ // tripping we re-read the orchestrator's own liveness so a long-but-alive tool
4
+ // (its self-deadline plus a grace ceiling) defers the trip instead of aborting
5
+ // real work. Extracted from turn.mjs; the trip ACTION stays there because it
6
+ // unwinds turn-scoped UI state.
7
+ export function createTurnWatchdog({
8
+ runtime,
9
+ timeoutMs,
10
+ toolMaxMs,
11
+ isCurrentTurn,
12
+ onTrip,
13
+ }) {
14
+ let timer = null;
15
+ let tripped = false;
16
+ let lastProgressAt = Date.now();
17
+ let lastProgressLabel = 'start';
18
+ // While a tool runs, the trip is deferred until this wall-clock ceiling.
19
+ let deferralCeilingAt = 0;
20
+
21
+ function clear() {
22
+ if (timer) { clearTimeout(timer); timer = null; }
23
+ }
24
+
25
+ // Adopt the orchestrator's liveness as progress when it is fresher than the
26
+ // local view; returns true when the trip was deferred.
27
+ function refreshFromRuntimeLiveness() {
28
+ let liveness;
29
+ try { liveness = runtime.getTurnLiveness?.(); } catch { return false; }
30
+ if (liveness?.stage !== 'tool_running') deferralCeilingAt = 0;
31
+ const progressAt = Number(liveness?.lastProgressAt);
32
+ const now = Date.now();
33
+ if (!liveness || !Number.isFinite(progressAt) || progressAt <= now - timeoutMs) return false;
34
+
35
+ if (liveness.stage === 'tool_running') {
36
+ deferralCeilingAt = 0;
37
+ const toolStartedAt = Number(liveness.toolStartedAt);
38
+ if (!Number.isFinite(toolStartedAt) || toolStartedAt <= 0) return false;
39
+ const toolSelfDeadlineMs = Number(liveness.toolSelfDeadlineMs);
40
+ const toolCeilingMs = Math.max(
41
+ Number.isFinite(toolSelfDeadlineMs) && toolSelfDeadlineMs > 0 ? toolSelfDeadlineMs + 60_000 : 0,
42
+ toolMaxMs,
43
+ );
44
+ if (now - toolStartedAt >= toolCeilingMs) return false;
45
+ deferralCeilingAt = toolStartedAt + toolCeilingMs;
46
+ }
47
+
48
+ lastProgressAt = progressAt;
49
+ lastProgressLabel = `orchestrator:${String(liveness.stage || 'unknown')}`;
50
+ arm();
51
+ return true;
52
+ }
53
+
54
+ function arm() {
55
+ clear();
56
+ if (tripped) return;
57
+ const now = Date.now();
58
+ const remaining = Math.max(1, timeoutMs - Math.max(0, now - lastProgressAt));
59
+ const ceilingRemaining = deferralCeilingAt > 0 ? Math.max(1, deferralCeilingAt - now) : Infinity;
60
+ timer = setTimeout(() => {
61
+ if (!isCurrentTurn() || tripped) return;
62
+ const firedAt = Date.now();
63
+ const idleMs = firedAt - lastProgressAt;
64
+ if (idleMs < timeoutMs) {
65
+ if (deferralCeilingAt > 0 && firedAt >= deferralCeilingAt) {
66
+ if (refreshFromRuntimeLiveness()) return;
67
+ } else {
68
+ arm();
69
+ return;
70
+ }
71
+ } else if (refreshFromRuntimeLiveness()) return;
72
+ tripped = true;
73
+ onTrip({ idleMs, lastProgressLabel });
74
+ }, Math.min(remaining, ceilingRemaining));
75
+ timer.unref?.();
76
+ }
77
+
78
+ function markProgress(label) {
79
+ if (!isCurrentTurn()) return false;
80
+ if (tripped) return false;
81
+ lastProgressAt = Date.now();
82
+ lastProgressLabel = String(label || 'progress');
83
+ return true;
84
+ }
85
+
86
+ return {
87
+ arm,
88
+ clear,
89
+ markProgress,
90
+ get tripped() { return tripped; },
91
+ };
92
+ }