@zq-silk/yui 0.13.8 → 0.13.9

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 (67) hide show
  1. package/README.md +9 -8
  2. package/dist/cli/commandCatalog.js +21 -2
  3. package/dist/cli.js +42 -13
  4. package/dist/commands/agentCommands.js +1 -1
  5. package/dist/commands/configCommands.js +1 -86
  6. package/dist/commands/executionAuditCommands.js +17 -16
  7. package/dist/commands/globalRoleCommands.js +4 -4
  8. package/dist/commands/sessionCommands.js +2 -6
  9. package/dist/commands/taskActor.js +1 -2
  10. package/dist/commands/taskCommands.js +92 -9
  11. package/dist/commands/taskRoleRuntimeStatus.js +3 -3
  12. package/dist/config/configCatalog.js +1 -6
  13. package/dist/config/yuiConfig.js +0 -79
  14. package/dist/controller/clientRuntime.js +40 -3
  15. package/dist/controller/controller.js +29 -52
  16. package/dist/controller/fileSchedulerStoreAdapter.js +305 -1056
  17. package/dist/controller/runtime.js +12 -5
  18. package/dist/controller/runtimeHookRunFence.js +3 -9
  19. package/dist/controller/runtimeLaunchCoordinator.js +44 -67
  20. package/dist/controller/structuredProviderObservation.js +18 -5
  21. package/dist/coordination/workMailbox.js +4 -4
  22. package/dist/execution/executionHealth.js +1 -1
  23. package/dist/executor/agentExecutor.js +92 -68
  24. package/dist/executor/executorRegistry.js +8 -20
  25. package/dist/executor/fileRoleLaunchPlanner.js +24 -90
  26. package/dist/executor/turnCompletion.js +5 -5
  27. package/dist/lifecycle/exactRunTerminalization.js +1 -1
  28. package/dist/observability/executionAudit.js +40 -94
  29. package/dist/operator/operatorSessionHistory.js +7 -5
  30. package/dist/role/role.js +1 -1
  31. package/dist/run/agentRun.js +4 -54
  32. package/dist/runtime/agentDriver.js +2 -0
  33. package/dist/runtime/agentError.js +114 -0
  34. package/dist/runtime/agentHost.js +55 -82
  35. package/dist/runtime/builtinAgentDrivers.js +21 -9
  36. package/dist/runtime/builtinAgentErrorMappers.js +150 -0
  37. package/dist/runtime/exactControlPlane.js +6 -12
  38. package/dist/runtime/index.js +0 -1
  39. package/dist/runtime/launchBroker.js +5 -19
  40. package/dist/runtime/lifecycleReservation.js +20 -4
  41. package/dist/runtime/providerRuntimeIdentity.js +3 -2
  42. package/dist/runtime/runtimeBinding.js +0 -27
  43. package/dist/runtime/runtimeObservation.js +7 -16
  44. package/dist/runtime/runtimeSessionCandidate.js +3 -10
  45. package/dist/runtime/sessionLaunchRequest.js +1 -2
  46. package/dist/runtime/sessionReconciliation.js +2 -2
  47. package/dist/runtime/structuredProviderHost.js +44 -79
  48. package/dist/runtime/taskRuntimeIsolation.js +0 -7
  49. package/dist/runtime/tmuxAdapters.js +6 -49
  50. package/dist/scheduler/activeRoleRunDelivery.js +218 -168
  51. package/dist/scheduler/leaderWakeupProcessor.js +126 -86
  52. package/dist/scheduler/roleRunLiveness.js +4 -1
  53. package/dist/scheduler/roleRunStall.js +7 -11
  54. package/dist/scheduler/wakeReason.js +4 -0
  55. package/dist/storage/migration/productionRegistry.js +332 -0
  56. package/dist/storage/sqliteSchema.js +54 -2
  57. package/dist/storage/sqliteStore.js +11 -44
  58. package/dist/storage/taskStore.js +7 -35
  59. package/package.json +1 -1
  60. package/skills/yui-leader/SKILL.md +30 -8
  61. package/skills/yui-operator/SKILL.md +14 -6
  62. package/skills/yui-runtime/SKILL.md +6 -4
  63. package/dist/lifecycle/providerErrorClass.js +0 -152
  64. package/dist/run/providerRetry.js +0 -226
  65. package/dist/run/providerRetryConfig.js +0 -27
  66. package/dist/runtime/providerErrorCodes.js +0 -278
  67. package/dist/runtime/providerRecoveryDecision.js +0 -55
@@ -1,12 +1,12 @@
1
1
  import { createAgentRun } from "../run/agentRun.js";
