@zq-silk/yui 0.11.3 → 0.12.1

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 (108) hide show
  1. package/ARCHITECTURE.md +23 -4
  2. package/README.md +44 -22
  3. package/dist/cli/commandCatalog.js +13 -10
  4. package/dist/cli/interactionPolicy.js +5 -6
  5. package/dist/cli/updateCommand.js +3 -1
  6. package/dist/cli/updateOrchestrator.js +173 -28
  7. package/dist/cli/updatePorts.js +137 -8
  8. package/dist/cli/upgradeCommand.js +19 -9
  9. package/dist/cli.js +156 -57
  10. package/dist/commands/configCommands.js +13 -46
  11. package/dist/commands/executionAuditCommands.js +3 -2
  12. package/dist/commands/jobCommands.js +4 -10
  13. package/dist/commands/taskCommands.js +234 -200
  14. package/dist/commands/taskCompletionGate.js +29 -54
  15. package/dist/commands/taskContextCommand.js +61 -13
  16. package/dist/commands/taskInputCommands.js +8 -5
  17. package/dist/commands/taskNextActionCommand.js +56 -6
  18. package/dist/commands/taskOverviewCommand.js +17 -31
  19. package/dist/commands/taskRoleRuntimeStatus.js +55 -7
  20. package/dist/commands/taskWorkspaceCommands.js +5 -2
  21. package/dist/config/configCatalog.js +3 -3
  22. package/dist/config/yuiConfig.js +3 -7
  23. package/dist/context/wakeNotification.js +20 -5
  24. package/dist/controller/agentRuntimeObserver.js +247 -51
  25. package/dist/controller/clientRuntime.js +39 -3
  26. package/dist/controller/controller.js +31 -27
  27. package/dist/controller/fileSchedulerStoreAdapter.js +390 -332
  28. package/dist/controller/runtime.js +58 -3
  29. package/dist/controller/runtimeEventInbox.js +17 -7
  30. package/dist/controller/runtimeEventProcessor.js +12 -19
  31. package/dist/controller/runtimeHookRunFence.js +19 -4
  32. package/dist/controller/runtimeObservationHook.js +45 -0
  33. package/dist/controller/structuredProviderObservation.js +20 -3
  34. package/dist/core/controllerClient.js +20 -2
  35. package/dist/core/controllerServer.js +1 -0
  36. package/dist/execution/resourceBroker.js +5 -4
  37. package/dist/executor/agentExecutor.js +52 -50
  38. package/dist/executor/effectiveLaunch.js +8 -48
  39. package/dist/executor/fileRoleLaunchPlanner.js +129 -45
  40. package/dist/executor/workspacePreflightClassification.js +23 -2
  41. package/dist/interaction/operatorPresentation.js +33 -89
  42. package/dist/lifecycle/exactRunTerminalization.js +69 -5
  43. package/dist/observability/executionAudit.js +5 -0
  44. package/dist/observability/orchestrationMetrics.js +8 -3
  45. package/dist/profile/agentProfile.js +1 -1
  46. package/dist/release/cliHomeReleaseFence.js +123 -0
  47. package/dist/release/runtimeRelease.js +20 -0
  48. package/dist/repository/taskWorkspacePreparer.js +176 -60
  49. package/dist/resources/sqliteResourceRegistry.js +1 -1
  50. package/dist/review/deltaRecheck.js +12 -51
  51. package/dist/review/reviewAcceptance.js +26 -0
  52. package/dist/review/reviewConfig.js +0 -31
  53. package/dist/review/reviewDecision.js +113 -0
  54. package/dist/review/reviewOutcomeClassifier.js +1 -1
  55. package/dist/review/reviewRound.js +1 -1
  56. package/dist/review/reviewerAvailability.js +69 -0
  57. package/dist/run/recoveryProjection.js +45 -6
  58. package/dist/runtime/agentDriverObservation.js +24 -10
  59. package/dist/runtime/agentHost.js +159 -85
  60. package/dist/runtime/builtinAgentDrivers.js +4 -3
  61. package/dist/runtime/builtinTranscriptObserver.js +301 -64
  62. package/dist/runtime/builtinTranscriptUsage.js +9 -7
  63. package/dist/runtime/conversationSwitch.js +277 -0
  64. package/dist/runtime/index.js +2 -1
  65. package/dist/runtime/launchBroker.js +12 -0
  66. package/dist/runtime/processExitOutbox.js +88 -0
  67. package/dist/runtime/providerRuntimeIdentity.js +29 -1
  68. package/dist/runtime/runtimeHealthPolicy.js +5 -5
  69. package/dist/runtime/runtimeObservation.js +28 -0
  70. package/dist/runtime/runtimeProjection.js +22 -32
  71. package/dist/runtime/sessionTokenMetrics.js +181 -0
  72. package/dist/runtime/structuredProviderHost.js +7 -1
  73. package/dist/runtime/tmuxAdapters.js +4 -1
  74. package/dist/scheduler/activeRoleRunDelivery.js +34 -199
  75. package/dist/scheduler/activeTaskProgress.js +7 -6
  76. package/dist/scheduler/leaderWakeupProcessor.js +42 -138
  77. package/dist/scheduler/operatorEvent.js +34 -0
  78. package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
  79. package/dist/scheduler/roleRunStall.js +53 -31
  80. package/dist/scheduler/taskExecutionProjection.js +97 -76
  81. package/dist/scheduler/taskObservabilityProjection.js +10 -11
  82. package/dist/storage/migration/productionRegistry.js +379 -0
  83. package/dist/storage/sqliteSchema.js +66 -23
  84. package/dist/storage/sqliteStore.js +46 -23
  85. package/dist/storage/storeRpc.js +0 -1
  86. package/dist/storage/taskStore.js +15 -30
  87. package/dist/storage/upgrade/homeClassification.js +52 -0
  88. package/dist/storage/upgrade/offlineUpgradeInventory.js +145 -7
  89. package/dist/storage/upgrade/recordVersions.js +3 -2
  90. package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
  91. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
  92. package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
  93. package/dist/storage/upgrade/upgradeOrchestrator.js +333 -12
  94. package/dist/task/completionReadiness.js +10 -6
  95. package/dist/task/nextAction.js +82 -58
  96. package/dist/telemetry/sqliteTelemetryStore.js +1 -1
  97. package/dist/web/assets/client/components.js +13 -3
  98. package/dist/web/assets/client/i18n.js +6 -2
  99. package/dist/web/webSnapshot.js +7 -1
  100. package/i18n/README.zh-CN.md +2 -2
  101. package/package.json +1 -1
  102. package/skills/yui-leader/SKILL.md +60 -20
  103. package/skills/yui-operator/SKILL.md +13 -4
  104. package/skills/yui-reviewer/SKILL.md +35 -11
  105. package/dist/context/sessionContextBudget.js +0 -71
  106. package/dist/lifecycle/contextBudgetRollover.js +0 -81
  107. package/dist/lifecycle/taskRoleSessionReset.js +0 -126
  108. package/dist/scheduler/operatorNotification.js +0 -59
