@dotdrelle/wiki-manager 0.11.9 → 0.12.0
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/README.md +32 -7
- package/agents.docker-compose.yml +3 -0
- package/docker-compose.yml +1 -0
- package/package.json +3 -2
- package/src/activity/activityAggregator.js +109 -0
- package/src/activity/activityAggregator.test.js +60 -0
- package/src/activity/activityDeduplicator.js +50 -0
- package/src/activity/progressCalculator.js +61 -0
- package/src/activity/runSynthesis.js +15 -0
- package/src/agent/graph.js +65 -30
- package/src/agent/graph.test.js +28 -5
- package/src/cli/wiki-manager.js +22 -0
- package/src/contracts/schemas.js +249 -13
- package/src/contracts/schemas.test.js +145 -0
- package/src/core/activity.js +8 -5
- package/src/core/agentEvents.js +260 -23
- package/src/core/agentEvents.test.js +53 -0
- package/src/core/compose.js +6 -1
- package/src/core/dockerCompose.test.js +12 -0
- package/src/core/documentIntake.js +33 -38
- package/src/core/documentIntake.test.js +25 -0
- package/src/core/env.js +11 -1
- package/src/core/jobQueue.js +26 -2
- package/src/core/mcp.js +1 -1
- package/src/core/plan.js +46 -4
- package/src/core/plan.test.js +16 -1
- package/src/core/planPatch.js +64 -3
- package/src/core/planPatch.test.js +110 -1
- package/src/core/queueStore.test.js +21 -0
- package/src/core/runtimeLog.js +119 -0
- package/src/core/runtimeLog.test.js +84 -0
- package/src/core/wikirc.js +22 -0
- package/src/core/wikirc.test.js +49 -1
- package/src/core/workflow.js +14 -3
- package/src/graph/graphAggregator.js +5 -0
- package/src/graph/graphPatch.js +8 -0
- package/src/graph/graphSnapshot.js +14 -0
- package/src/graph/graphVisibilityPolicy.js +40 -0
- package/src/graph/runGraphProjector.js +87 -0
- package/src/graph/runGraphProjector.test.js +111 -0
- package/src/orchestrator/agentRegistry.js +172 -0
- package/src/orchestrator/agentRegistry.test.js +114 -0
- package/src/orchestrator/approvalPolicy.js +126 -0
- package/src/orchestrator/approvalPolicy.test.js +78 -0
- package/src/orchestrator/assignmentManager.js +80 -0
- package/src/orchestrator/attemptManager.js +168 -0
- package/src/orchestrator/attemptManager.test.js +160 -0
- package/src/orchestrator/budgetManager.js +127 -0
- package/src/orchestrator/capabilityRegistry.js +64 -0
- package/src/orchestrator/capabilityRegistry.test.js +59 -0
- package/src/orchestrator/capabilityResolver.js +151 -0
- package/src/orchestrator/capabilityResolver.test.js +127 -0
- package/src/orchestrator/dependencyResolver.js +112 -0
- package/src/orchestrator/dispatcher.js +302 -0
- package/src/orchestrator/lockManager.js +51 -0
- package/src/orchestrator/planIntegrator.js +268 -0
- package/src/orchestrator/planIntegrator.test.js +213 -0
- package/src/orchestrator/planValidator.js +534 -0
- package/src/orchestrator/planValidator.test.js +262 -0
- package/src/orchestrator/resultAggregator.js +248 -0
- package/src/orchestrator/resultAggregator.test.js +211 -0
- package/src/orchestrator/scheduler.js +103 -0
- package/src/orchestrator/scheduler.test.js +138 -0
- package/src/runtime/approvals.js +130 -1
- package/src/runtime/donna-contract.test.js +293 -30
- package/src/runtime/recoveryManager.js +176 -0
- package/src/runtime/recoveryManager.test.js +162 -0
- package/src/runtime/runner.e2e.test.js +98 -12
- package/src/runtime/runner.js +261 -227
- package/src/runtime/runner.test.js +249 -452
- package/src/runtime/server.js +137 -20
- package/src/runtime/server.test.js +137 -7
- package/src/runtime/store.js +837 -2
- package/src/runtime/store.test.js +245 -4
- package/src/runtime/supervisor.js +33 -1
- package/src/runtime/supervisor.test.js +47 -1
- package/src/shell/RightPane.tsx +8 -5
- package/src/shell/repl.js +4 -9
- package/src/shell/repl.test.js +25 -4
- package/src/shell/tui.tsx +1 -0
- package/src/shell/useSession.ts +38 -10
package/src/runtime/runner.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { createAgentEvent, dispatchAgentEvent } from '../core/agentEvents.js';
|
|
2
|
-
import {
|
|
2
|
+
import { sessionActivities, terminalFailures } from '../core/activity.js';
|
|
3
3
|
import { runAgenticLoop, throwIfAborted } from '../core/agentLoop.js';
|
|
4
4
|
import { formatPlanStatus } from '../core/plan.js';
|
|
5
|
-
import {
|
|
5
|
+
import { readyPlanTasks, sanitizePlanForExecution } from '../core/planPatch.js';
|
|
6
|
+
import { createAssignmentManager } from '../orchestrator/assignmentManager.js';
|
|
7
|
+
import { createAttemptManager } from '../orchestrator/attemptManager.js';
|
|
8
|
+
import { createBudgetManager, BudgetExceededError } from '../orchestrator/budgetManager.js';
|
|
9
|
+
import { createDispatcher } from '../orchestrator/dispatcher.js';
|
|
10
|
+
import { assertValidatedFragment } from '../orchestrator/planValidator.js';
|
|
11
|
+
import { createResultAggregator } from '../orchestrator/resultAggregator.js';
|
|
12
|
+
import { drainActive, resolveSchedulerConcurrency, startReadyTasks } from '../orchestrator/scheduler.js';
|
|
6
13
|
import { emitRuntimeLog, pollActivitiesOnce } from './supervisor.js';
|
|
7
14
|
|
|
8
15
|
const DEFAULT_MAX_REPLANS = 2;
|
|
9
|
-
const DEFAULT_SCHEDULER_CONCURRENCY = 3;
|
|
10
16
|
|
|
11
17
|
async function waitForRuntimeActivities(session, startedActivities, { timeoutMs, signal, pollBusy }) {
|
|
12
18
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -87,6 +93,8 @@ export async function runRuntimeAgenticWorkflow(agent, session, input, {
|
|
|
87
93
|
pollBusy,
|
|
88
94
|
evaluate = true,
|
|
89
95
|
maxReplans = resolveMaxReplans(),
|
|
96
|
+
callTool = null,
|
|
97
|
+
dispatcherPollIntervalMs = 250,
|
|
90
98
|
} = {}) {
|
|
91
99
|
let currentInput = initialInput ?? input;
|
|
92
100
|
let replansLeft = Math.max(0, Math.floor(Number(maxReplans) || 0));
|
|
@@ -100,6 +108,8 @@ export async function runRuntimeAgenticWorkflow(agent, session, input, {
|
|
|
100
108
|
maxTurns,
|
|
101
109
|
runId,
|
|
102
110
|
pollBusy,
|
|
111
|
+
callTool,
|
|
112
|
+
dispatcherPollIntervalMs,
|
|
103
113
|
})
|
|
104
114
|
: await runRuntimeAgenticLoop(agent, session, currentInput, {
|
|
105
115
|
signal,
|
|
@@ -209,10 +219,28 @@ export async function runRuntimeParallelPlan(agent, session, input, {
|
|
|
209
219
|
runId = null,
|
|
210
220
|
pollBusy,
|
|
211
221
|
concurrency = resolveSchedulerConcurrency(),
|
|
222
|
+
fragment = null,
|
|
223
|
+
assignmentManager = null,
|
|
224
|
+
attemptManager = null,
|
|
225
|
+
dispatcher = null,
|
|
226
|
+
resultAggregator = null,
|
|
227
|
+
budgetManager = null,
|
|
228
|
+
budgets = {},
|
|
229
|
+
callTool = null,
|
|
230
|
+
dispatcherPollIntervalMs = 250,
|
|
212
231
|
} = {}) {
|
|
232
|
+
if (fragment != null) assertValidatedFragment(fragment);
|
|
213
233
|
const limit = Math.max(1, Math.floor(Number(concurrency) || 1));
|
|
214
234
|
const active = new Map();
|
|
215
|
-
const
|
|
235
|
+
const attempts = attemptManager ?? createAttemptManager();
|
|
236
|
+
const assigner = assignmentManager ?? createAssignmentManager({ session });
|
|
237
|
+
const executor = dispatcher ?? createDispatcher({
|
|
238
|
+
session,
|
|
239
|
+
...(callTool ? { callTool } : {}),
|
|
240
|
+
pollIntervalMs: dispatcherPollIntervalMs,
|
|
241
|
+
});
|
|
242
|
+
const aggregator = resultAggregator ?? createResultAggregator({ session, runId });
|
|
243
|
+
const budget = budgetManager ?? createBudgetManager({ budgets, runId });
|
|
216
244
|
const failures = [];
|
|
217
245
|
const previousIdentity = session._currentRunIdentity;
|
|
218
246
|
const previousPlanUpdate = session._onPlanUpdate;
|
|
@@ -232,10 +260,10 @@ export async function runRuntimeParallelPlan(agent, session, input, {
|
|
|
232
260
|
try {
|
|
233
261
|
while (true) {
|
|
234
262
|
if (signal?.aborted) {
|
|
235
|
-
await drainActive(active,
|
|
263
|
+
await drainActive(active, attempts);
|
|
236
264
|
throwIfAborted(signal, 'Runtime run cancelled.');
|
|
237
265
|
}
|
|
238
|
-
const pending = (session.headlessPlan ?? []).filter((step) => step.status
|
|
266
|
+
const pending = (session.headlessPlan ?? []).filter((step) => pendingSchedulerStatus(step.status));
|
|
239
267
|
if (pending.length === 0 && active.size === 0) {
|
|
240
268
|
emitRuntimeLog(session, 'scheduler: all plan tasks terminal');
|
|
241
269
|
return failures.length > 0
|
|
@@ -243,18 +271,85 @@ export async function runRuntimeParallelPlan(agent, session, input, {
|
|
|
243
271
|
: { ok: true };
|
|
244
272
|
}
|
|
245
273
|
|
|
246
|
-
const started = startReadyTasks(
|
|
274
|
+
const started = startReadyTasks({
|
|
275
|
+
plan: session.headlessPlan,
|
|
247
276
|
active,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
pollBusy,
|
|
277
|
+
attemptManager: attempts,
|
|
278
|
+
lockManager: attempts,
|
|
279
|
+
budgetManager: budget,
|
|
280
|
+
registry: session.capabilityRegistry ?? null,
|
|
281
|
+
approvals: session.agentProjection?.approvals ?? session.approvals ?? [],
|
|
254
282
|
limit,
|
|
283
|
+
onDuplicateTask: (taskId) => {
|
|
284
|
+
// Two distinct ready tasks resolved to the same id/step — starting
|
|
285
|
+
// both would let plan_step_updated resolve to whichever one the
|
|
286
|
+
// reducer finds first, silently marking the wrong task done. Skip the
|
|
287
|
+
// duplicate rather than risk misattribution; it stays pending and
|
|
288
|
+
// will surface as a stalled plan instead of corrupting sibling state.
|
|
289
|
+
emitRuntimeLog(session, `scheduler: skipping task with duplicate id/step "${taskId}"`);
|
|
290
|
+
},
|
|
291
|
+
onTaskReady: (task) => {
|
|
292
|
+
emitRuntimeLog(session, taskLogPayload('task.ready', task, { runId, detail: 'ready for assignment' }));
|
|
293
|
+
},
|
|
294
|
+
onAttemptCreated: (task, attempt) => {
|
|
295
|
+
emitRuntimeLog(session, taskLogPayload('attempt.created', task, {
|
|
296
|
+
runId,
|
|
297
|
+
attempt,
|
|
298
|
+
detail: 'attempt created',
|
|
299
|
+
}));
|
|
300
|
+
emitRuntimeLog(session, taskLogPayload('lock.acquired', task, {
|
|
301
|
+
runId,
|
|
302
|
+
attempt,
|
|
303
|
+
detail: attempt?.locks?.join(',') || 'no locks',
|
|
304
|
+
}));
|
|
305
|
+
},
|
|
306
|
+
onTaskStarting: (task) => {
|
|
307
|
+
const taskId = planTaskId(task);
|
|
308
|
+
dispatchAgentEvent(session, createAgentEvent('plan_step_updated', {
|
|
309
|
+
origin: 'runtime',
|
|
310
|
+
runId,
|
|
311
|
+
taskId,
|
|
312
|
+
payload: { taskId, status: 'running' },
|
|
313
|
+
}));
|
|
314
|
+
emitRuntimeLog(session, taskLogPayload('task.starting', task, { runId, detail: `starting task ${taskId}` }));
|
|
315
|
+
},
|
|
316
|
+
startTask: (task, attempt) => {
|
|
317
|
+
const taskAbort = createTaskAbortSignal(signal);
|
|
318
|
+
const promise = runDispatchedTask(task, {
|
|
319
|
+
session,
|
|
320
|
+
assignmentManager: assigner,
|
|
321
|
+
dispatcher: executor,
|
|
322
|
+
resultAggregator: aggregator,
|
|
323
|
+
signal: taskAbort.signal,
|
|
324
|
+
timeoutMs,
|
|
325
|
+
runId,
|
|
326
|
+
pollBusy,
|
|
327
|
+
attempt,
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
330
|
+
promise,
|
|
331
|
+
controller: taskAbort.controller,
|
|
332
|
+
signal: taskAbort.signal,
|
|
333
|
+
cleanup: taskAbort.cleanup,
|
|
334
|
+
};
|
|
335
|
+
},
|
|
255
336
|
});
|
|
256
337
|
if (started === 0 && active.size === 0) {
|
|
257
|
-
const
|
|
338
|
+
const exceeded = budget.exceeded?.();
|
|
339
|
+
if (exceeded) {
|
|
340
|
+
dispatchAgentEvent(session, createAgentEvent('run_error', {
|
|
341
|
+
origin: 'runtime',
|
|
342
|
+
runId,
|
|
343
|
+
payload: {
|
|
344
|
+
runId,
|
|
345
|
+
message: `Run budget exceeded: ${exceeded.reason}`,
|
|
346
|
+
budget: exceeded,
|
|
347
|
+
},
|
|
348
|
+
}));
|
|
349
|
+
emitRuntimeLog(session, `scheduler: budget exceeded (${exceeded.reason})`);
|
|
350
|
+
return { ok: false, budgetExceeded: true, reason: exceeded.reason, budget: exceeded, completed: sessionActivities(session), failures };
|
|
351
|
+
}
|
|
352
|
+
const reason = pending.every((step) => approvalWaitingStatus(step.status)) ? 'awaiting_approval' : 'no_ready_plan_task';
|
|
258
353
|
emitRuntimeLog(session, `scheduler: stalled (${reason})`);
|
|
259
354
|
return { ok: false, stalled: true, reason, completed: sessionActivities(session), failures };
|
|
260
355
|
}
|
|
@@ -263,15 +358,45 @@ export async function runRuntimeParallelPlan(agent, session, input, {
|
|
|
263
358
|
const activeEntry = active.get(settled.taskId);
|
|
264
359
|
active.delete(settled.taskId);
|
|
265
360
|
activeEntry?.cleanup?.();
|
|
266
|
-
for (const lock of settled.locks) locks.delete(lock);
|
|
267
361
|
if (settled.cancelled) {
|
|
268
362
|
if (signal?.aborted) {
|
|
269
|
-
await drainActive(active,
|
|
363
|
+
await drainActive(active, attempts);
|
|
270
364
|
throwIfAborted(signal, 'Runtime run cancelled.');
|
|
271
365
|
}
|
|
272
366
|
continue;
|
|
273
367
|
}
|
|
274
|
-
|
|
368
|
+
try {
|
|
369
|
+
budget.recordTaskResult?.(settled.result ?? {});
|
|
370
|
+
} catch (err) {
|
|
371
|
+
if (err instanceof BudgetExceededError) {
|
|
372
|
+
dispatchAgentEvent(session, createAgentEvent('run_error', {
|
|
373
|
+
origin: 'runtime',
|
|
374
|
+
runId,
|
|
375
|
+
payload: {
|
|
376
|
+
runId,
|
|
377
|
+
message: err.message,
|
|
378
|
+
budget: { reason: err.reason, details: err.details },
|
|
379
|
+
},
|
|
380
|
+
}));
|
|
381
|
+
emitRuntimeLog(session, `scheduler: budget exceeded (${err.reason})`);
|
|
382
|
+
return { ok: false, budgetExceeded: true, reason: err.reason, budget: err.details, completed: sessionActivities(session), failures };
|
|
383
|
+
}
|
|
384
|
+
throw err;
|
|
385
|
+
}
|
|
386
|
+
if (!settled.ok) {
|
|
387
|
+
const task = (session.headlessPlan ?? []).find((item) => planTaskId(item) === settled.taskId);
|
|
388
|
+
const retry = attempts.scheduleRetry?.(task, settled, {
|
|
389
|
+
assignment: settled.assignment,
|
|
390
|
+
registry: session.capabilityRegistry ?? null,
|
|
391
|
+
session,
|
|
392
|
+
runId,
|
|
393
|
+
});
|
|
394
|
+
if (retry?.scheduled) {
|
|
395
|
+
emitRuntimeLog(session, `scheduler: retry scheduled for task ${settled.taskId}${retry.newAgentInstanceId ? ` on ${retry.newAgentInstanceId}` : ''}`);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
failures.push(settled);
|
|
399
|
+
}
|
|
275
400
|
}
|
|
276
401
|
} finally {
|
|
277
402
|
if (previousIdentity) session._currentRunIdentity = previousIdentity;
|
|
@@ -295,15 +420,6 @@ function sanitizeSessionPlanForExecution(session, runId = null) {
|
|
|
295
420
|
}));
|
|
296
421
|
}
|
|
297
422
|
|
|
298
|
-
async function drainActive(active, locks) {
|
|
299
|
-
if (active.size === 0) return;
|
|
300
|
-
const entries = [...active.values()];
|
|
301
|
-
await Promise.all(entries.map((entry) => entry.promise));
|
|
302
|
-
for (const entry of entries) entry.cleanup?.();
|
|
303
|
-
active.clear();
|
|
304
|
-
locks.clear();
|
|
305
|
-
}
|
|
306
|
-
|
|
307
423
|
function abortCancelledActiveTasks(session, active) {
|
|
308
424
|
for (const [taskId, entry] of active.entries()) {
|
|
309
425
|
const current = (session.headlessPlan ?? []).find((step) => planTaskId(step) === taskId);
|
|
@@ -313,6 +429,14 @@ function abortCancelledActiveTasks(session, active) {
|
|
|
313
429
|
}
|
|
314
430
|
}
|
|
315
431
|
|
|
432
|
+
function pendingSchedulerStatus(status) {
|
|
433
|
+
return ['pending', 'pending_approval', 'waiting_approval'].includes(String(status ?? ''));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function approvalWaitingStatus(status) {
|
|
437
|
+
return ['pending_approval', 'waiting_approval'].includes(String(status ?? ''));
|
|
438
|
+
}
|
|
439
|
+
|
|
316
440
|
// Scope the evaluator/replanner's view of "completed" activities to the
|
|
317
441
|
// tasks that actually failed, instead of every activity the whole run has
|
|
318
442
|
// ever recorded — a long-running sibling's stale (or unrelated, already
|
|
@@ -340,64 +464,6 @@ function ensurePlanProjection(session, runId) {
|
|
|
340
464
|
}));
|
|
341
465
|
}
|
|
342
466
|
|
|
343
|
-
function startReadyTasks(agent, session, input, {
|
|
344
|
-
active,
|
|
345
|
-
locks,
|
|
346
|
-
signal,
|
|
347
|
-
timeoutMs,
|
|
348
|
-
maxTurns,
|
|
349
|
-
runId,
|
|
350
|
-
pollBusy,
|
|
351
|
-
limit,
|
|
352
|
-
}) {
|
|
353
|
-
let started = 0;
|
|
354
|
-
const seenTaskIds = new Set(active.keys());
|
|
355
|
-
for (const task of readyPlanTasks(session.headlessPlan)) {
|
|
356
|
-
if (active.size >= limit) break;
|
|
357
|
-
const taskId = planTaskId(task);
|
|
358
|
-
if (active.has(taskId)) continue;
|
|
359
|
-
if (seenTaskIds.has(taskId)) {
|
|
360
|
-
// Two distinct ready tasks resolved to the same id/step — starting
|
|
361
|
-
// both would let plan_step_updated resolve to whichever one the
|
|
362
|
-
// reducer finds first, silently marking the wrong task done. Skip the
|
|
363
|
-
// duplicate rather than risk misattribution; it stays pending and
|
|
364
|
-
// will surface as a stalled plan instead of corrupting sibling state.
|
|
365
|
-
emitRuntimeLog(session, `scheduler: skipping task with duplicate id/step "${taskId}"`);
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
seenTaskIds.add(taskId);
|
|
369
|
-
const taskLocks = locksForTask(task);
|
|
370
|
-
if (taskLocks.some((lock) => locks.has(lock))) continue;
|
|
371
|
-
for (const lock of taskLocks) locks.add(lock);
|
|
372
|
-
dispatchAgentEvent(session, createAgentEvent('plan_step_updated', {
|
|
373
|
-
origin: 'runtime',
|
|
374
|
-
runId,
|
|
375
|
-
taskId,
|
|
376
|
-
payload: { taskId, status: 'running' },
|
|
377
|
-
}));
|
|
378
|
-
emitRuntimeLog(session, `scheduler: starting task ${taskId}`);
|
|
379
|
-
const taskAbort = createTaskAbortSignal(signal);
|
|
380
|
-
const promise = runParallelTask(agent, session, input, task, {
|
|
381
|
-
signal: taskAbort.signal,
|
|
382
|
-
timeoutMs,
|
|
383
|
-
maxTurns,
|
|
384
|
-
runId,
|
|
385
|
-
pollBusy,
|
|
386
|
-
locks: taskLocks,
|
|
387
|
-
});
|
|
388
|
-
active.set(taskId, {
|
|
389
|
-
taskId,
|
|
390
|
-
locks: taskLocks,
|
|
391
|
-
promise,
|
|
392
|
-
controller: taskAbort.controller,
|
|
393
|
-
signal: taskAbort.signal,
|
|
394
|
-
cleanup: taskAbort.cleanup,
|
|
395
|
-
});
|
|
396
|
-
started += 1;
|
|
397
|
-
}
|
|
398
|
-
return started;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
467
|
function createTaskAbortSignal(parentSignal) {
|
|
402
468
|
const controller = new AbortController();
|
|
403
469
|
if (!parentSignal) return { controller, signal: controller.signal, cleanup: null };
|
|
@@ -414,55 +480,112 @@ function createTaskAbortSignal(parentSignal) {
|
|
|
414
480
|
};
|
|
415
481
|
}
|
|
416
482
|
|
|
417
|
-
async function
|
|
483
|
+
async function runDispatchedTask(task, {
|
|
484
|
+
session,
|
|
485
|
+
assignmentManager,
|
|
486
|
+
dispatcher,
|
|
487
|
+
resultAggregator,
|
|
418
488
|
signal,
|
|
419
489
|
timeoutMs,
|
|
420
|
-
maxTurns,
|
|
421
490
|
runId,
|
|
422
491
|
pollBusy,
|
|
423
|
-
|
|
492
|
+
attempt,
|
|
424
493
|
}) {
|
|
425
494
|
const taskId = planTaskId(task);
|
|
426
|
-
|
|
495
|
+
let assignment = null;
|
|
427
496
|
try {
|
|
428
|
-
|
|
497
|
+
emitRuntimeLog(session, taskLogPayload('capability.resolving', task, {
|
|
498
|
+
runId,
|
|
499
|
+
attempt,
|
|
500
|
+
detail: `resolving ${task.requiredCapability}`,
|
|
501
|
+
}));
|
|
502
|
+
assignment = await assignmentManager.assign(task, {
|
|
503
|
+
session,
|
|
504
|
+
signal,
|
|
505
|
+
});
|
|
506
|
+
emitRuntimeLog(session, taskLogPayload('agent.selected', task, {
|
|
507
|
+
runId,
|
|
508
|
+
attempt,
|
|
509
|
+
assignment,
|
|
510
|
+
detail: assignment.agentInstanceId,
|
|
511
|
+
}));
|
|
512
|
+
dispatchAgentEvent(session, createAgentEvent('task.assigned', {
|
|
513
|
+
origin: 'assignment_manager',
|
|
514
|
+
runId,
|
|
515
|
+
taskId,
|
|
516
|
+
payload: {
|
|
517
|
+
runId,
|
|
518
|
+
taskId,
|
|
519
|
+
attemptId: attempt?.attemptId ?? null,
|
|
520
|
+
assignment: {
|
|
521
|
+
...assignment,
|
|
522
|
+
attemptId: attempt?.attemptId ?? null,
|
|
523
|
+
agent: undefined,
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
}));
|
|
527
|
+
emitRuntimeLog(session, taskLogPayload('task.assigned', task, {
|
|
528
|
+
runId,
|
|
529
|
+
attempt,
|
|
530
|
+
assignment,
|
|
531
|
+
detail: 'assignment created',
|
|
532
|
+
}));
|
|
533
|
+
const result = await dispatcher.execute(task, assignment, {
|
|
534
|
+
session,
|
|
429
535
|
signal,
|
|
430
536
|
timeoutMs,
|
|
431
|
-
maxTurns,
|
|
432
537
|
runId,
|
|
433
538
|
pollBusy,
|
|
539
|
+
attempt,
|
|
434
540
|
});
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
541
|
+
const aggregate = await resultAggregator.accept(result, { task, assignment });
|
|
542
|
+
if (!aggregate.ok) {
|
|
543
|
+
emitRuntimeLog(session, taskLogPayload('task.failed', task, {
|
|
438
544
|
runId,
|
|
439
|
-
|
|
440
|
-
|
|
545
|
+
attempt,
|
|
546
|
+
assignment,
|
|
547
|
+
jobId: result?.jobId,
|
|
548
|
+
error: result?.error?.code ?? result?.error?.message ?? result?.status ?? 'failed',
|
|
549
|
+
detail: 'task terminal',
|
|
441
550
|
}));
|
|
442
|
-
return { ok: false, taskId,
|
|
443
|
-
}
|
|
444
|
-
if ((parentSession.headlessPlan ?? []).find((step) => planTaskId(step) === taskId)?.status === 'cancelled') {
|
|
445
|
-
return { ok: false, taskId, locks, cancelled: true };
|
|
551
|
+
return { ok: false, taskId, result, assignment };
|
|
446
552
|
}
|
|
447
|
-
|
|
448
|
-
origin: 'runtime',
|
|
553
|
+
emitRuntimeLog(session, taskLogPayload('task.completed', task, {
|
|
449
554
|
runId,
|
|
450
|
-
|
|
451
|
-
|
|
555
|
+
attempt,
|
|
556
|
+
assignment,
|
|
557
|
+
jobId: result?.jobId,
|
|
558
|
+
outputs: result?.outputRefs ?? [],
|
|
559
|
+
detail: 'returned to donna',
|
|
452
560
|
}));
|
|
453
|
-
|
|
454
|
-
return { ok: true, taskId, locks, result };
|
|
561
|
+
return { ok: true, taskId, result, assignment };
|
|
455
562
|
} catch (err) {
|
|
456
563
|
if (err?.name === 'AbortError') {
|
|
457
|
-
|
|
564
|
+
attempt?.release?.();
|
|
565
|
+
dispatchAgentEvent(session, createAgentEvent('plan_step_updated', {
|
|
458
566
|
origin: 'runtime',
|
|
459
567
|
runId,
|
|
460
568
|
taskId,
|
|
461
569
|
payload: { taskId, status: 'cancelled' },
|
|
462
570
|
}));
|
|
463
|
-
return { ok: false, taskId,
|
|
571
|
+
return { ok: false, taskId, cancelled: true, assignment };
|
|
464
572
|
}
|
|
465
|
-
|
|
573
|
+
attempt?.release?.();
|
|
574
|
+
const result = {
|
|
575
|
+
ok: false,
|
|
576
|
+
taskId,
|
|
577
|
+
attemptId: attempt?.attemptId ?? null,
|
|
578
|
+
status: 'failed',
|
|
579
|
+
outputRefs: [],
|
|
580
|
+
metrics: {},
|
|
581
|
+
error: {
|
|
582
|
+
code: 'dispatcher_error',
|
|
583
|
+
message: err instanceof Error ? err.message : String(err),
|
|
584
|
+
retryable: false,
|
|
585
|
+
},
|
|
586
|
+
};
|
|
587
|
+
await resultAggregator.accept(result, { task, assignment });
|
|
588
|
+
dispatchAgentEvent(session, createAgentEvent('plan_step_updated', {
|
|
466
589
|
origin: 'runtime',
|
|
467
590
|
runId,
|
|
468
591
|
taskId,
|
|
@@ -471,131 +594,49 @@ async function runParallelTask(agent, parentSession, input, task, {
|
|
|
471
594
|
return {
|
|
472
595
|
ok: false,
|
|
473
596
|
taskId,
|
|
474
|
-
|
|
597
|
+
result,
|
|
598
|
+
assignment,
|
|
475
599
|
error: err instanceof Error ? err.message : String(err),
|
|
476
600
|
};
|
|
477
601
|
}
|
|
478
602
|
}
|
|
479
603
|
|
|
480
|
-
function
|
|
481
|
-
|
|
482
|
-
...parent,
|
|
483
|
-
// Copy (not share) the parent's activities: a child's own diffing needs
|
|
484
|
-
// isolation from mutation, but local guards like productionLockBusy must
|
|
485
|
-
// still see activities already running when this task started.
|
|
486
|
-
activities: { ...parent.activities },
|
|
487
|
-
agentEvents: [],
|
|
488
|
-
agentProjection: null,
|
|
489
|
-
_agentProjectionState: null,
|
|
490
|
-
headlessPlan: null,
|
|
491
|
-
_currentRunIdentity: {
|
|
492
|
-
...(parent._currentRunIdentity ?? {}),
|
|
493
|
-
runId,
|
|
494
|
-
taskId,
|
|
495
|
-
workspace: parent.workspace ?? parent._currentRunIdentity?.workspace ?? null,
|
|
496
|
-
},
|
|
497
|
-
_onAgentEvent: (event) => {
|
|
498
|
-
if (event.type === 'plan_set') return;
|
|
499
|
-
if (event.type === 'plan_step_updated') {
|
|
500
|
-
// The child always targets its own single task, regardless of what
|
|
501
|
-
// step/id the child's LLM passed to wiki__plan_done — a child has no
|
|
502
|
-
// visibility into the parent's real multi-task numbering.
|
|
503
|
-
dispatchAgentEvent(parent, {
|
|
504
|
-
...event,
|
|
505
|
-
taskId: taskId,
|
|
506
|
-
runId: event.runId ?? runId,
|
|
507
|
-
payload: { ...event.payload, taskId },
|
|
508
|
-
});
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
if (event.type === 'activity_upserted') {
|
|
512
|
-
stampOwnerActivityKey(parent, taskId, event.payload?.activity);
|
|
513
|
-
}
|
|
514
|
-
dispatchAgentEvent(parent, {
|
|
515
|
-
...event,
|
|
516
|
-
taskId: event.taskId ?? taskId,
|
|
517
|
-
runId: event.runId ?? runId,
|
|
518
|
-
});
|
|
519
|
-
},
|
|
520
|
-
};
|
|
521
|
-
Object.defineProperty(child, '_runApprovalResolved', {
|
|
522
|
-
get: () => parent._runApprovalResolved,
|
|
523
|
-
set: (value) => { parent._runApprovalResolved = value; },
|
|
524
|
-
enumerable: true,
|
|
525
|
-
configurable: true,
|
|
526
|
-
});
|
|
527
|
-
if (parent._requestApproval) {
|
|
528
|
-
child._requestApproval = (request) => {
|
|
529
|
-
if (request?.scope === 'tool') return parent._requestApproval(request);
|
|
530
|
-
// Run-level approval is one decision for the whole run: concurrent
|
|
531
|
-
// children must join the same in-flight request instead of each
|
|
532
|
-
// minting their own (only one of which /approve could ever resolve).
|
|
533
|
-
parent._pendingRunApproval ??= parent._requestApproval(request)
|
|
534
|
-
.finally(() => { delete parent._pendingRunApproval; });
|
|
535
|
-
return parent._pendingRunApproval;
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
// Seed the plan through the real reducer (not a raw property write): the
|
|
539
|
-
// child's own _agentProjectionState knows nothing about this task
|
|
540
|
-
// otherwise, and the very next session-projection event (even a plain
|
|
541
|
-
// runtime_log from onTurnStart) would overwrite headlessPlan back to null
|
|
542
|
-
// via applyAgentProjectionToSession. The _onAgentEvent filter above already
|
|
543
|
-
// keeps this plan_set from being forwarded to the parent.
|
|
544
|
-
dispatchAgentEvent(child, createAgentEvent('plan_set', {
|
|
545
|
-
origin: 'runtime',
|
|
546
|
-
runId,
|
|
547
|
-
taskId,
|
|
548
|
-
payload: { steps: [{ ...task, status: 'running' }] },
|
|
549
|
-
}));
|
|
550
|
-
return child;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function stampOwnerActivityKey(parent, taskId, rawActivity) {
|
|
554
|
-
if (!rawActivity) return;
|
|
555
|
-
const entry = (parent.headlessPlan ?? []).find((item) => String(item.id ?? item.step) === taskId);
|
|
556
|
-
if (!entry || entry.ownerActivityKey) return;
|
|
557
|
-
entry.ownerActivityKey = activityKey(rawActivity);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
function buildTaskPrompt(input, plan, task) {
|
|
561
|
-
return [
|
|
562
|
-
'Original task:',
|
|
563
|
-
input || '(unknown)',
|
|
564
|
-
'',
|
|
565
|
-
`Plan status:\n${formatPlanStatus(plan)}`,
|
|
566
|
-
'',
|
|
567
|
-
formatReadyTaskPrompt(task),
|
|
568
|
-
'',
|
|
569
|
-
'Execute exactly this task as an autonomous child task.',
|
|
570
|
-
'Do not start sibling tasks. Do not modify unrelated plan steps.',
|
|
571
|
-
'If this task cannot complete, report the blocker clearly.',
|
|
572
|
-
].join('\n');
|
|
604
|
+
function shouldUseParallelScheduler(plan) {
|
|
605
|
+
return readyPlanTasks(plan).some((task) => task.requiredCapability && task.operation);
|
|
573
606
|
}
|
|
574
607
|
|
|
575
|
-
function
|
|
576
|
-
|
|
608
|
+
function taskLogPayload(event, task, {
|
|
609
|
+
runId = null,
|
|
610
|
+
attempt = null,
|
|
611
|
+
assignment = null,
|
|
612
|
+
jobId = null,
|
|
613
|
+
outputs = null,
|
|
614
|
+
error = null,
|
|
615
|
+
detail = null,
|
|
616
|
+
} = {}) {
|
|
617
|
+
return {
|
|
618
|
+
event,
|
|
619
|
+
runId,
|
|
620
|
+
planRevision: task?.planRevision ?? null,
|
|
621
|
+
groupId: task?.groupId ?? null,
|
|
622
|
+
taskId: planTaskId(task),
|
|
623
|
+
attemptId: attempt?.attemptId ?? null,
|
|
624
|
+
agentType: assignment?.agent?.description?.agentType ?? assignment?.description?.agentType ?? null,
|
|
625
|
+
agentInstanceId: assignment?.agentInstanceId ?? null,
|
|
626
|
+
agentId: assignment?.agentId ?? null,
|
|
627
|
+
jobId,
|
|
628
|
+
capability: task?.requiredCapability ?? assignment?.capability ?? null,
|
|
629
|
+
operation: task?.operation ?? assignment?.operation ?? null,
|
|
630
|
+
outputs,
|
|
631
|
+
error,
|
|
632
|
+
detail,
|
|
633
|
+
};
|
|
577
634
|
}
|
|
578
635
|
|
|
579
636
|
function planTaskId(task) {
|
|
580
637
|
return String(task.id ?? task.step);
|
|
581
638
|
}
|
|
582
639
|
|
|
583
|
-
function locksForTask(task) {
|
|
584
|
-
const locks = new Set();
|
|
585
|
-
const explicit = task.locks ?? task.writeLocks ?? null;
|
|
586
|
-
if (Array.isArray(explicit)) {
|
|
587
|
-
for (const lock of explicit) if (lock) locks.add(String(lock));
|
|
588
|
-
} else if (explicit && typeof explicit === 'object') {
|
|
589
|
-
if (explicit.workspaceWrite || explicit.workspace) locks.add('workspace:write');
|
|
590
|
-
for (const value of explicit.deliverableWrites ?? explicit.deliverables ?? []) locks.add(`deliverable:${value}`);
|
|
591
|
-
for (const value of explicit.wikiPageWrites ?? explicit.wikiPages ?? []) locks.add(`wiki-page:${value}`);
|
|
592
|
-
}
|
|
593
|
-
for (const value of task.deliverableWrites ?? []) locks.add(`deliverable:${value}`);
|
|
594
|
-
for (const value of task.wikiPageWrites ?? []) locks.add(`wiki-page:${value}`);
|
|
595
|
-
if (task.workspaceWrite) locks.add('workspace:write');
|
|
596
|
-
return [...locks].sort();
|
|
597
|
-
}
|
|
598
|
-
|
|
599
640
|
export async function finishRuntimeRun(session, input, {
|
|
600
641
|
runId,
|
|
601
642
|
signal = null,
|
|
@@ -934,10 +975,3 @@ function resolveMaxReplans(value = process.env.WIKI_MANAGER_REPLANNER_MAX_REPLAN
|
|
|
934
975
|
const parsed = Number(value);
|
|
935
976
|
return Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : DEFAULT_MAX_REPLANS;
|
|
936
977
|
}
|
|
937
|
-
|
|
938
|
-
function resolveSchedulerConcurrency(value = process.env.WIKI_MANAGER_SCHEDULER_CONCURRENCY) {
|
|
939
|
-
const parsed = Number(value);
|
|
940
|
-
return Number.isFinite(parsed) && parsed > 0
|
|
941
|
-
? Math.max(1, Math.floor(parsed))
|
|
942
|
-
: DEFAULT_SCHEDULER_CONCURRENCY;
|
|
943
|
-
}
|