2
- import { createRunAssignment } from "../context/runContextContract.js";
2
+ import { createRunAssignment, serializeRunBootstrapEnvelope } from "../context/runContextContract.js";
3
3
  import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
4
4
  import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession } from "../executor/effectiveLaunch.js";
5
5
  import { roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
6
6
  import { RuntimeLifecycleBusyError } from "../runtime/lifecycleReservation.js";
7
- import { recordLeaderFailure } from "./leaderFailure.js";
8
7
  import { isSchedulerTaskWorkspaceReady } from "./ports.js";
9
8
  import { RuntimeLaunchError } from "../runtime/ports.js";
9
+ import { serializeAgentErrorRaw } from "../runtime/agentError.js";
10
10
  export async function processLeaderWakeups(store, delivery, now, selection) {
11
11
  const results = [];
12
12
  const wakeups = selection === undefined || selection.full
@@ -42,6 +42,13 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
42
42
  results.push({ taskId: task.id, status: "skipped", reason: "busy" });
43
43
  continue;
44
44
  }
45
+ // A durable wake is already the complete next intent. Do not materialize
46
+ // another Run while the Provider still owns the previous Turn: doing so
47
+ // would make its late terminal compete with a newer Run generation.
48
+ if (providerTurnIsUnsettled(store, task.id, role.name)) {
49
+ results.push({ taskId: task.id, status: "skipped", reason: "busy" });
50
+ continue;
51
+ }
45
52
  const reopening = wakeup.reasons.includes("task-reopened");
46
53
  let existingSession = store.getRoleSession(task.id, role.name, reopening ? undefined : role.effective.agentId);
47
54
  let effectiveSession = existingSession;
@@ -50,6 +57,7 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
50
57
  let run = null;
51
58
  let prepared;
52
59
  let preStartFencePersisted = false;
60
+ let mode = "new";
53
61
  try {
54
62
  const compatibleSession = existingSession !== null
55
63
  && (reopening
@@ -60,16 +68,15 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
60
68
  && !compatibleSession;
61
69
  if (hasNativeSession(existingSession) && !compatibleSession
62
70
  && !reopenIdentityDrift
63
- && existingSession.status !== "stopped" && existingSession.status !== "broken") {
71
+ && existingSession.status === "active") {
64
72
  throw new Error(`Leader Session is incompatible with desired effective launch: ${task.id}/${role.name}.`);
65
73
  }
66
74
  const resumableSession = hasNativeSession(existingSession)
67
- && existingSession.status !== "stopped"
68
- && existingSession.status !== "broken";
75
+ && existingSession.status === "active";
69
76
  // Reuse a healthy Session. A terminal Session is disposable and the next
70
77
  // Run starts fresh without consulting historical recovery records.
71
78
  const sessionSet = store.getTaskRoleSessionSet?.(task.id, role.name) ?? null;
72
- const mode = reopenIdentityDrift
79
+ mode = reopenIdentityDrift
73
80
  ? "new"
74
81
  : sessionSet === null
75
82
  ? resumableSession && compatibleSession ? "resume" : "new"
@@ -130,25 +137,17 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
130
137
  mode,
131
138
  runId: run.id,
132
139
  ...(mode === "resume" ? { nativeSessionId: existingSession.nativeSessionId } : {}),
140
+ ...(mode !== "resume" || existingSession?.launchId === undefined
141
+ ? {}
142
+ : { hostActivationId: existingSession.launchId }),
133
143
  beforeHostStart: (preflight) => {
134
144
  persistPreStartFence(store, task, role, run, existingSession, mode, preflight, now);
135
145
  effectiveSession = preflightSession(role, run.effective, existingSession, mode, preflight);
136
146
  preStartFencePersisted = true;
137
147
  }
138
148
  });
139
- // A managed Host may have submitted the exact Run Turn after its
140
- // two-phase launch handshake. Once preparation returns that transport
141
- // fact, any
142
- // later readiness or aggregate-write failure is delivery uncertainty,
143
- // not a launch failure: preserve the Run and its reservation for the
144
- // matching provider Hook instead of terminalizing it.
145
- deliveryAttempted = prepared.turnAcceptedDuringLaunch === true
146
- || prepared.turnDeliveryUnknownDuringLaunch === true;
147
- // Persist the exact preparation fence before waiting on provider
148
- // readiness. A pre-input lifecycle Hook may fire during that wait; it
149
- // must be able to resolve this Run/Session/launch generation from durable
150
- // state rather than an in-memory delivery object. Fresh providers that
151
- // discover their native Session later intentionally persist `null`.
149
+ // Persist the Session identity before submitting the Leader Turn.
150
+ // Starting/restoring a Session is independent from sending Run input.
152
151
  if (!preStartFencePersisted) {
153
152
  const preparedFenceSession = prepared.session === undefined
154
153
  ? existingSession
@@ -164,9 +163,6 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
164
163
  });