@@ -0,0 +1,181 @@
1
+ import { builtinDriverIdForAdapter } from "./builtinAgentDrivers.js";
2
+ import { isRuntimeTokenEvidence, runtimeObservationFromTaskEvent } from "./runtimeObservation.js";
3
+ const UNOBSERVED = Object.freeze({ status: "unobserved" });
4
+ export function resolveSessionTokenIdentity(input) {
5
+ if (input?.taskId === undefined
6
+ || input.roleName === undefined
7
+ || input.agentId === undefined
8
+ || input.adapterId === undefined
9
+ || input.launchId === undefined
10
+ || input.nativeSessionId === undefined)
11
+ return null;
12
+ let driverId;
13
+ try {
14
+ driverId = builtinDriverIdForAdapter(input.adapterId);
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ return Object.freeze({
20
+ taskId: input.taskId,
21
+ roleName: input.roleName,
22
+ agentId: input.agentId,
23
+ driverId,
24
+ launchId: input.launchId,
25
+ nativeSessionId: input.nativeSessionId,
26
+ sessionGenerationId: input.launchId
27
+ });
28
+ }
29
+ /**
30
+ * Read-only projection over canonical runtime observations. Token values never
31
+ * decide lifecycle, health, scheduling, resource admission, or workflow state.
32
+ */
33
+ export function projectSessionTokenMetrics(events, identity) {
34
+ if (identity === null)
35
+ return unobservedSessionTokenMetrics();
36
+ const observations = events
37
+ .map(runtimeObservationFromTaskEvent)
38
+ .filter((observation) => (observation !== null
39
+ && isRuntimeTokenEvidence(observation)
40
+ && matchesSessionGeneration(observation, identity)))
41
+ .sort(compareObservations);
42
+ const incompleteBoundaries = observations.filter(({ payload }) => (payload.usage === undefined));
43
+ const request = observations.filter(({ payload }) => (payload.usage?.semantics === "request-context"));
44
+ const cumulative = observations.filter(({ payload }) => (payload.usage?.semantics === "cumulative-session"));
45
+ // A generation cannot switch counter semantics without an explicit fact
46
+ // explaining how the two streams overlap. Remaining-context is capacity,
47
+ // not consumption, and is intentionally absent from both metrics.
48
+ if (request.length > 0 && cumulative.length > 0) {
49
+ return unobservedSessionTokenMetrics(identity);
50
+ }
51
+ if (request.length > 0) {
52
+ return projectRequestSnapshots(identity, request, incompleteBoundaries.length > 0);
53
+ }
54
+ if (cumulative.length > 0) {
55
+ return projectCumulativeSnapshots(identity, cumulative, incompleteBoundaries.length > 0);
56
+ }
57
+ return unobservedSessionTokenMetrics(identity);
58
+ }
59
+ export function unobservedSessionTokenMetrics(identity = null) {
60
+ return Object.freeze({
61
+ identity: identity === null ? null : Object.freeze({ ...identity }),
62
+ cumulativeTotal: UNOBSERVED,
63
+ maximumRequestInput: UNOBSERVED
64
+ });
65
+ }
66
+ function projectRequestSnapshots(identity, observations, hasIncompleteBoundary) {
67
+ if (hasIncompleteBoundary || observations.some(({ payload }) => (payload.observationQuality === "partial"
68
+ || payload.observationQuality === "unavailable")))
69
+ return unobservedSessionTokenMetrics(identity);
70
+ const requests = new Map();
71
+ for (const observation of observations) {
72
+ const usage = observation.payload.usage;
73
+ const requestId = observation.payload.activityId ?? observation.semanticKey;
74
+ // Structured providers may publish multiple snapshots for one stable
75
+ // request identity. The latest snapshot supersedes the earlier delivery;
76
+ // replays with the same identity never become a second request.
77
+ requests.set(requestId, usage);
78
+ }
79
+ const values = [...requests.values()];
80
+ const inputTokens = safeSum(values.map((usage) => usage.inputTokens));
81
+ const outputTokens = safeSum(values.map((usage) => usage.outputTokens));
82
+ if (inputTokens === null || outputTokens === null) {
83
+ return unobservedSessionTokenMetrics(identity);
84
+ }
85
+ const totalTokens = safeAdd(inputTokens, outputTokens);
86
+ if (totalTokens === null)
87
+ return unobservedSessionTokenMetrics(identity);
88
+ const cachedInputTokens = values.every((usage) => usage.cachedInputTokens !== undefined)
89
+ ? safeSum(values.map((usage) => usage.cachedInputTokens))
90
+ : null;
91
+ const reasoningTokens = values.every((usage) => usage.reasoningTokens !== undefined)
92
+ ? safeSum(values.map((usage) => usage.reasoningTokens))
93
+ : null;
94
+ return Object.freeze({
95
+ identity: Object.freeze({ ...identity }),
96
+ cumulativeTotal: Object.freeze({
97
+ status: "observed",
98
+ totalTokens,
99
+ inputTokens,
100
+ outputTokens,
101
+ ...(cachedInputTokens === null ? {} : { cachedInputTokens }),
102
+ ...(reasoningTokens === null ? {} : { reasoningTokens })
103
+ }),
104
+ maximumRequestInput: Object.freeze({
105
+ status: "observed",
106
+ inputTokens: Math.max(...values.map((usage) => usage.inputTokens))
107
+ })
108
+ });
109
+ }
110
+ function projectCumulativeSnapshots(identity, observations, hasIncompleteBoundary) {
111
+ const values = observations.map(({ payload }) => payload.usage);
112
+ const requestBoundaryIncomplete = hasIncompleteBoundary || observations.some(({ payload }) => (payload.observationQuality === "partial"
113
+ || payload.observationQuality === "unavailable"));
114
+ let counterRollback = false;
115
+ let maximumRequestInput = null;
116
+ for (let index = 1; index < values.length; index += 1) {
117
+ const previous = values[index - 1];
118
+ const current = values[index];
119
+ if (current.inputTokens < previous.inputTokens
120
+ || current.outputTokens < previous.outputTokens)
121
+ counterRollback = true;
122
+ const requestInput = Math.max(0, current.inputTokens - previous.inputTokens);
123
+ maximumRequestInput = maximumRequestInput === null
124
+ ? requestInput
125
+ : Math.max(maximumRequestInput, requestInput);
126
+ }
127
+ const latest = values.at(-1);
128
+ const totalTokens = safeAdd(latest.inputTokens, latest.outputTokens);
129
+ const cumulativeTotal = counterRollback || totalTokens === null
130
+ ? UNOBSERVED
131
+ : Object.freeze({
132
+ status: "observed",
133
+ totalTokens,
134
+ inputTokens: latest.inputTokens,
135
+ outputTokens: latest.outputTokens,
136
+ ...(latest.cachedInputTokens === undefined
137
+ ? {}
138
+ : { cachedInputTokens: latest.cachedInputTokens }),
139
+ ...(latest.reasoningTokens === undefined
140
+ ? {}
141
+ : { reasoningTokens: latest.reasoningTokens })
142
+ });
143
+ return Object.freeze({
144
+ identity: Object.freeze({ ...identity }),
145
+ cumulativeTotal,
146
+ maximumRequestInput: counterRollback
147
+ || requestBoundaryIncomplete
148
+ || maximumRequestInput === null
149
+ ? UNOBSERVED
150
+ : Object.freeze({ status: "observed", inputTokens: maximumRequestInput })
151
+ });
152
+ }
153
+ function matchesSessionGeneration(observation, identity) {
154
+ const fence = observation.fence;
155
+ return fence.taskId === identity.taskId
156
+ && fence.roleName === identity.roleName
157
+ && fence.agentId === identity.agentId
158
+ && fence.driverId === identity.driverId
159
+ && fence.launchId === identity.launchId
160
+ && fence.nativeSessionId === identity.nativeSessionId
161
+ && fence.sessionGenerationId === identity.sessionGenerationId;
162
+ }
163
+ function compareObservations(left, right) {
164
+ return left.receivedAt.localeCompare(right.receivedAt)
165
+ || (left.sequence ?? -1) - (right.sequence ?? -1)
166
+ || (left.ordinal ?? -1) - (right.ordinal ?? -1)
167
+ || left.eventId.localeCompare(right.eventId);
168
+ }
169
+ function safeSum(values) {
170
+ let total = 0;
171
+ for (const value of values) {
172
+ total = safeAdd(total, value) ?? Number.NaN;
173
+ if (!Number.isSafeInteger(total))
174
+ return null;
175
+ }
176
+ return total;
177
+ }
178
+ function safeAdd(left, right) {
179
+ const total = left + right;
180
+ return Number.isSafeInteger(total) && total >= 0 ? total : null;
181
+ }
@@ -391,7 +391,13 @@ class ClaudeStructuredProviderSession {
391
391
  waiter.reject(new ProviderDeliveryUnknownError("Claude replayed input for a different native Session.", waiter.attemptId));
392
392
  return;
393
393
  }
394
- const nativeTurnId = `claude-input:${waiter.attemptId}`;
394
+ const nativeTurnId = optionalId(message.uuid);
395
+ if (nativeTurnId === undefined) {
396
+ clearTimeout(waiter.timer);
397
+ this.#waiter = undefined;
398
+ waiter.reject(new ProviderDeliveryUnknownError("Claude replayed input without a native message identity.", waiter.attemptId));
399
+ return;
400
+ }
395
401
  this.#activeTurnId = nativeTurnId;
396
402
  clearTimeout(waiter.timer);
397
403
  this.#waiter = undefined;
@@ -408,7 +408,10 @@ export class TmuxSessionHost {
408
408
  throw error;
409
409
  }
410
410
  if (providerDispatchObserved
411
- && hostCreated
411
+ // A fresh Host always starts a Provider process. mode=new also starts
412
+ // one inside a reused Host by replacing (or creating) the native
413
+ // Conversation. Same-Conversation resume must retain its inherited key.
414
+ && (hostCreated || request.mode === "new")
412
415
  && request.owner.scope === "task"
413
416
  && planned.launch.env.YUI_JOB_CALLER_KEY !== undefined
414
417
  && this.planner.commitTaskCallerKey !== undefined) {
@@ -1,20 +1,14 @@
1
1
  import { selectedSchedulerRoles, selectedActiveSchedulerTasks } from "./ports.js";
2
2
  import { isSchedulerTaskWorkspaceReady } from "./ports.js";
3
3
  import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
4
- import { prefixYuiTitleInput } from "../run/runIdentity.js";
5
- import { resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
6
4
  import { agentRunDeliveryReceiptId } from "../run/agentRun.js";
7
- import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain } from "../executor/effectiveLaunch.js";
5
+ import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession } from "../executor/effectiveLaunch.js";
8
6
  import { RuntimeLaunchError } from "../runtime/ports.js";
9
7
  import { RuntimeLaunchFailure } from "../runtime/launchDiagnostics.js";
10
- import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
11
8
  import { mailboxHasWork, nextPendingBatch } from "../coordination/workMailbox.js";
12
9
  import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
13
10
  import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
14
11
  import { hasRuntimeLifecycleWork, RuntimeLifecycleBusyError, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
15
- import { serializeRunBootstrapEnvelope } from "../context/runContextContract.js";
16
- import { serializeProviderRetryEnvelope } from "../run/providerRetry.js";
17
- import { serializeWorkflowOutcomeRequestEnvelope } from "../run/runControlRequest.js";
18
12
  /**
19
13
  * Delivers durable Work AgentRuns before liveness reconciliation. Task command
20
14
  * handlers only record intent; this Controller path is the sole automated
@@ -109,9 +103,14 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
109
103
  let preparedSession = existingSession;
110
104
  let preStartFencePersisted = false;
111
105
  let deliveryAttempted = false;
112
- let providerSubmissionBegun = false;
113
106
  try {
114
- const nativeSessionId = run.mode === "resume"
107
+ // Workflow-outcome control is always another Turn on the already-bound
108
+ // Conversation. Provider retry alone may carry an exact missing-
109
+ // Conversation decision and therefore retains its explicit new mode.
110
+ const launchMode = run.controlRequest?.state === "dispatching"
111
+ ? "resume"
112
+ : run.mode;
113
+ const nativeSessionId = launchMode === "resume"
115
114
  ? requireResumeSession(role, run.effective, existingSession)
116
115
  : undefined;
117
116
  prepared = await delivery.prepareRoleSession({
@@ -124,12 +123,12 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
124
123
  ...(run.workspace === undefined
125
124
  ? {}
126
125
  : { managedWorkspace: run.workspace }),
127
- mode: run.mode,
126
+ mode: launchMode,
128
127
  runId: run.id,
129
128
  ...(nativeSessionId === undefined ? {} : { nativeSessionId }),
130
129
  beforeHostStart: (preflight) => {
131
130
  persistPreStartFence(store, task, role, run, existingSession, preflight, now);
132
- preparedSession = preflightSession(role, run.effective, existingSession, run.mode, preflight);
131
+ preparedSession = preflightSession(role, run.effective, existingSession, launchMode, preflight);
133
132
  preStartFencePersisted = true;
134
133
  }
135
134
  });
@@ -177,20 +176,32 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
177
176
  now
178
177
  });
179
178
  if (ready.prepared.turnRejectedDuringLaunch === true) {
180
- delivery.forgetPrepared?.({
181
- taskId: task.id,
182
- roleName: role.name,
183
- runId: run.id,
184
- ...(ready.prepared.launchId === undefined
185
- ? {}
186
- : { launchId: ready.prepared.launchId })
179
+ const persisted = store.saveRoleRunDeliveryFailure({
180
+ ...roleRunDeliveryFailure(run, processing.batchId, session, ready.prepared.launchId),
181
+ summary: `Provider conclusively rejected the exact initial Run input: ${run.id}.`,
182
+ cleanupRequired: false,
183
+ now
187
184
  });
185
+ if (persisted === "failed") {
186
+ delivery.forgetPrepared?.({
187
+ taskId: task.id,
188
+ roleName: role.name,
189
+ runId: run.id,
190
+ ...(ready.prepared.launchId === undefined
191
+ ? {}
192
+ : { launchId: ready.prepared.launchId })
193
+ });
194
+ }
188
195
  results.push({
189
196
  taskId: task.id,
190
197
  roleName: role.name,
191
198
  runId: run.id,
192
- status: "skipped",
193
- reason: "not-ready"
199
+ status: persisted === "failed" ? "failed" : "skipped",
200
+ reason: "provider-rejected",
201
+ terminalized: persisted === "failed",
202
+ ...(persisted === "failed"
203
+ ? {}
204
+ : { error: "Run state changed while recording exact Provider rejection." })
194
205
  });
195
206
  continue;
196
207
  }
@@ -252,171 +263,10 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
252
263
  });
253
264
  continue;
254
265
  }
255
- // Pre-input readiness gate. For an adapter whose capability proves a
256
- // native event fires before the first prompt (e.g. Claude SessionStart),
257
- // a freshly-started host must not be pushed until that provider-ready
258
- // fact has been folded. Unsupported adapters (e.g. Codex) have no
259
- // pre-input event, so their push proceeds and acceptance is confirmed
260
- // only by the later exact provider-accepted fold. The gate reads the
261
- // adapter capability and the durable ready projection — never a sleep,
262
- // screen scrape, or pane/PID inference — and fails closed for a
263
- // supported adapter whose readiness cannot be confirmed.
264
- if (ready.prepared.sessionStarted
265
- && builtinAgentDriverRegistry().requireByAdapterId(run.effective.adapterId)
266
- .capabilities.observation.preInputReadiness === "exact"
267
- && !providerReadyForPush(store, {
268
- taskId: task.id,
269
- roleName: role.name,
270
- agentId: run.effective.agentId,
271
- ...(ready.prepared.launchId === undefined ? {} : { launchId: ready.prepared.launchId }),
272
- ...(session?.nativeSessionId === undefined
273
- ? {}
274
- : { nativeSessionId: session.nativeSessionId })
275
- })) {
276
- results.push({
277
- taskId: task.id,
278
- roleName: role.name,
279
- runId: run.id,
280
- status: "skipped",
281
- reason: "not-ready"
282
- });
283
- continue;
284
- }
285
- if (session === null || session.launchId === undefined
286
- || !hasText(session.nativeSessionId)
287
- || store.beginRoleRunProviderTurn?.({
288
- taskId: task.id,
289
- roleName: role.name,
290
- runId: run.id,
291
- agentId: run.effective.agentId,
292
- launchId: session.launchId,
293
- nativeSessionId: session.nativeSessionId,
294
- attemptId: receiptId,
295
- now
296
- }) !== true) {
297
- throw new Error("Provider Turn intent could not be durably fenced before delivery.");
298
- }
299
- providerSubmissionBegun = true;
300
- deliveryAttempted = true;
301
- const launchText = run.controlRequest?.state === "dispatching"
302
- ? serializeWorkflowOutcomeRequestEnvelope({
303
- taskId: task.id,
304
- runId: run.id,
305
- roleName: role.name,
306
- request: run.controlRequest
307
- })
308
- : run.providerRetry?.state === "dispatching"
309
- ? serializeProviderRetryEnvelope({
310
- taskId: task.id,
311
- runId: run.id,
312
- roleName: role.name,
313
- retry: run.providerRetry
314
- })
315
- : serializeRunBootstrapEnvelope(run.bootstrapEnvelope);
316
- const outcome = await delivery.sendOnce({
317
- delivery: ready,
318
- receiptId,
319
- text: prefixYuiTitleInput(launchText, resolveTaskRoleSessionTitle(session.title, task, role.name))
320
- });
321
- if (outcome === "busy" || outcome === "unavailable") {
322
- store.resolveRoleRunProviderSubmission?.({
323
- taskId: task.id,
324
- roleName: role.name,
325
- runId: run.id,
326
- attemptId: receiptId,
327
- status: "rejected",
328
- reason: outcome === "busy"
329
- ? "Agent Host was busy before Provider mutation."
330
- : "Agent Host was unavailable before Provider mutation.",
331
- now
332
- });
333
- providerSubmissionBegun = false;
334
- results.push({
335
- taskId: task.id,
336
- roleName: role.name,
337
- runId: run.id,
338
- status: "skipped",
339
- reason: outcome === "busy" ? "not-ready" : "runtime-unavailable",
340
- terminalFailure: roleRunDeliveryFailure(run, processing.batchId, session, ready.prepared.launchId)
341
- });
342
- continue;
343
- }
344
- if (outcome === "rejected") {
345
- store.resolveRoleRunProviderSubmission?.({
346
- taskId: task.id,
347
- roleName: role.name,
348
- runId: run.id,
349
- attemptId: receiptId,
350
- status: "rejected",
351
- reason: "Provider returned an exact negative acknowledgement.",
352
- now
353
- });
354
- providerSubmissionBegun = false;
355
- results.push({
356
- taskId: task.id,
357
- roleName: role.name,
358
- runId: run.id,
359
- status: "skipped",
360
- reason: "not-ready"
361
- });
362
- continue;
363
- }
364
- if (outcome === "delivery-unknown") {
365
- store.resolveRoleRunProviderSubmission?.({
366
- taskId: task.id,
367
- roleName: role.name,
368
- runId: run.id,
369
- attemptId: receiptId,
370
- status: "delivery-unknown",
371
- reason: "Provider Turn delivery is ambiguous; automatic retry is fenced.",
372
- now
373
- });
374
- providerSubmissionBegun = false;
375
- store.saveRoleRunDelivery({
376
- task,
377
- role,
378
- run,
379
- session,
380
- ...(ready.prepared.launchId === undefined
381
- ? {}
382
- : { launchId: ready.prepared.launchId }),
383
- now
384
- });
385
- results.push({
386
- taskId: task.id,
387
- roleName: role.name,
388
- runId: run.id,
389
- status: "skipped",
390
- reason: "delivery-uncertain"
391
- });
392
- continue;
393
- }
394
- const status = outcome === "sent" ? "delivered" : "already-delivered";
395
- store.saveRoleRunDelivery({
396
- task,
397
- role,
398
- run,
399
- session,
400
- ...(ready.prepared.launchId === undefined
401
- ? {}
402
- : { launchId: ready.prepared.launchId }),
403
- now
404
- });
405
- results.push({ taskId: task.id, roleName: role.name, runId: run.id, status });
266
+ throw new Error("Managed Provider launch completed without an initial Turn outcome.");
406
267
  }
407
268
  catch (error) {
408
269
  const message = error instanceof Error ? error.message : String(error);
409
- if (providerSubmissionBegun) {
410
- store.resolveRoleRunProviderSubmission?.({
411
- taskId: task.id,
412
- roleName: role.name,
413
- runId: run.id,
414
- attemptId: receiptId,
415
- status: "delivery-unknown",
416
- reason: message,
417
- now
418
- });
419
- }
420
270
  // Scheduler single-flight backpressure: the Role runtime lifecycle
421
271
  // lane was busy when the launch was reserved. The Run stays
422
272
  // active-unpushed and its mailbox claim is retained; the next pass
@@ -881,7 +731,7 @@ function requireResumeSession(role, effective, session) {
881
731
  if (session === null || !hasText(session.nativeSessionId)) {
882
732
  throw new Error(`Role resume has no fixed native session: ${role.taskId}/${role.name}.`);
883
733
  }
884
- if (!effectiveLaunchSnapshotsCompatibleForTaskMain(session.effective, effective, role.managedWorkspace)) {
734
+ if (!effectiveLaunchSnapshotsCompatibleForTaskSession(session.effective, effective)) {
885
735
  throw new Error(`Role resume effective snapshot drifted: ${role.taskId}/${role.name}.`);
886
736
  }
887
737
  return session.nativeSessionId;
@@ -948,16 +798,11 @@ function validateRoleSession(role, effective, existing, mode, session) {
948
798
  throw new Error(`Ready Role session identity changed: ${role.taskId}/${role.name}.`);
949
799
  }
950
800
  const compatible = mode === "resume"
951
- ? effectiveLaunchSnapshotsCompatibleForTaskMain(session.effective, effective, role.managedWorkspace)
801
+ ? effectiveLaunchSnapshotsCompatibleForTaskSession(session.effective, effective)
952
802
  : effectiveLaunchSnapshotsCompatible(session.effective, effective);
953
803
  if (!compatible) {
954
804
  throw new Error(`Ready Role session effective snapshot changed: ${role.taskId}/${role.name}.`);
955
805
  }
956
- if (existing?.nativeSessionId !== undefined
957
- && effectiveLaunchSnapshotsCompatibleForTaskMain(existing.effective, effective, role.managedWorkspace)
958
- && session.nativeSessionId !== existing.nativeSessionId) {
959
- throw new Error(`Ready Role session changed the fixed native session id: ${role.taskId}/${role.name}.`);
960
- }
961
806
  if (mode === "resume" && session.nativeSessionId !== existing?.nativeSessionId) {
962
807
  throw new Error(`Role resume changed the fixed native session id: ${role.taskId}/${role.name}.`);
963
808
  }
@@ -972,13 +817,3 @@ function validateRoleSession(role, effective, existing, mode, session) {
972
817
  function hasText(value) {
973
818
  return typeof value === "string" && value.trim().length > 0;
974
819
  }
975
- /**
976
- * Fail-closed readiness check for a supported-readiness adapter's first push.
977
- * When the store cannot answer (no implementation), the push is held rather than
978
- * proceeding blind — a supported adapter must have a proven provider-ready fold.
979
- */
980
- function providerReadyForPush(store, input) {
981
- if (store.isRoleGenerationProviderReady === undefined)
982
- return false;
983
- return store.isRoleGenerationProviderReady(input);
984
- }
@@ -35,8 +35,7 @@ export function repairOrphanedActiveTasks(store, now, selection) {
35
35
  runs: activeRuns,
36
36
  pendingWakeup: store.getPendingWakeup(task.id),
37
37
  leaderMailbox,
38
- leaderFailure: store.getLeaderFailure(task.id),
39
- operatorNotification: store.getOperatorNotification(task.id)
38
+ leaderFailure: store.getLeaderFailure(task.id)
40
39
  });
41
40
  const queuedAlongsideActiveSibling = hasDispatchableQueuedResourceLane(store, task.id)
42
41
  && !activeRuns.some(({ roleName }) => roleName === "leader")
@@ -45,7 +44,6 @@ export function repairOrphanedActiveTasks(store, now, selection) {
45
44
  if ((queuedAlongsideActiveSibling ? hasLeaderInFlightTurn : hasInFlightTurn)
46
45
  || store.hasOpenInputRequest(task.id)
47
46
  || store.getLeaderFailure(task.id) !== null
48
- || store.getOperatorNotification(task.id) !== null
49
47
  || (projection.status !== "needs-leader-action" && !queuedAlongsideActiveSibling)
50
48
  || hasUnclaimedLeaderWork(store, task.id, leaderMailbox)) {
51
49
  continue;
@@ -121,8 +119,11 @@ function findLastLeaderRun(store, taskId) {
121
119
  };
122
120
  }
123
121
  function hasUnclaimedLeaderWork(store, taskId, leaderMailbox) {
122
+ if (store.getPendingWakeup(taskId) !== null)
123
+ return true;
124
124
  if (leaderMailbox === null)
125
- return store.getPendingWakeup(taskId) !== null;
126
- return leaderMailbox?.processing === null
127
- && leaderMailbox.pending !== null;
125
+ return false;
126
+ return leaderMailbox.processing === null
127
+ && (leaderMailbox.pending.normal !== null
128
+ || leaderMailbox.pending.userCorrection !== null);
128
129
  }