mixdog 0.9.67 → 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/session-lifecycle.mjs +3 -0
- 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-summary-index.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
- 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/config.mjs +6 -5
- package/src/runtime/channels/lib/owned-runtime.mjs +65 -5
- package/src/runtime/channels/lib/scheduler.mjs +7 -15
- package/src/runtime/channels/lib/tool-dispatch.mjs +6 -1
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +6 -2
- package/src/runtime/channels/lib/webhook.mjs +36 -46
- package/src/runtime/channels/lib/worker-main.mjs +45 -92
- package/src/runtime/shared/automation-attachments.mjs +72 -0
- package/src/runtime/shared/automation-workflow.mjs +41 -0
- package/src/runtime/shared/schedule-session-run.mjs +10 -1
- package/src/runtime/shared/schedules-db.mjs +18 -3
- package/src/runtime/shared/webhook-session-run.mjs +57 -0
- package/src/runtime/shared/webhooks-db.mjs +19 -3
- package/src/session-runtime/channel-config-api.mjs +8 -1
- package/src/session-runtime/lifecycle-api.mjs +7 -0
- 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 +184 -660
- 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-admin.mjs +36 -1
- package/src/standalone/channel-daemon-client.mjs +5 -1
- package/src/standalone/channel-daemon-transport.mjs +112 -20
- package/src/standalone/channel-daemon.mjs +6 -4
- package/src/standalone/channel-worker.mjs +7 -13
- package/src/tui/App.jsx +2 -32
- package/src/tui/app/channel-pickers.mjs +2 -143
- package/src/tui/app/slash-commands.mjs +1 -3
- package/src/tui/app/slash-dispatch.mjs +3 -3
- package/src/tui/components/StatusLine.jsx +6 -5
- package/src/tui/dist/index.mjs +162 -259
- package/src/tui/engine/labels.mjs +0 -8
- package/src/tui/engine/session-api-ext.mjs +45 -10
- package/src/tui/engine/turn-watchdog.mjs +92 -0
- package/src/tui/engine/turn.mjs +18 -70
- package/src/tui/engine.mjs +14 -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
|
+
}
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
deleteEndpoint as dbDeleteEndpoint,
|
|
39
39
|
setEndpointEnabled as dbSetEndpointEnabled,
|
|
40
40
|
} from '../runtime/shared/webhooks-db.mjs';
|
|
41
|
+
import { normalizeAutomationAttachments } from '../runtime/shared/automation-attachments.mjs';
|
|
41
42
|
import { readHookPublicBase } from '../runtime/channels/lib/webhook/relay-tunnel.mjs';
|
|
42
43
|
|
|
43
44
|
const NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
@@ -358,6 +359,11 @@ function scheduleToDisplay(s) {
|
|
|
358
359
|
channel: s.channelId || undefined,
|
|
359
360
|
model: s.model || undefined,
|
|
360
361
|
cwd: s.cwd || undefined,
|
|
362
|
+
workflow: s.workflow || undefined,
|
|
363
|
+
attachments: s.attachments || undefined,
|
|
364
|
+
// Delivery mode: legacy channel-target rows (pre-delivery) behaved as
|
|
365
|
+
// relay+visible, which maps to 'both'.
|
|
366
|
+
delivery: s.delivery || (s.target === 'channel' ? 'both' : 'app'),
|
|
361
367
|
enabled: s.enabled !== false,
|
|
362
368
|
instructions: s.prompt,
|
|
363
369
|
route: s.target === 'channel' ? `channel:${s.channelId}` : 'session',
|
|
@@ -383,6 +389,9 @@ export async function saveSchedule({
|
|
|
383
389
|
channel,
|
|
384
390
|
model,
|
|
385
391
|
cwd,
|
|
392
|
+
workflow,
|
|
393
|
+
attachments,
|
|
394
|
+
delivery,
|
|
386
395
|
enabled,
|
|
387
396
|
instructions,
|
|
388
397
|
overwrite = false,
|
|
@@ -391,6 +400,11 @@ export async function saveSchedule({
|
|
|
391
400
|
const body = String(instructions || '').trim();
|
|
392
401
|
if (!body) throw new Error('schedule instructions are required');
|
|
393
402
|
if (channel && !model) throw new Error('model is required when channel is set');
|
|
403
|
+
// 'app' → session-only; 'channel'/'both' → the run result relays to the
|
|
404
|
+
// main channel (target 'channel', channelId resolved at fire time).
|
|
405
|
+
const mode = ['app', 'channel', 'both'].includes(String(delivery || '').trim())
|
|
406
|
+
? String(delivery).trim()
|
|
407
|
+
: (channel ? 'both' : 'app');
|
|
394
408
|
const hasTime = time != null && String(time).trim() !== '';
|
|
395
409
|
const hasAt = at != null && String(at).trim() !== '';
|
|
396
410
|
if (hasTime && hasAt) throw new Error('provide either `time` (recurring) or `at` (one-shot), not both');
|
|
@@ -406,10 +420,13 @@ export async function saveSchedule({
|
|
|
406
420
|
whenCron,
|
|
407
421
|
whenAt,
|
|
408
422
|
timezone: timezone ? String(timezone).trim() : null,
|
|
409
|
-
target:
|
|
423
|
+
target: mode === 'app' ? 'session' : 'channel',
|
|
410
424
|
channelId: channel ? String(channel).trim() : null,
|
|
411
425
|
model: model ? String(model).trim() : null,
|
|
412
426
|
cwd: cwd ? String(cwd).trim() : null,
|
|
427
|
+
workflow: workflow ? String(workflow).trim() : null,
|
|
428
|
+
attachments: normalizeAutomationAttachments(attachments),
|
|
429
|
+
delivery: mode,
|
|
413
430
|
prompt: body,
|
|
414
431
|
enabled: enabled !== false,
|
|
415
432
|
});
|
|
@@ -441,6 +458,10 @@ async function listWebhooks() {
|
|
|
441
458
|
parser: ep.parser || 'github',
|
|
442
459
|
...(ep.channelId ? { channel: ep.channelId } : {}),
|
|
443
460
|
...(ep.model ? { model: ep.model } : {}),
|
|
461
|
+
...(ep.cwd ? { cwd: ep.cwd } : {}),
|
|
462
|
+
...(ep.workflow ? { workflow: ep.workflow } : {}),
|
|
463
|
+
...(ep.attachments ? { attachments: ep.attachments } : {}),
|
|
464
|
+
delivery: ep.delivery || 'app',
|
|
444
465
|
enabled: ep.enabled,
|
|
445
466
|
// The store never projects the plaintext secret through list paths; it
|
|
446
467
|
// exposes a presence flag (secretSet) instead.
|
|
@@ -458,6 +479,10 @@ export async function saveWebhook({
|
|
|
458
479
|
secret,
|
|
459
480
|
channel,
|
|
460
481
|
model,
|
|
482
|
+
cwd,
|
|
483
|
+
workflow,
|
|
484
|
+
attachments,
|
|
485
|
+
delivery,
|
|
461
486
|
enabled,
|
|
462
487
|
instructions,
|
|
463
488
|
overwrite = false,
|
|
@@ -486,6 +511,12 @@ export async function saveWebhook({
|
|
|
486
511
|
parser: nextParser,
|
|
487
512
|
channelId: channel ? String(channel).trim() : null,
|
|
488
513
|
model: model ? String(model).trim() : null,
|
|
514
|
+
cwd: cwd ? String(cwd).trim() : null,
|
|
515
|
+
workflow: workflow ? String(workflow).trim() : null,
|
|
516
|
+
attachments: normalizeAutomationAttachments(attachments),
|
|
517
|
+
delivery: ['app', 'channel', 'both'].includes(String(delivery || '').trim())
|
|
518
|
+
? String(delivery).trim()
|
|
519
|
+
: 'app',
|
|
489
520
|
secret: secretValue,
|
|
490
521
|
instructions: body,
|
|
491
522
|
enabled: enabled !== false,
|
|
@@ -496,6 +527,10 @@ export async function saveWebhook({
|
|
|
496
527
|
parser: saved.parser,
|
|
497
528
|
...(saved.channelId ? { channel: saved.channelId } : {}),
|
|
498
529
|
...(saved.model ? { model: saved.model } : {}),
|
|
530
|
+
...(saved.cwd ? { cwd: saved.cwd } : {}),
|
|
531
|
+
...(saved.workflow ? { workflow: saved.workflow } : {}),
|
|
532
|
+
...(saved.attachments ? { attachments: saved.attachments } : {}),
|
|
533
|
+
delivery: saved.delivery || 'app',
|
|
499
534
|
...(enabled === false ? { enabled: false } : {}),
|
|
500
535
|
secret: secretValue,
|
|
501
536
|
instructions: body,
|
|
@@ -147,7 +147,11 @@ export async function attachToDaemon({
|
|
|
147
147
|
token: serverToken,
|
|
148
148
|
method: 'POST',
|
|
149
149
|
path: '/client/register',
|
|
150
|
-
|
|
150
|
+
// Initial attachment is transport-only. Mark it with the legacy
|
|
151
|
+
// non-stealing registration intent as well as relying on the current
|
|
152
|
+
// daemon contract, so a newly updated desktop cannot supersede a
|
|
153
|
+
// terminal still served by an older machine-global daemon.
|
|
154
|
+
body: { leadPid, cwd, reattach: true },
|
|
151
155
|
timeoutMs: 3000,
|
|
152
156
|
});
|
|
153
157
|
} catch (err) {
|