@zq-silk/yui 0.13.7 → 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 (78) hide show
  1. package/ARCHITECTURE.md +40 -4
  2. package/README.md +23 -4
  3. package/dist/cli/commandCatalog.js +21 -2
  4. package/dist/cli/updatePorts.js +4 -4
  5. package/dist/cli.js +43 -14
  6. package/dist/commands/agentCommands.js +1 -1
  7. package/dist/commands/configCommands.js +1 -86
  8. package/dist/commands/executionAuditCommands.js +17 -16
  9. package/dist/commands/globalRoleCommands.js +4 -4
  10. package/dist/commands/sessionCommands.js +2 -6
  11. package/dist/commands/taskActor.js +1 -2
  12. package/dist/commands/taskCommands.js +93 -21
  13. package/dist/commands/taskContextCommand.js +1 -1
  14. package/dist/commands/taskExecutionCommands.js +0 -5
  15. package/dist/commands/taskOverviewCommand.js +5 -1
  16. package/dist/commands/taskRoleRuntimeStatus.js +3 -16
  17. package/dist/config/configCatalog.js +1 -6
  18. package/dist/config/yuiConfig.js +0 -79
  19. package/dist/context/sessionBootstrapManifest.js +26 -23
  20. package/dist/controller/clientRuntime.js +40 -3
  21. package/dist/controller/controller.js +29 -54
  22. package/dist/controller/fileSchedulerStoreAdapter.js +372 -1117
  23. package/dist/controller/runtime.js +12 -5
  24. package/dist/controller/runtimeHookRunFence.js +3 -9
  25. package/dist/controller/runtimeLaunchCoordinator.js +44 -67
  26. package/dist/controller/structuredProviderObservation.js +18 -5
  27. package/dist/coordination/workMailbox.js +4 -4
  28. package/dist/execution/executionHealth.js +1 -1
  29. package/dist/executor/agentExecutor.js +103 -93
  30. package/dist/executor/executorRegistry.js +8 -20
  31. package/dist/executor/fileRoleLaunchPlanner.js +26 -93
  32. package/dist/executor/turnCompletion.js +5 -5
  33. package/dist/lifecycle/exactRunTerminalization.js +2 -4
  34. package/dist/observability/executionAudit.js +40 -94
  35. package/dist/operator/operatorSessionHistory.js +7 -5
  36. package/dist/output/rolePresentation.js +0 -1
  37. package/dist/repository/taskWorkspacePreparer.js +0 -1
  38. package/dist/role/role.js +13 -21
  39. package/dist/run/agentRun.js +4 -54
  40. package/dist/runtime/agentDriver.js +2 -0
  41. package/dist/runtime/agentError.js +114 -0
  42. package/dist/runtime/agentHost.js +55 -82
  43. package/dist/runtime/builtinAgentDrivers.js +21 -9
  44. package/dist/runtime/builtinAgentErrorMappers.js +150 -0
  45. package/dist/runtime/exactControlPlane.js +6 -12
  46. package/dist/runtime/index.js +1 -2
  47. package/dist/runtime/launchBroker.js +5 -19
  48. package/dist/runtime/lifecycleReservation.js +20 -4
  49. package/dist/runtime/providerRuntimeIdentity.js +11 -19
  50. package/dist/runtime/runtimeBinding.js +0 -27
  51. package/dist/runtime/runtimeObservation.js +7 -16
  52. package/dist/runtime/runtimeSessionCandidate.js +3 -10
  53. package/dist/runtime/sessionLaunchRequest.js +1 -2
  54. package/dist/runtime/sessionReconciliation.js +2 -2
  55. package/dist/runtime/structuredProviderHost.js +44 -79
  56. package/dist/runtime/taskRuntimeIsolation.js +0 -7
  57. package/dist/runtime/tmuxAdapters.js +6 -49
  58. package/dist/scheduler/activeRoleRunDelivery.js +220 -178
  59. package/dist/scheduler/activeTaskProgress.js +1 -4
  60. package/dist/scheduler/leaderWakeupProcessor.js +123 -88
  61. package/dist/scheduler/roleRunLiveness.js +4 -1
  62. package/dist/scheduler/roleRunStall.js +10 -14
  63. package/dist/scheduler/wakeReason.js +4 -0
  64. package/dist/storage/migration/productionRegistry.js +475 -0
  65. package/dist/storage/sqliteSchema.js +54 -2
  66. package/dist/storage/sqliteStore.js +11 -44
  67. package/dist/storage/taskStore.js +8 -36
  68. package/dist/web/webSnapshot.js +3 -0
  69. package/i18n/README.zh-CN.md +11 -3
  70. package/package.json +1 -1
  71. package/skills/yui-leader/SKILL.md +30 -8
  72. package/skills/yui-operator/SKILL.md +14 -6
  73. package/skills/yui-runtime/SKILL.md +11 -8
  74. package/dist/lifecycle/providerErrorClass.js +0 -152
  75. package/dist/run/providerRetry.js +0 -226
  76. package/dist/run/providerRetryConfig.js +0 -27
  77. package/dist/runtime/providerErrorCodes.js +0 -278
  78. package/dist/runtime/providerRecoveryDecision.js +0 -55
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { hasRecentTurnId, rememberRecentTurnId, validatePendingTurnCompletion, validateRecentTurnIds } from "./turnCompletion.js";
3
3
  import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession, validateEffectiveLaunchSnapshot } from "./effectiveLaunch.js";
