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.
- package/package.json +8 -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/owned-runtime.mjs +8 -1
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +6 -2
- package/src/runtime/channels/lib/webhook.mjs +13 -1
- package/src/runtime/media/adapters/codex-image.mjs +109 -0
- package/src/runtime/media/adapters/gemini-image.mjs +68 -0
- package/src/runtime/media/adapters/gemini-video.mjs +119 -0
- package/src/runtime/media/adapters/xai-media.mjs +116 -0
- package/src/runtime/media/auth.mjs +51 -0
- package/src/runtime/media/index.mjs +17 -0
- package/src/runtime/media/jobs.mjs +174 -0
- package/src/runtime/media/lanes.mjs +230 -0
- package/src/runtime/media/store.mjs +164 -0
- package/src/runtime/media/upstream-error.mjs +39 -0
- package/src/session-runtime/channel-config-api.mjs +12 -1
- package/src/session-runtime/media-api.mjs +47 -0
- package/src/session-runtime/memory-daemon-probe.mjs +61 -0
- package/src/session-runtime/model-capabilities.mjs +6 -4
- package/src/session-runtime/model-route-api.mjs +4 -1
- 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 +160 -706
- 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/warmup-schedulers.mjs +14 -1
- package/src/session-runtime/workflow-agents-api.mjs +235 -7
- 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 +144 -95
- package/src/tui/engine/labels.mjs +0 -8
- package/src/tui/engine/session-api-ext.mjs +31 -8
- package/src/tui/engine/turn-watchdog.mjs +92 -0
- package/src/tui/engine/turn.mjs +18 -70
- package/src/tui/engine.mjs +39 -1
- package/src/workflows/default/WORKFLOW.md +1 -1
- package/src/workflows/solo/WORKFLOW.md +1 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readFileSync, statSync } from 'node:fs';
|
|
2
1
|
import { dirname, resolve } from 'node:path';
|
|
3
2
|
import { fileURLToPath } from 'node:url';
|
|
4
3
|
import {
|
|
@@ -12,20 +11,16 @@ import {
|
|
|
12
11
|
taskIdFromArgs,
|
|
13
12
|
} from '../runtime/shared/background-tasks.mjs';
|
|
14
13
|
import { presentErrorText, errorLine } from '../runtime/shared/err-text.mjs';
|
|
15
|
-
import { updateJsonAtomicSync } from '../runtime/shared/atomic-file.mjs';
|
|
16
14
|
import { normalizeAgentPermission } from '../runtime/shared/markdown-frontmatter.mjs';
|
|
17
15
|
import { ensureProcessListenerHeadroom } from '../runtime/shared/process-listener-headroom.mjs';
|
|
18
16
|
import { resourceAdmission } from '../runtime/shared/resource-admission.mjs';
|
|
19
17
|
import { prepareAgentSession } from '../runtime/agent/orchestrator/agent-runtime/session-builder.mjs';
|
|
20
18
|
import {
|
|
21
|
-
abortAgentProgressWatchdog,
|
|
22
|
-
agentWatchdogPolicyActive,
|
|
23
|
-
evaluateAgentWatchdogAbort,
|
|
24
19
|
resolveAgentWatchdogPolicy,
|
|
25
20
|
resolveHandoffMessageStartIndex,
|
|
26
21
|
watchdogPartialHandoffFromError,
|
|
27
|
-
AgentStallAbortError,
|
|
28
22
|
} from '../runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs';
|
|
23
|
+
import { createProgressWatchdogRegistry } from './agent-watchdog-registry.mjs';
|
|
29
24
|
import { buildAgentTaskProgressFields } from './agent-task-status.mjs';
|
|
30
25
|
import { AGENT_OWNER } from '../runtime/agent/orchestrator/agent-owner.mjs';
|
|
31
26
|
import {
|
|
@@ -57,15 +52,13 @@ import { createNotify } from './agent-tool/notify.mjs';
|
|
|
57
52
|
import { resolveAgentSpawnPreset } from './agent-tool/spawn-preset.mjs';
|
|
58
53
|
import {
|
|
59
54
|
TAG_TOMBSTONE_TTL_MS,
|
|
60
|
-
applyWorkerRowUpsert,
|
|
61
55
|
isTerminalWorkerStatus,
|
|
62
|
-
normalizeTagTombstones,
|
|
63
56
|
tagTombstoneKey,
|
|
64
|
-
workerRowKey,
|
|
65
57
|
workerRowTime,
|
|
66
58
|
workerRowToSession,
|
|
67
59
|
} from './agent-tool/worker-rows.mjs';
|
|
68
60
|
import { resolveAgentTerminalReapMs } from '../session-runtime/config-helpers.mjs';
|
|
61
|
+
import { createWorkerIndex } from './agent-tool/worker-index.mjs';
|
|
69
62
|
// Re-export the static tool descriptor so importers of this facade keep the
|
|
70
63
|
// identical public surface (`import { AGENT_TOOL } from './agent-tool.mjs'`).
|
|
71
64
|
export { AGENT_TOOL };
|
|
@@ -102,253 +95,20 @@ export function createStandaloneAgent({
|
|
|
102
95
|
const tagAgents = new Map();
|
|
103
96
|
const tagCwds = new Map();
|
|
104
97
|
const reapTimers = new Map();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function normalizeWorkerRows(value) {
|
|
121
|
-
const source = Array.isArray(value?.workers)
|
|
122
|
-
? value.workers
|
|
123
|
-
: (value?.workers && typeof value.workers === 'object'
|
|
124
|
-
? Object.values(value.workers)
|
|
125
|
-
: (Array.isArray(value) ? value : []));
|
|
126
|
-
return source
|
|
127
|
-
.filter((row) => row && typeof row === 'object')
|
|
128
|
-
.map((row) => ({
|
|
129
|
-
tag: clean(row.tag),
|
|
130
|
-
sessionId: clean(row.sessionId),
|
|
131
|
-
agent: clean(row.agent) || null,
|
|
132
|
-
provider: clean(row.provider) || null,
|
|
133
|
-
model: clean(row.model) || null,
|
|
134
|
-
preset: clean(row.preset) || null,
|
|
135
|
-
effort: clean(row.effort) || null,
|
|
136
|
-
fast: row.fast === true ? true : (row.fast === false ? false : null),
|
|
137
|
-
status: clean(row.status) || 'idle',
|
|
138
|
-
stage: clean(row.stage) || clean(row.status) || 'idle',
|
|
139
|
-
createdAt: clean(row.createdAt) || null,
|
|
140
|
-
updatedAt: clean(row.updatedAt) || null,
|
|
141
|
-
lastUsedAt: clean(row.lastUsedAt) || null,
|
|
142
|
-
finishedAt: clean(row.finishedAt) || null,
|
|
143
|
-
clientHostPid: positiveInt(row.clientHostPid),
|
|
144
|
-
cwd: clean(row.cwd) || null,
|
|
145
|
-
task_id: clean(row.task_id || row.taskId) || null,
|
|
146
|
-
error: clean(row.error) || null,
|
|
147
|
-
permission: clean(row.permission) || null,
|
|
148
|
-
toolPermission: clean(row.toolPermission) || null,
|
|
149
|
-
messages: positiveInt(row.messages) || 0,
|
|
150
|
-
tools: positiveInt(row.tools) || 0,
|
|
151
|
-
}))
|
|
152
|
-
.filter(keepWorkerRow);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Mtime-keyed parse cache for the worker index. A single spawn calls
|
|
156
|
-
// refreshTagsFromSessions()/resolveTag()/nextTag() which each re-read and
|
|
157
|
-
// re-JSON.parse this file; across a parallel fanout that is O(spawns^2)
|
|
158
|
-
// synchronous reads of the same bytes on the event loop. Cache the parsed,
|
|
159
|
-
// normalized rows and reuse them while the file mtime+size is unchanged.
|
|
160
|
-
// Writes bump _workerRowsCacheDirty so the very next read re-parses.
|
|
161
|
-
let _workerRowsCache = null; // { mtimeMs, size, rows, tombstones }
|
|
162
|
-
let _workerRowsCacheDirty = true;
|
|
163
|
-
function invalidateWorkerRowsCache() {
|
|
164
|
-
_workerRowsCacheDirty = true;
|
|
165
|
-
}
|
|
166
|
-
function readAllWorkerRows() {
|
|
167
|
-
const file = workerIndexPath();
|
|
168
|
-
if (!file) return [];
|
|
169
|
-
let st = null;
|
|
170
|
-
try { st = statSync(file); } catch { _workerRowsCache = null; return []; }
|
|
171
|
-
if (!_workerRowsCacheDirty
|
|
172
|
-
&& _workerRowsCache
|
|
173
|
-
&& _workerRowsCache.mtimeMs === st.mtimeMs
|
|
174
|
-
&& _workerRowsCache.size === st.size) {
|
|
175
|
-
return _workerRowsCache.rows;
|
|
176
|
-
}
|
|
177
|
-
let rows = [];
|
|
178
|
-
let tombstones = [];
|
|
179
|
-
try {
|
|
180
|
-
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
181
|
-
rows = normalizeWorkerRows(parsed);
|
|
182
|
-
tombstones = normalizeTagTombstones(parsed);
|
|
183
|
-
} catch {
|
|
184
|
-
rows = [];
|
|
185
|
-
tombstones = [];
|
|
186
|
-
}
|
|
187
|
-
_workerRowsCache = { mtimeMs: st.mtimeMs, size: st.size, rows, tombstones };
|
|
188
|
-
_workerRowsCacheDirty = false;
|
|
189
|
-
return rows;
|
|
190
|
-
}
|
|
191
|
-
function readAllTagTombstones() {
|
|
192
|
-
readAllWorkerRows();
|
|
193
|
-
return _workerRowsCache?.tombstones || [];
|
|
194
|
-
}
|
|
195
|
-
function readTagTombstones(context = {}) {
|
|
196
|
-
return readAllTagTombstones().filter((row) => rowMatchesContext(row, context));
|
|
197
|
-
}
|
|
198
|
-
function readWorkerRows(context = {}) {
|
|
199
|
-
const rows = readAllWorkerRows();
|
|
200
|
-
if (rows.length === 0) return rows;
|
|
201
|
-
return rows.filter((row) => rowMatchesContext(row, context));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function workerRowsForUpdate(cur) {
|
|
205
|
-
return normalizeWorkerRows(cur);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function writeWorkerRows(mutator) {
|
|
209
|
-
const file = workerIndexPath();
|
|
210
|
-
if (!file || typeof mutator !== 'function') return null;
|
|
211
|
-
try {
|
|
212
|
-
const result = updateJsonAtomicSync(file, (cur) => {
|
|
213
|
-
const byKey = new Map();
|
|
214
|
-
for (const row of workerRowsForUpdate(cur)) {
|
|
215
|
-
const key = workerRowKey(row);
|
|
216
|
-
if (key) byKey.set(key, row);
|
|
217
|
-
}
|
|
218
|
-
const tombstonesByKey = new Map();
|
|
219
|
-
for (const row of normalizeTagTombstones(cur, { cap: false })) {
|
|
220
|
-
tombstonesByKey.set(tagTombstoneKey(row), row);
|
|
221
|
-
}
|
|
222
|
-
const priorityTombstoneKeys = new Set();
|
|
223
|
-
mutator(byKey, tombstonesByKey, priorityTombstoneKeys);
|
|
224
|
-
const workers = {};
|
|
225
|
-
for (const row of [...byKey.values()].filter(keepWorkerRow)) {
|
|
226
|
-
const key = workerRowKey(row);
|
|
227
|
-
if (key) workers[key] = row;
|
|
228
|
-
}
|
|
229
|
-
const tombstones = {};
|
|
230
|
-
for (const row of normalizeTagTombstones(
|
|
231
|
-
{ tombstones: [...tombstonesByKey.values()] },
|
|
232
|
-
{ priorityKeys: priorityTombstoneKeys },
|
|
233
|
-
)) {
|
|
234
|
-
tombstones[tagTombstoneKey(row)] = row;
|
|
235
|
-
}
|
|
236
|
-
return { version: 2, updatedAt: new Date().toISOString(), workers, tombstones };
|
|
237
|
-
}, { lock: true });
|
|
238
|
-
// This process just rewrote the index; force the next read to re-parse
|
|
239
|
-
// even if the new mtime/size happen to collide with the cached stat.
|
|
240
|
-
invalidateWorkerRowsCache();
|
|
241
|
-
return result;
|
|
242
|
-
} catch {
|
|
243
|
-
return null;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function flushWorkerIndexMutations() {
|
|
248
|
-
if (workerIndexFlushTimer) {
|
|
249
|
-
try { clearImmediate(workerIndexFlushTimer); } catch {}
|
|
250
|
-
workerIndexFlushTimer = null;
|
|
251
|
-
}
|
|
252
|
-
if (workerIndexMutators.length === 0) return;
|
|
253
|
-
const batch = workerIndexMutators.splice(0, workerIndexMutators.length);
|
|
254
|
-
writeWorkerRows((byKey) => {
|
|
255
|
-
for (const mutator of batch) {
|
|
256
|
-
try { mutator(byKey); } catch {}
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function queueWorkerIndexMutation(mutator) {
|
|
262
|
-
if (typeof mutator !== 'function') return false;
|
|
263
|
-
if (!workerIndexPath()) return false;
|
|
264
|
-
workerIndexMutators.push(mutator);
|
|
265
|
-
if (!workerIndexFlushTimer) {
|
|
266
|
-
workerIndexFlushTimer = setImmediate(flushWorkerIndexMutations);
|
|
267
|
-
workerIndexFlushTimer.unref?.();
|
|
268
|
-
}
|
|
269
|
-
return true;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function workerRowFromSession(session, fallbackTag = '', extra = {}) {
|
|
273
|
-
const tag = agentTagOf(session) || clean(fallbackTag) || clean(extra.tag);
|
|
274
|
-
const sessionId = clean(session?.id || extra.sessionId);
|
|
275
|
-
if (!tag || !sessionId) return null;
|
|
276
|
-
const runtime = mgr.getSessionRuntime?.(sessionId);
|
|
277
|
-
const status = clean(extra.status) || (session?.closed === true ? 'closed' : clean(session?.status) || 'idle');
|
|
278
|
-
const stage = clean(extra.stage) || clean(runtime?.stage) || status;
|
|
279
|
-
const nowIso = new Date().toISOString();
|
|
280
|
-
return {
|
|
281
|
-
tag,
|
|
282
|
-
sessionId,
|
|
283
|
-
agent: clean(extra.agent) || clean(session?.agent) || null,
|
|
284
|
-
provider: clean(extra.provider) || clean(session?.provider) || null,
|
|
285
|
-
model: clean(extra.model) || clean(session?.model) || null,
|
|
286
|
-
preset: clean(extra.preset) || clean(session?.presetName) || null,
|
|
287
|
-
effort: clean(extra.effort) || clean(session?.effort) || null,
|
|
288
|
-
fast: extra.fast === true || extra.fast === false ? extra.fast : (session?.fast === true ? true : null),
|
|
289
|
-
status,
|
|
290
|
-
stage,
|
|
291
|
-
createdAt: clean(session?.createdAt) || clean(extra.createdAt) || nowIso,
|
|
292
|
-
updatedAt: clean(extra.updatedAt) || nowIso,
|
|
293
|
-
lastUsedAt: clean(session?.lastUsedAt) || null,
|
|
294
|
-
finishedAt: clean(extra.finishedAt) || null,
|
|
295
|
-
clientHostPid: positiveInt(extra.clientHostPid) || positiveInt(session?.clientHostPid),
|
|
296
|
-
cwd: clean(session?.cwd) || clean(extra.cwd) || null,
|
|
297
|
-
task_id: clean(extra.task_id || extra.taskId) || null,
|
|
298
|
-
error: clean(extra.error) || null,
|
|
299
|
-
permission: clean(session?.permission) || null,
|
|
300
|
-
toolPermission: clean(session?.toolPermission) || null,
|
|
301
|
-
messages: Array.isArray(session?.messages) ? session.messages.length : 0,
|
|
302
|
-
tools: Array.isArray(session?.tools) ? session.tools.length : 0,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function upsertWorkerRow(row, { defer = false } = {}) {
|
|
307
|
-
const normalized = normalizeWorkerRows({ workers: [row] })[0];
|
|
308
|
-
if (!normalized) return false;
|
|
309
|
-
tags.set(normalized.tag, normalized.sessionId);
|
|
310
|
-
if (normalized.agent) tagAgents.set(normalized.tag, normalized.agent);
|
|
311
|
-
if (normalized.cwd) tagCwds.set(normalized.tag, normalized.cwd);
|
|
312
|
-
if (defer) {
|
|
313
|
-
return queueWorkerIndexMutation((byKey) => applyWorkerRowUpsert(byKey, normalized));
|
|
314
|
-
}
|
|
315
|
-
writeWorkerRows((byKey) => {
|
|
316
|
-
applyWorkerRowUpsert(byKey, normalized);
|
|
317
|
-
});
|
|
318
|
-
return true;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function upsertWorkerSession(session, fallbackTag = '', extra = {}) {
|
|
322
|
-
return upsertWorkerRow(workerRowFromSession(session, fallbackTag, extra));
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function upsertWorkerSessionDeferred(session, fallbackTag = '', extra = {}) {
|
|
326
|
-
return upsertWorkerRow(workerRowFromSession(session, fallbackTag, extra), { defer: true });
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function removeWorkerRow({ tag = '', sessionId = '' } = {}) {
|
|
330
|
-
const targetTag = clean(tag);
|
|
331
|
-
const targetSessionId = clean(sessionId);
|
|
332
|
-
flushWorkerIndexMutations();
|
|
333
|
-
writeWorkerRows((byKey) => {
|
|
334
|
-
for (const [key, row] of [...byKey.entries()]) {
|
|
335
|
-
if ((targetSessionId && row.sessionId === targetSessionId) || (targetTag && row.tag === targetTag)) {
|
|
336
|
-
byKey.delete(key);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function refreshTagsFromIndex(context = {}) {
|
|
343
|
-
const rows = readWorkerRows(context);
|
|
344
|
-
for (const row of rows) {
|
|
345
|
-
if (!row.tag || !row.sessionId) continue;
|
|
346
|
-
tags.set(row.tag, row.sessionId);
|
|
347
|
-
if (row.agent) tagAgents.set(row.tag, row.agent);
|
|
348
|
-
if (row.cwd) tagCwds.set(row.tag, row.cwd);
|
|
349
|
-
}
|
|
350
|
-
return rows;
|
|
351
|
-
}
|
|
98
|
+
// Worker-index persistence (row store, parse cache, batched atomic writer,
|
|
99
|
+
// tag projection) lives in agent-tool/worker-index.mjs; the tag maps are
|
|
100
|
+
// shared by reference so both sides see the same live state.
|
|
101
|
+
const {
|
|
102
|
+
readAllTagTombstones,
|
|
103
|
+
readTagTombstones,
|
|
104
|
+
readWorkerRows,
|
|
105
|
+
writeWorkerRows,
|
|
106
|
+
flushWorkerIndexMutations,
|
|
107
|
+
upsertWorkerSession,
|
|
108
|
+
upsertWorkerSessionDeferred,
|
|
109
|
+
removeWorkerRow,
|
|
110
|
+
refreshTagsFromIndex,
|
|
111
|
+
} = createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCwds });
|
|
352
112
|
|
|
353
113
|
function wantsSessionScan(args = {}) {
|
|
354
114
|
return args.recover === true || args.scanSessions === true || args.scan_sessions === true;
|
|
@@ -1025,95 +785,11 @@ export function createStandaloneAgent({
|
|
|
1025
785
|
}, notifyContext);
|
|
1026
786
|
}
|
|
1027
787
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
try { clearInterval(progressWatchdogTimer); } catch {}
|
|
1034
|
-
progressWatchdogTimer = null;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
function checkProgressIdleWatchdog(state) {
|
|
1038
|
-
const {
|
|
1039
|
-
sessionId,
|
|
1040
|
-
watchdogPolicy,
|
|
1041
|
-
agent,
|
|
1042
|
-
controller,
|
|
1043
|
-
anchorTs,
|
|
1044
|
-
} = state;
|
|
1045
|
-
if (controller.signal?.aborted) {
|
|
1046
|
-
progressWatchdogs.delete(sessionId);
|
|
1047
|
-
stopProgressWatchdogTimerIfIdle();
|
|
1048
|
-
return;
|
|
1049
|
-
}
|
|
1050
|
-
const now = Date.now();
|
|
1051
|
-
const snapshot = typeof mgr.getSessionProgressSnapshot === 'function'
|
|
1052
|
-
? mgr.getSessionProgressSnapshot(sessionId)
|
|
1053
|
-
: null;
|
|
1054
|
-
const abortErr = snapshot
|
|
1055
|
-
? evaluateAgentWatchdogAbort(snapshot, now, watchdogPolicy)
|
|
1056
|
-
: null;
|
|
1057
|
-
const sess = typeof mgr.getSession === 'function' ? mgr.getSession(sessionId) : null;
|
|
1058
|
-
const iteration = typeof sess?.lastIterationIndex === 'number' ? sess.lastIterationIndex : null;
|
|
1059
|
-
if (!abortErr && !snapshot) {
|
|
1060
|
-
const reported = mgr.getSessionLastProgressAt(sessionId);
|
|
1061
|
-
const last = reported || anchorTs;
|
|
1062
|
-
if (watchdogPolicy.idleStaleMs > 0 && now - last > watchdogPolicy.idleStaleMs) {
|
|
1063
|
-
abortAgentProgressWatchdog(controller, {
|
|
1064
|
-
sessionId,
|
|
1065
|
-
agent,
|
|
1066
|
-
error: new AgentStallAbortError(`agent task stale (${watchdogPolicy.idleStaleMs}ms without progress)`),
|
|
1067
|
-
policy: watchdogPolicy,
|
|
1068
|
-
now,
|
|
1069
|
-
anchorTs,
|
|
1070
|
-
lastProgressAt: reported,
|
|
1071
|
-
iteration,
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
return;
|
|
1075
|
-
}
|
|
1076
|
-
if (abortErr) {
|
|
1077
|
-
abortAgentProgressWatchdog(controller, {
|
|
1078
|
-
sessionId,
|
|
1079
|
-
agent,
|
|
1080
|
-
error: abortErr,
|
|
1081
|
-
snapshot,
|
|
1082
|
-
policy: watchdogPolicy,
|
|
1083
|
-
now,
|
|
1084
|
-
anchorTs,
|
|
1085
|
-
iteration,
|
|
1086
|
-
});
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
function ensureProgressWatchdogTimer() {
|
|
1091
|
-
if (progressWatchdogTimer || progressWatchdogs.size === 0) return;
|
|
1092
|
-
progressWatchdogTimer = setInterval(() => {
|
|
1093
|
-
for (const state of [...progressWatchdogs.values()]) {
|
|
1094
|
-
checkProgressIdleWatchdog(state);
|
|
1095
|
-
}
|
|
1096
|
-
}, 1000);
|
|
1097
|
-
progressWatchdogTimer.unref?.();
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
function startProgressIdleWatchdog(sessionId, watchdogPolicy, agent = null) {
|
|
1101
|
-
if (!sessionId || !agentWatchdogPolicyActive(watchdogPolicy)) return null;
|
|
1102
|
-
if (typeof mgr.getSessionProgressSnapshot !== 'function' && typeof mgr.getSessionLastProgressAt !== 'function') return null;
|
|
1103
|
-
if (typeof mgr.linkParentSignalToSession !== 'function') return null;
|
|
1104
|
-
const controller = new AbortController();
|
|
1105
|
-
const anchorTs = Date.now();
|
|
1106
|
-
try { mgr.linkParentSignalToSession(sessionId, controller.signal); } catch { return null; }
|
|
1107
|
-
const state = { sessionId, watchdogPolicy, agent, controller, anchorTs };
|
|
1108
|
-
progressWatchdogs.set(sessionId, state);
|
|
1109
|
-
ensureProgressWatchdogTimer();
|
|
1110
|
-
return {
|
|
1111
|
-
stop: () => {
|
|
1112
|
-
if (progressWatchdogs.get(sessionId) === state) progressWatchdogs.delete(sessionId);
|
|
1113
|
-
stopProgressWatchdogTimerIfIdle();
|
|
1114
|
-
},
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
788
|
+
// Idle-progress watchdogs (shared sweep timer) live in
|
|
789
|
+
// agent-watchdog-registry.mjs.
|
|
790
|
+
const progressWatchdogs = createProgressWatchdogRegistry({ mgr });
|
|
791
|
+
const startProgressIdleWatchdog = (sessionId, watchdogPolicy, agent = null) =>
|
|
792
|
+
progressWatchdogs.start(sessionId, watchdogPolicy, agent);
|
|
1117
793
|
|
|
1118
794
|
async function prepareSpawn(args, callerCwd = null, context = {}, prepState = null) {
|
|
1119
795
|
refreshTagsFromSessions({ context });
|
|
@@ -1824,4 +1500,4 @@ export function createStandaloneAgent({
|
|
|
1824
1500
|
},
|
|
1825
1501
|
closeAll,
|
|
1826
1502
|
};
|
|
1827
|
-
}
|
|
1503
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Idle-progress watchdogs for running agent sessions, extracted from
|
|
2
|
+
// agent-tool.mjs. One shared 1s timer sweeps every watched session, so a burst
|
|
3
|
+
// of agents costs a single interval; the timer stops as soon as the last
|
|
4
|
+
// watchdog is released. Abort policy itself lives in the runtime module.
|
|
5
|
+
import {
|
|
6
|
+
abortAgentProgressWatchdog,
|
|
7
|
+
agentWatchdogPolicyActive,
|
|
8
|
+
evaluateAgentWatchdogAbort,
|
|
9
|
+
AgentStallAbortError,
|
|
10
|
+
} from '../runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs';
|
|
11
|
+
|
|
12
|
+
const WATCHDOG_SWEEP_INTERVAL_MS = 1000;
|
|
13
|
+
|
|
14
|
+
export function createProgressWatchdogRegistry({ mgr }) {
|
|
15
|
+
const watched = new Map();
|
|
16
|
+
let timer = null;
|
|
17
|
+
|
|
18
|
+
function stopTimerIfIdle() {
|
|
19
|
+
if (watched.size > 0 || !timer) return;
|
|
20
|
+
try { clearInterval(timer); } catch {}
|
|
21
|
+
timer = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function check(state) {
|
|
25
|
+
const { sessionId, watchdogPolicy, agent, controller, anchorTs } = state;
|
|
26
|
+
if (controller.signal?.aborted) {
|
|
27
|
+
watched.delete(sessionId);
|
|
28
|
+
stopTimerIfIdle();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
const snapshot = typeof mgr.getSessionProgressSnapshot === 'function'
|
|
33
|
+
? mgr.getSessionProgressSnapshot(sessionId)
|
|
34
|
+
: null;
|
|
35
|
+
const abortErr = snapshot ? evaluateAgentWatchdogAbort(snapshot, now, watchdogPolicy) : null;
|
|
36
|
+
const sess = typeof mgr.getSession === 'function' ? mgr.getSession(sessionId) : null;
|
|
37
|
+
const iteration = typeof sess?.lastIterationIndex === 'number' ? sess.lastIterationIndex : null;
|
|
38
|
+
// No progress snapshot: fall back to the coarse last-progress timestamp so a
|
|
39
|
+
// session that never reports structured progress can still stall out.
|
|
40
|
+
if (!abortErr && !snapshot) {
|
|
41
|
+
const reported = mgr.getSessionLastProgressAt(sessionId);
|
|
42
|
+
const last = reported || anchorTs;
|
|
43
|
+
if (watchdogPolicy.idleStaleMs > 0 && now - last > watchdogPolicy.idleStaleMs) {
|
|
44
|
+
abortAgentProgressWatchdog(controller, {
|
|
45
|
+
sessionId,
|
|
46
|
+
agent,
|
|
47
|
+
error: new AgentStallAbortError(`agent task stale (${watchdogPolicy.idleStaleMs}ms without progress)`),
|
|
48
|
+
policy: watchdogPolicy,
|
|
49
|
+
now,
|
|
50
|
+
anchorTs,
|
|
51
|
+
lastProgressAt: reported,
|
|
52
|
+
iteration,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (abortErr) {
|
|
58
|
+
abortAgentProgressWatchdog(controller, {
|
|
59
|
+
sessionId,
|
|
60
|
+
agent,
|
|
61
|
+
error: abortErr,
|
|
62
|
+
snapshot,
|
|
63
|
+
policy: watchdogPolicy,
|
|
64
|
+
now,
|
|
65
|
+
anchorTs,
|
|
66
|
+
iteration,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ensureTimer() {
|
|
72
|
+
if (timer || watched.size === 0) return;
|
|
73
|
+
timer = setInterval(() => {
|
|
74
|
+
for (const state of [...watched.values()]) check(state);
|
|
75
|
+
}, WATCHDOG_SWEEP_INTERVAL_MS);
|
|
76
|
+
timer.unref?.();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
/** Watch a session until the returned handle is stopped; null when the
|
|
81
|
+
* policy is off or the manager cannot link an abort signal. */
|
|
82
|
+
start(sessionId, watchdogPolicy, agent = null) {
|
|
83
|
+
if (!sessionId || !agentWatchdogPolicyActive(watchdogPolicy)) return null;
|
|
84
|
+
if (typeof mgr.getSessionProgressSnapshot !== 'function'
|
|
85
|
+
&& typeof mgr.getSessionLastProgressAt !== 'function') return null;
|
|
86
|
+
if (typeof mgr.linkParentSignalToSession !== 'function') return null;
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
const anchorTs = Date.now();
|
|
89
|
+
try { mgr.linkParentSignalToSession(sessionId, controller.signal); } catch { return null; }
|
|
90
|
+
const state = { sessionId, watchdogPolicy, agent, controller, anchorTs };
|
|
91
|
+
watched.set(sessionId, state);
|
|
92
|
+
ensureTimer();
|
|
93
|
+
return {
|
|
94
|
+
stop: () => {
|
|
95
|
+
if (watched.get(sessionId) === state) watched.delete(sessionId);
|
|
96
|
+
stopTimerIfIdle();
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -253,21 +253,12 @@ export function createStandaloneChannelWorker({
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
function start() {
|
|
256
|
+
if (stopPromise) return stopPromise.then(() => start());
|
|
256
257
|
if (!useProcessWorker) return startInProcess();
|
|
257
258
|
// Daemon mode (default): spawn-or-attach to the machine-global channels
|
|
258
259
|
// daemon. The fork code below is unreachable while useProcessWorker is true.
|
|
259
|
-
return ensureDaemonAttached().then(() => status());
|
|
260
|
-
if (stopPromise) {
|
|
261
|
-
return stopPromise.then(() => start());
|
|
262
|
-
}
|
|
263
|
-
if (child && child.exitCode == null && !child.killed) return readyPromise || Promise.resolve(status());
|
|
264
260
|
stopRequested = false;
|
|
265
|
-
|
|
266
|
-
readyResolve = resolve;
|
|
267
|
-
readyReject = reject;
|
|
268
|
-
});
|
|
269
|
-
spawnWorkerChild(1);
|
|
270
|
-
return readyPromise;
|
|
261
|
+
return ensureDaemonAttached().then(() => status());
|
|
271
262
|
}
|
|
272
263
|
|
|
273
264
|
function spawnWorkerChild(attempt) {
|
|
@@ -745,10 +736,13 @@ export function createStandaloneChannelWorker({
|
|
|
745
736
|
daemonClient = null;
|
|
746
737
|
attachPromise = null;
|
|
747
738
|
daemonPid = null;
|
|
748
|
-
|
|
739
|
+
stopPromise = Promise.all([
|
|
749
740
|
client ? client.close(reason).then(() => true).catch(() => true) : Promise.resolve(false),
|
|
750
741
|
Promise.resolve(inFlightAttach).catch(() => null),
|
|
751
|
-
]).then(([detached]) => detached)
|
|
742
|
+
]).then(([detached]) => detached).finally(() => {
|
|
743
|
+
stopPromise = null;
|
|
744
|
+
});
|
|
745
|
+
return stopPromise;
|
|
752
746
|
}
|
|
753
747
|
if (!child) {
|
|
754
748
|
return Promise.resolve(false);
|
|
@@ -682,16 +682,17 @@ function StatusLineView({ sessionId, clientHostPid, provider, model, effort, fas
|
|
|
682
682
|
|
|
683
683
|
const lines = line ? line.split('\n').slice(0, 2) : [' ', ' '];
|
|
684
684
|
const workflowLabel = workflowModeLabel(workflow, remoteEnabled);
|
|
685
|
-
// Footer footprint
|
|
686
|
-
// the
|
|
687
|
-
// the
|
|
685
|
+
// Footer footprint is exactly 2 rows (L1 + L2) so the statusline sits tight
|
|
686
|
+
// under the prompt box — the old third "breathing" row left a dead band at
|
|
687
|
+
// the terminal bottom. Left/right insets match the prompt box's text column
|
|
688
|
+
// (1 border + 1 padding) so both edges line up vertically with the input.
|
|
688
689
|
return (
|
|
689
|
-
<Box flexDirection="column" width="100%" height={
|
|
690
|
+
<Box flexDirection="column" width="100%" height={2} overflow="hidden" justifyContent="flex-start" paddingLeft={2} paddingRight={2} backgroundColor={surfaceBackground()}>
|
|
690
691
|
<Box flexDirection="row" width="100%" overflow="hidden">
|
|
691
692
|
<Box flexGrow={1} flexShrink={1} flexBasis={0} overflow="hidden">
|
|
692
693
|
<Text wrap="truncate">{lines[0] || ' '}</Text>
|
|
693
694
|
</Box>
|
|
694
|
-
<Box flexShrink={0} marginLeft={1}
|
|
695
|
+
<Box flexShrink={0} marginLeft={1}>
|
|
695
696
|
<Text color={theme.statusText} wrap="truncate">{workflowLabel}</Text>
|
|
696
697
|
</Box>
|
|
697
698
|
</Box>
|