@zq-silk/yui 0.8.1 → 0.8.2

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.
Files changed (38) hide show
  1. package/ARCHITECTURE.md +27 -28
  2. package/README.md +35 -46
  3. package/dist/cli/commandCatalog.js +49 -14
  4. package/dist/cli/interactionPolicy.js +4 -10
  5. package/dist/cli/invocationRouter.js +2 -1
  6. package/dist/cli.js +73 -21
  7. package/dist/commands/taskCommands.js +108 -53
  8. package/dist/controller/fileSchedulerStoreAdapter.js +389 -70
  9. package/dist/controller/resourceInventory.js +9 -5
  10. package/dist/controller/runtime.js +80 -7
  11. package/dist/controller/runtimeLaunchCoordinator.js +18 -78
  12. package/dist/controller/structuredProviderObservation.js +273 -0
  13. package/dist/executor/agentAdapter.js +40 -0
  14. package/dist/executor/agentExecutor.js +31 -7
  15. package/dist/executor/executorRegistry.js +11 -49
  16. package/dist/executor/fileRoleLaunchPlanner.js +115 -37
  17. package/dist/lifecycle/canonicalLifecycleEvent.js +5 -2
  18. package/dist/run/agentRun.js +2 -2
  19. package/dist/runtime/agentHost.js +767 -158
  20. package/dist/runtime/builtinAgentDrivers.js +1 -5
  21. package/dist/runtime/codexAppServerRuntime.js +67 -60
  22. package/dist/runtime/exactControlPlane.js +7 -2
  23. package/dist/runtime/index.js +6 -2
  24. package/dist/runtime/launchBroker.js +30 -8
  25. package/dist/runtime/providerAuthorityFence.js +24 -0
  26. package/dist/runtime/providerControl.js +63 -0
  27. package/dist/runtime/providerRecoveryDecision.js +55 -0
  28. package/dist/runtime/providerRuntimeIdentity.js +269 -19
  29. package/dist/runtime/runtimeBinding.js +20 -11
  30. package/dist/runtime/structuredProviderHost.js +476 -0
  31. package/dist/runtime/tmuxAdapters.js +143 -42
  32. package/dist/scheduler/activeRoleRunDelivery.js +206 -120
  33. package/dist/scheduler/leaderWakeupProcessor.js +141 -16
  34. package/dist/storage/migration/productionRegistry.js +111 -0
  35. package/dist/storage/taskStore.js +1 -1
  36. package/dist/tmux/tmuxManager.js +1 -1
  37. package/i18n/README.zh-CN.md +11 -8
  38. package/package.json +1 -1
@@ -25,7 +25,9 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
25
25
  for (const task of selectedActiveSchedulerTasks(store, selection)) {
26
26
  for (const role of selectedSchedulerRoles(store, task.id, selection)) {
27
27
  const run = store.getActiveAgentRun(task.id, role.name);
28
- if (run !== null && run.pushedAt !== undefined) {
28
+ if (run !== null && run.pushedAt !== undefined
29
+ && run.providerRetry?.state !== "dispatching"
30
+ && run.controlRequest?.state !== "dispatching") {
29
31
  const continuation = await processActiveRunContinuation(store, delivery, task, role, run, now, inputDeliveryRecoveryCutoff);
30
32
  if (continuation !== null)
31
33
  results.push(continuation);
@@ -107,6 +109,7 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
107
109
  let preparedSession = existingSession;
108
110
  let preStartFencePersisted = false;
109
111
  let deliveryAttempted = false;
112
+ let providerSubmissionBegun = false;
110
113
  try {
111
114
  const nativeSessionId = run.mode === "resume"
112
115
  ? requireResumeSession(role, run.effective, existingSession)
@@ -130,13 +133,14 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
130
133
  preStartFencePersisted = true;
131
134
  }
132
135
  });
133
- // A managed host may already have submitted the exact Run prompt as
134
- // part of process launch. Once preparation returns that transport
136
+ // A managed Host may have submitted the exact Run Turn after its
137
+ // two-phase launch handshake. Once preparation returns that transport
135
138
  // fact, any
136
139
  // later readiness or aggregate-write failure is delivery uncertainty,
137
140
  // not a launch failure: preserve the Run and its reservation for the
138
141
  // matching provider Hook instead of terminalizing it.
139
- deliveryAttempted = prepared.inputSubmittedAtLaunch === true;
142
+ deliveryAttempted = prepared.turnAcceptedDuringLaunch === true
143
+ || prepared.turnDeliveryUnknownDuringLaunch === true;
140
144
  // Preparation may already have an exact native Session identity while
141
145
  // the provider is still starting. Persist that Session + the Run fence
142
146
  // before awaiting readiness so a pre-input provider Hook can validate
@@ -158,7 +162,8 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
158
162
  }
159
163
  const ready = await delivery.waitUntilReady(prepared);
160
164
  deliveryAttempted = deliveryAttempted
161
- || ready.prepared.inputSubmittedAtLaunch === true;
165
+ || ready.prepared.turnAcceptedDuringLaunch === true
166
+ || ready.prepared.turnDeliveryUnknownDuringLaunch === true;
162
167
  const session = validateLaunchSubmittedRecoverySession(role, run.effective, existingSession, run.mode, ready.prepared, ready.session);
163
168
  preparedSession = session;
164
169
  store.saveRoleRunPrepared({
@@ -171,11 +176,56 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
171
176
  : { launchId: ready.prepared.launchId }),
172
177
  now
173
178
  });
