@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,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, 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: 7,
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"
@@ -314,28 +324,6 @@ export function bindTaskRoleRun(set, fence, preparedAt, mode) {
314
324
  };
315
325
  return validateRoleSessionSet(updated);
316
326
  }
317
- /** Re-fences the same active Run for an exact Provider retry without losing its Conversation. */
318
- export function prepareTaskRoleRunRedispatch(set, fence, preparedAt) {
319
- validateRoleSessionSet(set);
320
- const normalized = normalizeTaskRoleRunFence(fence);
321
- if (set.inFlight === null
322
- || set.inFlight.agentId !== normalized.agentId
323
- || set.inFlight.runId !== normalized.runId) {
324
- throw new Error("Provider retry does not match the in-flight Task Run.");
325
- }
326
- if (set.providerBinding !== null
327
- && set.providerBinding.turn !== null
328
- && ["submitting", "accepted", "running", "delivery-unknown"]
329
- .includes(set.providerBinding.turn.status)) {
330
- throw new Error("Provider retry cannot redispatch an unsettled Turn.");
331
- }
332
- const timestamp = requireDate(preparedAt, "Task Role retry preparedAt");
333
- return validateRoleSessionSet({
334
- ...set,
335
- inFlight: { ...normalized, preparedAt: timestamp },
336
- updatedAt: timestamp
337
- });
338
- }
339
327
  export function bindTaskRoleProviderRuntime(set, binding, updatedAt) {
340
328
  validateRoleSessionSet(set);
341
329
  const normalized = validateProviderRuntimeBinding(binding);
@@ -365,33 +353,65 @@ export function updateTaskRoleProviderRuntime(set, binding, updatedAt) {
365
353
  });
366
354
  }
367
355
  /**
368
- * Records an exact local Host exit without forgetting the Provider
369
- * Conversation. The ended Activation releases writer authority, while the
370
- * 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.
371
359
  */