165
164
  }
166
165
  const ready = await delivery.waitUntilReady(prepared);
167
- deliveryAttempted = deliveryAttempted
168
- || ready.prepared.turnAcceptedDuringLaunch === true
169
- || ready.prepared.turnDeliveryUnknownDuringLaunch === true;
170
166
  const latestTask = store.getTask(task.id);
171
167
  if (latestTask === null
172
168
  || latestTask.status !== "active"
@@ -193,10 +189,13 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
193
189
  : { launchId: ready.prepared.launchId }),
194
190
  now
195
191
  });
196
- if (ready.prepared.turnBusyDuringLaunch === true) {
197
- // A direct Desktop/user Turn is ordinary thread activity. Keep the
198
- // claimed Run and wake batch intact; active-Run delivery retries after
199
- // that Turn reaches its native terminal boundary.
192
+ deliveryAttempted = true;
193
+ const outcome = await delivery.sendOnce({
194
+ delivery: ready,
195
+ receiptId: formatAgentRunReceiptId(task.id, run.id),
196
+ text: serializeRunBootstrapEnvelope(run.bootstrapEnvelope)
197
+ });
198
+ if (outcome === "busy" || outcome === "unavailable") {
200
199
  delivery.forgetPrepared?.({
201
200
  taskId: task.id,
202
201
  roleName: role.name,
@@ -209,12 +208,12 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
209
208
  taskId: task.id,
210
209
  runId: run.id,
211
210
  status: "skipped",
212
- reason: "busy"
211
+ reason: outcome === "busy" ? "busy" : "not-ready"
213
212
  });
214
213
  continue;
215
214
  }
216
- if (ready.prepared.turnRejectedDuringLaunch === true) {
217
- const persisted = store.saveRoleRunDeliveryFailure({
215
+ if (outcome === "rejected") {
216
+ const terminal = store.saveRoleRunDeliveryFailure({
218
217
  taskId: task.id,
219
218
  roleName: role.name,
220
219
  agentId: run.effective.agentId,
@@ -227,43 +226,10 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
227
226
  ...(ready.prepared.launchId === undefined
228
227
  ? {}
229
228
  : { launchId: ready.prepared.launchId }),
230
- summary: `Provider conclusively rejected the exact initial Leader Run input: ${run.id}.`,
229
+ summary: "Provider rejected the Leader Turn before acceptance; Operator recovery is required.",
231
230
  cleanupRequired: false,
232
231
  now
233
232
  });
234
- if (persisted === "failed") {
235
- delivery.forgetPrepared?.({
236
- taskId: task.id,
237
- roleName: role.name,
238
- runId: run.id,
239
- ...(ready.prepared.launchId === undefined
240
- ? {}
241
- : { launchId: ready.prepared.launchId })
242
- });
243
- }
244
- results.push({
245
- taskId: task.id,
246
- runId: run.id,
247
- status: persisted === "failed" ? "failed" : "skipped",
248
- reason: "provider-rejected",
249
- terminalized: persisted === "failed",
250
- ...(persisted === "failed"
251
- ? {}
252
- : { error: "Run state changed while recording exact Provider rejection." })
253
- });
254
- continue;
255
- }
256
- if (ready.prepared.turnDeliveryUnknownDuringLaunch === true) {
257
- store.saveRoleRunDelivery({
258
- task,
259
- role,
260
- run,
261
- session: effectiveSession,
262
- ...(ready.prepared.launchId === undefined
263
- ? {}
264
- : { launchId: ready.prepared.launchId }),
265
- now
266
- });
267
233
  delivery.forgetPrepared?.({
268
234
  taskId: task.id,
269
235
  roleName: role.name,
@@ -275,15 +241,13 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
275
241
  results.push({
276
242
  taskId: task.id,
277
243
  runId: run.id,
278
- status: "skipped",
279
- reason: "delivery-uncertain"
244
+ status: terminal === "failed" ? "failed" : "skipped",
245
+ reason: "provider-rejected",
246
+ ...(terminal === "failed" ? { terminalized: true } : {})
280
247
  });
281
248
  continue;
282
249
  }
283
- if (ready.prepared.turnAcceptedDuringLaunch === true) {
284
- // The Agent Host submitted the exact first Turn through structured
285
- // Provider control. Persist the transport fence without a second write;
286
- // the matching structured acknowledgement owns Run acceptance.
250
+ if (outcome === "delivery-unknown") {
287
251
  store.saveRoleRunDelivery({
288
252
  task,
289
253
  role,
@@ -302,10 +266,34 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
302
266
  ? {}
303
267
  : { launchId: ready.prepared.launchId })
304
268
  });
305
- results.push({ taskId: task.id, runId: run.id, status: "dispatched" });
269
+ results.push({
270
+ taskId: task.id,
271
+ runId: run.id,
272
+ status: "skipped",
273
+ reason: "delivery-uncertain"
274
+ });
306
275
  continue;
307
276
  }
308
- throw new Error("Managed Provider launch completed without an initial Turn outcome.");
277
+ store.saveRoleRunDelivery({
278
+ task,
279
+ role,
280
+ run,
281
+ session: effectiveSession,
282
+ ...(ready.prepared.launchId === undefined
283
+ ? {}
284
+ : { launchId: ready.prepared.launchId }),
285
+ now
286
+ });
287
+ delivery.forgetPrepared?.({
288
+ taskId: task.id,
289
+ roleName: role.name,
290
+ runId: run.id,
291
+ ...(ready.prepared.launchId === undefined
292
+ ? {}
293
+ : { launchId: ready.prepared.launchId })
294
+ });
295
+ results.push({ taskId: task.id, runId: run.id, status: "dispatched" });
296
+ continue;
309
297
  }
310
298
  catch (error) {
311
299
  const detail = error instanceof Error ? error.message : String(error);
@@ -331,6 +319,57 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
331
319
  });
332
320
  continue;
333
321
  }
322
+ store.recordAgentError?.({
323
+ taskId: task.id,
324
+ roleName: role.name,
325
+ runId: run.id,
326
+ source: error instanceof RuntimeLaunchError ? "host" : "yui",
327
+ phase: deliveryAttempted
328
+ ? "turn-submit"
329
+ : mode === "new" ? "session-start" : "session-restore",
330
+ message: detail,
331
+ raw: serializeAgentErrorRaw(error),
332
+ inputDisposition: deliveryAttempted ? "unknown" : "not-accepted"
333
+ }, now);
334
+ if (!deliveryAttempted) {
335
+ const failedSession = store.getRoleSession(task.id, role.name, run.effective.agentId);
336
+ const terminal = store.saveRoleRunDeliveryFailure({
337
+ taskId: task.id,
338
+ roleName: role.name,
339
+ agentId: run.effective.agentId,
340
+ adapterId: run.effective.adapterId,
341
+ runId: run.id,
342
+ mailboxBatchId: formatAgentRunReceiptId(task.id, run.id),
343
+ ...(failedSession?.nativeSessionId === undefined
344
+ ? {}
345
+ : { nativeSessionId: failedSession.nativeSessionId }),
346
+ ...(prepared?.launchId === undefined && failedSession?.launchId === undefined
347
+ ? {}
348
+ : { launchId: prepared?.launchId ?? failedSession.launchId }),
349
+ summary: `Leader Session preparation failed before Turn acceptance: ${message}`,
350
+ cleanupRequired: false,
351
+ now
352
+ });
353
+ if (terminal === "failed") {
354
+ delivery.forgetPrepared?.({
355
+ taskId: task.id,
356
+ roleName: role.name,
357
+ runId: run.id,
358
+ ...(prepared?.launchId === undefined
359
+ ? {}
360
+ : { launchId: prepared.launchId })
361
+ });
362
+ results.push({
363
+ taskId: task.id,
364
+ runId: run.id,
365
+ status: "failed",
366
+ reason: "unavailable",
367
+ error: message,
368
+ terminalized: true
369
+ });
370
+ continue;
371
+ }
372
+ }
334
373
  // A managed Provider lifecycle operation may still be settling. Keep
335
374
  // this newly-claimed Run as the sole mailbox owner; active-Run delivery
336
375
  // retries after the lifecycle lane clears.
@@ -367,20 +406,13 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
367
406
  ? {}
368
407
  : { launchId: prepared.launchId })
369
408
  });
