@zq-silk/yui 0.6.8 → 0.6.10
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 +10 -3
- package/dist/cli/commandCatalog.js +1 -1
- package/dist/commands/taskCommands.js +63 -28
- package/dist/commands/taskContextCommand.js +27 -1
- package/dist/commands/taskRoleRuntimeStatus.js +17 -6
- package/dist/controller/agentRuntimeObserver.js +6 -3
- package/dist/controller/controller.js +29 -47
- package/dist/controller/fileSchedulerStoreAdapter.js +572 -258
- package/dist/controller/runtime.js +8 -1
- package/dist/controller/runtimeEventInbox.js +16 -5
- package/dist/controller/runtimeHookRunFence.js +51 -5
- package/dist/controller/runtimeObservationHook.js +8 -2
- package/dist/coordination/workMailbox.js +408 -28
- package/dist/coordination/workMailboxQueue.js +12 -10
- package/dist/executor/agentExecutor.js +101 -94
- package/dist/executor/executorRegistry.js +47 -2
- package/dist/executor/fileRoleLaunchPlanner.js +4 -2
- package/dist/lifecycle/exactRunTerminalization.js +1 -7
- package/dist/repository/taskWorkspaceCoordinator.js +9 -4
- package/dist/runtime/agentDriver.js +83 -4
- package/dist/runtime/agentDriverObservation.js +25 -10
- package/dist/runtime/builtinAgentDrivers.js +168 -18
- package/dist/runtime/codexAppServerRuntime.js +355 -0
- package/dist/runtime/continuationManager.js +117 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/lifecycleReservation.js +4 -3
- package/dist/runtime/promptEnvelope.js +14 -3
- package/dist/runtime/providerContinuation.js +225 -0
- package/dist/runtime/providerContinuationReconciliationService.js +172 -0
- package/dist/runtime/providerRuntimeIdentity.js +232 -0
- package/dist/runtime/providerRuntimeReconciler.js +166 -0
- package/dist/runtime/runtimeContinuationProjection.js +34 -0
- package/dist/runtime/runtimeObservation.js +217 -6
- package/dist/runtime/runtimeProjection.js +172 -11
- package/dist/scheduler/activeRoleRunDelivery.js +314 -1
- package/dist/scheduler/leaderWakeupProcessor.js +2 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +3 -2
- package/dist/scheduler/roleRunLiveness.js +8 -7
- package/dist/scheduler/roleRunStall.js +4 -2
- package/dist/scheduler/taskExecutionProjection.js +2 -2
- package/dist/storage/migration/productionRegistry.js +474 -1
- package/dist/storage/sqliteSchema.js +102 -21
- package/dist/storage/sqliteStore.js +52 -110
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +40 -53
- package/dist/storage/upgrade/sqliteStateMigration.js +0 -21
- package/dist/task/nextAction.js +1 -1
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/components.js +233 -4
- package/dist/web/assets/client/i18n.js +166 -2
- package/dist/web/assets/client/view.js +30 -13
- package/dist/web/assets/styles/cards.js +62 -0
- package/dist/web/assets/styles/widgets.js +1 -0
- package/dist/web/webSnapshot.js +11 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +33 -24
- package/skills/yui-operator/SKILL.md +7 -5
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import { selectedSchedulerRoles, selectedActiveSchedulerTasks } from "./ports.js";
|
|
2
2
|
import { isSchedulerTaskWorkspaceReady } from "./ports.js";
|
|
3
3
|
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
4
|
+
import { markYuiRunInput } from "../run/runIdentity.js";
|
|
5
|
+
import { taskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
4
6
|
import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain } from "../executor/effectiveLaunch.js";
|
|
5
7
|
import { RuntimeLaunchError } from "../runtime/ports.js";
|
|
6
8
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
9
|
+
import { mailboxHasWork, nextPendingBatch } from "../coordination/workMailbox.js";
|
|
7
10
|
/**
|
|
8
11
|
* Delivers durable Work AgentRuns before liveness reconciliation. Task command
|
|
9
12
|
* handlers only record intent; this Controller path is the sole automated
|
|
10
13
|
* route into the Agent terminal, through tmux receipt-backed delivery.
|
|
11
14
|
*/
|
|
12
|
-
export async function processActiveRoleRunDeliveries(store, delivery, now, selection) {
|
|
15
|
+
export async function processActiveRoleRunDeliveries(store, delivery, now, selection, inputDeliveryRecoveryCutoff) {
|
|
13
16
|
const results = [];
|
|
14
17
|
for (const task of selectedActiveSchedulerTasks(store, selection)) {
|
|
15
18
|
for (const role of selectedSchedulerRoles(store, task.id, selection)) {
|
|
16
19
|
const run = store.getActiveAgentRun(task.id, role.name);
|
|
20
|
+
if (run !== null && run.pushedAt !== undefined) {
|
|
21
|
+
const continuation = await processActiveRunContinuation(store, delivery, task, role, run, now, inputDeliveryRecoveryCutoff);
|
|
22
|
+
if (continuation !== null)
|
|
23
|
+
results.push(continuation);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
17
26
|
// A crash after a Leader wake is durably claimed but before tmux input
|
|
18
27
|
// is recoverable through the same receipt-backed delivery path. The
|
|
19
28
|
// re-push guard keys on pushedAt (transport), not deliveredAt (provider
|
|
@@ -308,6 +317,310 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
|
|
|
308
317
|
}
|
|
309
318
|
return results;
|
|
310
319
|
}
|
|
320
|
+
async function processActiveRunContinuation(store, delivery, task, role, run, now, inputDeliveryRecoveryCutoff) {
|
|
321
|
+
const target = { kind: "role", taskId: task.id, roleName: role.name };
|
|
322
|
+
const mailbox = store.getWorkMailbox(target);
|
|
323
|
+
if (mailbox === null || !mailboxHasWork(mailbox))
|
|
324
|
+
return null;
|
|
325
|
+
const session = store.getRoleSession(task.id, role.name, run.effective.agentId);
|
|
326
|
+
if (session === null || session.launchId === undefined || !hasText(session.nativeSessionId)) {
|
|
327
|
+
return {
|
|
328
|
+
taskId: task.id,
|
|
329
|
+
roleName: role.name,
|
|
330
|
+
runId: run.id,
|
|
331
|
+
status: "skipped",
|
|
332
|
+
reason: "not-ready"
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const originalReceipt = formatAgentRunReceiptId(task.id, run.id);
|
|
336
|
+
if (mailbox.processing !== null && (mailbox.processing.executionRef?.type !== "run"
|
|
337
|
+
|| mailbox.processing.executionRef.taskId !== task.id
|
|
338
|
+
|| mailbox.processing.executionRef.id !== run.id
|
|
339
|
+
|| mailbox.processing.batchId !== originalReceipt)) {
|
|
340
|
+
return {
|
|
341
|
+
taskId: task.id,
|
|
342
|
+
roleName: role.name,
|
|
343
|
+
runId: run.id,
|
|
344
|
+
status: "skipped",
|
|
345
|
+
reason: "mailbox-busy"
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
if (mailbox.inputDelivery !== null) {
|
|
349
|
+
const existing = mailbox.inputDelivery;
|
|
350
|
+
if ((existing.status === "delivery-unknown"
|
|
351
|
+
|| (existing.status === "dispatching"
|
|
352
|
+
&& inputDeliveryRecoveryCutoff !== undefined
|
|
353
|
+
&& Date.parse(existing.startedAt) < inputDeliveryRecoveryCutoff.getTime()))) {
|
|
354
|
+
return reconcileStrandedInputDelivery(store, delivery, task, role, run, session, existing, now);
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
taskId: task.id,
|
|
358
|
+
roleName: role.name,
|
|
359
|
+
runId: run.id,
|
|
360
|
+
status: "skipped",
|
|
361
|
+
reason: "delivery-uncertain"
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
const pending = nextPendingBatch(mailbox);
|
|
365
|
+
const lane = mailbox.pending.userCorrection === pending ? "user-correction" : "normal";
|
|
366
|
+
const requestedBatchId = pending === null
|
|
367
|
+
? originalReceipt
|
|
368
|
+
: `agent-input:${task.id}/${run.id}/${lane}:${pending.fromSequence}-${pending.toSequence}`;
|
|
369
|
+
if (requestedBatchId === originalReceipt) {
|
|
370
|
+
// Initial provider acceptance is still outstanding. It owns this claim;
|
|
371
|
+
// never reinterpret the original Run prompt as a continuation.
|
|
372
|
+
return {
|
|
373
|
+
taskId: task.id,
|
|
374
|
+
roleName: role.name,
|
|
375
|
+
runId: run.id,
|
|
376
|
+
status: "skipped",
|
|
377
|
+
reason: "delivery-uncertain"
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
if (pending === null || session.launchId === undefined)
|
|
381
|
+
return null;
|
|
382
|
+
if (session.status === "broken") {
|
|
383
|
+
return {
|
|
384
|
+
taskId: task.id,
|
|
385
|
+
roleName: role.name,
|
|
386
|
+
runId: run.id,
|
|
387
|
+
status: "skipped",
|
|
388
|
+
reason: "runtime-unavailable"
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const activeTurn = store.getActiveProviderTurnFence?.({
|
|
392
|
+
taskId: task.id,
|
|
393
|
+
roleName: role.name,
|
|
394
|
+
runId: run.id,
|
|
395
|
+
agentId: run.effective.agentId,
|
|
396
|
+
launchId: session.launchId,
|
|
397
|
+
nativeSessionId: session.nativeSessionId
|
|
398
|
+
}) ?? null;
|
|
399
|
+
const mode = lane === "user-correction"
|
|
400
|
+
? activeTurn !== null && delivery.canRouteProviderInput?.(run.effective.adapterId) === true
|
|
401
|
+
? "steer-if-safe"
|
|
402
|
+
: "followup"
|
|
403
|
+
: pending.deliveryModes.includes("followup") ? "followup" : "inject";
|
|
404
|
+
// Normal facts never enter an active Turn. A user correction may steer only
|
|
405
|
+
// through the exact Turn fence above; without it, retain the high-priority
|
|
406
|
+
// lane until the current Turn ends and deliver it as the next followup.
|
|
407
|
+
if (mode === "followup" && session.status === "running") {
|
|
408
|
+
return {
|
|
409
|
+
taskId: task.id,
|
|
410
|
+
roleName: role.name,
|
|
411
|
+
runId: run.id,
|
|
412
|
+
status: "skipped",
|
|
413
|
+
reason: "not-ready"
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
if (mode === "inject" && (delivery.routeProviderInput === undefined
|
|
417
|
+
|| delivery.canRouteProviderInput?.(run.effective.adapterId) !== true)) {
|
|
418
|
+
return {
|
|
419
|
+
taskId: task.id,
|
|
420
|
+
roleName: role.name,
|
|
421
|
+
runId: run.id,
|
|
422
|
+
status: "skipped",
|
|
423
|
+
reason: "delivery-unsupported"
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
let deliveryAttempted = false;
|
|
427
|
+
let inputDelivery;
|
|
428
|
+
try {
|
|
429
|
+
const prepared = await delivery.prepareRoleSession({
|
|
430
|
+
taskId: task.id,
|
|
431
|
+
roleName: role.name,
|
|
432
|
+
agentId: run.effective.agentId,
|
|
433
|
+
adapterId: run.effective.adapterId,
|
|
434
|
+
effective: run.effective,
|
|
435
|
+
workspace: run.effective.workspace.root,
|
|
436
|
+
...(run.workspace === undefined ? {} : { managedWorkspace: run.workspace }),
|
|
437
|
+
mode: "resume",
|
|
438
|
+
runId: run.id,
|
|
439
|
+
nativeSessionId: session.nativeSessionId
|
|
440
|
+
});
|
|
441
|
+
const ready = await delivery.waitUntilReady(prepared);
|
|
442
|
+
const readySession = ready.session ?? session;
|
|
443
|
+
if (readySession.launchId === undefined || !hasText(readySession.nativeSessionId)) {
|
|
444
|
+
throw new Error("Continuation delivery has no Provider Activation fence.");
|
|
445
|
+
}
|
|
446
|
+
const providerFence = mode === "steer-if-safe"
|
|
447
|
+
? activeTurn
|
|
448
|
+
: {
|
|
449
|
+
conversationId: readySession.nativeSessionId,
|
|
450
|
+
activationId: readySession.launchId
|
|
451
|
+
};
|
|
452
|
+
const claim = store.claimInputDelivery({
|
|
453
|
+
target,
|
|
454
|
+
attemptId: requestedBatchId,
|
|
455
|
+
lane,
|
|
456
|
+
mode,
|
|
457
|
+
owner: "controller",
|
|
458
|
+
now,
|
|
459
|
+
executionRef: { type: "run", taskId: task.id, id: run.id },
|
|
460
|
+
providerFence
|
|
461
|
+
});
|
|
462
|
+
if (claim.status === "empty")
|
|
463
|
+
return null;
|
|
464
|
+
if (claim.delivery.attemptId !== requestedBatchId || claim.status === "delivery") {
|
|
465
|
+
return {
|
|
466
|
+
taskId: task.id,
|
|
467
|
+
roleName: role.name,
|
|
468
|
+
runId: run.id,
|
|
469
|
+
status: "skipped",
|
|
470
|
+
reason: "delivery-uncertain"
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
inputDelivery = claim.delivery;
|
|
474
|
+
deliveryAttempted = true;
|
|
475
|
+
const text = continuationInput(task, role, run, inputDelivery.attemptId, inputDelivery.batch);
|
|
476
|
+
if (mode !== "followup") {
|
|
477
|
+
const routed = await delivery.routeProviderInput({
|
|
478
|
+
delivery: ready,
|
|
479
|
+
attemptId: inputDelivery.attemptId,
|
|
480
|
+
mode,
|
|
481
|
+
text,
|
|
482
|
+
fence: providerFence
|
|
483
|
+
});
|
|
484
|
+
if (routed === "accepted") {
|
|
485
|
+
store.completeInputDelivery(target, inputDelivery.attemptId, now);
|
|
486
|
+
return { taskId: task.id, roleName: role.name, runId: run.id, status: "delivered" };
|
|
487
|
+
}
|
|
488
|
+
if (routed === "unknown") {
|
|
489
|
+
store.markInputDeliveryUnknown(target, inputDelivery.attemptId, "Provider input acceptance could not be reconciled.", now);
|
|
490
|
+
return {
|
|
491
|
+
taskId: task.id,
|
|
492
|
+
roleName: role.name,
|
|
493
|
+
runId: run.id,
|
|
494
|
+
status: "skipped",
|
|
495
|
+
reason: "delivery-uncertain"
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
store.releaseInputDelivery(target, inputDelivery.attemptId);
|
|
499
|
+
return {
|
|
500
|
+
taskId: task.id,
|
|
501
|
+
roleName: role.name,
|
|
502
|
+
runId: run.id,
|
|
503
|
+
status: "skipped",
|
|
504
|
+
reason: routed === "unavailable" ? "runtime-unavailable" : "not-ready"
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
const outcome = await delivery.sendOnce({
|
|
508
|
+
delivery: ready,
|
|
509
|
+
receiptId: inputDelivery.attemptId,
|
|
510
|
+
text
|
|
511
|
+
});
|
|
512
|
+
if (outcome === "busy" || outcome === "unavailable") {
|
|
513
|
+
store.releaseInputDelivery(target, inputDelivery.attemptId);
|
|
514
|
+
return {
|
|
515
|
+
taskId: task.id,
|
|
516
|
+
roleName: role.name,
|
|
517
|
+
runId: run.id,
|
|
518
|
+
status: "skipped",
|
|
519
|
+
reason: outcome === "busy" ? "not-ready" : "runtime-unavailable"
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
store.markInputDeliveryPushed(target, inputDelivery.attemptId, now);
|
|
523
|
+
// Keep the exact claim until the matching provider turn.accepted Hook
|
|
524
|
+
// folds it. sendOnce is receipt-idempotent, so Controller recovery cannot
|
|
525
|
+
// inject a second Enter for the same batch.
|
|
526
|
+
return {
|
|
527
|
+
taskId: task.id,
|
|
528
|
+
roleName: role.name,
|
|
529
|
+
runId: run.id,
|
|
530
|
+
status: outcome === "sent" ? "delivered" : "already-delivered"
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
if (inputDelivery === undefined) {
|
|
535
|
+
// No durable input intent existed and no provider input method was
|
|
536
|
+
// called. Session preparation may be retried safely.
|
|
537
|
+
}
|
|
538
|
+
else if (!deliveryAttempted)
|
|
539
|
+
store.releaseInputDelivery(target, inputDelivery.attemptId);
|
|
540
|
+
else
|
|
541
|
+
store.markInputDeliveryUnknown(target, inputDelivery.attemptId, error instanceof Error ? error.message : String(error), now);
|
|
542
|
+
return {
|
|
543
|
+
taskId: task.id,
|
|
544
|
+
roleName: role.name,
|
|
545
|
+
runId: run.id,
|
|
546
|
+
status: "skipped",
|
|
547
|
+
reason: deliveryAttempted ? "delivery-uncertain" : "runtime-unavailable",
|
|
548
|
+
error: error instanceof Error ? error.message : String(error)
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
async function reconcileStrandedInputDelivery(store, delivery, task, role, run, session, input, now) {
|
|
553
|
+
const target = { kind: "role", taskId: task.id, roleName: role.name };
|
|
554
|
+
const uncertain = (reason) => {
|
|
555
|
+
store.markInputDeliveryUnknown(target, input.attemptId, reason, now);
|
|
556
|
+
return {
|
|
557
|
+
taskId: task.id,
|
|
558
|
+
roleName: role.name,
|
|
559
|
+
runId: run.id,
|
|
560
|
+
status: "skipped",
|
|
561
|
+
reason: "delivery-uncertain"
|
|
562
|
+
};
|
|
563
|
+
};
|
|
564
|
+
if (input.providerFence === undefined
|
|
565
|
+
|| delivery.reconcileProviderInput === undefined
|
|
566
|
+
|| delivery.canRouteProviderInput?.(run.effective.adapterId) !== true) {
|
|
567
|
+
return uncertain("Controller restarted without exact Provider input readback.");
|
|
568
|
+
}
|
|
569
|
+
try {
|
|
570
|
+
// Exact readback is addressed entirely from the durable fence. It must not
|
|
571
|
+
// resume a Session, create an Activation, or call any model-starting port.
|
|
572
|
+
const reconciled = await delivery.reconcileProviderInput({
|
|
573
|
+
taskId: task.id,
|
|
574
|
+
roleName: role.name,
|
|
575
|
+
agentId: run.effective.agentId,
|
|
576
|
+
adapterId: run.effective.adapterId,
|
|
577
|
+
launchId: session.launchId,
|
|
578
|
+
nativeSessionId: session.nativeSessionId,
|
|
579
|
+
attemptId: input.attemptId,
|
|
580
|
+
mode: input.mode,
|
|
581
|
+
fence: input.providerFence
|
|
582
|
+
});
|
|
583
|
+
if (reconciled === "accepted") {
|
|
584
|
+
store.completeInputDelivery(target, input.attemptId, now);
|
|
585
|
+
return {
|
|
586
|
+
taskId: task.id,
|
|
587
|
+
roleName: role.name,
|
|
588
|
+
runId: run.id,
|
|
589
|
+
status: "already-delivered"
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
if (reconciled === "not-accepted") {
|
|
593
|
+
store.resolveInputDeliveryNotAccepted(target, input.attemptId);
|
|
594
|
+
return {
|
|
595
|
+
taskId: task.id,
|
|
596
|
+
roleName: role.name,
|
|
597
|
+
runId: run.id,
|
|
598
|
+
status: "skipped",
|
|
599
|
+
reason: "not-ready"
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
return uncertain(reconciled === "unavailable"
|
|
603
|
+
? "Provider input readback is unavailable after Controller restart."
|
|
604
|
+
: "Provider input acceptance remains unknown after metadata readback.");
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
return uncertain(`Provider input readback failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function continuationInput(task, role, run, attemptId, batch) {
|
|
611
|
+
const references = batch.refs.map((ref) => ("taskId" in ref
|
|
612
|
+
? `${ref.type}:${ref.taskId}/${ref.id}`
|
|
613
|
+
: `${ref.type}:${ref.id}`));
|
|
614
|
+
return markYuiRunInput([
|
|
615
|
+
`Yui Task Event Batch: ${attemptId}.`,
|
|
616
|
+
"New durable task events are available for the current Yui Run.",
|
|
617
|
+
"Read the referenced shared context through the Yui CLI, incorporate it, and decide whether to continue work or wait for more results.",
|
|
618
|
+
"Do not create a new Yui Run merely because this is a new Provider Turn.",
|
|
619
|
+
`Reasons: ${batch.reasons.join(", ")}.`,
|
|
620
|
+
...(batch.sources.length === 0 ? [] : [`Sources: ${batch.sources.join(", ")}.`]),
|
|
621
|
+
...(references.length === 0 ? [] : [`References: ${references.join(", ")}.`])
|
|
622
|
+
].join("\n"), run.id, taskRoleSessionTitle(task, role.name));
|
|
623
|
+
}
|
|
311
624
|
function roleRunDeliveryFailure(run, mailboxBatchId, session, launchId) {
|
|
312
625
|
return {
|
|
313
626
|
taskId: run.taskId,
|
|
@@ -410,7 +410,8 @@ function leaderWakeupInput(taskId, runId, reasons, projectBindings) {
|
|
|
410
410
|
: `Project Policy references: ${projectBindings.map((binding) => `${binding.directory} (${binding.projectId})`).join(", ")}. Read each with yui project show <project>, then yui project knowledge list <project> and yui project knowledge show <project> <knowledge>.`,
|
|
411
411
|
"Use narrower Task message, WorkItem, decision, milestone, and input commands only when a specific record needs closer inspection.",
|
|
412
412
|
`When the requested outcome is finished and there are no active Worker Runs or unresolved inputs, complete the Task with yui task complete ${taskId} --summary-file - and a quoted heredoc containing the final outcome and evidence.`,
|
|
413
|
-
|
|
413
|
+
"Provider-native subagents remain inside this Leader AgentRun. Yui observes their structured lifecycle and keeps the Run active across intermediate provider Turn boundaries while children remain active; let the provider deliver completion notifications and continue synthesis without polling or yielding merely for that native wait.",
|
|
414
|
+
`A Provider Turn ending does not end this Yui Run. Leave the Run active when later native-subagent results, managed Role results, reviewer results, or user corrections still belong to the same objective; Yui will aggregate those durable events and resume this Session with another Turn. Use yui task run yield ${runId} --summary-file - only for a deliberate Yui-level handoff that should close this Run rather than for ordinary waiting. If used, the yield command must be the final tool action: after it succeeds, stop immediately.`
|
|
414
415
|
];
|
|
415
416
|
return lines.join("\n");
|
|
416
417
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { createInputRequestOperatorPresentation, createLeaderRecoveryOperatorPresentation, createLeaderStallOperatorPresentation, createTaskTerminalOperatorPresentation } from "../interaction/operatorPresentation.js";
|
|
2
|
+
import { mailboxHasWork, nextPendingBatch } from "../coordination/workMailbox.js";
|
|
2
3
|
export async function processOperatorInputNotifications(store, delivery, selection, now = new Date()) {
|
|
3
4
|
if (selection !== undefined && !selection.full && !selection.operator)
|
|
4
5
|
return [];
|
|
5
6
|
const targetMailbox = { kind: "operator" };
|
|
6
7
|
const mailbox = store.getWorkMailbox(targetMailbox);
|
|
7
|
-
if (mailbox === null || (mailbox
|
|
8
|
+
if (mailbox === null || !mailboxHasWork(mailbox))
|
|
8
9
|
return [];
|
|
9
|
-
const pending = mailbox
|
|
10
|
+
const pending = nextPendingBatch(mailbox);
|
|
10
11
|
const claim = store.claimWorkMailbox({
|
|
11
12
|
target: targetMailbox,
|
|
12
13
|
batchId: pending === null
|
|
@@ -4,8 +4,12 @@ import { queueLeaderWakeup } from "./wakeupQueue.js";
|
|
|
4
4
|
import { currentRoleRunProgressAt, DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS } from "./roleRunStall.js";
|
|
5
5
|
export const EXITED_ROLE_RUN_SUMMARY = "The role's tmux session exited before the run yielded.";
|
|
6
6
|
/**
|
|
7
|
-
* Lightweight liveness only
|
|
8
|
-
*
|
|
7
|
+
* Lightweight liveness only. Host absence may fail a Run only before any
|
|
8
|
+
* prompt bytes were pushed. After push, acceptance may be unknown even when
|
|
9
|
+
* deliveredAt is absent; pane/process loss is therefore runtime-health
|
|
10
|
+
* evidence, not an application-level outcome. The Run stays active so native
|
|
11
|
+
* child work or another observer can still contribute facts, while the stall
|
|
12
|
+
* path raises bounded attention independently.
|
|
9
13
|
*/
|
|
10
14
|
export async function reconcileExitedRoleRuns(store, delivery, now, selection, excludedRunRefs = new Set(), liveStatuses, resourceEvidence, targetedInventory = selection !== undefined && !selection.full) {
|
|
11
15
|
const failed = [];
|
|
@@ -35,11 +39,8 @@ export async function reconcileExitedRoleRuns(store, delivery, now, selection, e
|
|
|
35
39
|
})));
|
|
36
40
|
if (candidates.length === 0)
|
|
37
41
|
return failed;
|
|
38
|
-
const
|
|
39
|
-
.
|
|
40
|
-
const completing = new Set(store.listPendingRuntimeTurnCompletions(candidateTaskIds).map((completion) => (`${completion.taskId}\0${completion.roleName}\0${completion.runId}`)));
|
|
41
|
-
const eligible = candidates.filter(({ task, role, run }) => (!excludedRunRefs.has(formatTaskRecordReference(task.id, run.id, "agentRun"))
|
|
42
|
-
&& !completing.has(`${task.id}\0${role.name}\0${run.id}`)));
|
|
42
|
+
const eligible = candidates.filter(({ task, run }) => (!excludedRunRefs.has(formatTaskRecordReference(task.id, run.id, "agentRun"))
|
|
43
|
+
&& run.pushedAt === undefined));
|
|
43
44
|
// Full reconciliation builds one complete provider inventory for every
|
|
44
45
|
// active Run, including delivery-uncertain and completion-pending Runs.
|
|
45
46
|
// The stall phase reuses that snapshot so one full pass never probes the
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { selectedSchedulerRoles, selectedActiveSchedulerTasks } from "./ports.js";
|
|
2
|
+
import { nextPendingBatch } from "../coordination/workMailbox.js";
|
|
2
3
|
/**
|
|
3
4
|
* Default window of no durable progress before a live-but-idle Run becomes a
|
|
4
5
|
* traceable needs-attention signal. It is deliberately long: a healthy Run that
|
|
@@ -845,7 +846,7 @@ function classifyLeaderStall(store, taskId, observed, now, windowMs) {
|
|
|
845
846
|
const downstreamPresent = downstream.some((entry) => entry.live === "present");
|
|
846
847
|
const leader = [...observed.values()].find((entry) => (entry.candidate.task.id === taskId && entry.candidate.role.name === "leader"));
|
|
847
848
|
const mailbox = store.getWorkMailbox({ kind: "role", taskId, roleName: "leader" });
|
|
848
|
-
const pending = mailbox
|
|
849
|
+
const pending = mailbox === null || mailbox === undefined ? null : nextPendingBatch(mailbox);
|
|
849
850
|
const processing = mailbox?.processing;
|
|
850
851
|
const processingCurrent = processing?.executionRef?.type === "run"
|
|
851
852
|
&& processing.executionRef.taskId === taskId
|
|
@@ -952,7 +953,8 @@ function leaderStallEvidence(store, taskId, observed, now, windowMs) {
|
|
|
952
953
|
const active = downstream.filter((entry) => entry.candidate.run !== null).length;
|
|
953
954
|
const healthy = downstream.filter((entry) => entry.live === "present" && !entry.stalled).length;
|
|
954
955
|
const stalled = downstream.filter((entry) => entry.live === "present" && entry.stalled).length;
|
|
955
|
-
const
|
|
956
|
+
const leaderMailbox = store.getWorkMailbox({ kind: "role", taskId, roleName: "leader" });
|
|
957
|
+
const pending = leaderMailbox === null ? null : nextPendingBatch(leaderMailbox);
|
|
956
958
|
const pendingAge = pending === undefined || pending === null
|
|
957
959
|
? "none"
|
|
958
960
|
: Number.isFinite(Date.parse(pending.lastQueuedAt))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { currentWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
2
|
+
import { mailboxBatches } from "../coordination/workMailbox.js";
|
|
2
3
|
import { summarizeExecutionGroup } from "../execution/executionGroup.js";
|
|
3
4
|
import { isRoleRunStalled, latestStallProgressAt } from "./roleRunStall.js";
|
|
4
5
|
/**
|
|
@@ -537,8 +538,7 @@ function isRecoveryPending(wakeup, mailbox, failure, notification) {
|
|
|
537
538
|
return false;
|
|
538
539
|
const reasons = [
|
|
539
540
|
...(wakeup?.reasons ?? []),
|
|
540
|
-
...(mailbox
|
|
541
|
-
...(mailbox?.processing?.batch.reasons ?? [])
|
|
541
|
+
...(mailbox === null ? [] : mailboxBatches(mailbox).flatMap((batch) => batch.reasons))
|
|
542
542
|
];
|
|
543
543
|
return reasons.some((reason) => /(?:recover|stalled|failed|uncertain|orphan)/iu.test(reason));
|
|
544
544
|
}
|