@zq-silk/yui 0.8.1 → 0.8.2
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/ARCHITECTURE.md +27 -28
- package/README.md +35 -46
- package/dist/cli/commandCatalog.js +49 -14
- package/dist/cli/interactionPolicy.js +4 -10
- package/dist/cli/invocationRouter.js +2 -1
- package/dist/cli.js +73 -21
- package/dist/commands/taskCommands.js +108 -53
- package/dist/controller/fileSchedulerStoreAdapter.js +389 -70
- package/dist/controller/resourceInventory.js +9 -5
- package/dist/controller/runtime.js +80 -7
- package/dist/controller/runtimeLaunchCoordinator.js +18 -78
- package/dist/controller/structuredProviderObservation.js +273 -0
- package/dist/executor/agentAdapter.js +40 -0
- package/dist/executor/agentExecutor.js +31 -7
- package/dist/executor/executorRegistry.js +11 -49
- package/dist/executor/fileRoleLaunchPlanner.js +115 -37
- package/dist/lifecycle/canonicalLifecycleEvent.js +5 -2
- package/dist/run/agentRun.js +2 -2
- package/dist/runtime/agentHost.js +767 -158
- package/dist/runtime/builtinAgentDrivers.js +1 -5
- package/dist/runtime/codexAppServerRuntime.js +67 -60
- package/dist/runtime/exactControlPlane.js +7 -2
- package/dist/runtime/index.js +6 -2
- package/dist/runtime/launchBroker.js +30 -8
- package/dist/runtime/providerAuthorityFence.js +24 -0
- package/dist/runtime/providerControl.js +63 -0
- package/dist/runtime/providerRecoveryDecision.js +55 -0
- package/dist/runtime/providerRuntimeIdentity.js +269 -19
- package/dist/runtime/runtimeBinding.js +20 -11
- package/dist/runtime/structuredProviderHost.js +476 -0
- package/dist/runtime/tmuxAdapters.js +143 -42
- package/dist/scheduler/activeRoleRunDelivery.js +206 -120
- package/dist/scheduler/leaderWakeupProcessor.js +141 -16
- package/dist/storage/migration/productionRegistry.js +111 -0
- package/dist/storage/taskStore.js +1 -1
- package/dist/tmux/tmuxManager.js +1 -1
- package/i18n/README.zh-CN.md +11 -8
- package/package.json +1 -1
|
@@ -17,9 +17,9 @@ import { AsyncTaskStoreClient, resolveStoreWorkerEnabledForHome } from "../stora
|
|
|
17
17
|
import { FileTaskWorkspacePreparer } from "../repository/taskWorkspacePreparer.js";
|
|
18
18
|
import { NodeCommandExecutor } from "../tmux/commandExecutor.js";
|
|
19
19
|
import { TmuxManager, yuiTmuxServerName } from "../tmux/tmuxManager.js";
|
|
20
|
-
import {
|
|
20
|
+
import { AgentHostPromptPushAdapter, FileTaskRuntimeIsolation, TmuxSessionHost, ProviderContinuationReconciliationService } from "../runtime/index.js";
|
|
21
21
|
import { startFileTaskController } from "./controller.js";
|
|
22
|
-
import { FileSchedulerStoreAdapter } from "./fileSchedulerStoreAdapter.js";
|
|
22
|
+
import { AgentHostProviderTurnFenceError, FileSchedulerStoreAdapter } from "./fileSchedulerStoreAdapter.js";
|
|
23
23
|
import { openSchedulerTelemetry } from "../telemetry/telemetryWiring.js";
|
|
24
24
|
import { createFileArtifactPort, createLinuxProcessPort, DurableJobSupervisor } from "./jobSupervisor.js";
|
|
25
25
|
import { createDurableJobControl } from "./jobControl.js";
|
|
@@ -38,7 +38,7 @@ import { launchBrokerForHome } from "../runtime/launchBroker.js";
|
|
|
38
38
|
import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
|
|
39
39
|
import { appendGlobalProcessExitObservation } from "../runtime/globalProcessExitStore.js";
|
|
40
40
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
41
|
-
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
41
|
+
import { createRuntimeObservation, runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
42
42
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
43
43
|
/** Refreshes only the exact Task runtime generation folded by the event transaction. */
|
|
44
44
|
export function refreshAppliedTaskRuntimeDescriptor(store, planner, input) {
|
|
@@ -198,7 +198,7 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
198
198
|
}
|
|
199
199
|
});
|
|
200
200
|
const promptPush = options.promptPush
|
|
201
|
-
?? new
|
|
201
|
+
?? new AgentHostPromptPushAdapter(home);
|
|
202
202
|
const runtimeIsolation = options.runtimeIsolation
|
|
203
203
|
?? new FileTaskRuntimeIsolation({
|
|
204
204
|
// A sibling of the exact control Home keeps provider data/cache/tmp out
|
|
@@ -283,9 +283,6 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
283
283
|
sessionHost,
|
|
284
284
|
promptPush,
|
|
285
285
|
launchCoordinator,
|
|
286
|
-
...(options.providerInputRouting === undefined
|
|
287
|
-
? {}
|
|
288
|
-
: { providerInputRouting: options.providerInputRouting }),
|
|
289
286
|
roleResourceInventory: async (panes, inputs) => {
|
|
290
287
|
const inventory = await scanInventory(panes);
|
|
291
288
|
return inventory.resources.flatMap((resource) => {
|
|
@@ -550,6 +547,55 @@ export function createRuntimeLifecycleDispatcher(store, schedulerStore, sessionH
|
|
|
550
547
|
});
|
|
551
548
|
const lifecycleTails = new Map();
|
|
552
549
|
return async (method, params) => {
|
|
550
|
+
if (method === "runtime.observation-apply") {
|
|
551
|
+
return {
|
|
552
|
+
outcome: schedulerStore.observeRuntimeObservation(createRuntimeObservation(params), new Date())
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
if (method === "runtime.provider-turn-begin") {
|
|
556
|
+
const value = providerTurnControlParams(params);
|
|
557
|
+
try {
|
|
558
|
+
schedulerStore.beginAgentHostProviderTurn({
|
|
559
|
+
taskId: value.taskId,
|
|
560
|
+
roleName: value.roleName,
|
|
561
|
+
runId: value.runId,
|
|
562
|
+
agentId: value.agentId,
|
|
563
|
+
launchId: value.launchId,
|
|
564
|
+
nativeSessionId: value.nativeSessionId,
|
|
565
|
+
attemptId: value.attemptId,
|
|
566
|
+
authorityEpoch: value.authorityEpoch,
|
|
567
|
+
authorityOwner: value.authorityOwner,
|
|
568
|
+
holderId: value.holderId,
|
|
569
|
+
now: value.now
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
if (error instanceof AgentHostProviderTurnFenceError) {
|
|
574
|
+
throw applicationError("INVALID_PARAMS", error.message);
|
|
575
|
+
}
|
|
576
|
+
throw error;
|
|
577
|
+
}
|
|
578
|
+
return { recorded: true };
|
|
579
|
+
}
|
|
580
|
+
if (method === "runtime.provider-turn-submission-resolve") {
|
|
581
|
+
const value = providerTurnControlParams(params);
|
|
582
|
+
const status = params.status;
|
|
583
|
+
const reason = params.reason;
|
|
584
|
+
if ((status !== "rejected" && status !== "delivery-unknown")
|
|
585
|
+
|| typeof reason !== "string" || reason.trim().length === 0) {
|
|
586
|
+
throw applicationError("INVALID_PARAMS", "Provider Turn resolution is invalid.");
|
|
587
|
+
}
|
|
588
|
+
schedulerStore.resolveAgentHostProviderTurnSubmission({
|
|
589
|
+
taskId: value.taskId,
|
|
590
|
+
roleName: value.roleName,
|
|
591
|
+
runId: value.runId,
|
|
592
|
+
attemptId: value.attemptId,
|
|
593
|
+
status,
|
|
594
|
+
reason,
|
|
595
|
+
now: value.now
|
|
596
|
+
});
|
|
597
|
+
return { recorded: true };
|
|
598
|
+
}
|
|
553
599
|
if (method === "runtime.process-exit-observe") {
|
|
554
600
|
const observation = validateRuntimeProcessExitObservation(params);
|
|
555
601
|
const run = observation.taskId === undefined || observation.runId === undefined
|
|
@@ -1032,6 +1078,33 @@ function requiredParam(value) {
|
|
|
1032
1078
|
}
|
|
1033
1079
|
return value;
|
|
1034
1080
|
}
|
|
1081
|
+
function providerTurnControlParams(params) {
|
|
1082
|
+
if (typeof params !== "object" || params === null || Array.isArray(params)) {
|
|
1083
|
+
throw applicationError("INVALID_PARAMS", "Provider Turn control params are invalid.");
|
|
1084
|
+
}
|
|
1085
|
+
const value = params;
|
|
1086
|
+
const authorityEpoch = value.authorityEpoch;
|
|
1087
|
+
const authorityOwner = value.authorityOwner;
|
|
1088
|
+
const observedAt = requiredParam(value.observedAt);
|
|
1089
|
+
if (!Number.isSafeInteger(authorityEpoch) || authorityEpoch < 1
|
|
1090
|
+
|| (authorityOwner !== "controller" && authorityOwner !== "human")
|
|
1091
|
+
|| !Number.isFinite(Date.parse(observedAt))) {
|
|
1092
|
+
throw applicationError("INVALID_PARAMS", "Provider Turn control fence is invalid.");
|
|
1093
|
+
}
|
|
1094
|
+
return {
|
|
1095
|
+
taskId: requiredParam(value.taskId),
|
|
1096
|
+
roleName: requiredParam(value.roleName),
|
|
1097
|
+
runId: requiredParam(value.runId),
|
|
1098
|
+
agentId: requiredParam(value.agentId),
|
|
1099
|
+
launchId: requiredParam(value.launchId),
|
|
1100
|
+
nativeSessionId: requiredParam(value.nativeSessionId),
|
|
1101
|
+
attemptId: requiredParam(value.attemptId),
|
|
1102
|
+
authorityEpoch: authorityEpoch,
|
|
1103
|
+
authorityOwner,
|
|
1104
|
+
holderId: requiredParam(value.holderId),
|
|
1105
|
+
now: new Date(observedAt)
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1035
1108
|
function abortableDelay(milliseconds, signal) {
|
|
1036
1109
|
return new Promise((resolve, reject) => {
|
|
1037
1110
|
const timer = setTimeout(() => {
|
|
@@ -8,12 +8,6 @@ class RuntimeBindingContractError extends Error {
|
|
|
8
8
|
this.name = "RuntimeBindingContractError";
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
class LaunchSubmittedHostBusyError extends RuntimeBindingContractError {
|
|
12
|
-
constructor(message) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.name = "LaunchSubmittedHostBusyError";
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
11
|
class RuntimeLaunchStateChangedError extends Error {
|
|
18
12
|
constructor(message, options) {
|
|
19
13
|
super(message, options);
|
|
@@ -103,12 +97,6 @@ export class RuntimeLaunchCoordinator {
|
|
|
103
97
|
...(request.runId === undefined ? {} : { runId: request.runId })
|
|
104
98
|
}, assertLaunchCurrent, this.#now());
|
|
105
99
|
let reusedConfirmedRunningHost = false;
|
|
106
|
-
const launchCarriesExactRunPrompt = carriesExactRunPrompt(request);
|
|
107
|
-
// Preserve the existing Codex recovery path; managed Claude is the one
|
|
108
|
-
// finite-process protocol that must never fall back to tmux key delivery
|
|
109
|
-
// when a newly-reserved Run encounters an older live pane.
|
|
110
|
-
let launchPromptAcknowledgementRequired = request.adapterId === "claude"
|
|
111
|
-
&& launchCarriesExactRunPrompt;
|
|
112
100
|
if (reservation.status === "existing") {
|
|
113
101
|
if (!reservation.launchId.startsWith(generationPrefix)) {
|
|
114
102
|
this.#requireCleanup(request.owner);
|
|
@@ -167,12 +155,6 @@ export class RuntimeLaunchCoordinator {
|
|
|
167
155
|
throw new Error(`Runtime launch reservation belongs to another Run whose host is still running: ${reservation.runId}.`);
|
|
168
156
|
}
|
|
169
157
|
reusedConfirmedRunningHost = true;
|
|
170
|
-
// A fresh Codex generation carries the prompt in its launch argv. A
|
|
171
|
-
// managed Claude generation carries it through stream-json stdin for
|
|
172
|
-
// both new and native-resume modes. Only recovery of the exact same
|
|
173
|
-
// reserved Run may bridge a lost in-memory launch acknowledgement.
|
|
174
|
-
launchPromptAcknowledgementRequired = sameRunReservation
|
|
175
|
-
&& launchCarriesExactRunPrompt;
|
|
176
158
|
runtimeIsolation = this.#preflightRuntimeIsolation(request, reservation.launchId, reservation.launchId.slice(generationPrefix.length), true);
|
|
177
159
|
assertLaunchCurrent();
|
|
178
160
|
}
|
|
@@ -229,11 +211,8 @@ export class RuntimeLaunchCoordinator {
|
|
|
229
211
|
: { environment: request.environment }),
|
|
230
212
|
nativeSessionId: requireText(request.nativeSessionId, "Native session id")
|
|
231
213
|
}, beforeHostStart === undefined ? undefined : observePreflight);
|
|
232
|
-
binding = requireMatchingRuntimeBinding(rawBinding, request, launchId
|
|
233
|
-
if (!preflightObserved
|
|
234
|
-
&& !(binding.hostCreated === false
|
|
235
|
-
&& request.adapterId === "claude"
|
|
236
|
-
&& launchCarriesExactRunPrompt)) {
|
|
214
|
+
binding = requireMatchingRuntimeBinding(rawBinding, request, launchId);
|
|
215
|
+
if (!preflightObserved) {
|
|
237
216
|
throw new Error("Runtime session host did not expose a pre-host-start launch fence.");
|
|
238
217
|
}
|
|
239
218
|
}
|
|
@@ -244,8 +223,7 @@ export class RuntimeLaunchCoordinator {
|
|
|
244
223
|
// terminalize that reservation.
|
|
245
224
|
throw new RuntimeLaunchError(true, launchId, error.message, error.reason);
|
|
246
225
|
}
|
|
247
|
-
if (
|
|
248
|
-
|| error instanceof RuntimeHostContentionError)
|
|
226
|
+
if (error instanceof RuntimeHostContentionError
|
|
249
227
|
&& !reusedConfirmedRunningHost) {
|
|
250
228
|
let exactCleanupAttempted = false;
|
|
251
229
|
let completed = false;
|
|
@@ -266,12 +244,6 @@ export class RuntimeLaunchCoordinator {
|
|
|
266
244
|
this.#requireCleanup(request.owner);
|
|
267
245
|
throw new Error("Busy managed runtime launch reservation changed during retry release.");
|
|
268
246
|
}
|
|
269
|
-
if (error instanceof LaunchSubmittedHostBusyError) {
|
|
270
|
-
// A terminal Run no longer owns its finite provider process. Move
|
|
271
|
-
// that exact Role owner through the durable cleanup lane before a
|
|
272
|
-
// later retry creates the successor generation.
|
|
273
|
-
this.#requireCleanup(request.owner);
|
|
274
|
-
}
|
|
275
247
|
throw new RuntimeLaunchError(true, launchId, error.message, error instanceof RuntimeHostContentionError
|
|
276
248
|
? error.reason
|
|
277
249
|
: "previous-process");
|
|
@@ -291,7 +263,6 @@ export class RuntimeLaunchCoordinator {
|
|
|
291
263
|
}
|
|
292
264
|
await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, new Error(`Runtime host was recreated while recovering an existing generation: ${request.owner.roleName}.`));
|
|
293
265
|
}
|
|
294
|
-
let reservationConfirmation;
|
|
295
266
|
try {
|
|
296
267
|
assertLaunchCurrent();
|
|
297
268
|
if (persistence === "immediate" && binding.nativeSessionId !== undefined) {
|
|
@@ -308,7 +279,7 @@ export class RuntimeLaunchCoordinator {
|
|
|
308
279
|
// Deferred scheduler persistence records a known native identity while
|
|
309
280
|
// retaining the reservation until exact Run delivery. Fresh Codex has
|
|
310
281
|
// no identity yet and keeps it until its matching generation Hook.
|
|
311
|
-
|
|
282
|
+
this.reservations.confirmRuntimeLaunchReservation({
|
|
312
283
|
owner: request.owner,
|
|
313
284
|
launchId
|
|
314
285
|
}, assertLaunchCurrent);
|
|
@@ -317,18 +288,6 @@ export class RuntimeLaunchCoordinator {
|
|
|
317
288
|
catch (error) {
|
|
318
289
|
await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, error);
|
|
319
290
|
}
|
|
320
|
-
if (launchCarriesExactRunPrompt
|
|
321
|
-
&& binding.hostCreated !== false
|
|
322
|
-
&& reservationConfirmation !== "provider-bound"
|
|
323
|
-
&& binding.initialPromptRunId !== request.runId) {
|
|
324
|
-
// A launch-carried prompt may return before its matching lifecycle Hook,
|
|
325
|
-
// but only an exact Run marker can bridge that asynchronous interval.
|
|
326
|
-
// This is transport evidence, never Provider acceptance; the
|
|
327
|
-
// reservation remains fenced until the matching Hook binds the native
|
|
328
|
-
// Session.
|
|
329
|
-
this.#requireCleanup(request.owner);
|
|
330
|
-
throw new RuntimeBindingContractError(`Session host cannot acknowledge the exact launch-carried prompt: ${request.owner.roleName}.`);
|
|
331
|
-
}
|
|
332
291
|
return binding;
|
|
333
292
|
}
|
|
334
293
|
async #settleFailedStart(request, launchId, reusedConfirmedRunningHost, runtimeIsolation) {
|
|
@@ -461,12 +420,12 @@ function validateRuntimeLaunchPreflight(preflight, request, launchId) {
|
|
|
461
420
|
|| !effectiveLaunchSnapshotsCompatible(preflight.effective, request.effective)
|
|
462
421
|
|| (request.mode === "resume"
|
|
463
422
|
&& preflight.nativeSessionId !== request.nativeSessionId)
|
|
464
|
-
|| (preflight.
|
|
465
|
-
&& preflight.
|
|
423
|
+
|| (preflight.initialTurnRunId !== undefined
|
|
424
|
+
&& preflight.initialTurnRunId !== request.runId)) {
|
|
466
425
|
throw new Error(`Session host pre-start launch fence does not match the requested runtime: ${request.owner.roleName}.`);
|
|
467
426
|
}
|
|
468
427
|
}
|
|
469
|
-
function requireMatchingRuntimeBinding(raw, request, launchId
|
|
428
|
+
function requireMatchingRuntimeBinding(raw, request, launchId) {
|
|
470
429
|
let binding;
|
|
471
430
|
try {
|
|
472
431
|
binding = createRuntimeBinding(raw);
|
|
@@ -479,9 +438,17 @@ function requireMatchingRuntimeBinding(raw, request, launchId, launchPromptAckno
|
|
|
479
438
|
&& (binding.owner.scope === "global"
|
|
480
439
|
|| (request.owner.scope === "task"
|
|
481
440
|
&& binding.owner.taskId === request.owner.taskId));
|
|
482
|
-
if (binding.
|
|
483
|
-
&& binding.
|
|
484
|
-
throw new RuntimeBindingContractError(`Session host returned
|
|
441
|
+
if (binding.initialTurnRunId !== undefined
|
|
442
|
+
&& binding.initialTurnRunId !== request.runId) {
|
|
443
|
+
throw new RuntimeBindingContractError(`Session host returned an initial structured Turn for another Run: ${request.owner.roleName}.`);
|
|
444
|
+
}
|
|
445
|
+
if (binding.initialTurnDeliveryUnknownRunId !== undefined
|
|
446
|
+
&& binding.initialTurnDeliveryUnknownRunId !== request.runId) {
|
|
447
|
+
throw new RuntimeBindingContractError(`Session host returned a delivery-unknown initial structured Turn for another Run: ${request.owner.roleName}.`);
|
|
448
|
+
}
|
|
449
|
+
if (binding.initialTurnRejectedRunId !== undefined
|
|
450
|
+
&& binding.initialTurnRejectedRunId !== request.runId) {
|
|
451
|
+
throw new RuntimeBindingContractError(`Session host returned a rejected initial structured Turn for another Run: ${request.owner.roleName}.`);
|
|
485
452
|
}
|
|
486
453
|
if (binding.launchId !== launchId
|
|
487
454
|
|| !ownerMatches
|
|
@@ -491,35 +458,8 @@ function requireMatchingRuntimeBinding(raw, request, launchId, launchPromptAckno
|
|
|
491
458
|
&& binding.nativeSessionId !== request.nativeSessionId)) {
|
|
492
459
|
throw new RuntimeBindingContractError(`Session host returned a binding that does not match the requested runtime: ${request.owner.roleName}.`);
|
|
493
460
|
}
|
|
494
|
-
if (launchPromptAcknowledgementRequired
|
|
495
|
-
&& binding.initialPromptRunId !== request.runId
|
|
496
|
-
&& !(launchPromptUncertaintyAllowed && binding.hostCreated === false)) {
|
|
497
|
-
throw binding.hostCreated === false
|
|
498
|
-
? new LaunchSubmittedHostBusyError(`An earlier managed runtime is still exiting: ${request.owner.roleName}.`)
|
|
499
|
-
: new RuntimeBindingContractError(`Session host cannot acknowledge the exact launch-carried prompt: ${request.owner.roleName}.`);
|
|
500
|
-
}
|
|
501
|
-
if (launchPromptAcknowledgementRequired
|
|
502
|
-
&& launchPromptUncertaintyAllowed
|
|
503
|
-
&& binding.hostCreated === false
|
|
504
|
-
&& request.runId !== undefined
|
|
505
|
-
&& binding.initialPromptRunId !== request.runId) {
|
|
506
|
-
// A Controller restart can lose only the in-memory fact that a still-
|
|
507
|
-
// running generation carried this Run at process launch. Keep the
|
|
508
|
-
// uncertainty explicitly tied to the exact reservation/Run; the matching
|
|
509
|
-
// Provider Hook remains the sole acceptance authority.
|
|
510
|
-
return {
|
|
511
|
-
...binding,
|
|
512
|
-
launchPromptUncertainRunId: request.runId
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
461
|
return binding;
|
|
516
462
|
}
|
|
517
|
-
function carriesExactRunPrompt(request) {
|
|
518
|
-
if (request.owner.scope !== "task" || request.runId === undefined)
|
|
519
|
-
return false;
|
|
520
|
-
return request.adapterId === "claude"
|
|
521
|
-
|| (request.adapterId === "codex" && request.mode === "new");
|
|
522
|
-
}
|
|
523
463
|
function defaultLaunchFingerprint(request) {
|
|
524
464
|
return createHash("sha256").update(JSON.stringify([
|
|
525
465
|
request.owner,
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { callController } from "../core/controllerClient.js";
|
|
3
|
+
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
4
|
+
import { createRuntimeObservation, runtimeObservationSemanticKey } from "../runtime/runtimeObservation.js";
|
|
5
|
+
import { runtimeLifecycleSignalKey } from "../runtime/lifecycleReservation.js";
|
|
6
|
+
import { FileRuntimeEventInbox } from "./runtimeEventInbox.js";
|
|
7
|
+
import { resolveRuntimeHookRunFence } from "./runtimeHookRunFence.js";
|
|
8
|
+
let structuredSequence = 0;
|
|
9
|
+
export async function publishStructuredProviderAccepted(input) {
|
|
10
|
+
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
11
|
+
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
12
|
+
const startupSession = driver.capabilities.observation.sessionBootstrap;
|
|
13
|
+
const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.receipt.nativeSessionId, {
|
|
14
|
+
startupSession,
|
|
15
|
+
nativeTurnId: input.receipt.nativeTurnId
|
|
16
|
+
});
|
|
17
|
+
const observedAt = input.receipt.acceptedAt;
|
|
18
|
+
const commonFence = {
|
|
19
|
+
taskId: fence.taskId,
|
|
20
|
+
roleName: fence.roleName,
|
|
21
|
+
runId: fence.runId,
|
|
22
|
+
agentId: fence.agentId,
|
|
23
|
+
driverId: driver.id,
|
|
24
|
+
launchId: fence.launchId,
|
|
25
|
+
sessionGenerationId: fence.launchId,
|
|
26
|
+
conversationId: input.receipt.conversationId,
|
|
27
|
+
activationId: requireIdentity(input.activationId, "Provider Activation id"),
|
|
28
|
+
nativeSessionId: input.receipt.nativeSessionId,
|
|
29
|
+
nativeTurnId: input.receipt.nativeTurnId,
|
|
30
|
+
// The structured Host owns the exact input attempt identity. Runtime
|
|
31
|
+
// descriptor receipts name the Run bootstrap and must not overwrite a
|
|
32
|
+
// continuation or human-takeover Turn.
|
|
33
|
+
receiptId: input.receipt.attemptId
|
|
34
|
+
};
|
|
35
|
+
const baseSequence = nextStructuredSequence();
|
|
36
|
+
const observations = [observation({
|
|
37
|
+
kind: startupSession === "preallocated" ? "session.ready" : "session.started",
|
|
38
|
+
observedAt,
|
|
39
|
+
sequence: baseSequence,
|
|
40
|
+
ordinal: 0,
|
|
41
|
+
fence: commonFence
|
|
42
|
+
}), observation({
|
|
43
|
+
kind: "conversation.observed",
|
|
44
|
+
observedAt,
|
|
45
|
+
sequence: baseSequence,
|
|
46
|
+
ordinal: 1,
|
|
47
|
+
fence: commonFence,
|
|
48
|
+
payload: { recoverability: "recoverable" }
|
|
49
|
+
}), observation({
|
|
50
|
+
kind: "activation.started",
|
|
51
|
+
observedAt,
|
|
52
|
+
sequence: baseSequence,
|
|
53
|
+
ordinal: 2,
|
|
54
|
+
fence: commonFence
|
|
55
|
+
}), observation({
|
|
56
|
+
kind: "turn.accepted",
|
|
57
|
+
observedAt,
|
|
58
|
+
sequence: baseSequence,
|
|
59
|
+
ordinal: 3,
|
|
60
|
+
fence: commonFence
|
|
61
|
+
})];
|
|
62
|
+
await persistAndApply(input.home, observations, fence.taskId, fence.roleName);
|
|
63
|
+
}
|
|
64
|
+
export async function publishStructuredProviderOpened(input) {
|
|
65
|
+
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
66
|
+
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
67
|
+
const startupSession = driver.capabilities.observation.sessionBootstrap;
|
|
68
|
+
const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.nativeSessionId, { startupSession });
|
|
69
|
+
const commonFence = {
|
|
70
|
+
taskId: fence.taskId,
|
|
71
|
+
roleName: fence.roleName,
|
|
72
|
+
runId: fence.runId,
|
|
73
|
+
agentId: fence.agentId,
|
|
74
|
+
driverId: driver.id,
|
|
75
|
+
launchId: fence.launchId,
|
|
76
|
+
sessionGenerationId: fence.launchId,
|
|
77
|
+
conversationId: input.conversationId,
|
|
78
|
+
activationId: requireIdentity(input.activationId, "Provider Activation id"),
|
|
79
|
+
nativeSessionId: input.nativeSessionId,
|
|
80
|
+
...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
|
|
81
|
+
};
|
|
82
|
+
const sequence = nextStructuredSequence();
|
|
83
|
+
const observations = [observation({
|
|
84
|
+
kind: startupSession === "preallocated" ? "session.ready" : "session.started",
|
|
85
|
+
observedAt: input.observedAt,
|
|
86
|
+
sequence,
|
|
87
|
+
ordinal: 0,
|
|
88
|
+
fence: commonFence
|
|
89
|
+
}), observation({
|
|
90
|
+
kind: "conversation.observed",
|
|
91
|
+
observedAt: input.observedAt,
|
|
92
|
+
sequence,
|
|
93
|
+
ordinal: 1,
|
|
94
|
+
fence: commonFence,
|
|
95
|
+
payload: { recoverability: input.recoverability }
|
|
96
|
+
}), observation({
|
|
97
|
+
kind: "activation.started",
|
|
98
|
+
observedAt: input.observedAt,
|
|
99
|
+
sequence,
|
|
100
|
+
ordinal: 2,
|
|
101
|
+
fence: commonFence
|
|
102
|
+
})];
|
|
103
|
+
await persistAndApply(input.home, observations, fence.taskId, fence.roleName);
|
|
104
|
+
}
|
|
105
|
+
export async function publishStructuredConversationRecoverability(input) {
|
|
106
|
+
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
107
|
+
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
108
|
+
const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.conversationId);
|
|
109
|
+
const sequence = nextStructuredSequence();
|
|
110
|
+
const observationFence = {
|
|
111
|
+
taskId: fence.taskId,
|
|
112
|
+
roleName: fence.roleName,
|
|
113
|
+
runId: fence.runId,
|
|
114
|
+
agentId: fence.agentId,
|
|
115
|
+
driverId: driver.id,
|
|
116
|
+
launchId: fence.launchId,
|
|
117
|
+
sessionGenerationId: fence.launchId,
|
|
118
|
+
conversationId: input.conversationId,
|
|
119
|
+
activationId: requireIdentity(input.activationId, "Provider Activation id"),
|
|
120
|
+
nativeSessionId: input.conversationId,
|
|
121
|
+
...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
|
|
122
|
+
};
|
|
123
|
+
const observations = [observation({
|
|
124
|
+
kind: "activation.started",
|
|
125
|
+
observedAt: input.observedAt,
|
|
126
|
+
sequence,
|
|
127
|
+
ordinal: 0,
|
|
128
|
+
fence: observationFence
|
|
129
|
+
}), observation({
|
|
130
|
+
kind: "conversation.observed",
|
|
131
|
+
observedAt: input.observedAt,
|
|
132
|
+
sequence,
|
|
133
|
+
ordinal: 1,
|
|
134
|
+
fence: observationFence,
|
|
135
|
+
payload: { recoverability: input.recoverability }
|
|
136
|
+
})];
|
|
137
|
+
if (input.recoverability === "unrecoverable") {
|
|
138
|
+
observations.push(observation({
|
|
139
|
+
kind: "activation.failed",
|
|
140
|
+
observedAt: input.observedAt,
|
|
141
|
+
sequence,
|
|
142
|
+
ordinal: 2,
|
|
143
|
+
fence: observationFence,
|
|
144
|
+
payload: {}
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
await persistAndApply(input.home, observations, fence.taskId, fence.roleName);
|
|
148
|
+
}
|
|
149
|
+
export async function publishStructuredProviderTerminal(input) {
|
|
150
|
+
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
151
|
+
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
152
|
+
const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.terminal.nativeSessionId, { terminal: true, nativeTurnId: input.terminal.nativeTurnId });
|
|
153
|
+
const kind = input.terminal.status === "completed"
|
|
154
|
+
? "turn.completed"
|
|
155
|
+
: input.terminal.status === "cancelled" ? "turn.cancelled" : "turn.failed";
|
|
156
|
+
const payload = kind === "turn.completed"
|
|
157
|
+
? { ...(input.terminal.summary === undefined ? {} : { summary: input.terminal.summary }) }
|
|
158
|
+
: kind === "turn.failed"
|
|
159
|
+
? {
|
|
160
|
+
failure: {
|
|
161
|
+
code: "provider-structured-failure",
|
|
162
|
+
...(input.terminal.error === undefined
|
|
163
|
+
? {}
|
|
164
|
+
: { details: input.terminal.error })
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
: {};
|
|
168
|
+
const terminalObservation = observation({
|
|
169
|
+
kind,
|
|
170
|
+
observedAt: input.terminal.observedAt,
|
|
171
|
+
sequence: nextStructuredSequence(),
|
|
172
|
+
ordinal: 0,
|
|
173
|
+
fence: {
|
|
174
|
+
taskId: fence.taskId,
|
|
175
|
+
roleName: fence.roleName,
|
|
176
|
+
runId: fence.runId,
|
|
177
|
+
agentId: fence.agentId,
|
|
178
|
+
driverId: driver.id,
|
|
179
|
+
launchId: fence.launchId,
|
|
180
|
+
sessionGenerationId: fence.launchId,
|
|
181
|
+
conversationId: input.terminal.conversationId,
|
|
182
|
+
activationId: requireIdentity(input.activationId, "Provider Activation id"),
|
|
183
|
+
nativeSessionId: input.terminal.nativeSessionId,
|
|
184
|
+
nativeTurnId: input.terminal.nativeTurnId,
|
|
185
|
+
...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
|
|
186
|
+
},
|
|
187
|
+
payload
|
|
188
|
+
});
|
|
189
|
+
await persistAndApply(input.home, [terminalObservation], fence.taskId, fence.roleName);
|
|
190
|
+
}
|
|
191
|
+
export async function publishStructuredProviderActivationTerminal(input) {
|
|
192
|
+
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
193
|
+
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
194
|
+
const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.nativeSessionId);
|
|
195
|
+
const terminal = observation({
|
|
196
|
+
kind: input.status === "failed" ? "activation.failed" : "activation.ended",
|
|
197
|
+
observedAt: input.observedAt,
|
|
198
|
+
sequence: nextStructuredSequence(),
|
|
199
|
+
ordinal: 0,
|
|
200
|
+
fence: {
|
|
201
|
+
taskId: fence.taskId,
|
|
202
|
+
roleName: fence.roleName,
|
|
203
|
+
runId: fence.runId,
|
|
204
|
+
agentId: fence.agentId,
|
|
205
|
+
driverId: driver.id,
|
|
206
|
+
launchId: fence.launchId,
|
|
207
|
+
sessionGenerationId: fence.launchId,
|
|
208
|
+
conversationId: requireIdentity(input.conversationId, "Provider Conversation id"),
|
|
209
|
+
activationId: requireIdentity(input.activationId, "Provider Activation id"),
|
|
210
|
+
nativeSessionId: requireIdentity(input.nativeSessionId, "native Session id"),
|
|
211
|
+
...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
|
|
212
|
+
},
|
|
213
|
+
payload: {}
|
|
214
|
+
});
|
|
215
|
+
await persistAndApply(input.home, [terminal], fence.taskId, fence.roleName);
|
|
216
|
+
}
|
|
217
|
+
function observation(input) {
|
|
218
|
+
const eventId = `agent-host-${randomUUID()}`;
|
|
219
|
+
const partial = {
|
|
220
|
+
eventId,
|
|
221
|
+
kind: input.kind,
|
|
222
|
+
fence: input.fence,
|
|
223
|
+
sequence: input.sequence,
|
|
224
|
+
payload: input.payload ?? {}
|
|
225
|
+
};
|
|
226
|
+
return createRuntimeObservation({
|
|
227
|
+
schemaVersion: 2,
|
|
228
|
+
eventId,
|
|
229
|
+
semanticKey: runtimeObservationSemanticKey(partial),
|
|
230
|
+
kind: input.kind,
|
|
231
|
+
authority: "provider-structured",
|
|
232
|
+
receivedAt: new Date().toISOString(),
|
|
233
|
+
observedAt: input.observedAt,
|
|
234
|
+
sequence: input.sequence,
|
|
235
|
+
ordinal: input.ordinal,
|
|
236
|
+
fence: input.fence,
|
|
237
|
+
payload: input.payload ?? {}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async function signalController(home, taskId, roleName) {
|
|
241
|
+
await callController(home, "scheduler.signal", {
|
|
242
|
+
key: runtimeLifecycleSignalKey({ scope: "task", taskId, roleName })
|
|
243
|
+
}, { timeoutMs: 100 }).catch(() => { });
|
|
244
|
+
}
|
|
245
|
+
function nextStructuredSequence() {
|
|
246
|
+
structuredSequence = (structuredSequence + 1) % Number.MAX_SAFE_INTEGER;
|
|
247
|
+
return structuredSequence;
|
|
248
|
+
}
|
|
249
|
+
function requireIdentity(value, label) {
|
|
250
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
|
|
251
|
+
throw new Error(`${label} is invalid.`);
|
|
252
|
+
}
|
|
253
|
+
return value.trim();
|
|
254
|
+
}
|
|
255
|
+
async function persistAndApply(home, observations, taskId, roleName) {
|
|
256
|
+
const inbox = new FileRuntimeEventInbox(home);
|
|
257
|
+
for (const entry of observations)
|
|
258
|
+
inbox.enqueueObservation(entry);
|
|
259
|
+
for (const entry of observations) {
|
|
260
|
+
const result = await callController(home, "runtime.observation-apply", entry, {
|
|
261
|
+
timeoutMs: 10_000
|
|
262
|
+
});
|
|
263
|
+
// A fast Provider can accept the initial Turn before the scheduler call
|
|
264
|
+
// that launched this Host has returned and committed `run.pushed`. The
|
|
265
|
+
// immutable inbox entry already makes that exact fenced fact durable;
|
|
266
|
+
// `deferred` therefore means "retained for replay", not delivery failure.
|
|
267
|
+
// The signal below schedules the replay after the transport transaction.
|
|
268
|
+
if (result.outcome !== "applied" && result.outcome !== "deferred") {
|
|
269
|
+
throw new Error(`Structured Provider observation was not applied: ${result.outcome ?? "unknown"}.`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
await signalController(home, taskId, roleName);
|
|
273
|
+
}
|
|
@@ -159,6 +159,24 @@ class CodexAdapter extends BaseAdapter {
|
|
|
159
159
|
const launch = this.compileNew(input);
|
|
160
160
|
return { ...launch, argv: [...launch.argv, "resume", nativeId(input.nativeSessionId)] };
|
|
161
161
|
}
|
|
162
|
+
compileManagedControl(input, _mode, _nativeSessionId) {
|
|
163
|
+
const launch = this.compileNew(input);
|
|
164
|
+
const argv = [...launch.argv];
|
|
165
|
+
if (input.config.model !== undefined) {
|
|
166
|
+
const modelFlag = argv.findIndex((value, index) => (value === "--model" && argv[index + 1] === input.config.model));
|
|
167
|
+
if (modelFlag < 0)
|
|
168
|
+
throw new Error("Managed Codex launch lost its selected model.");
|
|
169
|
+
argv.splice(modelFlag, 2);
|
|
170
|
+
// App Server thread/start reads the model from resolved configuration;
|
|
171
|
+
// the interactive --model shortcut is not inherited by new threads.
|
|
172
|
+
argv.push("--config", `model=${tomlString(input.config.model)}`);
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
...launch,
|
|
176
|
+
argv: [...argv, "app-server", "--stdio"],
|
|
177
|
+
transport: "codex-app-server-stdio"
|
|
178
|
+
};
|
|
179
|
+
}
|
|
162
180
|
}
|
|
163
181
|
class ClaudeAdapter extends BaseAdapter {
|
|
164
182
|
id = "claude";
|
|
@@ -261,6 +279,28 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
261
279
|
const launch = super.compileNew(input);
|
|
262
280
|
return { ...launch, argv: [...launch.argv, "--resume", nativeId(input.nativeSessionId)] };
|
|
263
281
|
}
|
|
282
|
+
compileManagedControl(input, mode, nativeSessionId) {
|
|
283
|
+
if (nativeSessionId === undefined) {
|
|
284
|
+
throw new Error("Managed Claude control requires a preallocated native Session id.");
|
|
285
|
+
}
|
|
286
|
+
const sessionId = nativeId(nativeSessionId);
|
|
287
|
+
const launch = mode === "new"
|
|
288
|
+
? super.compileNew(input)
|
|
289
|
+
: this.compileResume({ ...input, nativeSessionId: sessionId });
|
|
290
|
+
return {
|
|
291
|
+
...launch,
|
|
292
|
+
argv: [
|
|
293
|
+
...launch.argv,
|
|
294
|
+
...(mode === "new" ? ["--session-id", sessionId] : []),
|
|
295
|
+
"-p",
|
|
296
|
+
"--output-format", "stream-json",
|
|
297
|
+
"--input-format", "stream-json",
|
|
298
|
+
"--verbose",
|
|
299
|
+
"--replay-user-messages"
|
|
300
|
+
],
|
|
301
|
+
transport: "claude-stream-json"
|
|
302
|
+
};
|
|
303
|
+
}
|
|
264
304
|
}
|
|
265
305
|
function driverPreInputReadiness(adapterId) {
|
|
266
306
|
const capability = builtinAgentDriverRegistry().requireByAdapterId(adapterId)
|