@zq-silk/yui 0.8.1 → 0.8.3

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 (52) hide show
  1. package/ARCHITECTURE.md +27 -28
  2. package/README.md +57 -46
  3. package/dist/cli/commandCatalog.js +57 -17
  4. package/dist/cli/interactionPolicy.js +4 -10
  5. package/dist/cli/invocationRouter.js +2 -1
  6. package/dist/cli.js +106 -27
  7. package/dist/commands/taskCommands.js +458 -77
  8. package/dist/commands/taskCompletionGate.js +152 -0
  9. package/dist/context/runContextPack.js +19 -4
  10. package/dist/context/sessionBootstrapManifest.js +1 -1
  11. package/dist/controller/fileSchedulerStoreAdapter.js +389 -70
  12. package/dist/controller/resourceInventory.js +9 -5
  13. package/dist/controller/runtime.js +80 -7
  14. package/dist/controller/runtimeLaunchCoordinator.js +18 -78
  15. package/dist/controller/structuredProviderObservation.js +273 -0
  16. package/dist/executor/agentAdapter.js +40 -0
  17. package/dist/executor/agentExecutor.js +31 -7
  18. package/dist/executor/executorRegistry.js +11 -49
  19. package/dist/executor/fileRoleLaunchPlanner.js +115 -37
  20. package/dist/lifecycle/canonicalLifecycleEvent.js +5 -2
  21. package/dist/repository/gitWorkspace.js +7 -4
  22. package/dist/repository/taskBaseFreshness.js +4 -2
  23. package/dist/run/agentRun.js +4 -4
  24. package/dist/runtime/agentHost.js +767 -158
  25. package/dist/runtime/builtinAgentDrivers.js +1 -5
  26. package/dist/runtime/codexAppServerRuntime.js +67 -60
  27. package/dist/runtime/exactControlPlane.js +7 -2
  28. package/dist/runtime/index.js +6 -2
  29. package/dist/runtime/launchBroker.js +30 -8
  30. package/dist/runtime/providerAuthorityFence.js +24 -0
  31. package/dist/runtime/providerControl.js +63 -0
  32. package/dist/runtime/providerRecoveryDecision.js +55 -0
  33. package/dist/runtime/providerRuntimeIdentity.js +269 -19
  34. package/dist/runtime/runtimeBinding.js +20 -11
  35. package/dist/runtime/structuredProviderHost.js +476 -0
  36. package/dist/runtime/tmuxAdapters.js +143 -42
  37. package/dist/scheduler/activeRoleRunDelivery.js +206 -120
  38. package/dist/scheduler/leaderWakeupProcessor.js +141 -16
  39. package/dist/scheduler/wakeReason.js +1 -0
  40. package/dist/storage/migration/productionRegistry.js +111 -0
  41. package/dist/storage/sqliteStore.js +2 -0
  42. package/dist/storage/taskStore.js +3 -1
  43. package/dist/task/completionReadiness.js +43 -0
  44. package/dist/task/nextAction.js +6 -4
  45. package/dist/task/publicationReference.js +1 -0
  46. package/dist/tmux/tmuxManager.js +1 -1
  47. package/dist/workItem/workItem.js +12 -0
  48. package/dist/workspace/workItemChangeSetManager.js +2 -1
  49. package/i18n/README.zh-CN.md +11 -8
  50. package/package.json +1 -1
  51. package/skills/yui-leader/SKILL.md +8 -3
  52. package/skills/yui-runtime/SKILL.md +7 -2