370
- const failureResult = store.saveLeaderDispatchFailure({
371
- task,
372
- role,
373
- session: effectiveSession,
374
- claimed: { run, wakeup },
375
- failure: recordLeaderFailure(task.id, effectiveSession?.nativeSessionId ?? "(unregistered)", message, now, store.getLeaderFailure(task.id)),
376
- now
409
+ results.push({
410
+ taskId: task.id,
411
+ runId: run.id,
412
+ status: "skipped",
413
+ reason: "not-ready",
414
+ error: message
377
415
  });
378
- if (failureResult === "state-changed") {
379
- results.push({ taskId: task.id, status: "skipped", reason: "state-changed" });
380
- }
381
- else {
382
- results.push({ taskId: task.id, runId: run.id, status: "failed", error: message });
383
- }
384
416
  continue;
385
417
  }
386
418
  results.push({
@@ -394,6 +426,14 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
394
426
  }
395
427
  return results;
396
428
  }
429
+ function providerTurnIsUnsettled(store, taskId, roleName) {
430
+ if (store.getRoleSession(taskId, roleName)?.status !== "active")
431
+ return false;
432
+ const status = store.getTaskRoleSessionSet?.(taskId, roleName)
433
+ ?.providerBinding?.turn?.status;
434
+ return status !== undefined
435
+ && ["submitting", "accepted", "running", "delivery-unknown"].includes(status);
436
+ }
397
437
  function validateReadySession(role, effective, existing, mode, session) {
398
438
  if (mode === "new" && session === null)
399
439
  return null;
@@ -416,7 +456,7 @@ function validateReadySession(role, effective, existing, mode, session) {
416
456
  }
417
457
  return {
418
458
  ...session,
419
- status: "running",
459
+ status: "active",
420
460
  ...(mode === "resume" && existing !== null
421
461
  ? { effective: existing.effective }
422
462
  : {})
@@ -449,7 +489,7 @@ function preflightSession(role, effective, existing, mode, preflight) {
449
489
  nativeSessionId: preflight.nativeSessionId,
450
490
  launchId: preflight.launchId,
451
491
  ...(preflight.sessionTitle === undefined ? {} : { title: preflight.sessionTitle }),
452
- status: "ready",
492
+ status: "active",
453
493
  effective: preflight.effective
454
494
  };
455
495
  return validateReadySession(role, effective, existing, mode, session);
@@ -125,7 +125,10 @@ export async function reconcileExitedRoleRuns(store, delivery, now, selection, e
125
125
  ...(run.workspace === undefined ? {} : { managedWorkspace: run.workspace }),
126
126
  mode: "resume",
127
127
  runId: run.id,
128
- nativeSessionId: session.nativeSessionId
128
+ nativeSessionId: session.nativeSessionId,
129
+ ...(session.launchId === undefined
130
+ ? {}
131
+ : { hostActivationId: session.launchId })
129
132
  });
130
133
  store.saveRoleRunPrepared({
131
134
  task,
@@ -56,16 +56,12 @@ export function projectRoleRunHealth(input) {
56
56
  const session = input.nativeSession;
57
57
  const nativeSession = session === null || session === undefined
58
58
  ? "missing"
59
- : session.status === "stopped"
60
- ? "stopped"
61
- : session.status === "broken"
62
- ? "broken"
63
- : session.status !== "ready" && session.status !== "running"
64
- ? "unknown"
65
- : !hasResourceIdentityText(session.nativeSessionId)
66
- && !hasResourceIdentityText(session.launchId)
67
- ? "unknown"
68
- : "matching";
59
+ : session.status === "ended"
60
+ ? session.endReason === "failed" ? "broken" : "stopped"
61
+ : !hasResourceIdentityText(session.nativeSessionId)
62
+ && !hasResourceIdentityText(session.launchId)
63
+ ? "unknown"
64
+ : "matching";
69
65
  const resourceActivity = hostLiveness === "present"
70
66
  && nativeSession === "matching"
71
67
  && resourceEvidenceIsFresh(input.resource, input.now, windowMs, diagnosticAfterMs)
@@ -689,7 +685,7 @@ export async function reconcileStalledRoleRuns(store, delivery, now, selection,
689
685
  || (candidate.session.agentId === runAgentId
690
686
  && candidate.session.adapterId === runAdapterId);
691
687
  const sessionUsable = candidate.session === null
692
- || (candidate.session.status !== "stopped" && candidate.session.status !== "broken");
688
+ || candidate.session.status === "active";
693
689
  const expectedResourceIdentity = candidate.session === null
694
690
  ? undefined
695
691
  : {
@@ -17,8 +17,12 @@ export const WAKE_REASON_KINDS = Object.freeze([
17
17
  "task-orphaned",
18
18
  "input-timeout",
19
19
  "role-run-stalled",
20
+ "agent-error",
21
+ "agent-session-recovery",
20
22
  "review-failed",
21
23
  "task-reopened",
24
+ "agent-error",
25
+ "agent-session-recovery",
22
26
  "leader-run-failed",
23
27
  "role-run-failed",
24
28
  "job-finished",