174
- if (ready.prepared.inputSubmittedAtLaunch === true) {
175
- // The exact Run prompt was already carried by the newly-created
176
- // Provider command. Persist transport success without writing any
177
- // terminal bytes; only the later matching Provider Hook may mark
178
- // the Run delivered/accepted.
179
+ if (ready.prepared.turnRejectedDuringLaunch === true) {
180
+ delivery.forgetPrepared?.({
181
+ taskId: task.id,
182
+ roleName: role.name,
183
+ runId: run.id,
184
+ ...(ready.prepared.launchId === undefined
185
+ ? {}
186
+ : { launchId: ready.prepared.launchId })
187
+ });
188
+ results.push({
189
+ taskId: task.id,
190
+ roleName: role.name,
191
+ runId: run.id,
192
+ status: "skipped",
193
+ reason: "not-ready"
194
+ });
195
+ continue;
196
+ }
197
+ if (ready.prepared.turnDeliveryUnknownDuringLaunch === true) {
198
+ store.saveRoleRunDelivery({
199
+ task,
200
+ role,
201
+ run,
202
+ session,
203
+ ...(ready.prepared.launchId === undefined
204
+ ? {}
205
+ : { launchId: ready.prepared.launchId }),
206
+ now
207
+ });
208
+ delivery.forgetPrepared?.({
209
+ taskId: task.id,
210
+ roleName: role.name,
211
+ runId: run.id,
212
+ ...(ready.prepared.launchId === undefined
213
+ ? {}
214
+ : { launchId: ready.prepared.launchId })
215
+ });
216
+ results.push({
217
+ taskId: task.id,
218
+ roleName: role.name,
219
+ runId: run.id,
220
+ status: "skipped",
221
+ reason: "delivery-uncertain"
222
+ });
223
+ continue;
224
+ }
225
+ if (ready.prepared.turnAcceptedDuringLaunch === true) {
226
+ // The newly-created Agent Host already submitted the exact Run Turn
227
+ // through Provider-native control. Persist launch transport success;
228
+ // only the matching structured acknowledgement marks it accepted.
179
229
  store.saveRoleRunDelivery({
180
230
  task,
181
231
  role,
@@ -232,6 +282,21 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
232
282
  });
233
283
  continue;
234
284
  }
285
+ if (session === null || session.launchId === undefined
286
+ || !hasText(session.nativeSessionId)
287
+ || store.beginRoleRunProviderTurn?.({
288
+ taskId: task.id,
289
+ roleName: role.name,
290
+ runId: run.id,
291
+ agentId: run.effective.agentId,
292
+ launchId: session.launchId,
293
+ nativeSessionId: session.nativeSessionId,
294
+ attemptId: receiptId,
295
+ now
296
+ }) !== true) {
297
+ throw new Error("Provider Turn intent could not be durably fenced before delivery.");
298
+ }
299
+ providerSubmissionBegun = true;
235
300
  deliveryAttempted = true;
236
301
  const outcome = await delivery.sendOnce({
237
302
  delivery: ready,
@@ -253,6 +318,18 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
253
318
  : serializeRunBootstrapEnvelope(run.bootstrapEnvelope)
254
319
  });
255
320
  if (outcome === "busy" || outcome === "unavailable") {
321
+ store.resolveRoleRunProviderSubmission?.({
322
+ taskId: task.id,
323
+ roleName: role.name,
324
+ runId: run.id,
325
+ attemptId: receiptId,
326
+ status: "rejected",
327
+ reason: outcome === "busy"
328
+ ? "Agent Host was busy before Provider mutation."
329
+ : "Agent Host was unavailable before Provider mutation.",
330
+ now
331
+ });
332
+ providerSubmissionBegun = false;
256
333
  results.push({
257
334
  taskId: task.id,
258
335
  roleName: role.name,
@@ -263,6 +340,56 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
263
340
  });
264
341
  continue;
265
342
  }
343
+ if (outcome === "rejected") {
344
+ store.resolveRoleRunProviderSubmission?.({
345
+ taskId: task.id,
346
+ roleName: role.name,
347
+ runId: run.id,
348
+ attemptId: receiptId,
349
+ status: "rejected",
350
+ reason: "Provider returned an exact negative acknowledgement.",
351
+ now
352
+ });
353
+ providerSubmissionBegun = false;
354
+ results.push({
355
+ taskId: task.id,
356
+ roleName: role.name,
357
+ runId: run.id,
358
+ status: "skipped",
359
+ reason: "not-ready"
360
+ });
361
+ continue;
362
+ }
363
+ if (outcome === "delivery-unknown") {
364
+ store.resolveRoleRunProviderSubmission?.({
365
+ taskId: task.id,
366
+ roleName: role.name,
367
+ runId: run.id,
368
+ attemptId: receiptId,
369
+ status: "delivery-unknown",
370
+ reason: "Provider Turn delivery is ambiguous; automatic retry is fenced.",
371
+ now
372
+ });
373
+ providerSubmissionBegun = false;
374
+ store.saveRoleRunDelivery({
375
+ task,
376
+ role,
377
+ run,
378
+ session,
379
+ ...(ready.prepared.launchId === undefined
380
+ ? {}
381
+ : { launchId: ready.prepared.launchId }),
382
+ now
383
+ });
384
+ results.push({
385
+ taskId: task.id,
386
+ roleName: role.name,
387
+ runId: run.id,
388
+ status: "skipped",
389
+ reason: "delivery-uncertain"
390
+ });
391
+ continue;
392
+ }
266
393
  const status = outcome === "sent" ? "delivered" : "already-delivered";
267
394
  store.saveRoleRunDelivery({
268
395
  task,
@@ -278,6 +405,17 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
278
405
  }
279
406
  catch (error) {
280
407
  const message = error instanceof Error ? error.message : String(error);
408
+ if (providerSubmissionBegun) {
409
+ store.resolveRoleRunProviderSubmission?.({
410
+ taskId: task.id,
411
+ roleName: role.name,
412
+ runId: run.id,
413
+ attemptId: receiptId,
414
+ status: "delivery-unknown",
415
+ reason: message,
416
+ now
417
+ });
418
+ }
281
419
  // Scheduler single-flight backpressure: the Role runtime lifecycle
282
420
  // lane was busy when the launch was reserved. The Run stays
283
421
  // active-unpushed and its mailbox claim is retained; the next pass
@@ -428,6 +566,23 @@ async function processActiveRunContinuation(store, delivery, task, role, run, no
428
566
  reason: "not-ready"
429
567
  };
430
568
  }
569
+ const writer = store.getProviderAuthorityFence?.({
570
+ taskId: task.id,
571
+ roleName: role.name,
572
+ runId: run.id,
573
+ agentId: run.effective.agentId,
574
+ launchId: session.launchId,
575
+ nativeSessionId: session.nativeSessionId
576
+ }) ?? null;
577
+ if (writer !== null && writer.owner !== "controller") {
578
+ return {
579
+ taskId: task.id,
580
+ roleName: role.name,
581
+ runId: run.id,
582
+ status: "skipped",
583
+ reason: "writer-attached"
584
+ };
585
+ }
431
586
  const originalReceipt = formatAgentRunReceiptId(task.id, run.id);
432
587
  if (mailbox.processing !== null && (mailbox.processing.executionRef?.type !== "run"
433
588
  || mailbox.processing.executionRef.taskId !== task.id
@@ -447,7 +602,7 @@ async function processActiveRunContinuation(store, delivery, task, role, run, no
447
602
  || (existing.status === "dispatching"
448
603
  && inputDeliveryRecoveryCutoff !== undefined
449
604
  && Date.parse(existing.startedAt) < inputDeliveryRecoveryCutoff.getTime()))) {
450
- return reconcileStrandedInputDelivery(store, delivery, task, role, run, session, existing, now);
605
+ return reconcileStrandedInputDelivery(store, task, role, run, existing, now);
451
606
  }
452
607
  return {
453
608
  taskId: task.id,
@@ -484,23 +639,10 @@ async function processActiveRunContinuation(store, delivery, task, role, run, no
484
639
  reason: "runtime-unavailable"
485
640
  };
486
641
  }
487
- const activeTurn = store.getActiveProviderTurnFence?.({
488
- taskId: task.id,
489
- roleName: role.name,
490
- runId: run.id,
491
- agentId: run.effective.agentId,
492
- launchId: session.launchId,
493
- nativeSessionId: session.nativeSessionId
494
- }) ?? null;
495
- const mode = lane === "user-correction"
496
- ? activeTurn !== null && delivery.canRouteProviderInput?.(run.effective.adapterId) === true
497
- ? "steer-if-safe"
498
- : "followup"
499
- : pending.deliveryModes.includes("followup") ? "followup" : "inject";
500
- // Normal facts never enter an active Turn. A user correction may steer only
501
- // through the exact Turn fence above; without it, retain the high-priority
502
- // lane until the current Turn ends and deliver it as the next followup.
503
- if (mode === "followup" && session.status === "running") {
642
+ // Managed input has one write path: a new structured Turn at a settled
643
+ // boundary. Corrections retain their priority while the current Turn runs.
644
+ const mode = "followup";
645
+ if (session.status === "running") {
504
646
  return {
505
647
  taskId: task.id,
506
648
  roleName: role.name,
@@ -509,14 +651,13 @@ async function processActiveRunContinuation(store, delivery, task, role, run, no
509
651
  reason: "not-ready"
510
652
  };
511
653
  }
512
- if (mode === "inject" && (delivery.routeProviderInput === undefined
513
- || delivery.canRouteProviderInput?.(run.effective.adapterId) !== true)) {
654
+ if (writer === null) {
514
655
  return {
515
656
  taskId: task.id,
516
657
  roleName: role.name,
517
658
  runId: run.id,
518
659
  status: "skipped",
519
- reason: "delivery-unsupported"
660
+ reason: "not-ready"
520
661
  };
521
662
  }
522
663
  let deliveryAttempted = false;
@@ -539,12 +680,10 @@ async function processActiveRunContinuation(store, delivery, task, role, run, no
539
680
  if (readySession.launchId === undefined || !hasText(readySession.nativeSessionId)) {
540
681
  throw new Error("Continuation delivery has no Provider Activation fence.");
541
682
  }
542
- const providerFence = mode === "steer-if-safe"
543
- ? activeTurn
544
- : {
545
- conversationId: readySession.nativeSessionId,
546
- activationId: readySession.launchId
547
- };
683
+ const providerFence = {
684
+ conversationId: writer.conversationId,
685
+ activationId: writer.activationId
686
+ };
548
687
  const claim = store.claimInputDelivery({
549
688
  target,
550
689
  attemptId: requestedBatchId,
@@ -569,44 +708,13 @@ async function processActiveRunContinuation(store, delivery, task, role, run, no
569
708
  inputDelivery = claim.delivery;
570
709
  deliveryAttempted = true;
571
710
  const text = continuationInput(task, role, run, inputDelivery.attemptId, inputDelivery.batch, boundedContinuationSummaries(store, task.id, inputDelivery.batch.refs));
572
- if (mode !== "followup") {
573
- const routed = await delivery.routeProviderInput({
574
- delivery: ready,
575
- attemptId: inputDelivery.attemptId,
576
- mode,
577
- text,
578
- fence: providerFence
579
- });
580
- if (routed === "accepted") {
581
- store.completeInputDelivery(target, inputDelivery.attemptId, now);
582
- return { taskId: task.id, roleName: role.name, runId: run.id, status: "delivered" };
583
- }
584
- if (routed === "unknown") {
585
- store.markInputDeliveryUnknown(target, inputDelivery.attemptId, "Provider input acceptance could not be reconciled.", now);
586
- return {
587
- taskId: task.id,
588
- roleName: role.name,
589
- runId: run.id,
590
- status: "skipped",
591
- reason: "delivery-uncertain"
592
- };
593
- }
594
- store.releaseInputDelivery(target, inputDelivery.attemptId);
595
- return {
596
- taskId: task.id,
597
- roleName: role.name,
598
- runId: run.id,
599
- status: "skipped",
600
- reason: routed === "unavailable" ? "runtime-unavailable" : "not-ready"
601
- };
602
- }
603
711
  const outcome = await delivery.sendOnce({
604
712
  delivery: ready,
605
713
  receiptId: inputDelivery.attemptId,
606
714
  text
607
715
  });
608
716
  if (outcome === "busy" || outcome === "unavailable") {
609
- store.releaseInputDelivery(target, inputDelivery.attemptId);
717
+ store.releaseInputDelivery(target, inputDelivery.attemptId, now);
610
718
  return {
611
719
  taskId: task.id,
612
720
  roleName: role.name,
@@ -615,10 +723,30 @@ async function processActiveRunContinuation(store, delivery, task, role, run, no
615
723
  reason: outcome === "busy" ? "not-ready" : "runtime-unavailable"
616
724
  };
617
725
  }
726
+ if (outcome === "rejected") {
727
+ store.resolveInputDeliveryNotAccepted(target, inputDelivery.attemptId, now);
728
+ return {
729
+ taskId: task.id,
730
+ roleName: role.name,
731
+ runId: run.id,
732
+ status: "skipped",
733
+ reason: "not-ready"
734
+ };
735
+ }
736
+ if (outcome === "delivery-unknown") {
737
+ store.markInputDeliveryUnknown(target, inputDelivery.attemptId, "Provider Turn delivery is ambiguous; automatic retry is fenced.", now);
738
+ return {
739
+ taskId: task.id,
740
+ roleName: role.name,
741
+ runId: run.id,
742
+ status: "skipped",
743
+ reason: "delivery-uncertain"
744
+ };
745
+ }
618
746
  store.markInputDeliveryPushed(target, inputDelivery.attemptId, now);
619
747
  // Keep the exact claim until the matching provider turn.accepted Hook
620
- // folds it. sendOnce is receipt-idempotent, so Controller recovery cannot
621
- // inject a second Enter for the same batch.
748
+ // folds it. An interrupted acknowledgement becomes delivery-unknown and
749
+ // cannot be resubmitted automatically.
622
750
  return {
623
751
  taskId: task.id,
624
752
  roleName: role.name,
@@ -632,7 +760,7 @@ async function processActiveRunContinuation(store, delivery, task, role, run, no
632
760
  // called. Session preparation may be retried safely.
633
761
  }
634
762
  else if (!deliveryAttempted)
635
- store.releaseInputDelivery(target, inputDelivery.attemptId);
763
+ store.releaseInputDelivery(target, inputDelivery.attemptId, now);
636
764
  else
637
765
  store.markInputDeliveryUnknown(target, inputDelivery.attemptId, error instanceof Error ? error.message : String(error), now);
638
766
  return {
@@ -645,7 +773,7 @@ async function processActiveRunContinuation(store, delivery, task, role, run, no
645
773
  };
646
774
  }
647
775
  }
648
- async function reconcileStrandedInputDelivery(store, delivery, task, role, run, session, input, now) {
776
+ async function reconcileStrandedInputDelivery(store, task, role, run, input, now) {
649
777
  const target = { kind: "role", taskId: task.id, roleName: role.name };
650
778
  const uncertain = (reason) => {
651
779
  store.markInputDeliveryUnknown(target, input.attemptId, reason, now);
@@ -657,51 +785,7 @@ async function reconcileStrandedInputDelivery(store, delivery, task, role, run,
657
785
  reason: "delivery-uncertain"
658
786
  };
659
787
  };
660
- if (input.providerFence === undefined
661
- || delivery.reconcileProviderInput === undefined
662
- || delivery.canRouteProviderInput?.(run.effective.adapterId) !== true) {
663
- return uncertain("Controller restarted without exact Provider input readback.");
664
- }
665
- try {
666
- // Exact readback is addressed entirely from the durable fence. It must not
667
- // resume a Session, create an Activation, or call any model-starting port.
668
- const reconciled = await delivery.reconcileProviderInput({
669
- taskId: task.id,
670
- roleName: role.name,
671
- agentId: run.effective.agentId,
672
- adapterId: run.effective.adapterId,
673
- launchId: session.launchId,
674
- nativeSessionId: session.nativeSessionId,
675
- attemptId: input.attemptId,
676
- mode: input.mode,
677
- fence: input.providerFence
678
- });
679
- if (reconciled === "accepted") {
680
- store.completeInputDelivery(target, input.attemptId, now);
681
- return {
682
- taskId: task.id,
683
- roleName: role.name,
684
- runId: run.id,
685
- status: "already-delivered"
686
- };
687
- }
688
- if (reconciled === "not-accepted") {
689
- store.resolveInputDeliveryNotAccepted(target, input.attemptId);
690
- return {
691
- taskId: task.id,
692
- roleName: role.name,
693
- runId: run.id,
694
- status: "skipped",
695
- reason: "not-ready"
696
- };
697
- }
698
- return uncertain(reconciled === "unavailable"
699
- ? "Provider input readback is unavailable after Controller restart."
700
- : "Provider input acceptance remains unknown after metadata readback.");
701
- }
702
- catch (error) {
703
- return uncertain(`Provider input readback failed: ${error instanceof Error ? error.message : String(error)}`);
704
- }
788
+ return uncertain("Controller restarted with unsettled Provider delivery; automatic resubmission is fenced.");
705
789
  }
706
790
  function continuationInput(task, role, run, attemptId, batch, resultSummaries) {
707
791
  const references = batch.refs.map((ref) => ("taskId" in ref
@@ -834,13 +918,15 @@ function preflightSession(role, effective, existing, mode, preflight) {
834
918
  }
835
919
  /**
836
920
  * A Controller restart can recover an exact launch-submitted Run while its
837
- * finite provider process is still alive. The host intentionally does not
921
+ * persistent Provider process is still alive. The Host intentionally does not
838
922
  * re-plan or re-submit that Run and therefore may return no native identity;
839
923
  * retain only the durable Session fenced to the same launch generation.
840
924
  */
841
925
  function validateLaunchSubmittedRecoverySession(role, effective, existing, mode, prepared, session) {
842
926
  if (session === null
843
- && prepared.inputSubmittedAtLaunch === true
927
+ && (prepared.turnAcceptedDuringLaunch === true
928
+ || prepared.turnDeliveryUnknownDuringLaunch === true
929
+ || prepared.turnRejectedDuringLaunch === true)
844
930
  && prepared.sessionStarted === false
845
931
  && existing?.launchId !== undefined
846
932
  && existing.launchId === prepared.launchId) {