mixdog 0.9.68 → 0.9.69
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.
- package/package.json +6 -4
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +1 -6
- package/src/runtime/agent/orchestrator/context/collect.mjs +42 -27
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +0 -6
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +161 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +8 -204
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +25 -13
- package/src/runtime/agent/orchestrator/session/manager.mjs +0 -11
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +613 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +9 -575
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +41 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +154 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +78 -18
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +9 -144
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +40 -3
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +24 -8
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +6 -2
- package/src/runtime/channels/lib/webhook.mjs +13 -1
- package/src/session-runtime/memory-daemon-probe.mjs +61 -0
- package/src/session-runtime/model-capabilities.mjs +6 -4
- package/src/session-runtime/notification-bus.mjs +82 -0
- package/src/session-runtime/provider-auth-api.mjs +16 -1
- package/src/session-runtime/provider-usage.mjs +3 -0
- package/src/session-runtime/remote-control.mjs +166 -0
- package/src/session-runtime/remote-transcript.mjs +126 -0
- package/src/session-runtime/remote-transition-queue.mjs +14 -0
- package/src/session-runtime/runtime-core.mjs +143 -705
- package/src/session-runtime/runtime-tunables.mjs +57 -0
- package/src/session-runtime/self-update.mjs +129 -0
- package/src/session-runtime/skills-api.mjs +77 -0
- package/src/session-runtime/tool-surface.mjs +83 -0
- package/src/session-runtime/workflow-agents-api.mjs +206 -3
- package/src/session-runtime/workflow.mjs +84 -17
- package/src/standalone/agent-tool/worker-index.mjs +287 -0
- package/src/standalone/agent-tool.mjs +22 -346
- package/src/standalone/agent-watchdog-registry.mjs +101 -0
- package/src/standalone/channel-worker.mjs +7 -13
- package/src/tui/components/StatusLine.jsx +6 -5
- package/src/tui/dist/index.mjs +124 -94
- package/src/tui/engine/labels.mjs +0 -8
- package/src/tui/engine/session-api-ext.mjs +17 -8
- package/src/tui/engine/turn-watchdog.mjs +92 -0
- package/src/tui/engine/turn.mjs +18 -70
- package/src/workflows/default/WORKFLOW.md +1 -1
- package/src/workflows/solo/WORKFLOW.md +1 -1
package/src/tui/dist/index.mjs
CHANGED
|
@@ -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:
|
|
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,
|
|
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
|
|
26754
|
-
|
|
26755
|
-
|
|
26756
|
-
|
|
26757
|
-
|
|
26758
|
-
|
|
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
|
-
}
|
|
26855
|
-
|
|
26856
|
-
|
|
26857
|
-
|
|
26858
|
-
if (
|
|
26859
|
-
|
|
26860
|
-
|
|
26861
|
-
|
|
26862
|
-
return true;
|
|
26875
|
+
}
|
|
26876
|
+
});
|
|
26877
|
+
const clearWatchdog = () => {
|
|
26878
|
+
watchdog.clear();
|
|
26879
|
+
if (watchdogGraceTimer) {
|
|
26880
|
+
clearTimeout(watchdogGraceTimer);
|
|
26881
|
+
watchdogGraceTimer = null;
|
|
26882
|
+
}
|
|
26863
26883
|
};
|
|
26864
|
-
|
|
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() ||
|
|
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}${
|
|
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
|
},
|
|
@@ -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
|
},
|
|
@@ -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
|
+
}
|
package/src/tui/engine/turn.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { toolResultText, toolErrorDisplay } from './tool-result-text.mjs';
|
|
|
8
8
|
import { toolCallId, toolResultCallId, toolCallName, toolCallArgs } from './tool-call-fields.mjs';
|
|
9
9
|
import { promptDisplayText, STEERING_SUPPRESSED_DISPLAY } from './queue-helpers.mjs';
|
|
10
10
|
import { yieldToRenderer } from './render-timing.mjs';
|
|
11
|
+
import { createTurnWatchdog } from './turn-watchdog.mjs';
|
|
11
12
|
import { aggregateRawResult, aggregateBucketForCategory, aggregateSummaries, assignAggregateSummaryOrder, failureDetailText, toolCallOutcome } from './tool-result-status.mjs';
|
|
12
13
|
|
|
13
14
|
export function createRunTurn(bag) {
|
|
@@ -82,69 +83,19 @@ export function createRunTurn(bag) {
|
|
|
82
83
|
// teardown runs. Keep this progress-based rather than total wall-clock:
|
|
83
84
|
// long multi-tool turns can legitimately exceed 5m while still producing
|
|
84
85
|
// model/tool events.
|
|
85
|
-
let watchdogTripped = false;
|
|
86
|
-
let watchdogTimer = null;
|
|
87
86
|
let watchdogGraceTimer = null;
|
|
88
|
-
let lastProgressAt = startedAt;
|
|
89
|
-
let lastProgressLabel = 'start';
|
|
90
|
-
let watchdogDeferralCeilingAt = 0;
|
|
91
87
|
const configuredLeadToolMaxMs = Number(process.env.MIXDOG_LEAD_TOOL_MAX_MS);
|
|
92
88
|
const leadToolMaxMs = Number.isFinite(configuredLeadToolMaxMs) && configuredLeadToolMaxMs > 0
|
|
93
89
|
? configuredLeadToolMaxMs
|
|
94
90
|
: 30 * 60 * 1000;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const progressAt = Number(liveness?.lastProgressAt);
|
|
104
|
-
const now = Date.now();
|
|
105
|
-
if (!liveness || !Number.isFinite(progressAt) || progressAt <= now - LEAD_TURN_TIMEOUT_MS) return false;
|
|
106
|
-
|
|
107
|
-
if (liveness.stage === 'tool_running') {
|
|
108
|
-
watchdogDeferralCeilingAt = 0;
|
|
109
|
-
const toolStartedAt = Number(liveness.toolStartedAt);
|
|
110
|
-
if (!Number.isFinite(toolStartedAt) || toolStartedAt <= 0) return false;
|
|
111
|
-
const toolSelfDeadlineMs = Number(liveness.toolSelfDeadlineMs);
|
|
112
|
-
const toolCeilingMs = Math.max(
|
|
113
|
-
Number.isFinite(toolSelfDeadlineMs) && toolSelfDeadlineMs > 0 ? toolSelfDeadlineMs + 60_000 : 0,
|
|
114
|
-
leadToolMaxMs,
|
|
115
|
-
);
|
|
116
|
-
if (now - toolStartedAt >= toolCeilingMs) return false;
|
|
117
|
-
watchdogDeferralCeilingAt = toolStartedAt + toolCeilingMs;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
lastProgressAt = progressAt;
|
|
121
|
-
lastProgressLabel = `orchestrator:${String(liveness.stage || 'unknown')}`;
|
|
122
|
-
armWatchdog();
|
|
123
|
-
return true;
|
|
124
|
-
};
|
|
125
|
-
const armWatchdog = () => {
|
|
126
|
-
if (watchdogTimer) { clearTimeout(watchdogTimer); watchdogTimer = null; }
|
|
127
|
-
if (watchdogTripped) return;
|
|
128
|
-
const now = Date.now();
|
|
129
|
-
const remaining = Math.max(1, LEAD_TURN_TIMEOUT_MS - Math.max(0, now - lastProgressAt));
|
|
130
|
-
const ceilingRemaining = watchdogDeferralCeilingAt > 0
|
|
131
|
-
? Math.max(1, watchdogDeferralCeilingAt - now)
|
|
132
|
-
: Infinity;
|
|
133
|
-
const delay = Math.min(remaining, ceilingRemaining);
|
|
134
|
-
watchdogTimer = setTimeout(() => {
|
|
135
|
-
if (!isCurrentTurn()) return;
|
|
136
|
-
if (watchdogTripped) return;
|
|
137
|
-
const now = Date.now();
|
|
138
|
-
const idleMs = now - lastProgressAt;
|
|
139
|
-
if (idleMs < LEAD_TURN_TIMEOUT_MS) {
|
|
140
|
-
if (watchdogDeferralCeilingAt > 0 && now >= watchdogDeferralCeilingAt) {
|
|
141
|
-
if (refreshWatchdogFromRuntimeLiveness()) return;
|
|
142
|
-
} else {
|
|
143
|
-
armWatchdog();
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
} else if (refreshWatchdogFromRuntimeLiveness()) return;
|
|
147
|
-
watchdogTripped = true;
|
|
91
|
+
// Scheduling/liveness lives in turn-watchdog.mjs; the trip ACTION stays here
|
|
92
|
+
// because it unwinds this turn's UI state.
|
|
93
|
+
const watchdog = createTurnWatchdog({
|
|
94
|
+
runtime,
|
|
95
|
+
timeoutMs: LEAD_TURN_TIMEOUT_MS,
|
|
96
|
+
toolMaxMs: leadToolMaxMs,
|
|
97
|
+
isCurrentTurn,
|
|
98
|
+
onTrip: ({ idleMs, lastProgressLabel }) => {
|
|
148
99
|
if (_batchTimer !== null) {
|
|
149
100
|
clearTimeout(_batchTimer);
|
|
150
101
|
_batchTimer = null;
|
|
@@ -189,17 +140,14 @@ export function createRunTurn(bag) {
|
|
|
189
140
|
flushEmitImmediate?.();
|
|
190
141
|
}, 5_000);
|
|
191
142
|
watchdogGraceTimer.unref?.();
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (
|
|
197
|
-
if (watchdogTripped) return;
|
|
198
|
-
lastProgressAt = Date.now();
|
|
199
|
-
lastProgressLabel = String(label || 'progress');
|
|
200
|
-
return true;
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
const clearWatchdog = () => {
|
|
146
|
+
watchdog.clear();
|
|
147
|
+
if (watchdogGraceTimer) { clearTimeout(watchdogGraceTimer); watchdogGraceTimer = null; }
|
|
201
148
|
};
|
|
202
|
-
|
|
149
|
+
const markTurnProgress = (label) => watchdog.markProgress(label);
|
|
150
|
+
watchdog.arm();
|
|
203
151
|
let currentAssistantText = '';
|
|
204
152
|
// Segments sealed as their own assistant item(s) this turn via
|
|
205
153
|
// commitAssistantSegment (e.g. a tool preamble, then a no-newline tail
|
|
@@ -1188,7 +1136,7 @@ export function createRunTurn(bag) {
|
|
|
1188
1136
|
flushStreamBatch();
|
|
1189
1137
|
},
|
|
1190
1138
|
onReasoningDelta: (chunk) => {
|
|
1191
|
-
if (!isCurrentTurn() ||
|
|
1139
|
+
if (!isCurrentTurn() || watchdog.tripped) return;
|
|
1192
1140
|
if (String(chunk ?? '')) {
|
|
1193
1141
|
if (!markTurnProgress('reasoning-delta')) return;
|
|
1194
1142
|
markPromptCommitted();
|
|
@@ -1395,7 +1343,7 @@ export function createRunTurn(bag) {
|
|
|
1395
1343
|
flushEmit?.();
|
|
1396
1344
|
_publishedThinkingActive = false; // turn teardown cleared getState().thinking
|
|
1397
1345
|
const finalStatus = cancelled ? 'cancelled' : (failed ? 'failed' : 'done');
|
|
1398
|
-
tuiDebug(`runTurn end turn=${turnIndex} status=${finalStatus} elapsedMs=${Date.now() - startedAt}${
|
|
1346
|
+
tuiDebug(`runTurn end turn=${turnIndex} status=${finalStatus} elapsedMs=${Date.now() - startedAt}${watchdog.tripped ? ' watchdogTripped=1' : ''} pending=${pending.length}`);
|
|
1399
1347
|
return finalStatus;
|
|
1400
1348
|
}
|
|
1401
1349
|
|