372
- export function stopTaskRoleRuntimeAfterPhysicalExit(set, now) {
360
+ export function detachRoleAgentSessionHost(set, now) {
373
361
  validateRoleSessionSet(set);
374
362
  const active = set.sessions[set.activeAgentId];
375
- if (active === undefined)
376
- return set;
377
- const activation = set.providerBinding === null
378
- ? null
379
- : currentProviderActivation(set.providerBinding);
380
- if (active.status === "stopped") {
381
- if (activation !== null) {
382
- throw new Error("Stopped Task Role Session cannot retain a live Provider Activation.");
383
- }
363
+ if (active === undefined || active.status === "ended")
384
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);
385
403
  }
386
- let updated = updateRoleAgentSessionStatus(set, set.activeAgentId, "stopped", now);
404
+ const activation = binding === null
405
+ ? null
406
+ : currentProviderActivation(binding);
387
407
  if (activation !== null) {
388
- updated = updateTaskRoleProviderRuntime(updated, endProviderActivation(updated.providerBinding, activation.activationId, {
408
+ taskSet = updateTaskRoleProviderRuntime(taskSet, endProviderActivation(binding, activation.activationId, {
389
409
  status: "ended",
390
410
  endedAt: now.toISOString(),
391
411
  reason: "runtime-physical-exit"
392
412
  }), now);
393
413
  }
394
- return updated;
414
+ return taskSet;
395
415
  }
396
416
  export function markTaskRoleRunPushed(set, fence, pushedAt) {
397
417
  validateRoleSessionSet(set);
@@ -445,7 +465,7 @@ export function recordTaskRoleTurnBoundary(set, input, completedAt) {
445
465
  assertTaskRoleSessionSet(set);
446
466
  const agentId = requireSafeIdentity(input.agentId, "Agent id");
447
467
  const nativeSessionId = requireText(input.nativeSessionId, "Native session id");
448
- const turnId = requireSafeIdentity(input.turnId, "Turn id");
468
+ const turnId = requireText(input.turnId, "Turn id");
449
469
  const session = set.sessions[agentId];
450
470
  if (session === undefined || session.nativeSessionId !== nativeSessionId) {
451
471
  throw new Error("Completed Turn has no matching Role Agent native session.");
@@ -456,16 +476,12 @@ export function recordTaskRoleTurnBoundary(set, input, completedAt) {
456
476
  throw new Error("Completed Turn Agent does not match the in-flight Run.");
457
477
  }
458
478
  const timestamp = requireDate(completedAt, "Turn completedAt");
459
- const status = session.status === "stopped" || session.status === "broken"
460
- ? session.status
461
- : "ready";
462
479
  return validateRoleSessionSet({
463
480
  ...set,
464
481
  sessions: {
465
482
  ...set.sessions,
466
483
  [agentId]: {
467
484
  ...session,
468
- status,
469
485
  recentCompletedTurnIds: rememberRecentTurnId(session.recentCompletedTurnIds, turnId),
470
486
  updatedAt: timestamp
471
487
  }
@@ -535,7 +551,7 @@ export function settleTaskRoleCompletion(set, expected, settledAt) {
535
551
  assertTaskRoleSessionSet(set);
536
552
  const agentId = requireSafeIdentity(expected.agentId, "Agent id");
537
553
  const runId = requireSafeIdentity(expected.runId, "Run id");
538
- const turnId = requireSafeIdentity(expected.turnId, "Turn id");
554
+ const turnId = requireText(expected.turnId, "Turn id");
539
555
  const inFlight = set.inFlight;
540
556
  if (inFlight === null || inFlight.agentId !== agentId || inFlight.runId !== runId) {
541
557
  throw new Error("Pending Turn completion does not match the in-flight Run.");
@@ -567,7 +583,7 @@ export function validateRoleSessionSet(set) {
567
583
  validateRoleAgentSession(session, agentId);
568
584
  }
569
585
  if (set.owner.scope === "global") {
570
- if (set.schemaVersion !== 3) {
586
+ if (set.schemaVersion !== 4) {
571
587
  throw new Error("Global Role session set schema version is invalid.");
572
588
  }
573
589
  if (Object.hasOwn(set, "inFlight")
@@ -579,14 +595,14 @@ export function validateRoleSessionSet(set) {
579
595
  for (const [ref, session] of Object.entries(history)) {
580
596
  requireSafeIdentity(ref, "Operator session ref");
581
597
  validateRoleAgentSession(session);
582
- if (session.status !== "stopped" && session.status !== "broken") {
598
+ if (session.status !== "ended") {
583
599
  throw new Error(`Operator history session must be stopped: ${ref}.`);
584
600
  }
585
601
  }
586
602
  }
587
603
  }
588
604
  else {
589
- if (set.schemaVersion !== 7) {
605
+ if (set.schemaVersion !== 8) {
590
606
  throw new Error("Task Role session set schema version is invalid.");
591
607
  }
592
608
  if (!Object.hasOwn(set, "inFlight")
@@ -601,7 +617,7 @@ export function validateRoleSessionSet(set) {
601
617
  const identities = new Set();
602
618
  for (const session of taskSet.history) {
603
619
  validateRoleAgentSession(session);
604
- if (session.status !== "stopped" && session.status !== "broken") {
620
+ if (session.status !== "ended") {
605
621
  throw new Error("Task Role session history must be terminal.");
606
622
  }
607
623
  const key = taskRoleSessionIdentity(session);
@@ -639,7 +655,7 @@ export function validateRoleSessionSet(set) {
639
655
  return set;
640
656
  }
641
657
  export function validateRoleAgentSession(session, expectedAgentId = session.agentId) {
642
- if (session.schemaVersion !== 3) {
658
+ if (session.schemaVersion !== 4) {
643
659
  throw new Error(`Role Agent session schema version is invalid: ${expectedAgentId}.`);
644
660
  }
645
661
  const agentId = requireSafeIdentity(session.agentId, "Agent id");
@@ -677,6 +693,14 @@ export function validateRoleAgentSession(session, expectedAgentId = session.agen
677
693
  if (!isAgentSessionStatus(session.status)) {
678
694
  throw new Error(`Role Agent session status is invalid: ${agentId}.`);
679
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
+ }
680
704
  validateRecentTurnIds(session.recentCompletedTurnIds);
681
705
  requireText(session.createdAt, "Role Agent session creation timestamp");
682
706
  requireText(session.updatedAt, "Role Agent session update timestamp");
@@ -806,7 +830,7 @@ function normalizeOwner(owner) {
806
830
  };
807
831
  }
808
832
  function isAgentSessionStatus(value) {
809
- return ["reserved", "ready", "running", "stopped", "broken"].includes(value);
833
+ return value === "active" || value === "ended";
810
834
  }
811
835
  function requireSafeIdentity(value, label) {
812
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) {
@@ -10,11 +10,6 @@ import { activeRoleAgentBinding } from "../role/role.js";
10
10
  import { writeTextFileAtomically } from "../storage/durableFile.js";
11
11
  import { compileRoleSessionContext, roleSessionKind } from "../context/roleSessionContext.js";
12
12
  import { materializeSessionBootstrap } from "../context/sessionBootstrapManifest.js";
13
- import { serializeRunBootstrapEnvelope, serializeRunHostRecoveryEnvelope } from "../context/runContextContract.js";
14
- import { serializeProviderRetryEnvelope } from "../run/providerRetry.js";
15
- import { serializeWorkflowOutcomeRequestEnvelope } from "../run/runControlRequest.js";
16
- import { prefixYuiTitleInput } from "../run/runIdentity.js";
17
- import { agentRunDeliveryReceiptId } from "../run/agentRun.js";
18
13
  import { resolveAgentAdapter } from "./agentAdapter.js";
19
14
  import { resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
20
15
  import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
@@ -302,7 +297,6 @@ export class FileRoleLaunchPlanner {
302
297
  if (runtimeIsolation !== undefined && (owner.scope !== "task"
303
298
  || runtimeIsolation.taskId !== owner.taskId
304
299
  || runtimeIsolation.workspace.root !== effectiveWorkspace
305
- || runtimeIsolation.generation.runId !== input.runId
306
300
  || runtimeIsolation.generation.launchId !== input.launchId)) {
307
301
  throw new Error("Role launch does not match its Task runtime isolation descriptor.");
308
302
  }
@@ -478,41 +472,31 @@ export class FileRoleLaunchPlanner {
478
472
  if (owner.scope === "task" && (input.mode === "new" || input.mode === "resume")) {
479
473
  jobCallerKey = randomBytes(32).toString("hex");
480
474
  }
481
- // Every managed launch for an unpushed Run owns that Run's first Provider
482
- // write, whether it creates or resumes the native Conversation. Keeping
483
- // resume on the same launch handshake prevents a durable AgentRun from
484
- // existing without a corresponding Provider Turn.
475
+ // Session lifecycle and Turn submission are separate atomic operations.
476
+ // Planning only starts or restores the exact native Session; delivery
477
+ // submits the Run input after that Session fact is durable.
485
478
  if (managedControl && (managedRun === null || managedRun.status !== "active")) {
486
479
  throw new Error(`Managed Run is no longer active: ${input.runId}.`);
487
480
  }
488
- const carriesInitialTurn = managedControl && (managedRun.pushedAt === undefined
489
- || managedRun.providerRetry?.state === "dispatching"
490
- || managedRun.controlRequest?.state === "dispatching");
491
481
  const providerAuthority = managedControl
492
482
  ? this.#providerAuthorityForLaunch(owner.taskId, role.name, input.launchId, input.mode)
493
483
  : undefined;
494
- const providerOwnedTurn = managedControl && !carriesInitialTurn
484
+ const providerOwnedTurn = managedControl && input.mode === "resume"
495
485
  ? this.#providerOwnedTurnForLaunch(owner.taskId, role.name, input.runId)
496
486
  : undefined;
497
487
  const providerNativeSessionId = binding.adapterId === "claude"
498
488
  ? preallocatedNativeSessionId
499
489
  : resumeNativeSessionId;
500
- const initialTurn = carriesInitialTurn
501
- ? {
502
- attemptId: agentRunDeliveryReceiptId(managedRun),
503
- boundedText: managedRunLaunchEnvelope(managedRun, input.mode, sessionContext.sessionManifestPath, sessionTitle)
504
- }
505
- : undefined;
506
490
  const providerControl = !managedControl
507
491
  ? undefined
508
- : initialTurn === undefined
492
+ : input.mode === "resume"
509
493
  ? {
510
494
  schemaVersion: 1,
511
495
  adapterId: binding.adapterId,
512
496
  transport: managedCompiled.transport,
513
- kind: "ensure",
497
+ kind: "restore",
514
498
  mode: "resume",
515
- nativeSessionId: requireText(providerNativeSessionId, "Managed Provider ensure native session id"),
499
+ nativeSessionId: requireText(providerNativeSessionId, "Managed Provider restore native session id"),
516
500
  ...(sessionTitle === undefined ? {} : { sessionTitle }),
517
501
  ...(managedCompiled.codexThread === undefined
518
502
  ? {}
@@ -520,44 +504,24 @@ export class FileRoleLaunchPlanner {
520
504
  ...(providerOwnedTurn === undefined ? {} : { ownedTurn: providerOwnedTurn }),
521
505
  authority: providerAuthority
522
506
  }
523
- : resumeNativeSessionId === undefined
524
- ? {
525
- schemaVersion: 1,
526
- adapterId: binding.adapterId,
527
- transport: managedCompiled.transport,
528
- kind: "new",
529
- mode: "new",
530
- ...(providerNativeSessionId === undefined
531
- ? {}
532
- : { nativeSessionId: providerNativeSessionId }),
533
- ...(sessionTitle === undefined ? {} : { sessionTitle }),
534
- ...(managedCompiled.codexThread === undefined
535
- ? {}
536
- : { codexThread: managedCompiled.codexThread }),
537
- authority: providerAuthority,
538
- initialTurn
539
- }
540
- : {
541
- schemaVersion: 1,
542
- adapterId: binding.adapterId,
543
- transport: managedCompiled.transport,
544
- kind: "resume",
545
- mode: "resume",
546
- nativeSessionId: requireText(providerNativeSessionId, "Managed Provider resume native session id"),
547
- ...(sessionTitle === undefined ? {} : { sessionTitle }),
548
- ...(managedCompiled.codexThread === undefined
549
- ? {}
550
- : { codexThread: managedCompiled.codexThread }),
551
- authority: providerAuthority,
552
- initialTurn
553
- };
507
+ : {
508
+ schemaVersion: 1,
509
+ adapterId: binding.adapterId,
510
+ transport: managedCompiled.transport,
511
+ kind: "start",
512
+ mode: "new",
513
+ ...(providerNativeSessionId === undefined
514
+ ? {}
515
+ : { nativeSessionId: providerNativeSessionId }),
516
+ ...(sessionTitle === undefined ? {} : { sessionTitle }),
517
+ ...(managedCompiled.codexThread === undefined
518
+ ? {}
519
+ : { codexThread: managedCompiled.codexThread }),
520
+ authority: providerAuthority
521
+ };
554
522
  const launch = {
555
523
  command,
556
524
  args,
557
- ...(managedRun?.providerRetry !== undefined
558
- && managedRun.providerRetry.state !== "dispatching"
559
- ? { deferProviderStart: true }
560
- : {}),
561
525
  ...(providerControl === undefined ? {} : { providerControl }),
562
526
  env: {
563
527
  ...launchEnvironment,
@@ -612,10 +576,7 @@ export class FileRoleLaunchPlanner {
612
576
  },
613
577
  launch: ordinaryConversationLaunch,
614
578
  session,
615
- ...(sessionTitle === undefined ? {} : { sessionTitle }),
616
- ...(carriesInitialTurn && input.runId !== undefined
617
- ? { initialTurnRunId: input.runId }
618
- : {})
579
+ ...(sessionTitle === undefined ? {} : { sessionTitle })
619
580
  };
620
581
  }
621
582
  #providerAuthorityForLaunch(taskId, roleName, launchId, mode) {
@@ -705,33 +666,6 @@ export class FileRoleLaunchPlanner {
705
666
  return selectEnvironment(source, names);
706
667
  }
707
668
  }
708
- function managedRunLaunchEnvelope(run, mode, sessionManifestPath, title) {
709
- const body = run.providerRetry?.state === "dispatching"
710
- ? serializeProviderRetryEnvelope({
711
- taskId: run.taskId,
712
- runId: run.id,
713
- roleName: run.roleName,
714
- retry: run.providerRetry
715
- })
716
- : run.controlRequest?.state === "dispatching"
717
- ? serializeWorkflowOutcomeRequestEnvelope({
718
- taskId: run.taskId,
719
- runId: run.id,
720
- roleName: run.roleName,
721
- request: run.controlRequest
722
- })
723
- : mode === "resume" && run.pushedAt !== undefined
724
- ? serializeRunHostRecoveryEnvelope(run.bootstrapEnvelope)
725
- : serializeRunBootstrapEnvelope(run.bootstrapEnvelope);
726
- const guided = [
727
- `Yui Session Manifest: ${sessionManifestPath}`,
728
- "Read the manifest and its matching Role Skill before using the yui CLI for this Task.",
729
- body
730
- ].join("\n");
731
- return title === undefined
732
- ? guided
733
- : prefixYuiTitleInput(guided, title);
734
- }
735
669
  export function nativeAgentWorkspace(workspace) {
736
670
  return workspace.entries.length === 1
737
671
  ? workspace.entries[0].path
@@ -961,7 +895,7 @@ function readySession(agentId, adapterId, nativeSessionId, effective) {
961
895
  agentId,
962
896
  adapterId,
963
897
  nativeSessionId: requireText(nativeSessionId, "Native session id"),
964
- status: "ready",
898
+ status: "active",
965
899
  effective
966
900
  };
967
901
  }
@@ -22,7 +22,7 @@ export function createPendingTurnCompletion(input) {
22
22
  roleName: requireSafeIdentity(input.roleName, "Role name"),
23
23
  agentId: requireSafeIdentity(input.agentId, "Agent id"),
24
24
  nativeSessionId: requireText(input.nativeSessionId, "Native session id"),
25
- turnId: requireSafeIdentity(input.turnId, "Turn id"),
25
+ turnId: requireText(input.turnId, "Turn id"),
26
26
  runId: requireSafeIdentity(input.runId, "Run id"),
27
27
  summary: requireText(input.summary, "Turn summary"),
28
28
  observedAt,
@@ -44,7 +44,7 @@ export function validatePendingTurnCompletion(value) {
44
44
  roleName: requireSafeIdentityValue(input.roleName, "Role name"),
45
45
  agentId: requireSafeIdentityValue(input.agentId, "Agent id"),
46
46
  nativeSessionId: requireTextValue(input.nativeSessionId, "Native session id"),
47
- turnId: requireSafeIdentityValue(input.turnId, "Turn id"),
47
+ turnId: requireTextValue(input.turnId, "Turn id"),
48
48
  runId: requireSafeIdentityValue(input.runId, "Run id"),
49
49
  summary: requireTextValue(input.summary, "Turn summary"),
50
50
  observedAt,
@@ -58,7 +58,7 @@ export function validateRecentTurnIds(value, limit = DEFAULT_RECENT_TURN_ID_LIMI
58
58
  if (value.length > normalizedLimit) {
59
59
  throw new Error(`Recent Turn ids must not contain more than ${normalizedLimit} entries.`);
60
60
  }
61
- const result = value.map((turnId) => requireSafeIdentityValue(turnId, "Recent Turn id"));
61
+ const result = value.map((turnId) => requireTextValue(turnId, "Recent Turn id"));
62
62
  if (new Set(result).size !== result.length) {
63
63
  throw new Error("Recent Turn ids must not contain duplicates.");
64
64
  }
@@ -67,7 +67,7 @@ export function validateRecentTurnIds(value, limit = DEFAULT_RECENT_TURN_ID_LIMI
67
67
  export function rememberRecentTurnId(recentTurnIds, turnId, limit = DEFAULT_RECENT_TURN_ID_LIMIT) {
68
68
  const normalizedLimit = requireLimit(limit);
69
69
  const existing = validateRecentTurnIds(recentTurnIds, normalizedLimit);
70
- const normalizedTurnId = requireSafeIdentity(turnId, "Recent Turn id");
70
+ const normalizedTurnId = requireText(turnId, "Recent Turn id");
71
71
  return [
72
72
  ...existing.filter((candidate) => candidate !== normalizedTurnId),
73
73
  normalizedTurnId
@@ -75,7 +75,7 @@ export function rememberRecentTurnId(recentTurnIds, turnId, limit = DEFAULT_RECE
75
75
  }
76
76
  export function hasRecentTurnId(recentTurnIds, turnId) {
77
77
  const existing = validateRecentTurnIds(recentTurnIds);
78
- return existing.includes(requireSafeIdentity(turnId, "Recent Turn id"));
78
+ return existing.includes(requireText(turnId, "Recent Turn id"));
79
79
  }
80
80
  function requireOrderedTimestamps(observedAt, dueAt) {
81
81
  if (Date.parse(dueAt) < Date.parse(observedAt)) {
@@ -269,7 +269,7 @@ export function retireExactActiveAgentRun(store, input, now) {
269
269
  || providerTurn?.status === "failed"
270
270
  || providerTurn?.status === "cancelled"
271
271
  || providerTurn?.status === "rejected");
272
- if (session?.status !== "stopped" && session?.status !== "broken" && !providerSettled) {
272
+ if (session?.status === "active" && !providerSettled) {
273
273
  return {
274
274
  disposition: "blocked",
275
275
  run: current,