4
- import { currentProviderActivation, endProviderActivation, rebindProviderRuntimeRun, validateProviderRuntimeBinding } from "../runtime/providerRuntimeIdentity.js";
4
+ import { currentProviderActivation, endProviderActivation, settleProviderTurn, settleProviderTurnSubmission, validateProviderRuntimeBinding } from "../runtime/providerRuntimeIdentity.js";
5
5
  import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
6
6
  export function createRoleSessionSet(owner, activeAgentId, now) {
7
7
  const base = {
@@ -11,10 +11,10 @@ export function createRoleSessionSet(owner, activeAgentId, now) {
11
11
  updatedAt: now.toISOString()
12
12
  };
13
13
  return owner.scope === "global"
14
- ? { ...base, schemaVersion: 3 }
14
+ ? { ...base, schemaVersion: 4 }
15
15
  : {
16
16
  ...base,
17
- schemaVersion: 6,
17
+ schemaVersion: 8,
18
18
  inFlight: null,
19
19
  providerBinding: null
20
20
  };
@@ -28,7 +28,7 @@ export function activeRoleAgentSession(set) {
28
28
  /** The immutable snapshot that remains actual until its native process terminates. */
29
29
  export function activeLiveRoleAgentSession(set) {
30
30
  const session = activeRoleAgentSession(set);
31
- return session === null || session.status === "stopped" || session.status === "broken"
31
+ return session === null || session.status === "ended"
32
32
  ? null
33
33
  : session;
34
34
  }
@@ -73,13 +73,13 @@ export function recordRoleAgentSession(set, input, now) {
73
73
  throw new Error(`Role Agent session effective launch cannot change: ${agentId}.`);
74
74
  }
75
75
  if (existing !== undefined && existing.nativeSessionId !== nativeSessionId
76
- && existing.status !== "stopped" && existing.status !== "broken") {
76
+ && existing.status === "active") {
77
77
  throw new Error(`Live Role Agent native session cannot be replaced: ${agentId}.`);
78
78
  }
79
79
  const timestamp = now.toISOString();
80
80
  const continuing = existing?.nativeSessionId === nativeSessionId ? existing : undefined;
81
81
  const session = {
82
- schemaVersion: 3,
82
+ schemaVersion: 4,
83
83
  agentId,
84
84
  adapterId,
85
85
  nativeSessionId,
@@ -91,6 +91,9 @@ export function recordRoleAgentSession(set, input, now) {
91
91
  policy: input.policy,
92
92
  effective: continuing?.effective ?? effective,
93
93
  status: input.status,
94
+ ...(input.status === "ended"
95
+ ? { endReason: input.endReason ?? continuing?.endReason ?? "stopped" }
96
+ : {}),
94
97
  recentCompletedTurnIds: continuing?.recentCompletedTurnIds ?? [],
95
98
  createdAt: continuing?.createdAt ?? timestamp,
96
99
  updatedAt: timestamp
@@ -127,7 +130,7 @@ export function replaceTaskRoleAgentSession(set, input, now) {
127
130
  if (existing === undefined || existing.nativeSessionId === input.nativeSessionId) {
128
131
  return recordRoleAgentSession(set, input, now);
129
132
  }
130
- const terminalized = updateRoleAgentSessionStatus(set, input.agentId, "stopped", now);
133
+ const terminalized = updateRoleAgentSessionStatus(set, input.agentId, "ended", now, "stopped");
131
134
  return recordRoleAgentSession(terminalized, input, now);
132
135
  }
133
136
  /**
@@ -147,7 +150,7 @@ export function createRoleAgentSession(input, now) {
147
150
  const set = createRoleSessionSet({ scope: "global", roleName: "session-factory" }, agentId, now);
148
151
  return recordRoleAgentSession(set, input, now).sessions[agentId];
149
152
  }
150
- export function updateRoleAgentSessionStatus(set, agentId, status, now) {
153
+ export function updateRoleAgentSessionStatus(set, agentId, status, now, endReason) {
151
154
  validateRoleSessionSet(set);
152
155
  const normalizedAgentId = requireSafeIdentity(agentId, "Agent id");
153
156
  if (!isAgentSessionStatus(status)) {
@@ -162,7 +165,12 @@ export function updateRoleAgentSessionStatus(set, agentId, status, now) {
162
165
  ...set,
163
166
  sessions: {
164
167
  ...set.sessions,
165
- [normalizedAgentId]: { ...existing, status, updatedAt: timestamp }
168
+ [normalizedAgentId]: {
169
+ ...existing,
170
+ status,
171
+ ...(status === "ended" ? { endReason: endReason ?? "stopped" } : { endReason: undefined }),
172
+ updatedAt: timestamp
173
+ }
166
174
  },
167
175
  updatedAt: timestamp
168
176
  };
@@ -174,7 +182,7 @@ export function retireTaskRoleSessionsForWorkspace(set, now) {
174
182
  if (set.inFlight !== null) {
175
183
  throw new Error("Cannot retire a Task Role session with unsettled Run state.");
176
184
  }
177
- const live = Object.values(set.sessions).find(({ status }) => status !== "stopped" && status !== "broken");
185
+ const live = Object.values(set.sessions).find(({ status }) => status === "active");
178
186
  if (live !== undefined) {
179
187
  throw new Error(`Task Role session must be stopped before workspace migration: ${live.agentId}.`);
180
188
  }
@@ -205,13 +213,18 @@ export function retireConfirmedAbsentInactiveTaskRolePlaceholders(set, now) {
205
213
  const sessions = Object.fromEntries(Object.entries(set.sessions).map(([agentId, session]) => {
206
214
  const isNeverStartedInactiveClaude = agentId !== set.activeAgentId
207
215
  && session.adapterId === "claude"
208
- && (session.status === "reserved" || session.status === "ready")
216
+ && session.status === "active"
209
217
  && session.launchId === undefined
210
218
  && session.recentCompletedTurnIds.length === 0;
211
219
  if (!isNeverStartedInactiveClaude)
212
220
  return [agentId, session];
213
221
  changed = true;
214
- return [agentId, { ...session, status: "broken", updatedAt: timestamp }];
222
+ return [agentId, {
223
+ ...session,
224
+ status: "ended",
225
+ endReason: "failed",
226
+ updatedAt: timestamp
227
+ }];
215
228
  }));
216
229
  if (!changed)
217
230
  return set;
@@ -231,7 +244,7 @@ export function rememberRoleAgentCompletedTurn(set, agentId, nativeSessionId, tu
231
244
  if (session.nativeSessionId !== requireText(nativeSessionId, "Native session id")) {
232
245
  throw new Error("Completed Turn native session does not match the Role Agent session.");
233
246
  }
234
- const normalizedTurnId = requireSafeIdentity(turnId, "Turn id");
247
+ const normalizedTurnId = requireText(turnId, "Turn id");
235
248
  if (hasRecentTurnId(session.recentCompletedTurnIds, normalizedTurnId))
236
249
  return set;
237
250
  const recentCompletedTurnIds = rememberRecentTurnId(session.recentCompletedTurnIds, normalizedTurnId);
@@ -244,9 +257,6 @@ export function rememberRoleAgentCompletedTurn(set, agentId, nativeSessionId, tu
244
257
  ...set.sessions,
245
258
  [normalizedAgentId]: {
246
259
  ...session,
247
- status: session.status === "stopped" || session.status === "broken"
248
- ? session.status
249
- : "ready",
250
260
  recentCompletedTurnIds,
251
261
  updatedAt: timestamp
252
262
  }
@@ -268,13 +278,13 @@ export function roleAgentSessionResumeMode(set, agentId, desired) {
268
278
  // permits the normal fresh-generation path.
269
279
  if (typeof session.nativeSessionId !== "string"
270
280
  || session.nativeSessionId.trim().length === 0) {
271
- if (session.status !== "stopped" && session.status !== "broken") {
281
+ if (session.status === "active") {
272
282
  throw new Error(`Role Agent session has no native Session identity: ${agentId}. `
273
283
  + "Restore the exact native Session or explicitly stop it before starting a fresh Session.");
274
284
  }
275
285
  return "new";
276
286
  }
277
- if (session.status === "stopped" || session.status === "broken") {
287
+ if (session.status === "ended") {
278
288
  return "new";
279
289
  }
280
290
  const compatible = set.owner.scope === "task"
@@ -298,15 +308,14 @@ export function bindTaskRoleRun(set, fence, preparedAt, mode) {
298
308
  throw new Error("Task Role session set already has an in-flight Run.");
299
309
  }
300
310
  const timestamp = requireDate(preparedAt, "Task Role Run preparedAt");
301
- // A fresh Session follows physical cleanup and does not inherit execution
302
- // fences from the disposable old Conversation. Runtime events retain its
303
- // audit evidence; the new Provider binds a fresh control identity when it is
304
- // observed. Resume keeps the existing exact Conversation fence.
311
+ // A fresh Session follows physical cleanup and does not inherit runtime
312
+ // evidence from the disposable old Conversation. A resumed Session keeps
313
+ // that evidence unchanged here: claiming a durable Run is a workflow
314
+ // mutation, not a Provider operation. AgentHost binds the Conversation to
315
+ // this Run only when it can actually submit the next serialized Turn.
305
316
  const providerBinding = mode === "new"
306
317
  ? null
307
- : set.providerBinding !== null
308
- ? rebindProviderRuntimeRun(set.providerBinding, normalized.runId)
309
- : null;
318
+ : set.providerBinding;
310
319
  const updated = {
311
320
  ...set,
312
321
  inFlight: { ...normalized, preparedAt: timestamp },
@@ -315,34 +324,9 @@ export function bindTaskRoleRun(set, fence, preparedAt, mode) {
315
324
  };
316
325
  return validateRoleSessionSet(updated);
317
326
  }
318
- /** Re-fences the same active Run for an exact Provider retry without losing its Conversation. */
319
- export function prepareTaskRoleRunRedispatch(set, fence, preparedAt) {
320
- validateRoleSessionSet(set);
321
- const normalized = normalizeTaskRoleRunFence(fence);
322
- if (set.inFlight === null
323
- || set.inFlight.agentId !== normalized.agentId
324
- || set.inFlight.runId !== normalized.runId) {
325
- throw new Error("Provider retry does not match the in-flight Task Run.");
326
- }
327
- if (set.providerBinding !== null
328
- && set.providerBinding.turn !== null
329
- && ["submitting", "accepted", "running", "delivery-unknown"]
330
- .includes(set.providerBinding.turn.status)) {
331
- throw new Error("Provider retry cannot redispatch an unsettled Turn.");
332
- }
333
- const timestamp = requireDate(preparedAt, "Task Role retry preparedAt");
334
- return validateRoleSessionSet({
335
- ...set,
336
- inFlight: { ...normalized, preparedAt: timestamp },
337
- updatedAt: timestamp
338
- });
339
- }
340
327
  export function bindTaskRoleProviderRuntime(set, binding, updatedAt) {
341
328
  validateRoleSessionSet(set);
342
329
  const normalized = validateProviderRuntimeBinding(binding);
343
- if (set.inFlight === null || set.inFlight.runId !== normalized.runId) {
344
- throw new Error("Provider Runtime Binding does not match the in-flight Run.");
345
- }
346
330
  if (set.providerBinding !== null) {
347
331
  if (JSON.stringify(set.providerBinding) === JSON.stringify(normalized))
348
332
  return set;
@@ -358,7 +342,6 @@ export function updateTaskRoleProviderRuntime(set, binding, updatedAt) {
358
342
  validateRoleSessionSet(set);
359
343
  const normalized = validateProviderRuntimeBinding(binding);
360
344
  if (set.providerBinding === null
361
- || normalized.runId !== set.providerBinding.runId
362
345
  || normalized.providerNamespace !== set.providerBinding.providerNamespace
363
346
  || normalized.accountScope !== set.providerBinding.accountScope) {
364
347
  throw new Error("Provider Runtime Binding identity cannot change in place.");
@@ -370,33 +353,65 @@ export function updateTaskRoleProviderRuntime(set, binding, updatedAt) {
370
353
  });
371
354
  }
372
355
  /**
373
- * Records an exact local Host exit without forgetting the Provider
374
- * Conversation. The ended Activation releases writer authority, while the
375
- * current Conversation and any unsettled Turn remain durable recovery fences.
356
+ * Detaches a confirmed-dead local Host without ending its resumable native
357
+ * Session. The Host launch and Provider Activation are disposable execution
358
+ * facts; the native Session remains the durable continuation identity.
376
359
  */
377
- export function stopTaskRoleRuntimeAfterPhysicalExit(set, now) {
360
+ export function detachRoleAgentSessionHost(set, now) {
378
361
  validateRoleSessionSet(set);
379
362
  const active = set.sessions[set.activeAgentId];
380
- if (active === undefined)
381
- return set;
382
- const activation = set.providerBinding === null
383
- ? null
384
- : currentProviderActivation(set.providerBinding);
385
- if (active.status === "stopped") {
386
- if (activation !== null) {
387
- throw new Error("Stopped Task Role Session cannot retain a live Provider Activation.");
388
- }
363
+ if (active === undefined || active.status === "ended")
389
364
  return set;
365
+ const timestamp = requireDate(now, "Role Host detach timestamp");
366
+ const { launchId: _launchId, endReason: _endReason, ...session } = active;
367
+ let updated = validateRoleSessionSet({
368
+ ...set,
369
+ sessions: {
370
+ ...set.sessions,
371
+ [set.activeAgentId]: {
372
+ ...session,
373
+ status: "active",
374
+ updatedAt: timestamp
375
+ }
376
+ },
377
+ updatedAt: timestamp
378
+ });
379
+ if (updated.owner.scope !== "task")
380
+ return updated;
381
+ let taskSet = updated;
382
+ let binding = taskSet.providerBinding;
383
+ const turn = binding?.turn;
384
+ if (binding !== null && binding !== undefined && turn !== null && turn !== undefined
385
+ && ["accepted", "running"].includes(turn.status)) {
386
+ binding = settleProviderTurn(binding, {
387
+ turnId: turn.turnId,
388
+ status: "cancelled",
389
+ settledAt: timestamp,
390
+ reason: "runtime-physical-exit"
391
+ });
392
+ taskSet = updateTaskRoleProviderRuntime(taskSet, binding, now);
393
+ }
394
+ else if (binding !== null && binding !== undefined && turn !== null && turn !== undefined
395
+ && ["submitting", "delivery-unknown"].includes(turn.status)) {
396
+ binding = settleProviderTurnSubmission(binding, {
397
+ attemptId: turn.attemptId,
398
+ status: "rejected",
399
+ resolvedAt: timestamp,
400
+ reason: "runtime-physical-exit"
401
+ });
402
+ taskSet = updateTaskRoleProviderRuntime(taskSet, binding, now);
390
403
  }
391
- let updated = updateRoleAgentSessionStatus(set, set.activeAgentId, "stopped", now);
404
+ const activation = binding === null
405
+ ? null
406
+ : currentProviderActivation(binding);
392
407
  if (activation !== null) {
393
- updated = updateTaskRoleProviderRuntime(updated, endProviderActivation(updated.providerBinding, activation.activationId, {
408
+ taskSet = updateTaskRoleProviderRuntime(taskSet, endProviderActivation(binding, activation.activationId, {
394
409
  status: "ended",
395
410
  endedAt: now.toISOString(),
396
411
  reason: "runtime-physical-exit"
397
412
  }), now);
398
413
  }
399
- return updated;
414
+ return taskSet;
400
415
  }
401
416
  export function markTaskRoleRunPushed(set, fence, pushedAt) {
402
417
  validateRoleSessionSet(set);
@@ -450,7 +465,7 @@ export function recordTaskRoleTurnBoundary(set, input, completedAt) {
450
465
  assertTaskRoleSessionSet(set);
451
466
  const agentId = requireSafeIdentity(input.agentId, "Agent id");
452
467
  const nativeSessionId = requireText(input.nativeSessionId, "Native session id");
453
- const turnId = requireSafeIdentity(input.turnId, "Turn id");
468
+ const turnId = requireText(input.turnId, "Turn id");
454
469
  const session = set.sessions[agentId];
455
470
  if (session === undefined || session.nativeSessionId !== nativeSessionId) {
456
471
  throw new Error("Completed Turn has no matching Role Agent native session.");
@@ -461,16 +476,12 @@ export function recordTaskRoleTurnBoundary(set, input, completedAt) {
461
476
  throw new Error("Completed Turn Agent does not match the in-flight Run.");
462
477
  }
463
478
  const timestamp = requireDate(completedAt, "Turn completedAt");
464
- const status = session.status === "stopped" || session.status === "broken"
465
- ? session.status
466
- : "ready";
467
479
  return validateRoleSessionSet({
468
480
  ...set,
469
481
  sessions: {
470
482
  ...set.sessions,
471
483
  [agentId]: {
472
484
  ...session,
473
- status,
474
485
  recentCompletedTurnIds: rememberRecentTurnId(session.recentCompletedTurnIds, turnId),
475
486
  updatedAt: timestamp
476
487
  }
@@ -525,28 +536,22 @@ export function clearTaskRoleRun(set, fence, clearedAt) {
525
536
  return validateRoleSessionSet(updated);
526
537
  }
527
538
  /**
528
- * Applies an authoritative application-level Run terminal fact to the native
529
- * session fence. A later native Hook is only advisory and must not be required
530
- * to make the next Run dispatchable.
539
+ * Applies an authoritative application-level Run terminal fact to its exact
540
+ * delivery receipt. Provider lifecycle remains independent: only the matching
541
+ * native Turn boundary may move the Session from running to ready. AgentHost
542
+ * serializes a retained next delivery while that Turn is still active.
531
543
  */
532
544
  export function terminalizeTaskRoleRunSession(set, fence, terminalAt) {
533
545
  validateRoleSessionSet(set);
534
546
  const inFlight = set.inFlight;
535
- let updated = inFlight === null
536
- ? set
537
- : clearTaskRoleRun(set, fence, terminalAt);
538
- const session = updated.sessions[updated.activeAgentId];
539
- if (session?.status === "running") {
540
- updated = updateRoleAgentSessionStatus(updated, updated.activeAgentId, "ready", terminalAt);
541
- }
542
- return updated;
547
+ return inFlight === null ? set : clearTaskRoleRun(set, fence, terminalAt);
543
548
  }
544
549
  export function settleTaskRoleCompletion(set, expected, settledAt) {
545
550
  validateRoleSessionSet(set);
546
551
  assertTaskRoleSessionSet(set);
547
552
  const agentId = requireSafeIdentity(expected.agentId, "Agent id");
548
553
  const runId = requireSafeIdentity(expected.runId, "Run id");
549
- const turnId = requireSafeIdentity(expected.turnId, "Turn id");
554
+ const turnId = requireText(expected.turnId, "Turn id");
550
555
  const inFlight = set.inFlight;
551
556
  if (inFlight === null || inFlight.agentId !== agentId || inFlight.runId !== runId) {
552
557
  throw new Error("Pending Turn completion does not match the in-flight Run.");
@@ -578,7 +583,7 @@ export function validateRoleSessionSet(set) {
578
583
  validateRoleAgentSession(session, agentId);
579
584
  }
580
585
  if (set.owner.scope === "global") {
581
- if (set.schemaVersion !== 3) {
586
+ if (set.schemaVersion !== 4) {
582
587
  throw new Error("Global Role session set schema version is invalid.");
583
588
  }
584
589
  if (Object.hasOwn(set, "inFlight")
@@ -590,14 +595,14 @@ export function validateRoleSessionSet(set) {
590
595
  for (const [ref, session] of Object.entries(history)) {
591
596
  requireSafeIdentity(ref, "Operator session ref");
592
597
  validateRoleAgentSession(session);
593
- if (session.status !== "stopped" && session.status !== "broken") {
598
+ if (session.status !== "ended") {
594
599
  throw new Error(`Operator history session must be stopped: ${ref}.`);
595
600
  }
596
601
  }
597
602
  }
598
603
  }
599
604
  else {
600
- if (set.schemaVersion !== 6) {
605
+ if (set.schemaVersion !== 8) {
601
606
  throw new Error("Task Role session set schema version is invalid.");
602
607
  }
603
608
  if (!Object.hasOwn(set, "inFlight")
@@ -612,7 +617,7 @@ export function validateRoleSessionSet(set) {
612
617
  const identities = new Set();
613
618
  for (const session of taskSet.history) {
614
619
  validateRoleAgentSession(session);
615
- if (session.status !== "stopped" && session.status !== "broken") {
620
+ if (session.status !== "ended") {
616
621
  throw new Error("Task Role session history must be terminal.");
617
622
  }
618
623
  const key = taskRoleSessionIdentity(session);
@@ -637,9 +642,6 @@ export function validateRoleSessionSet(set) {
637
642
  throw new Error("Task Role in-flight Run Agent must be active.");
638
643
  }
639
644
  if (providerBinding !== null) {
640
- if (inFlight !== null && providerBinding.runId !== inFlight.runId) {
641
- throw new Error("Provider Runtime Binding must match the in-flight Run.");
642
- }
643
645
  const session = taskSet.sessions[inFlight?.agentId ?? set.activeAgentId];
644
646
  if (session === undefined) {
645
647
  throw new Error("Provider Runtime Binding has no active Role Agent session.");
@@ -653,7 +655,7 @@ export function validateRoleSessionSet(set) {
653
655
  return set;
654
656
  }
655
657
  export function validateRoleAgentSession(session, expectedAgentId = session.agentId) {
656
- if (session.schemaVersion !== 3) {
658
+ if (session.schemaVersion !== 4) {
657
659
  throw new Error(`Role Agent session schema version is invalid: ${expectedAgentId}.`);
658
660
  }
659
661
  const agentId = requireSafeIdentity(session.agentId, "Agent id");
@@ -691,6 +693,14 @@ export function validateRoleAgentSession(session, expectedAgentId = session.agen
691
693
  if (!isAgentSessionStatus(session.status)) {
692
694
  throw new Error(`Role Agent session status is invalid: ${agentId}.`);
693
695
  }
696
+ if (session.status === "active" && session.endReason !== undefined) {
697
+ throw new Error(`Active Role Agent session cannot have an end reason: ${agentId}.`);
698
+ }
699
+ if (session.status === "ended"
700
+ && session.endReason !== "stopped"
701
+ && session.endReason !== "failed") {
702
+ throw new Error(`Ended Role Agent session requires an end reason: ${agentId}.`);
703
+ }
694
704
  validateRecentTurnIds(session.recentCompletedTurnIds);
695
705
  requireText(session.createdAt, "Role Agent session creation timestamp");
696
706
  requireText(session.updatedAt, "Role Agent session update timestamp");
@@ -820,7 +830,7 @@ function normalizeOwner(owner) {
820
830
  };
821
831
  }
822
832
  function isAgentSessionStatus(value) {
823
- return ["reserved", "ready", "running", "stopped", "broken"].includes(value);
833
+ return value === "active" || value === "ended";
824
834
  }
825
835
  function requireSafeIdentity(value, label) {
826
836
  const normalized = requireText(value, label);
@@ -82,7 +82,10 @@ export class ExecutorRegistry {
82
82
  : {
83
83
  ...common,
84
84
  mode: "resume",
85
- nativeSessionId: input.nativeSessionId
85
+ nativeSessionId: input.nativeSessionId,
86
+ ...(input.hostActivationId === undefined
87
+ ? {}
88
+ : { hostActivationId: input.hostActivationId })
86
89
  }, "deferred", undefined, input.beforeHostStart);
87
90
  }
88
91
  else {
@@ -100,7 +103,7 @@ export class ExecutorRegistry {
100
103
  });
101
104
  binding = request.mode === "new"
102
105
  ? await this.runtimePorts.sessionHost.start(request, input.beforeHostStart)
103
- : await this.runtimePorts.sessionHost.resume(request, input.beforeHostStart);
106
+ : await this.runtimePorts.sessionHost.restore(request, input.beforeHostStart);
104
107
  }
105
108
  sessionStarted = binding.hostCreated === true;
106
109
  session = binding.nativeSessionId === undefined
@@ -109,7 +112,7 @@ export class ExecutorRegistry {
109
112
  agentId: binding.agentId,
110
113
  adapterId: binding.adapterId,
111
114
  nativeSessionId: binding.nativeSessionId,
112
- status: "ready",
115
+ status: "active",
113
116
  effective: input.effective
114
117
  };
115
118
  }
@@ -118,22 +121,6 @@ export class ExecutorRegistry {
118
121
  ...(binding === undefined ? {} : { launchId: binding.launchId }),
119
122
  sessionStarted,
120
123
  session,
121
- ...(input.runId !== undefined
122
- && ((binding?.initialTurnRunId === input.runId))
123
- ? { turnAcceptedDuringLaunch: true }
124
- : {}),
125
- ...(input.runId !== undefined
126
- && binding?.initialTurnDeliveryUnknownRunId === input.runId
127
- ? { turnDeliveryUnknownDuringLaunch: true }
128
- : {}),
129
- ...(input.runId !== undefined
130
- && binding?.initialTurnBusyRunId === input.runId
131
- ? { turnBusyDuringLaunch: true }
132
- : {}),
133
- ...(input.runId !== undefined
134
- && binding?.initialTurnRejectedRunId === input.runId
135
- ? { turnRejectedDuringLaunch: true }
136
- : {})
137
124
  };
138
125
  this.#prepared.set(delivery.deliveryId, {
139
126
  delivery,
@@ -362,7 +349,8 @@ function preparedDeliveryId(input) {
362
349
  input.effective,
363
350
  input.mode,
364
351
  input.runId ?? null,
365
- input.nativeSessionId ?? null
352
+ input.nativeSessionId ?? null,
353
+ input.hostActivationId ?? null
366
354
  ])).digest("hex");
367
355
  }
368
356
  function hasText(value) {