@@ -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, effectiveLaunchSnapshotsCompatibleForTaskMain, validateEffectiveLaunchSnapshot } from "./effectiveLaunch.js";
4
- import { validateProviderRuntimeBinding } from "../runtime/providerRuntimeIdentity.js";
4
+ import { rebindProviderRuntimeRun, 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 = {
@@ -14,7 +14,7 @@ export function createRoleSessionSet(owner, activeAgentId, now) {
14
14
  ? { ...base, schemaVersion: 3 }
15
15
  : {
16
16
  ...base,
17
- schemaVersion: 5,
17
+ schemaVersion: 6,
18
18
  inFlight: null,
19
19
  providerBinding: null
20
20
  };
@@ -285,13 +285,39 @@ export function bindTaskRoleRun(set, fence, preparedAt) {
285
285
  throw new Error("Task Role session set already has an in-flight Run.");
286
286
  }
287
287
  const timestamp = requireDate(preparedAt, "Task Role Run preparedAt");
288
+ const providerBinding = set.providerBinding === null
289
+ ? null
290
+ : rebindProviderRuntimeRun(set.providerBinding, normalized.runId);
288
291
  const updated = {
289
292
  ...set,
290
293
  inFlight: { ...normalized, preparedAt: timestamp },
294
+ providerBinding,
291
295
  updatedAt: timestamp
292
296
  };
293
297
  return validateRoleSessionSet(updated);
294
298
  }
299
+ /** Re-fences the same active Run for an exact Provider retry without losing its Conversation. */
300
+ export function prepareTaskRoleRunRedispatch(set, fence, preparedAt) {
301
+ validateRoleSessionSet(set);
302
+ const normalized = normalizeTaskRoleRunFence(fence);
303
+ if (set.inFlight === null
304
+ || set.inFlight.agentId !== normalized.agentId
305
+ || set.inFlight.runId !== normalized.runId) {
306
+ throw new Error("Provider retry does not match the in-flight Task Run.");
307
+ }
308
+ if (set.providerBinding !== null
309
+ && set.providerBinding.turn !== null
310
+ && ["submitting", "accepted", "running", "delivery-unknown"]
311
+ .includes(set.providerBinding.turn.status)) {
312
+ throw new Error("Provider retry cannot redispatch an unsettled Turn.");
313
+ }
314
+ const timestamp = requireDate(preparedAt, "Task Role retry preparedAt");
315
+ return validateRoleSessionSet({
316
+ ...set,
317
+ inFlight: { ...normalized, preparedAt: timestamp },
318
+ updatedAt: timestamp
319
+ });
320
+ }
295
321
  export function bindTaskRoleProviderRuntime(set, binding, updatedAt) {
296
322
  validateRoleSessionSet(set);
297
323
  const normalized = validateProviderRuntimeBinding(binding);
@@ -446,7 +472,6 @@ export function clearTaskRoleRun(set, fence, clearedAt) {
446
472
  const updated = {
447
473
  ...set,
448
474
  inFlight: null,
449
- providerBinding: null,
450
475
  updatedAt: timestamp
451
476
  };
452
477
  return validateRoleSessionSet(updated);
@@ -519,7 +544,6 @@ export function settleTaskRoleCompletion(set, expected, settledAt) {
519
544
  }
520
545
  },
521
546
  inFlight: null,
522
- providerBinding: null,
523
547
  updatedAt: timestamp
524
548
  };
525
549
  return validateRoleSessionSet(updated);
@@ -550,7 +574,7 @@ export function validateRoleSessionSet(set) {
550
574
  }
551
575
  }
552
576
  else {
553
- if (set.schemaVersion !== 5) {
577
+ if (set.schemaVersion !== 6) {
554
578
  throw new Error("Task Role session set schema version is invalid.");
555
579
  }
556
580
  if (!Object.hasOwn(set, "inFlight")
@@ -590,10 +614,10 @@ export function validateRoleSessionSet(set) {
590
614
  throw new Error("Task Role in-flight Run Agent must be active.");
591
615
  }
592
616
  if (providerBinding !== null) {
593
- if (inFlight === null || providerBinding.runId !== inFlight.runId) {
617
+ if (inFlight !== null && providerBinding.runId !== inFlight.runId) {
594
618
  throw new Error("Provider Runtime Binding must match the in-flight Run.");
595
619
  }
596
- const session = taskSet.sessions[inFlight.agentId];
620
+ const session = taskSet.sessions[inFlight?.agentId ?? set.activeAgentId];
597
621
  if (session === undefined) {
598
622
  throw new Error("Provider Runtime Binding has no active Role Agent session.");
599
623
  }
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { createPromptEnvelope, createRuntimeBinding, createSessionLaunchRequest } from "../runtime/index.js";
2
+ import { createPromptEnvelope, createSessionLaunchRequest } from "../runtime/index.js";
3
3
  import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
4
4
  /**
5
5
  * rr13/test: Test-only liveness seam. Integration tests that spawn a real
@@ -26,9 +26,6 @@ export class ExecutorRegistry {
26
26
  this.readiness = readiness;
27
27
  this.runtimePorts = runtimePorts;
28
28
  }
29
- canRouteProviderInput(adapterId) {
30
- return adapterId === "codex" && this.runtimePorts?.providerInputRouting !== undefined;
31
- }
32
29
  async prepareRoleSession(input) {
33
30
  if (input.mode === "resume" && !hasText(input.nativeSessionId)) {
34
31
  throw new Error("Role session resume requires a native session id.");
@@ -122,10 +119,16 @@ export class ExecutorRegistry {
122
119
  sessionStarted,
123
120
  session,
124
121
  ...(input.runId !== undefined
125
- && ((sessionStarted
126
- && (planned?.initialPromptRunId ?? binding?.initialPromptRunId) === input.runId)
127
- || binding?.launchPromptUncertainRunId === input.runId)
128
- ? { inputSubmittedAtLaunch: true }
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?.initialTurnRejectedRunId === input.runId
131
+ ? { turnRejectedDuringLaunch: true }
129
132
  : {})
130
133
  };
131
134
  this.#prepared.set(delivery.deliveryId, {
@@ -196,47 +199,6 @@ export class ExecutorRegistry {
196
199
  }
197
200
  return outcome;
198
201
  }
199
- async routeProviderInput(input) {
200
- const prepared = this.requirePrepared(input.delivery.prepared);
201
- if (prepared.binding === undefined || this.runtimePorts?.providerInputRouting === undefined) {
202
- return "unavailable";
203
- }
204
- try {
205
- return await this.runtimePorts.providerInputRouting.route({
206
- binding: prepared.binding,
207
- attemptId: input.attemptId,
208
- mode: input.mode,
209
- text: input.text,
210
- fence: input.fence
211
- });
212
- }
213
- finally {
214
- // A routed mutation is fenced by its durable inputDelivery, not by this
215
- // process-local preparation. Never let a later Turn reuse a cached
216
- // Activation binding after this attempt (including an unknown result).
217
- this.#prepared.delete(input.delivery.prepared.deliveryId);
218
- }
219
- }
220
- async reconcileProviderInput(input) {
221
- if (this.runtimePorts?.providerInputRouting === undefined) {
222
- return "unavailable";
223
- }
224
- return this.runtimePorts.providerInputRouting.reconcile({
225
- binding: createRuntimeBinding({
226
- id: `metadata:${input.taskId}:${input.roleName}:${input.launchId}`,
227
- launchId: input.launchId,
228
- owner: { scope: "task", taskId: input.taskId, roleName: input.roleName },
229
- agentId: input.agentId,
230
- adapterId: input.adapterId,
231
- hostRef: "metadata-only",
232
- hostCreated: false,
233
- nativeSessionId: input.nativeSessionId
234
- }),
235
- attemptId: input.attemptId,
236
- mode: input.mode,
237
- fence: input.fence
238
- });
239
- }
240
202
  async notifyOperatorInputOnce(input) {
241
203
  const probe = this.readiness(input.adapterId, "operator");
242
204
  return this.tmux.sendRoleInputOnceIfReadyAsync === undefined
@@ -24,6 +24,7 @@ import { parseTaskRuntimeIsolationDescriptor, taskRuntimeIsolationEnvironment }
24
24
  import { ResourceRegistrar } from "../resources/resourceRegistrar.js";
25
25
  import { builtinAgentDriverRegistry, builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
26
26
  import { managedRuntimeAdmission } from "../runtime/agentDriver.js";
27
+ import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
27
28
  /** Builds managed native Agent launches from the authoritative Task records. */
28
29
  export class FileRoleLaunchPlanner {
29
30
  home;
@@ -223,7 +224,7 @@ export class FileRoleLaunchPlanner {
223
224
  if (input.mode === "resume" && !compatibleExisting) {
224
225
  throw new Error(`Task Role resume effective snapshot drifted: ${task.id}/${role.name}.`);
225
226
  }
226
- return this.#compile(role, input, { scope: "task", taskId: task.id }, taskRoleSessionTitle(task, role.name), compatibleExisting ? existing.nativeSessionId : undefined, runWorkspace, effective, {
227
+ return this.#compile(role, input, { scope: "task", taskId: task.id }, taskRoleSessionTitle(task, role.name), input.mode === "resume" && compatibleExisting ? existing.nativeSessionId : undefined, runWorkspace, effective, {
227
228
  purpose: activeRun?.purpose ?? "execution"
228
229
  });
229
230
  }
@@ -331,6 +332,9 @@ export class FileRoleLaunchPlanner {
331
332
  const managedRun = owner.scope === "task" && input.runId !== undefined
332
333
  ? this.store.getAgentRun(owner.taskId, input.runId)
333
334
  : null;
335
+ const managedSessionSet = owner.scope === "task" && input.runId !== undefined
336
+ ? this.store.getTaskRoleSessionSet(owner.taskId, role.name)
337
+ : null;
334
338
  const driver = builtinAgentDriverRegistry().require(builtinDriverIdForAdapter(configured.adapterId));
335
339
  if (owner.scope === "task" && input.runId !== undefined) {
336
340
  const admission = managedRuntimeAdmission(driver.capabilities);
@@ -369,12 +373,33 @@ export class FileRoleLaunchPlanner {
369
373
  const launchMode = resumeNativeSessionId === undefined
370
374
  ? "new"
371
375
  : "resume";
372
- const compiled = launchMode === "resume"
373
- ? adapter.compileResume({
374
- ...compileInput,
375
- nativeSessionId: resumeNativeSessionId
376
- })
377
- : adapter.compileNew(compileInput);
376
+ const managedControl = owner.scope === "task" && input.runId !== undefined;
377
+ const managedProviderEnvironment = managedControl
378
+ && configured.adapterId === "codex"
379
+ ? {
380
+ // Managed Codex Runs are non-interactive. Use the Codex execution
381
+ // identity for provider requests while clientInfo still identifies Yui.
382
+ CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "codex_exec"
383
+ }
384
+ : {};
385
+ const preallocatedManagedNativeSessionId = managedControl
386
+ && binding.adapterId === "claude"
387
+ && resumeNativeSessionId === undefined
388
+ ? requireText(input.launchId === undefined
389
+ ? this.#createNativeSessionId()
390
+ : nativeSessionIdForLaunch(this.home, input.launchId, input.agentId, input.adapterId), "Native session id")
391
+ : resumeNativeSessionId;
392
+ const managedCompiled = managedControl
393
+ ? adapter.compileManagedControl(compileInput, launchMode, preallocatedManagedNativeSessionId)
394
+ : undefined;
395
+ const compiled = managedCompiled !== undefined
396
+ ? managedCompiled
397
+ : launchMode === "resume"
398
+ ? adapter.compileResume({
399
+ ...compileInput,
400
+ nativeSessionId: resumeNativeSessionId
401
+ })
402
+ : adapter.compileNew(compileInput);
378
403
  for (const path of [
379
404
  bootstrap.manifestPath,
380
405
  bootstrap.sessionCliPath,
@@ -397,39 +422,32 @@ export class FileRoleLaunchPlanner {
397
422
  if (owner.scope !== "task" || input.runId === undefined) {
398
423
  args = addCodexSessionNotify(args, launchMode, this.#cliPath);
399
424
  }
400
- // A fresh Codex TUI has no provider event before its first prompt. Carry
401
- // the exact Run input as the provider's positional launch prompt so it is
402
- // submitted only after Codex completes startup; never race terminal bytes
403
- // and Enter against TUI initialization.
425
+ // Managed Codex runs use App Server lifecycle hooks and structured Turn
426
+ // submission. No Run prompt is placed in argv or written as terminal input.
404
427
  if (owner.scope === "task" && input.runId !== undefined) {
405
428
  if (managedRun === null || managedRun.status !== "active") {
406
429
  throw new Error(`Managed Codex Run is no longer active: ${input.runId}.`);
407
430
  }
408
- args = addCodexLifecycleHooks(args, launchMode, this.#cliPath);
409
- // End option parsing before the opaque prompt so a wakeup beginning
410
- // with '-' can never be reinterpreted as a Codex CLI flag.
411
- if (managedRun.pushedAt === undefined) {
412
- args.push("--", managedRunLaunchEnvelope(managedRun, input.mode));
413
- }
431
+ args = managedControl
432
+ ? addCodexManagedLifecycleHooks(args, this.#cliPath)
433
+ : addCodexLifecycleHooks(args, launchMode, this.#cliPath);
414
434
  }
415
435
  session = launchMode === "resume"
416
436
  ? readySession(input.agentId, binding.adapterId, resumeNativeSessionId, effective)
417
437
  : null;
418
438
  }
419
439
  else if (launchMode === "new") {
420
- if (owner.scope === "task" && input.runId !== undefined) {
421
- args.push("-p", "--output-format", "stream-json", "--input-format", "stream-json", "--verbose");
440
+ if (managedControl)
422
441
  args.push("--plugin-dir", ensureManagedClaudeLifecyclePlugin(this.home, this.#cliPath));
423
- }
424
- const nativeSessionId = requireText(input.launchId === undefined
425
- ? this.#createNativeSessionId()
426
- : nativeSessionIdForLaunch(this.home, input.launchId, input.agentId, input.adapterId), "Native session id");
427
- args.push("--session-id", nativeSessionId);
442
+ const nativeSessionId = requireText(preallocatedManagedNativeSessionId, "Native session id");
443
+ if (!managedControl)
444
+ args.push("--session-id", nativeSessionId);
445
+ else if (!args.includes("--session-id"))
446
+ args.push("--session-id", nativeSessionId);
428
447
  session = readySession(input.agentId, binding.adapterId, nativeSessionId, effective);
429
448
  }
430
449
  else {
431
- if (owner.scope === "task" && input.runId !== undefined) {
432
- args.push("-p", "--output-format", "stream-json", "--input-format", "stream-json", "--verbose");
450
+ if (managedControl) {
433
451
  args.push("--plugin-dir", ensureManagedClaudeLifecyclePlugin(this.home, this.#cliPath));
434
452
  }
435
453
  session = readySession(input.agentId, binding.adapterId, resumeNativeSessionId, effective);
@@ -469,6 +487,36 @@ export class FileRoleLaunchPlanner {
469
487
  if (owner.scope === "task" && (input.mode === "new" || input.mode === "resume")) {
470
488
  jobCallerKey = randomBytes(32).toString("hex");
471
489
  }
490
+ const carriesInitialTurn = managedControl
491
+ && managedRun?.pushedAt === undefined
492
+ && (managedSessionSet?.providerBinding === null
493
+ || managedSessionSet?.providerBinding === undefined);
494
+ const providerAuthority = managedControl
495
+ ? this.#providerAuthorityForLaunch(owner.taskId, role.name, input.launchId)
496
+ : undefined;
497
+ const providerNativeSessionId = binding.adapterId === "claude"
498
+ ? preallocatedManagedNativeSessionId
499
+ : resumeNativeSessionId;
500
+ const providerControl = managedControl
501
+ ? {
502
+ schemaVersion: 1,
503
+ adapterId: binding.adapterId,
504
+ transport: managedCompiled.transport,
505
+ mode: resumeNativeSessionId === undefined ? "new" : "resume",
506
+ ...(providerNativeSessionId === undefined
507
+ ? {}
508
+ : { nativeSessionId: providerNativeSessionId }),
509
+ authority: providerAuthority,
510
+ ...(carriesInitialTurn
511
+ ? {
512
+ initialTurn: {
513
+ attemptId: formatAgentRunReceiptId(owner.taskId, input.runId),
514
+ boundedText: managedRunLaunchEnvelope(managedRun, input.mode)
515
+ }
516
+ }
517
+ : {})
518
+ }
519
+ : undefined;
472
520
  const launch = {
473
521
  command,
474
522
  args,
@@ -476,16 +524,10 @@ export class FileRoleLaunchPlanner {
476
524
  && managedRun.providerRetry.state !== "dispatching"
477
525
  ? { deferProviderStart: true }
478
526
  : {}),
479
- ...(managedClaudeRun
480
- ? {
481
- providerInput: {
482
- kind: "stdin-json-user-message",
483
- boundedText: managedRunLaunchEnvelope(managedRun, input.mode)
484
- }
485
- }
486
- : {}),
527
+ ...(providerControl === undefined ? {} : { providerControl }),
487
528
  env: {
488
529
  ...launchEnvironment,
530
+ ...managedProviderEnvironment,
489
531
  YUI_HOME: resolve(this.home),
490
532
  YUI_SESSION_SCOPE: owner.scope,
491
533
  ...(owner.scope === "task" ? { YUI_TASK_ID: owner.taskId } : {}),
@@ -536,12 +578,36 @@ export class FileRoleLaunchPlanner {
536
578
  },
537
579
  launch: scopedLaunch,
538
580
  session,
539
- ...((binding.adapterId === "codex" && managedRun?.pushedAt === undefined
540
- || managedClaudeRun) && input.runId !== undefined
541
- ? { initialPromptRunId: input.runId }
581
+ ...(carriesInitialTurn && input.runId !== undefined
582
+ ? { initialTurnRunId: input.runId }
542
583
  : {})
543
584
  };
544
585
  }
586
+ #providerAuthorityForLaunch(taskId, roleName, launchId) {
587
+ const activationId = requireText(launchId, "Managed Provider Activation id");
588
+ const binding = this.store.getTaskRoleSessionSet(taskId, roleName)?.providerBinding;
589
+ if (binding === null || binding === undefined) {
590
+ return { epoch: 1, owner: "controller", holderId: activationId };
591
+ }
592
+ if (binding.authority.owner === "controller") {
593
+ return {
594
+ epoch: binding.authority.epoch,
595
+ owner: "controller",
596
+ holderId: binding.authority.holderId
597
+ };
598
+ }
599
+ if (binding.authority.owner === "human") {
600
+ throw new Error(`Provider writer is held by a human: ${taskId}/${roleName}.`);
601
+ }
602
+ if (binding.authority.owner === "none") {
603
+ return {
604
+ epoch: binding.authority.epoch + 1,
605
+ owner: "controller",
606
+ holderId: activationId
607
+ };
608
+ }
609
+ throw new Error(`Provider writer authority is unknown: ${taskId}/${roleName}.`);
610
+ }
545
611
  #applyWorkspaceScope(taskId, role, launch, workspaceOverride) {
546
612
  const workspace = workspaceOverride
547
613
  ?? (role.name === "leader"
@@ -786,6 +852,18 @@ function addCodexLifecycleHooks(args, mode, cliPath) {
786
852
  }
787
853
  return [...args.slice(0, -2), ...managed, ...args.slice(-2)];
788
854
  }
855
+ function addCodexManagedLifecycleHooks(args, cliPath) {
856
+ if (args.length < 2 || args.at(-2) !== "app-server" || args.at(-1) !== "--stdio") {
857
+ throw new Error("Managed Codex App Server launch shape is invalid.");
858
+ }
859
+ return [
860
+ ...args.slice(0, -2),
861
+ "--enable", "hooks",
862
+ "--config", codexLifecycleHooksConfig(cliPath),
863
+ "--dangerously-bypass-hook-trust",
864
+ ...args.slice(-2)
865
+ ];
866
+ }
789
867
  function readySession(agentId, adapterId, nativeSessionId, effective) {
790
868
  return {
791
869
  agentId,
@@ -185,9 +185,12 @@ export function foldCanonicalLifecycleEvent(event, expectation) {
185
185
  // Only an identity-matched durable native event can move accepted/delivered,
186
186
  // and only after the independently committed transport receipt. Provider
187
187
  // acceptance and transport acknowledgement are deliberately separate
188
- // evidence layers: neither may repair or infer the other.
188
+ // evidence layers: neither may repair or infer the other. A fresh managed
189
+ // Host can publish native acceptance immediately before its launch call
190
+ // returns and lets the scheduler persist the transport receipt. Retain
191
+ // that exact fenced fact for replay instead of misclassifying it as stale.
189
192
  if (!expectation.pushed)
190
- return { outcome: "fail-closed", reason: "accept-without-push" };
193
+ return { outcome: "deferred", reason: "accept-before-push" };
191
194
  if (expectation.terminal)
192
195
  return { outcome: "obsolete", reason: "accept-after-terminal" };
193
196
  if (expectation.accepted)
@@ -15,15 +15,18 @@ export class RemoteBaselineConflictError extends Error {
15
15
  }
16
16
  /** The small Git boundary used by project registration and Task workspaces. */
17
17
  export class NodeGitWorkspace {
18
+ async resolveTree(repositoryPath, commit) {
19
+ return gitLine([
20
+ "-C", repositoryPath,
21
+ "rev-parse", "--verify", "--end-of-options", `${commit}^{tree}`
22
+ ]);
23
+ }
18
24
  async findCommitWithSameTreeInHistory(input) {
19
25
  const source = (await this.inspect(input.repositoryPath, input.sourceCommit)).baseCommit;
20
26
  const history = (await this.inspect(input.repositoryPath, input.historyHead)).baseCommit;
21
27
  if (await this.isAncestor(input.repositoryPath, source, history))
22
28
  return source;
23
- const sourceTree = await gitLine([
24
- "-C", input.repositoryPath,
25
- "rev-parse", "--verify", "--end-of-options", `${source}^{tree}`
26
- ]);
29
+ const sourceTree = await this.resolveTree(input.repositoryPath, source);
27
30
  const pageSize = 1000;
28
31
  for (let skip = 0;; skip += pageSize) {
29
32
  const output = await git([
@@ -107,10 +107,12 @@ export async function inspectTaskBaseFreshness(taskId, store, options = {}) {
107
107
  }));
108
108
  return { taskId, refreshed: options.refresh === true, entries };
109
109
  }
110
- export function assertTaskBaseFreshnessForCompletion(report) {
110
+ export function assertTaskBaseFreshnessForCompletion(report, options = {}) {
111
111
  const warnings = [];
112
112
  for (const entry of report.entries) {
113
- if (entry.status === "behind" || entry.status === "diverged") {
113
+ const acceptedPublishedTree = options.acceptedPublishedTreeProjectId === entry.projectId;
114
+ if ((entry.status === "behind" || entry.status === "diverged")
115
+ && !acceptedPublishedTree) {
114
116
  throw usageError(`Task ${report.taskId} Project ${entry.projectId} base is ${entry.status}; `
115
117
  + `run 'yui task base status ${report.taskId} --refresh' and choose an explicit delivery base. `
116
118
  + "Safe resolutions are to rebase or merge the Task workspace onto the refreshed remote base, "
@@ -67,11 +67,11 @@ export function withAgentRunContextSnapshot(run, snapshot, deltaRefIds = []) {
67
67
  if (run.status !== "active" || run.pushedAt !== undefined || run.deliveredAt !== undefined) {
68
68
  throw new Error(`Cannot bind Context Snapshot after Run delivery: ${run.id}.`);
69
69
  }
70
- const assignment = validateRunAssignment(Object.freeze({
70
+ const assignment = createRunAssignment({
71
71
  ...run.assignment,
72
72
  contextSnapshotRef: snapshot,
73
73
  deltaRefIds
74
- }));
74
+ });
75
75
  return validateAgentRun(Object.freeze({
76
76
  ...run,
77
77
  assignment,
@@ -329,7 +329,7 @@ export function withProviderRetry(run, retry) {
329
329
  * remain historical facts; the new delivery receipt and `dispatching` retry
330
330
  * state make the delivery path send only the short continuation envelope.
331
331
  */
332
- export function reopenRunForProviderRetry(run, receiptId, now) {
332
+ export function reopenRunForProviderRetry(run, receiptId, now, mode = "resume") {
333
333
  if (run.status !== "active" || run.providerRetry === undefined) {
334
334
  throw new Error(`Agent run is not waiting for a provider retry: ${run.id}.`);
335
335
  }
@@ -337,7 +337,7 @@ export function reopenRunForProviderRetry(run, receiptId, now) {
337
337
  const { providerRetry, ...rest } = run;
338
338
  return validateAgentRun({
339
339
  ...rest,
340
- mode: "resume",
340
+ mode,
341
341
  deliveryReceiptId: receiptId,
342
342
  updatedAt: timestamp,
343
343
  providerRetry: prepareProviderRetryDispatch(providerRetry, receiptId, now)