@zq-silk/yui 0.2.0 → 0.4.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 +603 -133
- package/README.md +806 -31
- package/dist/agent/agent.js +2 -1
- package/dist/agent/argumentPolicy.js +3 -1
- package/dist/agent/launchEnvironment.js +106 -0
- package/dist/agent/managedRuntimeEnvironment.js +34 -0
- package/dist/brief/taskBrief.js +11 -1
- package/dist/cli/agentConfigurationPicker.js +287 -0
- package/dist/cli/commandCatalog.js +488 -60
- package/dist/cli/completion.js +146 -22
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli/interactionCandidates.js +53 -15
- package/dist/cli/interactionPolicy.js +267 -30
- package/dist/cli/interactiveSelection.js +6 -2
- package/dist/cli/invocationRouter.js +5 -1
- package/dist/cli/operatorWizard.js +87 -0
- package/dist/cli/roleOptionCatalog.js +1 -0
- package/dist/cli/roleWizard.js +185 -21
- package/dist/cli/updateCommand.js +62 -19
- package/dist/cli/updateOrchestrator.js +539 -0
- package/dist/cli/updatePorts.js +1119 -0
- package/dist/cli/upgradeCommand.js +112 -0
- package/dist/cli.js +1420 -86
- package/dist/commands/agentCommands.js +146 -3
- package/dist/commands/configCommands.js +126 -0
- package/dist/commands/controllerCommands.js +365 -0
- package/dist/commands/globalRoleCommands.js +168 -126
- package/dist/commands/jobCommands.js +18 -8
- package/dist/commands/operatorCommands.js +159 -9
- package/dist/commands/profileCommands.js +203 -0
- package/dist/commands/projectCommands.js +650 -0
- package/dist/commands/roleConfiguration.js +85 -24
- package/dist/commands/roleRuntimeGuard.js +12 -0
- package/dist/commands/roleSkillValidation.js +47 -0
- package/dist/commands/taskActor.js +127 -0
- package/dist/commands/taskCommands.js +4201 -313
- package/dist/commands/taskCompletionGate.js +131 -0
- package/dist/commands/taskContextCommand.js +244 -30
- package/dist/commands/taskInputCommands.js +177 -59
- package/dist/commands/taskIntegrationCommands.js +303 -0
- package/dist/commands/taskOverviewCommand.js +363 -0
- package/dist/commands/taskRoleRuntimeStatus.js +125 -19
- package/dist/commands/textInput.js +15 -0
- package/dist/completion/completionInstaller.js +26 -22
- package/dist/config/yuiConfig.js +4 -3
- package/dist/context/dispatchContext.js +90 -38
- package/dist/context/roleSessionContext.js +119 -0
- package/dist/controller/claudeLifecycleHook.js +203 -0
- package/dist/controller/clientRuntime.js +408 -56
- package/dist/controller/codexLifecycleHook.js +108 -0
- package/dist/controller/controller.js +1089 -32
- package/dist/controller/domainIdentity.js +505 -0
- package/dist/controller/ephemeralResourceReaper.js +131 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
- package/dist/controller/providerHookRunFence.js +127 -0
- package/dist/controller/resourceCleanupLinux.js +286 -0
- package/dist/controller/resourceInventory.js +531 -0
- package/dist/controller/resourceInventoryLinux.js +610 -0
- package/dist/controller/runtime.js +629 -10
- package/dist/controller/runtimeEventInbox.js +564 -0
- package/dist/controller/runtimeEventProcessor.js +248 -0
- package/dist/controller/runtimeLaunchCoordinator.js +477 -0
- package/dist/controller/sessionNotify.js +121 -78
- package/dist/coordination/deadlineScheduler.js +15 -0
- package/dist/coordination/mailboxScheduler.js +108 -0
- package/dist/coordination/workMailbox.js +329 -0
- package/dist/coordination/workMailboxQueue.js +86 -0
- package/dist/core/controllerClient.js +19 -5
- package/dist/core/controllerEndpoint.js +37 -0
- package/dist/core/controllerServer.js +218 -10
- package/dist/core/protocol.js +6 -2
- package/dist/decision/decision.js +2 -1
- package/dist/doctor/doctor.js +681 -32
- package/dist/domain/validation.js +53 -0
- package/dist/errors/cliError.js +5 -3
- package/dist/event/taskEvent.js +7 -3
- package/dist/execution/codexThreadNaming.js +160 -0
- package/dist/execution/executionGroup.js +579 -0
- package/dist/executor/agentAdapter.js +255 -40
- package/dist/executor/agentConfigurationCatalog.js +326 -0
- package/dist/executor/agentConfigurationProbe.js +506 -0
- package/dist/executor/agentExecutor.js +625 -10
- package/dist/executor/codexConfigConflict.js +290 -0
- package/dist/executor/effectiveLaunch.js +340 -0
- package/dist/executor/executorRegistry.js +238 -36
- package/dist/executor/fileRoleLaunchPlanner.js +550 -40
- package/dist/executor/turnCompletion.js +126 -0
- package/dist/input/inputRequest.js +30 -9
- package/dist/integration/changeSet.js +36 -0
- package/dist/integration/checkResult.js +24 -0
- package/dist/integration/gitIntegrationService.js +695 -0
- package/dist/integration/integrationAttempt.js +142 -0
- package/dist/interaction/operatorPresentation.js +96 -0
- package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
- package/dist/lifecycle/exactRunTerminalization.js +572 -0
- package/dist/lifecycle/providerLifecycleMapping.js +190 -0
- package/dist/lifecycle/taskRoleSessionReset.js +124 -0
- package/dist/message/message.js +23 -7
- package/dist/milestone/milestone.js +2 -1
- package/dist/operator/operatorSessionHistory.js +124 -0
- package/dist/output/agentConfigurationPresentation.js +43 -0
- package/dist/output/rolePresentation.js +34 -10
- package/dist/output/terminal.js +8 -0
- package/dist/output/timePresentation.js +55 -0
- package/dist/profile/agentProfile.js +128 -0
- package/dist/repository/gitWorkspace.js +578 -24
- package/dist/repository/project.js +213 -0
- package/dist/repository/taskWorkspaceCoordinator.js +392 -0
- package/dist/repository/taskWorkspacePreparer.js +1688 -191
- package/dist/review/reviewConfig.js +11 -0
- package/dist/review/reviewRound.js +399 -0
- package/dist/review/taskFinalReviewContract.js +90 -0
- package/dist/role/role.js +124 -23
- package/dist/run/agentRun.js +155 -12
- package/dist/run/runIdentity.js +82 -0
- package/dist/runtime/exactControlPlane.js +472 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/lifecycleReservation.js +38 -0
- package/dist/runtime/ports.js +11 -0
- package/dist/runtime/preallocatedNativeSession.js +13 -0
- package/dist/runtime/promptEnvelope.js +30 -0
- package/dist/runtime/runtimeBinding.js +31 -0
- package/dist/runtime/runtimeOwner.js +14 -0
- package/dist/runtime/sessionLaunchRequest.js +62 -0
- package/dist/runtime/sessionTitle.js +54 -0
- package/dist/runtime/taskRuntimeIsolation.js +643 -0
- package/dist/runtime/tmuxAdapters.js +315 -0
- package/dist/runtime/turnCompletion.js +3 -0
- package/dist/runtime/validation.js +23 -0
- package/dist/scheduler/activeRoleRunDelivery.js +342 -32
- package/dist/scheduler/activeTaskProgress.js +63 -0
- package/dist/scheduler/leaderFailure.js +2 -1
- package/dist/scheduler/leaderWakeupProcessor.js +307 -66
- package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
- package/dist/scheduler/operatorNotification.js +44 -2
- package/dist/scheduler/ports.js +28 -1
- package/dist/scheduler/roleRunLiveness.js +131 -25
- package/dist/scheduler/roleRunStall.js +951 -0
- package/dist/scheduler/taskExecutionProjection.js +544 -0
- package/dist/scheduler/wakeupQueue.js +3 -0
- package/dist/setup/setupCommand.js +302 -52
- package/dist/storage/compatibleTaskStore.js +102 -0
- package/dist/storage/migration/baseline.js +78 -0
- package/dist/storage/migration/classifier.js +51 -0
- package/dist/storage/migration/compatibleCodec.js +53 -0
- package/dist/storage/migration/engine.js +147 -0
- package/dist/storage/migration/index.js +33 -0
- package/dist/storage/migration/planner.js +154 -0
- package/dist/storage/migration/productionRegistry.js +486 -0
- package/dist/storage/migration/registry.js +169 -0
- package/dist/storage/migration/report.js +54 -0
- package/dist/storage/migration/types.js +31 -0
- package/dist/storage/storageSchema.js +147 -123
- package/dist/storage/storageVersions.js +11 -0
- package/dist/storage/taskStore.js +1793 -197
- package/dist/storage/upgrade/homeClassification.js +156 -0
- package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
- package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
- package/dist/storage/upgrade/recordVersionScan.js +176 -0
- package/dist/storage/upgrade/recordVersions.js +159 -0
- package/dist/storage/upgrade/switchProgress.js +80 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
- package/dist/storage/upgrade/upgradeReceipt.js +161 -0
- package/dist/storage/upgradeCoordination.js +186 -0
- package/dist/storage/upgradeFence.js +366 -0
- package/dist/task/task.js +132 -26
- package/dist/task/taskRecordReference.js +66 -0
- package/dist/tmux/commandExecutor.js +75 -2
- package/dist/tmux/tmuxManager.js +747 -49
- package/dist/version.js +23 -0
- package/dist/web/assets/assetManifest.js +62 -0
- package/dist/web/assets/client/app.js +631 -0
- package/dist/web/assets/client/components.js +605 -0
- package/dist/web/assets/client/dom.js +14 -0
- package/dist/web/assets/client/format.js +28 -0
- package/dist/web/assets/client/i18n.js +494 -0
- package/dist/web/assets/client/markdown.js +114 -0
- package/dist/web/assets/client/theme.js +32 -0
- package/dist/web/assets/client/view.js +458 -0
- package/dist/web/assets/fontData.js +12 -0
- package/dist/web/assets/fonts.js +12 -0
- package/dist/web/assets/shell.js +114 -0
- package/dist/web/assets/styles/cards.js +135 -0
- package/dist/web/assets/styles/layout.js +47 -0
- package/dist/web/assets/styles/markdown.js +29 -0
- package/dist/web/assets/styles/responsive.js +39 -0
- package/dist/web/assets/styles/tokens.js +101 -0
- package/dist/web/assets/styles/widgets.js +147 -0
- package/dist/web/tmuxWebTerminal.js +158 -0
- package/dist/web/webServer.js +463 -0
- package/dist/web/webSnapshot.js +148 -0
- package/dist/workItem/workItem.js +642 -23
- package/dist/workspace/gitChangeSetCapture.js +86 -0
- package/dist/workspace/workItemChangeSetManager.js +445 -0
- package/dist/worktree/managedWorkspace.js +202 -0
- package/docs/task-local-identity.md +62 -0
- package/i18n/README.zh-CN.md +406 -31
- package/package.json +10 -2
- package/skills/yui-leader/SKILL.md +601 -39
- package/skills/yui-operator/SKILL.md +255 -34
- package/skills/yui-reviewer/SKILL.md +57 -0
- package/skills/yui-worker/SKILL.md +214 -17
- package/dist/commands/repositoryCommands.js +0 -86
- package/dist/operator/operatorContext.js +0 -66
- package/dist/repository/repository.js +0 -55
- package/dist/scheduler/archivedTaskRuntime.js +0 -12
- package/dist/worktree/roleWorkspace.js +0 -62
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
3
|
+
import { createRuntimeBinding, RuntimeLaunchError } from "../runtime/index.js";
|
|
4
|
+
import { effectiveLaunchSnapshotsCompatible, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
|
|
5
|
+
class RuntimeBindingContractError extends Error {
|
|
6
|
+
constructor(message, options) {
|
|
7
|
+
super(message, options);
|
|
8
|
+
this.name = "RuntimeBindingContractError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* One application service owns the reservation -> host -> persistence
|
|
13
|
+
* protocol for both foreground enter and scheduler-driven Role launches.
|
|
14
|
+
*/
|
|
15
|
+
export class RuntimeLaunchCoordinator {
|
|
16
|
+
reservations;
|
|
17
|
+
host;
|
|
18
|
+
#createGenerationId;
|
|
19
|
+
#now;
|
|
20
|
+
#assertCurrent;
|
|
21
|
+
#launchFingerprint;
|
|
22
|
+
#onCleanupRequired;
|
|
23
|
+
#runtimeIsolation;
|
|
24
|
+
constructor(reservations, host, options = {}) {
|
|
25
|
+
this.reservations = reservations;
|
|
26
|
+
this.host = host;
|
|
27
|
+
this.#createGenerationId = options.createGenerationId ?? randomUUID;
|
|
28
|
+
this.#now = options.now ?? (() => new Date());
|
|
29
|
+
this.#assertCurrent = options.assertCurrent;
|
|
30
|
+
this.#launchFingerprint = options.launchFingerprint
|
|
31
|
+
?? defaultLaunchFingerprint;
|
|
32
|
+
this.#onCleanupRequired = options.onCleanupRequired;
|
|
33
|
+
this.#runtimeIsolation = options.runtimeIsolation;
|
|
34
|
+
}
|
|
35
|
+
async prepare(request, persistence, assertCurrent, beforeHostStart) {
|
|
36
|
+
const ownerKey = request.owner.scope === "task"
|
|
37
|
+
? `task\0${request.owner.taskId}\0${request.owner.roleName}`
|
|
38
|
+
: `global\0${request.owner.roleName}`;
|
|
39
|
+
const previous = this.#launchTails.get(ownerKey) ?? Promise.resolve();
|
|
40
|
+
let release;
|
|
41
|
+
const current = new Promise((resolve) => { release = resolve; });
|
|
42
|
+
const tail = previous.then(() => current);
|
|
43
|
+
this.#launchTails.set(ownerKey, tail);
|
|
44
|
+
await previous;
|
|
45
|
+
try {
|
|
46
|
+
return await this.#prepareUnlocked(request, persistence, assertCurrent, beforeHostStart);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
release();
|
|
50
|
+
if (this.#launchTails.get(ownerKey) === tail) {
|
|
51
|
+
this.#launchTails.delete(ownerKey);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
#launchTails = new Map();
|
|
56
|
+
async #prepareUnlocked(request, persistence, assertCurrent, beforeHostStart) {
|
|
57
|
+
const effective = validateEffectiveLaunchSnapshot(request.effective);
|
|
58
|
+
if (effective.agentId !== request.agentId
|
|
59
|
+
|| effective.adapterId !== request.adapterId
|
|
60
|
+
|| effective.workspace.root !== request.workspace) {
|
|
61
|
+
throw new TypeError("Runtime launch request does not match its effective snapshot.");
|
|
62
|
+
}
|
|
63
|
+
if (this.#runtimeIsolation !== undefined && request.owner.scope === "task") {
|
|
64
|
+
if (request.managedWorkspace === undefined) {
|
|
65
|
+
throw new Error("An authoritative ManagedWorkspace owner is required for a Task runtime launch.");
|
|
66
|
+
}
|
|
67
|
+
if (request.managedWorkspace.owner.taskId !== request.owner.taskId
|
|
68
|
+
|| request.managedWorkspace.root !== request.workspace) {
|
|
69
|
+
throw new Error("Task runtime launch does not match its authoritative ManagedWorkspace.");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (request.owner.scope === "global" && request.managedWorkspace !== undefined) {
|
|
73
|
+
throw new Error("A global runtime cannot use a Task ManagedWorkspace.");
|
|
74
|
+
}
|
|
75
|
+
const expectedFingerprint = requireText(this.#launchFingerprint(request), "Launch fingerprint");
|
|
76
|
+
const assertLaunchCurrent = () => {
|
|
77
|
+
this.#assertCurrent?.(request);
|
|
78
|
+
assertCurrent?.();
|
|
79
|
+
if (this.#launchFingerprint(request) !== expectedFingerprint) {
|
|
80
|
+
throw new Error(`Role or Agent launch state changed: ${request.owner.roleName}.`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const generationPrefix = `runtime-${expectedFingerprint}:generation:`;
|
|
84
|
+
const proposedGenerationId = requireText(this.#createGenerationId(), "Launch generation id");
|
|
85
|
+
const proposedLaunchId = `${generationPrefix}${proposedGenerationId}`;
|
|
86
|
+
let runtimeIsolation = this.#preflightRuntimeIsolation(request, proposedLaunchId, proposedGenerationId, false);
|
|
87
|
+
let reservation = this.reservations.reserveRuntimeLaunch({
|
|
88
|
+
owner: request.owner,
|
|
89
|
+
launchId: proposedLaunchId,
|
|
90
|
+
...(request.runId === undefined ? {} : { runId: request.runId })
|
|
91
|
+
}, assertLaunchCurrent, this.#now());
|
|
92
|
+
let reusedConfirmedRunningHost = false;
|
|
93
|
+
let launchPromptAcknowledgementRequired = false;
|
|
94
|
+
if (reservation.status === "existing") {
|
|
95
|
+
if (!reservation.launchId.startsWith(generationPrefix)) {
|
|
96
|
+
this.#requireCleanup(request.owner);
|
|
97
|
+
throw new Error(`Runtime launch reservation belongs to stale Role or Agent state: ${request.owner.roleName}.`);
|
|
98
|
+
}
|
|
99
|
+
const sameRunReservation = request.runId !== undefined
|
|
100
|
+
&& reservation.runId === request.runId;
|
|
101
|
+
const differentRunReservation = request.runId !== undefined
|
|
102
|
+
&& reservation.runId !== undefined
|
|
103
|
+
&& reservation.runId !== request.runId;
|
|
104
|
+
const inspection = await this.host.inspectOwner(request.owner);
|
|
105
|
+
if (inspection.state === "unavailable" || inspection.state === "starting") {
|
|
106
|
+
if (sameRunReservation) {
|
|
107
|
+
throw new RuntimeLaunchError(true, reservation.launchId, `Runtime is temporarily ${inspection.state}: ${request.runId}/${reservation.launchId}.`);
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`Runtime launch reservation cannot yet be verified: ${request.owner.roleName}.`);
|
|
110
|
+
}
|
|
111
|
+
if (inspection.state === "stopped") {
|
|
112
|
+
if (sameRunReservation) {
|
|
113
|
+
throw new RuntimeLaunchError(false, reservation.launchId, `Exact Run launch generation stopped before delivery: ${request.runId}/${reservation.launchId}.`);
|
|
114
|
+
}
|
|
115
|
+
const taskOwner = request.owner.scope === "task"
|
|
116
|
+
? request.owner
|
|
117
|
+
: undefined;
|
|
118
|
+
let exactCleanupAttempted = false;
|
|
119
|
+
let completed = false;
|
|
120
|
+
try {
|
|
121
|
+
completed = this.reservations.completeRuntimeLaunchReservation(request.owner, reservation.launchId, differentRunReservation ? reservation.runId : undefined, taskOwner !== undefined && this.#runtimeIsolation !== undefined
|
|
122
|
+
? () => {
|
|
123
|
+
exactCleanupAttempted = true;
|
|
124
|
+
this.#cleanupTaskLaunch(taskOwner, reservation.launchId);
|
|
125
|
+
}
|
|
126
|
+
: undefined);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (exactCleanupAttempted)
|
|
130
|
+
this.#requireCleanup(request.owner);
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
if (!completed) {
|
|
134
|
+
if (exactCleanupAttempted)
|
|
135
|
+
this.#requireCleanup(request.owner);
|
|
136
|
+
throw new Error("Runtime launch reservation changed during recovery.");
|
|
137
|
+
}
|
|
138
|
+
reservation = this.reservations.reserveRuntimeLaunch({
|
|
139
|
+
owner: request.owner,
|
|
140
|
+
launchId: proposedLaunchId,
|
|
141
|
+
...(request.runId === undefined ? {} : { runId: request.runId })
|
|
142
|
+
}, assertLaunchCurrent, this.#now());
|
|
143
|
+
if (reservation.status !== "reserved") {
|
|
144
|
+
throw new Error("Runtime launch reservation could not be renewed.");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
if (differentRunReservation) {
|
|
149
|
+
throw new Error(`Runtime launch reservation belongs to another Run whose host is still running: ${reservation.runId}.`);
|
|
150
|
+
}
|
|
151
|
+
reusedConfirmedRunningHost = true;
|
|
152
|
+
// Only a fresh Codex generation can carry the exact Run prompt in its
|
|
153
|
+
// launch argv. A resume request targets an existing native Session and
|
|
154
|
+
// must use the normal receipt-backed prompt push, even when the
|
|
155
|
+
// Controller is recovering the same launch reservation.
|
|
156
|
+
launchPromptAcknowledgementRequired = sameRunReservation
|
|
157
|
+
&& request.mode === "new"
|
|
158
|
+
&& request.adapterId === "codex";
|
|
159
|
+
runtimeIsolation = this.#preflightRuntimeIsolation(request, reservation.launchId, reservation.launchId.slice(generationPrefix.length), true);
|
|
160
|
+
assertLaunchCurrent();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const launchId = reservation.launchId;
|
|
164
|
+
let preflightObserved = beforeHostStart === undefined;
|
|
165
|
+
const observePreflight = (preflight) => {
|
|
166
|
+
if (preflightObserved) {
|
|
167
|
+
throw new Error("Runtime host reported its pre-start launch fence more than once.");
|
|
168
|
+
}
|
|
169
|
+
validateRuntimeLaunchPreflight(preflight, request, launchId);
|
|
170
|
+
preflightObserved = true;
|
|
171
|
+
beforeHostStart?.(preflight);
|
|
172
|
+
};
|
|
173
|
+
let binding;
|
|
174
|
+
try {
|
|
175
|
+
if (!reusedConfirmedRunningHost && runtimeIsolation !== undefined) {
|
|
176
|
+
// Activation is the first runtime-resource side effect and can occur
|
|
177
|
+
// only after the centralized read-only preflight and durable launch
|
|
178
|
+
// reservation have both succeeded.
|
|
179
|
+
this.#runtimeIsolation.activate(runtimeIsolation);
|
|
180
|
+
}
|
|
181
|
+
const rawBinding = request.mode === "new"
|
|
182
|
+
? await this.host.start({
|
|
183
|
+
mode: "new",
|
|
184
|
+
launchId,
|
|
185
|
+
owner: request.owner,
|
|
186
|
+
agentId: request.agentId,
|
|
187
|
+
adapterId: request.adapterId,
|
|
188
|
+
effective: request.effective,
|
|
189
|
+
workspace: request.workspace,
|
|
190
|
+
...(runtimeIsolation === undefined
|
|
191
|
+
? {}
|
|
192
|
+
: { runtimeIsolation: runtimeIsolation.descriptor }),
|
|
193
|
+
...(request.runId === undefined ? {} : { runId: request.runId }),
|
|
194
|
+
...(request.environment === undefined
|
|
195
|
+
? {}
|
|
196
|
+
: { environment: request.environment })
|
|
197
|
+
}, beforeHostStart === undefined ? undefined : observePreflight)
|
|
198
|
+
: await this.host.resume({
|
|
199
|
+
mode: "resume",
|
|
200
|
+
launchId,
|
|
201
|
+
owner: request.owner,
|
|
202
|
+
agentId: request.agentId,
|
|
203
|
+
adapterId: request.adapterId,
|
|
204
|
+
effective: request.effective,
|
|
205
|
+
workspace: request.workspace,
|
|
206
|
+
...(runtimeIsolation === undefined
|
|
207
|
+
? {}
|
|
208
|
+
: { runtimeIsolation: runtimeIsolation.descriptor }),
|
|
209
|
+
...(request.runId === undefined ? {} : { runId: request.runId }),
|
|
210
|
+
...(request.environment === undefined
|
|
211
|
+
? {}
|
|
212
|
+
: { environment: request.environment }),
|
|
213
|
+
nativeSessionId: requireText(request.nativeSessionId, "Native session id")
|
|
214
|
+
}, beforeHostStart === undefined ? undefined : observePreflight);
|
|
215
|
+
if (!preflightObserved) {
|
|
216
|
+
throw new Error("Runtime session host did not expose a pre-host-start launch fence.");
|
|
217
|
+
}
|
|
218
|
+
binding = requireMatchingRuntimeBinding(rawBinding, request, launchId, launchPromptAcknowledgementRequired, reusedConfirmedRunningHost);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
if (error instanceof RuntimeBindingContractError) {
|
|
222
|
+
// Never pass an untrusted hostRef to stop(). Reconcile the requested
|
|
223
|
+
// owner through the durable, owner-addressed cleanup lane instead.
|
|
224
|
+
this.#requireCleanup(request.owner);
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
await this.#settleFailedStart(request, launchId, reusedConfirmedRunningHost, runtimeIsolation);
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
if (reusedConfirmedRunningHost && binding.hostCreated === true) {
|
|
231
|
+
if (request.runId !== undefined && reservation.runId === request.runId) {
|
|
232
|
+
throw new RuntimeLaunchError(false, launchId, `Exact Run launch generation was unexpectedly recreated: ${request.runId}/${launchId}.`);
|
|
233
|
+
}
|
|
234
|
+
await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, new Error(`Runtime host was recreated while recovering an existing generation: ${request.owner.roleName}.`));
|
|
235
|
+
}
|
|
236
|
+
let reservationConfirmation;
|
|
237
|
+
try {
|
|
238
|
+
assertLaunchCurrent();
|
|
239
|
+
if (persistence === "immediate" && binding.nativeSessionId !== undefined) {
|
|
240
|
+
this.reservations.recordReservedRuntimeNativeSession({
|
|
241
|
+
owner: request.owner,
|
|
242
|
+
launchId,
|
|
243
|
+
agentId: request.agentId,
|
|
244
|
+
adapterId: request.adapterId,
|
|
245
|
+
nativeSessionId: binding.nativeSessionId,
|
|
246
|
+
effective: request.effective
|
|
247
|
+
}, assertLaunchCurrent, this.#now());
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// Deferred scheduler persistence records a known native identity while
|
|
251
|
+
// retaining the reservation until exact Run delivery. Fresh Codex has
|
|
252
|
+
// no identity yet and keeps it until its matching generation Hook.
|
|
253
|
+
reservationConfirmation = this.reservations.confirmRuntimeLaunchReservation({
|
|
254
|
+
owner: request.owner,
|
|
255
|
+
launchId
|
|
256
|
+
}, assertLaunchCurrent);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
await this.#compensateStartedHost(request.owner, binding, launchId, runtimeIsolation, error);
|
|
261
|
+
}
|
|
262
|
+
if (request.owner.scope === "task"
|
|
263
|
+
&& request.adapterId === "codex"
|
|
264
|
+
&& request.runId !== undefined
|
|
265
|
+
&& binding.hostCreated !== false
|
|
266
|
+
&& reservationConfirmation !== "provider-bound"
|
|
267
|
+
&& binding.initialPromptRunId !== request.runId) {
|
|
268
|
+
// Fresh Codex has no pre-input Provider event. It may return before its
|
|
269
|
+
// matching lifecycle Hook, but only an exact launch-carried Run marker
|
|
270
|
+
// can bridge that asynchronous interval. This is transport evidence,
|
|
271
|
+
// never Provider acceptance; the reservation remains fenced until the
|
|
272
|
+
// matching Hook binds the native Session.
|
|
273
|
+
this.#requireCleanup(request.owner);
|
|
274
|
+
throw new RuntimeBindingContractError(`Session host cannot acknowledge the exact launch-carried prompt: ${request.owner.roleName}.`);
|
|
275
|
+
}
|
|
276
|
+
return binding;
|
|
277
|
+
}
|
|
278
|
+
async #settleFailedStart(request, launchId, reusedConfirmedRunningHost, runtimeIsolation) {
|
|
279
|
+
let inspection;
|
|
280
|
+
try {
|
|
281
|
+
inspection = await this.host.inspectOwner(request.owner);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// A rebind/preflight failure did not create the already-confirmed host.
|
|
285
|
+
// Preserve its generation when the follow-up probe is merely unknown.
|
|
286
|
+
if (!reusedConfirmedRunningHost)
|
|
287
|
+
this.#requireCleanup(request.owner);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (inspection.state === "stopped") {
|
|
291
|
+
if (runtimeIsolation !== undefined) {
|
|
292
|
+
try {
|
|
293
|
+
this.#runtimeIsolation.cleanup(runtimeIsolation, "failure");
|
|
294
|
+
}
|
|
295
|
+
catch (cleanupError) {
|
|
296
|
+
this.#requireCleanup(request.owner);
|
|
297
|
+
throw new Error(`Task runtime launch failed and exact resource cleanup also failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`, { cause: cleanupError });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (!this.#settleConfirmedStopped(request, launchId)) {
|
|
301
|
+
this.#requireCleanup(request.owner);
|
|
302
|
+
}
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (reusedConfirmedRunningHost)
|
|
306
|
+
return;
|
|
307
|
+
this.#requireCleanup(request.owner);
|
|
308
|
+
}
|
|
309
|
+
async #compensateStartedHost(owner, binding, launchId, runtimeIsolation, cause) {
|
|
310
|
+
try {
|
|
311
|
+
await this.host.stop(binding);
|
|
312
|
+
}
|
|
313
|
+
catch (stopError) {
|
|
314
|
+
this.#requireCleanup(owner);
|
|
315
|
+
throw new Error(`Runtime launch state changed. Runtime cleanup also failed: ${stopError instanceof Error ? stopError.message : String(stopError)}`, { cause: stopError });
|
|
316
|
+
}
|
|
317
|
+
let stopped = false;
|
|
318
|
+
try {
|
|
319
|
+
stopped = (await this.host.inspectOwner(owner)).state === "stopped";
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// Durable owner cleanup below is the only safe fallback.
|
|
323
|
+
}
|
|
324
|
+
if (stopped && runtimeIsolation !== undefined) {
|
|
325
|
+
try {
|
|
326
|
+
this.#runtimeIsolation.cleanup(runtimeIsolation, "failure");
|
|
327
|
+
}
|
|
328
|
+
catch (cleanupError) {
|
|
329
|
+
this.#requireCleanup(owner);
|
|
330
|
+
throw new Error(`Runtime host stopped but exact Task resource cleanup failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`, { cause: cleanupError });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (!stopped
|
|
334
|
+
|| !this.reservations.settleStoppedRuntimeLaunch({
|
|
335
|
+
owner,
|
|
336
|
+
launchId,
|
|
337
|
+
agentId: binding.agentId,
|
|
338
|
+
adapterId: binding.adapterId,
|
|
339
|
+
...(binding.nativeSessionId === undefined
|
|
340
|
+
? {}
|
|
341
|
+
: { nativeSessionId: binding.nativeSessionId })
|
|
342
|
+
}, this.#now())) {
|
|
343
|
+
this.#requireCleanup(owner);
|
|
344
|
+
}
|
|
345
|
+
throw cause;
|
|
346
|
+
}
|
|
347
|
+
#preflightRuntimeIsolation(request, launchId, generationId, allowExactActive) {
|
|
348
|
+
if (this.#runtimeIsolation === undefined || request.owner.scope !== "task") {
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
return this.#runtimeIsolation.preflight({
|
|
352
|
+
workspace: request.managedWorkspace,
|
|
353
|
+
...(request.runId === undefined ? {} : { runId: request.runId }),
|
|
354
|
+
launchId,
|
|
355
|
+
generationId: requireText(generationId, "Launch generation id"),
|
|
356
|
+
...(request.runtimePolicy === undefined ? {} : { policy: request.runtimePolicy }),
|
|
357
|
+
...(allowExactActive ? { allowExactActive: true } : {})
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
#cleanupTaskLaunch(owner, launchId) {
|
|
361
|
+
const cleanupTaskLaunch = this.#runtimeIsolation?.cleanupTaskLaunch;
|
|
362
|
+
if (cleanupTaskLaunch === undefined) {
|
|
363
|
+
throw new Error("Exact Task runtime launch cleanup is unavailable.");
|
|
364
|
+
}
|
|
365
|
+
cleanupTaskLaunch.call(this.#runtimeIsolation, {
|
|
366
|
+
taskId: owner.taskId,
|
|
367
|
+
launchId,
|
|
368
|
+
reason: "interruption"
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
#settleConfirmedStopped(request, launchId) {
|
|
372
|
+
return this.reservations.settleStoppedRuntimeLaunch({
|
|
373
|
+
owner: request.owner,
|
|
374
|
+
launchId,
|
|
375
|
+
agentId: request.agentId,
|
|
376
|
+
adapterId: request.adapterId,
|
|
377
|
+
...(request.nativeSessionId === undefined
|
|
378
|
+
? {}
|
|
379
|
+
: { nativeSessionId: request.nativeSessionId })
|
|
380
|
+
}, this.#now());
|
|
381
|
+
}
|
|
382
|
+
#requireCleanup(owner) {
|
|
383
|
+
const target = this.reservations.enqueueRuntimeCleanup(owner, this.#now())
|
|
384
|
+
?? runtimeLifecycleTarget(owner);
|
|
385
|
+
try {
|
|
386
|
+
this.#onCleanupRequired?.(target);
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
// The durable cleanup lane is authoritative.
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function validateRuntimeLaunchPreflight(preflight, request, launchId) {
|
|
394
|
+
const ownerMatches = preflight.owner.scope === request.owner.scope
|
|
395
|
+
&& preflight.owner.roleName === request.owner.roleName
|
|
396
|
+
&& (preflight.owner.scope === "global"
|
|
397
|
+
|| (request.owner.scope === "task"
|
|
398
|
+
&& preflight.owner.taskId === request.owner.taskId));
|
|
399
|
+
if (preflight.launchId !== launchId
|
|
400
|
+
|| !ownerMatches
|
|
401
|
+
|| preflight.runId !== request.runId
|
|
402
|
+
|| preflight.agentId !== request.agentId
|
|
403
|
+
|| preflight.adapterId !== request.adapterId
|
|
404
|
+
|| !effectiveLaunchSnapshotsCompatible(preflight.effective, request.effective)
|
|
405
|
+
|| (request.mode === "resume"
|
|
406
|
+
&& preflight.nativeSessionId !== request.nativeSessionId)
|
|
407
|
+
|| (preflight.initialPromptRunId !== undefined
|
|
408
|
+
&& preflight.initialPromptRunId !== request.runId)) {
|
|
409
|
+
throw new Error(`Session host pre-start launch fence does not match the requested runtime: ${request.owner.roleName}.`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function requireMatchingRuntimeBinding(raw, request, launchId, launchPromptAcknowledgementRequired, launchPromptUncertaintyAllowed) {
|
|
413
|
+
let binding;
|
|
414
|
+
try {
|
|
415
|
+
binding = createRuntimeBinding(raw);
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
throw new RuntimeBindingContractError("Session host returned an invalid runtime binding.", { cause: error });
|
|
419
|
+
}
|
|
420
|
+
const ownerMatches = binding.owner.scope === request.owner.scope
|
|
421
|
+
&& binding.owner.roleName === request.owner.roleName
|
|
422
|
+
&& (binding.owner.scope === "global"
|
|
423
|
+
|| (request.owner.scope === "task"
|
|
424
|
+
&& binding.owner.taskId === request.owner.taskId));
|
|
425
|
+
if (binding.initialPromptRunId !== undefined
|
|
426
|
+
&& binding.initialPromptRunId !== request.runId) {
|
|
427
|
+
throw new RuntimeBindingContractError(`Session host returned a launch-carried prompt for another Run: ${request.owner.roleName}.`);
|
|
428
|
+
}
|
|
429
|
+
if (launchPromptAcknowledgementRequired
|
|
430
|
+
&& binding.initialPromptRunId !== request.runId
|
|
431
|
+
&& !(launchPromptUncertaintyAllowed && binding.hostCreated === false)) {
|
|
432
|
+
throw new RuntimeBindingContractError(`Session host cannot acknowledge the exact launch-carried prompt: ${request.owner.roleName}.`);
|
|
433
|
+
}
|
|
434
|
+
if (binding.launchId !== launchId
|
|
435
|
+
|| !ownerMatches
|
|
436
|
+
|| binding.agentId !== request.agentId
|
|
437
|
+
|| binding.adapterId !== request.adapterId
|
|
438
|
+
|| (request.mode === "resume"
|
|
439
|
+
&& binding.nativeSessionId !== request.nativeSessionId)) {
|
|
440
|
+
throw new RuntimeBindingContractError(`Session host returned a binding that does not match the requested runtime: ${request.owner.roleName}.`);
|
|
441
|
+
}
|
|
442
|
+
if (launchPromptAcknowledgementRequired
|
|
443
|
+
&& launchPromptUncertaintyAllowed
|
|
444
|
+
&& binding.hostCreated === false
|
|
445
|
+
&& request.runId !== undefined
|
|
446
|
+
&& binding.initialPromptRunId !== request.runId) {
|
|
447
|
+
// A Controller restart can lose only the in-memory fact that a still-
|
|
448
|
+
// running Codex generation carried this Run in its launch argv. Keep the
|
|
449
|
+
// uncertainty explicitly tied to the exact reservation/Run; the matching
|
|
450
|
+
// Provider Hook remains the sole acceptance authority.
|
|
451
|
+
return {
|
|
452
|
+
...binding,
|
|
453
|
+
launchPromptUncertainRunId: request.runId
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
return binding;
|
|
457
|
+
}
|
|
458
|
+
function defaultLaunchFingerprint(request) {
|
|
459
|
+
return createHash("sha256").update(JSON.stringify([
|
|
460
|
+
request.owner,
|
|
461
|
+
request.agentId,
|
|
462
|
+
request.adapterId,
|
|
463
|
+
request.effective,
|
|
464
|
+
request.workspace,
|
|
465
|
+
request.managedWorkspace,
|
|
466
|
+
request.runtimePolicy
|
|
467
|
+
])).digest("hex");
|
|
468
|
+
}
|
|
469
|
+
function requireText(value, label) {
|
|
470
|
+
if (typeof value !== "string"
|
|
471
|
+
|| value.length === 0
|
|
472
|
+
|| value.trim() !== value
|
|
473
|
+
|| value.includes("\0")) {
|
|
474
|
+
throw new Error(`${label} is invalid.`);
|
|
475
|
+
}
|
|
476
|
+
return value;
|
|
477
|
+
}
|