@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.
- package/README.md +9 -8
- package/dist/cli/commandCatalog.js +21 -2
- package/dist/cli.js +42 -13
- package/dist/commands/agentCommands.js +1 -1
- package/dist/commands/configCommands.js +1 -86
- package/dist/commands/executionAuditCommands.js +17 -16
- package/dist/commands/globalRoleCommands.js +4 -4
- package/dist/commands/sessionCommands.js +2 -6
- package/dist/commands/taskActor.js +1 -2
- package/dist/commands/taskCommands.js +92 -9
- package/dist/commands/taskRoleRuntimeStatus.js +3 -3
- package/dist/config/configCatalog.js +1 -6
- package/dist/config/yuiConfig.js +0 -79
- package/dist/controller/clientRuntime.js +40 -3
- package/dist/controller/controller.js +29 -52
- package/dist/controller/fileSchedulerStoreAdapter.js +305 -1056
- package/dist/controller/runtime.js +12 -5
- package/dist/controller/runtimeHookRunFence.js +3 -9
- package/dist/controller/runtimeLaunchCoordinator.js +44 -67
- package/dist/controller/structuredProviderObservation.js +18 -5
- package/dist/coordination/workMailbox.js +4 -4
- package/dist/execution/executionHealth.js +1 -1
- package/dist/executor/agentExecutor.js +92 -68
- package/dist/executor/executorRegistry.js +8 -20
- package/dist/executor/fileRoleLaunchPlanner.js +24 -90
- package/dist/executor/turnCompletion.js +5 -5
- package/dist/lifecycle/exactRunTerminalization.js +1 -1
- package/dist/observability/executionAudit.js +40 -94
- package/dist/operator/operatorSessionHistory.js +7 -5
- package/dist/role/role.js +1 -1
- package/dist/run/agentRun.js +4 -54
- package/dist/runtime/agentDriver.js +2 -0
- package/dist/runtime/agentError.js +114 -0
- package/dist/runtime/agentHost.js +55 -82
- package/dist/runtime/builtinAgentDrivers.js +21 -9
- package/dist/runtime/builtinAgentErrorMappers.js +150 -0
- package/dist/runtime/exactControlPlane.js +6 -12
- package/dist/runtime/index.js +0 -1
- package/dist/runtime/launchBroker.js +5 -19
- package/dist/runtime/lifecycleReservation.js +20 -4
- package/dist/runtime/providerRuntimeIdentity.js +3 -2
- package/dist/runtime/runtimeBinding.js +0 -27
- package/dist/runtime/runtimeObservation.js +7 -16
- package/dist/runtime/runtimeSessionCandidate.js +3 -10
- package/dist/runtime/sessionLaunchRequest.js +1 -2
- package/dist/runtime/sessionReconciliation.js +2 -2
- package/dist/runtime/structuredProviderHost.js +44 -79
- package/dist/runtime/taskRuntimeIsolation.js +0 -7
- package/dist/runtime/tmuxAdapters.js +6 -49
- package/dist/scheduler/activeRoleRunDelivery.js +218 -168
- package/dist/scheduler/leaderWakeupProcessor.js +126 -86
- package/dist/scheduler/roleRunLiveness.js +4 -1
- package/dist/scheduler/roleRunStall.js +7 -11
- package/dist/scheduler/wakeReason.js +4 -0
- package/dist/storage/migration/productionRegistry.js +332 -0
- package/dist/storage/sqliteSchema.js +54 -2
- package/dist/storage/sqliteStore.js +11 -44
- package/dist/storage/taskStore.js +7 -35
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +30 -8
- package/skills/yui-operator/SKILL.md +14 -6
- package/skills/yui-runtime/SKILL.md +6 -4
- package/dist/lifecycle/providerErrorClass.js +0 -152
- package/dist/run/providerRetry.js +0 -226
- package/dist/run/providerRetryConfig.js +0 -27
- package/dist/runtime/providerErrorCodes.js +0 -278
- 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:
|
|
14
|
+
? { ...base, schemaVersion: 4 }
|
|
15
15
|
: {
|
|
16
16
|
...base,
|
|
17
|
-
schemaVersion:
|
|
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 === "
|
|
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
|
|
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:
|
|
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, "
|
|
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]: {
|
|
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
|
|
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
|
-
&&
|
|
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, {
|
|
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 =
|
|
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
|
|
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 === "
|
|
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
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
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
|
|
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
|
-
|
|
404
|
+
const activation = binding === null
|
|
405
|
+
? null
|
|
406
|
+
: currentProviderActivation(binding);
|
|
387
407
|
if (activation !== null) {
|
|
388
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 !==
|
|
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 !== "
|
|
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 !==
|
|
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 !== "
|
|
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 !==
|
|
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
|
|
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.
|
|
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: "
|
|
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
|
-
//
|
|
482
|
-
//
|
|
483
|
-
//
|
|
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 &&
|
|
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
|
-
:
|
|
492
|
+
: input.mode === "resume"
|
|
509
493
|
? {
|
|
510
494
|
schemaVersion: 1,
|
|
511
495
|
adapterId: binding.adapterId,
|
|
512
496
|
transport: managedCompiled.transport,
|
|
513
|
-
kind: "
|
|
497
|
+
kind: "restore",
|
|
514
498
|
mode: "resume",
|
|
515
|
-
nativeSessionId: requireText(providerNativeSessionId, "Managed Provider
|
|
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
|
-
:
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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: "
|
|
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:
|
|
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:
|
|
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) =>
|
|
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 =
|
|
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(
|
|
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
|
|
272
|
+
if (session?.status === "active" && !providerSettled) {
|
|
273
273
|
return {
|
|
274
274
|
disposition: "blocked",
|
|
275
275
|
run: current,
|