@zq-silk/yui 0.14.1 → 0.15.0
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 +91 -47
- package/README.md +275 -145
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateCommand.js +17 -9
- package/dist/cli/updateOrchestrator.js +87 -19
- package/dist/cli/updatePorts.js +79 -13
- package/dist/cli/upgradeCommand.js +105 -23
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/controllerCommands.js +1 -1
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +6 -5
- package/dist/doctor/doctor.js +37 -14
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +16 -82
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/observability/runtimeIdentity.js +48 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/release/runtimeRelease.js +9 -1
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +22 -296
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/currentTaskStore.js +6 -4
- package/dist/storage/sqliteSchema.js +135 -80
- package/dist/storage/sqliteStore.js +69 -52
- package/dist/storage/storageSchema.js +92 -230
- package/dist/storage/storageVersions.js +12 -13
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +276 -85
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/version.js +3 -3
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +9 -9
- package/i18n/README.zh-CN.md +189 -74
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +22 -0
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- package/dist/storage/upgrade/recordVersions.js +0 -82
- package/dist/task/repairWave.js +0 -150
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
4
|
import { callController as defaultCallController } from "../core/controllerClient.js";
|
|
5
5
|
import { inspectStorageSchema } from "../storage/storageSchema.js";
|
|
6
6
|
import { yuiVersionIdentity } from "../version.js";
|
|
7
|
-
import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "./lifecycleReservation.js";
|
|
8
|
-
import { nativeSessionIdForLaunch } from "./preallocatedNativeSession.js";
|
|
9
|
-
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
10
7
|
export const EXACT_CONTROL_ARGUMENT = "--yui-control";
|
|
11
8
|
export const YUI_CONTROL_PLANE_DESCRIPTOR = "YUI_CONTROL_PLANE_DESCRIPTOR";
|
|
12
|
-
export const YUI_TASK_RUNTIME_DESCRIPTOR = "YUI_TASK_RUNTIME_DESCRIPTOR";
|
|
13
9
|
export function createExactControlPlaneDescriptor(input) {
|
|
14
10
|
const identity = validateVersionIdentity(input.identity ?? yuiVersionIdentity());
|
|
15
11
|
return Object.freeze({
|
|
@@ -25,25 +21,6 @@ export function createExactControlPlaneDescriptor(input) {
|
|
|
25
21
|
: { activeReleaseDigest: input.activeReleaseDigest })
|
|
26
22
|
});
|
|
27
23
|
}
|
|
28
|
-
export function createExactTaskRuntimeDescriptor(input) {
|
|
29
|
-
return Object.freeze({
|
|
30
|
-
schemaVersion: 1,
|
|
31
|
-
kind: "yui-task-runtime",
|
|
32
|
-
controlPlaneDigest: requireDigest(input.controlPlaneDigest),
|
|
33
|
-
taskId: requireIdentity(input.taskId, "Task id"),
|
|
34
|
-
roleName: requireIdentity(input.roleName, "Role name"),
|
|
35
|
-
agentId: requireIdentity(input.agentId, "Agent id"),
|
|
36
|
-
adapterId: requireAdapter(input.adapterId),
|
|
37
|
-
workspace: canonicalPath(input.workspace),
|
|
38
|
-
...(input.turnId === undefined ? {} : { turnId: requireIdentity(input.turnId, "Turn id") }),
|
|
39
|
-
...(input.launchId === undefined
|
|
40
|
-
? {}
|
|
41
|
-
: { launchId: requireIdentity(input.launchId, "Launch id") }),
|
|
42
|
-
...(input.nativeSessionId === undefined
|
|
43
|
-
? {}
|
|
44
|
-
: { nativeSessionId: requireIdentity(input.nativeSessionId, "Native session id") })
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
24
|
export function serializeExactDescriptor(descriptor) {
|
|
48
25
|
return JSON.stringify(descriptor);
|
|
49
26
|
}
|
|
@@ -64,47 +41,9 @@ export function parseExactControlPlaneDescriptor(value) {
|
|
|
64
41
|
: { activeReleaseDigest: record.activeReleaseDigest })
|
|
65
42
|
});
|
|
66
43
|
}
|
|
67
|
-
export function parseExactTaskRuntimeDescriptor(value) {
|
|
68
|
-
const record = parseDescriptorRecord(value);
|
|
69
|
-
assertDescriptorKind(record, "yui-task-runtime");
|
|
70
|
-
if (record.schemaVersion !== 1) {
|
|
71
|
-
throw new Error("Exact Task runtime descriptor schema version is invalid.");
|
|
72
|
-
}
|
|
73
|
-
return createExactTaskRuntimeDescriptor({
|
|
74
|
-
controlPlaneDigest: requireText(record.controlPlaneDigest, "Control-plane digest"),
|
|
75
|
-
taskId: requireText(record.taskId, "Task id"),
|
|
76
|
-
roleName: requireText(record.roleName, "Role name"),
|
|
77
|
-
agentId: requireText(record.agentId, "Agent id"),
|
|
78
|
-
adapterId: requireAdapter(record.adapterId),
|
|
79
|
-
workspace: requireText(record.workspace, "Task runtime workspace"),
|
|
80
|
-
...(record.turnId === undefined ? {} : { turnId: requireText(record.turnId, "Turn id") }),
|
|
81
|
-
...(record.launchId === undefined
|
|
82
|
-
? {}
|
|
83
|
-
: { launchId: requireText(record.launchId, "Launch id") }),
|
|
84
|
-
...(record.nativeSessionId === undefined
|
|
85
|
-
? {}
|
|
86
|
-
: { nativeSessionId: requireText(record.nativeSessionId, "Native session id") })
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
44
|
export function exactControlPlaneDigest(descriptor) {
|
|
90
45
|
return createHash("sha256").update(serializeExactDescriptor(descriptor)).digest("hex");
|
|
91
46
|
}
|
|
92
|
-
/** Stable per Task/Role/Agent location; volatile Turn/launch/native fields are content. */
|
|
93
|
-
export function exactTaskRuntimeDescriptorPath(home, descriptor) {
|
|
94
|
-
const validated = createExactTaskRuntimeDescriptor(descriptor);
|
|
95
|
-
const digest = createHash("sha256").update(JSON.stringify([
|
|
96
|
-
validated.controlPlaneDigest,
|
|
97
|
-
validated.taskId,
|
|
98
|
-
validated.roleName,
|
|
99
|
-
validated.agentId,
|
|
100
|
-
validated.adapterId,
|
|
101
|
-
validated.workspace
|
|
102
|
-
])).digest("hex");
|
|
103
|
-
return join(canonicalPath(home), "runtime", "exact-task-runtime", `${digest}.json`);
|
|
104
|
-
}
|
|
105
|
-
export function readExactTaskRuntimeDescriptorSource(source, home) {
|
|
106
|
-
return readTaskRuntimeSource(source, home).descriptor;
|
|
107
|
-
}
|
|
108
47
|
export function exactControlPlaneCommandPrefix(descriptor) {
|
|
109
48
|
return [
|
|
110
49
|
descriptor.executable,
|
|
@@ -134,7 +73,7 @@ export function extractExactControlArgument(args) {
|
|
|
134
73
|
}
|
|
135
74
|
/**
|
|
136
75
|
* One read-only gate shared by every managed Task control command. It verifies
|
|
137
|
-
* the frozen executable/CLI/Home/digest, protocol and
|
|
76
|
+
* the frozen executable/CLI/Home/digest, protocol and storage identity,
|
|
138
77
|
* on-disk schema, and any live Controller before command routing may construct
|
|
139
78
|
* a writable store. Package version alone may advance at the same managed path
|
|
140
79
|
* so an existing Session can cross an explicitly compatible in-place update.
|
|
@@ -153,15 +92,10 @@ export async function assertExactControlPlanePreflight(input, options = {}) {
|
|
|
153
92
|
if (storage.status !== "current") {
|
|
154
93
|
throw new Error(`Exact control-plane storage is not current: ${storage.status}.`);
|
|
155
94
|
}
|
|
156
|
-
if (storage.
|
|
157
|
-
throw new Error("Exact control-plane storage
|
|
158
|
-
+ `(expected ${descriptor.identity.
|
|
159
|
-
+ `${storage.
|
|
160
|
-
}
|
|
161
|
-
if (storage.currentAggregateSchemaVersion !== descriptor.identity.aggregateSchemaVersion) {
|
|
162
|
-
throw new Error("Exact control-plane aggregate schema does not match its frozen descriptor "
|
|
163
|
-
+ `(expected ${descriptor.identity.aggregateSchemaVersion}, found `
|
|
164
|
-
+ `${storage.currentAggregateSchemaVersion ?? "unknown"}).`);
|
|
95
|
+
if (storage.currentVersion !== descriptor.identity.storageVersion) {
|
|
96
|
+
throw new Error("Exact control-plane storage version does not match its frozen descriptor "
|
|
97
|
+
+ `(expected ${descriptor.identity.storageVersion}, found `
|
|
98
|
+
+ `${storage.currentVersion ?? "unknown"}).`);
|
|
165
99
|
}
|
|
166
100
|
// A frozen descriptor authenticates the command that created it; it no
|
|
167
101
|
// longer pins the Home's deployment pointer for the lifetime of a Session.
|
|
@@ -194,15 +128,10 @@ export async function assertCompatibleControlPlanePreflight(input, options = {})
|
|
|
194
128
|
if (storage.status !== "current") {
|
|
195
129
|
throw new Error(`Managed control-plane storage is not current: ${storage.status}.`);
|
|
196
130
|
}
|
|
197
|
-
if (storage.
|
|
198
|
-
throw new Error("Managed control-plane storage
|
|
199
|
-
+ `(expected ${identity.
|
|
200
|
-
+ `${storage.
|
|
201
|
-
}
|
|
202
|
-
if (storage.currentAggregateSchemaVersion !== identity.aggregateSchemaVersion) {
|
|
203
|
-
throw new Error("Managed control-plane aggregate schema is incompatible "
|
|
204
|
-
+ `(expected ${identity.aggregateSchemaVersion}, found `
|
|
205
|
-
+ `${storage.currentAggregateSchemaVersion ?? "unknown"}).`);
|
|
131
|
+
if (storage.currentVersion !== identity.storageVersion) {
|
|
132
|
+
throw new Error("Managed control-plane storage version is incompatible "
|
|
133
|
+
+ `(expected ${identity.storageVersion}, found `
|
|
134
|
+
+ `${storage.currentVersion ?? "unknown"}).`);
|
|
206
135
|
}
|
|
207
136
|
if (options.checkController !== false) {
|
|
208
137
|
const call = options.callController ?? defaultCallController;
|
|
@@ -223,210 +152,31 @@ export function assertControllerStatusIdentity(status, expected = yuiVersionIden
|
|
|
223
152
|
}
|
|
224
153
|
assertControllerField(status.protocolVersion, expected.controllerProtocolVersion, "protocol");
|
|
225
154
|
assertControllerField(status.version, expected.version, "version");
|
|
226
|
-
assertControllerField(status.
|
|
227
|
-
assertControllerField(status.
|
|
228
|
-
}
|
|
229
|
-
export function assertExactTaskRuntimeEnvironment(runtimeSource, environment, expectedControlDigest, expectedHome = requireText(environment.YUI_HOME, "YUI_HOME")) {
|
|
230
|
-
const resolved = readTaskRuntimeSource(runtimeSource, expectedHome);
|
|
231
|
-
const runtime = resolved.descriptor;
|
|
232
|
-
if (runtime.controlPlaneDigest !== requireDigest(expectedControlDigest)) {
|
|
233
|
-
throw new Error("Task runtime descriptor belongs to another exact control plane.");
|
|
234
|
-
}
|
|
235
|
-
assertEnvironment(runtime.taskId, environment.YUI_TASK_ID, "Task id");
|
|
236
|
-
assertEnvironment(runtime.roleName, environment.YUI_ROLE, "Role name");
|
|
237
|
-
assertEnvironment(runtime.agentId, environment.YUI_AGENT_ID, "Agent id");
|
|
238
|
-
assertEnvironment(runtime.adapterId, environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
239
|
-
assertSamePath(runtime.workspace, requireText(environment.YUI_WORKSPACE, "YUI_WORKSPACE"), "Task runtime workspace");
|
|
240
|
-
// A reused native pane retains its original process environment. Volatile
|
|
241
|
-
// identity therefore comes only from the atomically published descriptor
|
|
242
|
-
// plus durable state, never from stale ambient Turn/launch/native variables.
|
|
243
|
-
if (!resolved.fromFile) {
|
|
244
|
-
assertOptionalEnvironment(runtime.turnId, environment.YUI_TURN_ID, "Turn id");
|
|
245
|
-
assertOptionalEnvironment(runtime.launchId, environment.YUI_LAUNCH_ID, "Launch id");
|
|
246
|
-
assertOptionalEnvironment(runtime.nativeSessionId, environment.YUI_NATIVE_SESSION_ID, "Native session id");
|
|
247
|
-
}
|
|
248
|
-
return runtime;
|
|
249
|
-
}
|
|
250
|
-
/** Fences a descriptor to the one currently active durable Task runtime. */
|
|
251
|
-
export function assertExactTaskRuntimeState(runtime, store, options = {}) {
|
|
252
|
-
const task = store.getTask(runtime.taskId);
|
|
253
|
-
if (task === null
|
|
254
|
-
|| task.status !== "active"
|
|
255
|
-
|| task.executionGate.state !== "enabled") {
|
|
256
|
-
throw new Error("Exact Task runtime Task is not current and active.");
|
|
257
|
-
}
|
|
258
|
-
const role = store.getRole(runtime.taskId, runtime.roleName);
|
|
259
|
-
if (role === null || role.activeAgentId !== runtime.agentId) {
|
|
260
|
-
throw new Error("Exact Task runtime Role or Agent is not current.");
|
|
261
|
-
}
|
|
262
|
-
const run = store.getActiveTurn(runtime.taskId, runtime.roleName);
|
|
263
|
-
if (runtime.turnId === undefined) {
|
|
264
|
-
if (run !== null)
|
|
265
|
-
throw new Error("Exact Task runtime Turn is not current.");
|
|
266
|
-
}
|
|
267
|
-
else if (run === null
|
|
268
|
-
|| run.id !== runtime.turnId
|
|
269
|
-
|| run.status !== "active"
|
|
270
|
-
|| run.effective.agentId !== runtime.agentId
|
|
271
|
-
|| run.effective.adapterId !== runtime.adapterId
|
|
272
|
-
|| canonicalPath(run.effective.workspace.root) !== runtime.workspace) {
|
|
273
|
-
throw new Error("Exact Task runtime Turn is not current.");
|
|
274
|
-
}
|
|
275
|
-
const sessions = store.getTaskRoleSessionSet(runtime.taskId, runtime.roleName);
|
|
276
|
-
const session = sessions?.sessions[runtime.agentId];
|
|
277
|
-
if (sessions !== null && sessions.activeAgentId !== runtime.agentId) {
|
|
278
|
-
throw new Error("Exact Task runtime Session Agent is not current.");
|
|
279
|
-
}
|
|
280
|
-
const lifecycleMailbox = store.getWorkMailbox(runtimeLifecycleTarget({
|
|
281
|
-
scope: "task",
|
|
282
|
-
taskId: runtime.taskId,
|
|
283
|
-
roleName: runtime.roleName
|
|
284
|
-
}));
|
|
285
|
-
const reservation = runtime.launchId !== undefined
|
|
286
|
-
&& isRuntimeLaunchReservation(lifecycleMailbox?.processing, runtime.launchId);
|
|
287
|
-
const sessionLaunch = runtime.launchId !== undefined
|
|
288
|
-
&& session?.launchId === runtime.launchId;
|
|
289
|
-
const preallocated = options.preallocatedDriverSessionReservation;
|
|
290
|
-
const exactHostLaunchReservation = runtime.launchId !== undefined
|
|
291
|
-
&& reservation
|
|
292
|
-
&& !hasRuntimeCleanupObligation(lifecycleMailbox);
|
|
293
|
-
const terminalSessionReplacementReservation = exactHostLaunchReservation
|
|
294
|
-
&& session !== undefined
|
|
295
|
-
&& session.status === "ended";
|
|
296
|
-
const exactPreallocatedReservation = preallocated !== undefined
|
|
297
|
-
&& runtime.adapterId === preallocated.adapterId
|
|
298
|
-
&& exactHostLaunchReservation
|
|
299
|
-
&& runtime.nativeSessionId !== undefined
|
|
300
|
-
&& (session === undefined || terminalSessionReplacementReservation)
|
|
301
|
-
&& runtime.nativeSessionId === nativeSessionIdForLaunch(preallocated.yuiHome, runtime.launchId, runtime.agentId, runtime.adapterId);
|
|
302
|
-
if (runtime.launchId === undefined || (!reservation && !sessionLaunch)) {
|
|
303
|
-
throw new Error("Exact Task runtime launch fence is not current.");
|
|
304
|
-
}
|
|
305
|
-
if (runtime.nativeSessionId === undefined) {
|
|
306
|
-
if (session !== undefined && !terminalSessionReplacementReservation) {
|
|
307
|
-
throw new Error("Exact Task runtime native Session fence is missing.");
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
else if (session === undefined || terminalSessionReplacementReservation) {
|
|
311
|
-
if (!exactPreallocatedReservation) {
|
|
312
|
-
throw new Error("Exact Task runtime native Session fence is not current.");
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
else if (session.agentId !== runtime.agentId
|
|
316
|
-
|| session.adapterId !== runtime.adapterId
|
|
317
|
-
|| session.nativeSessionId !== runtime.nativeSessionId
|
|
318
|
-
|| session.launchId !== runtime.launchId
|
|
319
|
-
|| session.status === "ended"
|
|
320
|
-
|| session.effective.agentId !== runtime.agentId
|
|
321
|
-
|| session.effective.adapterId !== runtime.adapterId
|
|
322
|
-
|| canonicalPath(session.effective.workspace.root) !== runtime.workspace) {
|
|
323
|
-
throw new Error("Exact Task runtime native Session fence is not current.");
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
/** Publishes provider-discovered native identity without changing the stable source path. */
|
|
327
|
-
export function refreshExactTaskRuntimeDescriptorSource(source, home, store) {
|
|
328
|
-
const resolved = readTaskRuntimeSource(source, home);
|
|
329
|
-
if (!resolved.fromFile) {
|
|
330
|
-
throw new Error("A managed Task runtime descriptor must use its stable file source.");
|
|
331
|
-
}
|
|
332
|
-
const current = resolved.descriptor;
|
|
333
|
-
const session = store.getTaskRoleSessionSet(current.taskId, current.roleName)
|
|
334
|
-
?.sessions[current.agentId];
|
|
335
|
-
if (session === undefined) {
|
|
336
|
-
throw new Error("Exact Task runtime native Session is not available for refresh.");
|
|
337
|
-
}
|
|
338
|
-
const refreshed = createExactTaskRuntimeDescriptor({
|
|
339
|
-
...current,
|
|
340
|
-
nativeSessionId: session.nativeSessionId
|
|
341
|
-
});
|
|
342
|
-
if (exactTaskRuntimeDescriptorPath(home, refreshed) !== resolve(source)) {
|
|
343
|
-
throw new Error("Refreshed Task runtime descriptor changed its stable identity.");
|
|
344
|
-
}
|
|
345
|
-
assertExactTaskRuntimeState(refreshed, store);
|
|
346
|
-
writeTextFileAtomically(resolve(source), `${serializeExactDescriptor(refreshed)}\n`);
|
|
347
|
-
return refreshed;
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Advances a reused Hook's own exact source to the current durable Turn/launch
|
|
351
|
-
* generation before the volatile fence. The stable identity (control digest,
|
|
352
|
-
* Task, Role, Agent, adapter, workspace) and the native Session are preserved;
|
|
353
|
-
* only the volatile Turn/launch fields advance. A source may never jump to a
|
|
354
|
-
* replacement native Session.
|
|
355
|
-
*/
|
|
356
|
-
export function refreshReusedTaskRuntimeDescriptorSource(source, home, store, current) {
|
|
357
|
-
const resolved = readTaskRuntimeSource(source, home);
|
|
358
|
-
if (!resolved.fromFile) {
|
|
359
|
-
throw new Error("A managed Task runtime descriptor must use its stable file source.");
|
|
360
|
-
}
|
|
361
|
-
const previous = resolved.descriptor;
|
|
362
|
-
if (previous.nativeSessionId === undefined) {
|
|
363
|
-
if (previous.turnId !== current.turnId || previous.launchId !== current.launchId) {
|
|
364
|
-
throw new Error("Task runtime descriptor source cannot bind a native Session from another generation.");
|
|
365
|
-
}
|
|
366
|
-
return refreshExactTaskRuntimeDescriptorSource(source, home, store);
|
|
367
|
-
}
|
|
368
|
-
if (previous.nativeSessionId !== current.nativeSessionId) {
|
|
369
|
-
throw new Error("Task runtime descriptor source cannot jump to a replacement native Session.");
|
|
370
|
-
}
|
|
371
|
-
const refreshed = createExactTaskRuntimeDescriptor({
|
|
372
|
-
...previous,
|
|
373
|
-
turnId: current.turnId,
|
|
374
|
-
launchId: current.launchId,
|
|
375
|
-
nativeSessionId: current.nativeSessionId
|
|
376
|
-
});
|
|
377
|
-
if (exactTaskRuntimeDescriptorPath(home, refreshed) !== resolve(source)) {
|
|
378
|
-
throw new Error("Refreshed Task runtime descriptor changed its stable identity.");
|
|
379
|
-
}
|
|
380
|
-
assertExactTaskRuntimeState(refreshed, store);
|
|
381
|
-
writeTextFileAtomically(resolve(source), `${serializeExactDescriptor(refreshed)}\n`);
|
|
382
|
-
return refreshed;
|
|
383
|
-
}
|
|
384
|
-
function readTaskRuntimeSource(source, home) {
|
|
385
|
-
const value = requireText(source, "Exact Task runtime descriptor source");
|
|
386
|
-
if (value.trimStart().startsWith("{")) {
|
|
387
|
-
return { descriptor: parseExactTaskRuntimeDescriptor(value), fromFile: false };
|
|
388
|
-
}
|
|
389
|
-
const requested = resolve(value);
|
|
390
|
-
const root = resolve(canonicalPath(home), "runtime", "exact-task-runtime");
|
|
391
|
-
if (dirname(requested) !== root) {
|
|
392
|
-
throw new Error("Exact Task runtime descriptor path is outside its control plane.");
|
|
393
|
-
}
|
|
394
|
-
let actual;
|
|
395
|
-
try {
|
|
396
|
-
actual = realpathSync(requested);
|
|
397
|
-
}
|
|
398
|
-
catch (error) {
|
|
399
|
-
throw new Error("Exact Task runtime descriptor file is unavailable.", { cause: error });
|
|
400
|
-
}
|
|
401
|
-
if (actual !== requested) {
|
|
402
|
-
throw new Error("Exact Task runtime descriptor file must not be a symbolic link.");
|
|
403
|
-
}
|
|
404
|
-
const descriptor = parseExactTaskRuntimeDescriptor(readFileSync(actual, "utf8"));
|
|
405
|
-
if (exactTaskRuntimeDescriptorPath(home, descriptor) !== requested) {
|
|
406
|
-
throw new Error("Exact Task runtime descriptor path does not match its stable identity.");
|
|
407
|
-
}
|
|
408
|
-
return { descriptor, fromFile: true };
|
|
155
|
+
assertControllerField(status.storageVersion, expected.storageVersion, "storage version");
|
|
156
|
+
assertControllerField(status.minimumStorageVersion, expected.minimumStorageVersion, "minimum storage migration version");
|
|
409
157
|
}
|
|
410
158
|
function validateVersionIdentity(value) {
|
|
411
159
|
if (!isRecord(value))
|
|
412
160
|
throw new Error("Yui version identity is invalid.");
|
|
413
161
|
const version = requireText(value.version, "Yui version");
|
|
414
162
|
const controllerProtocolVersion = requireVersion(value.controllerProtocolVersion, "Controller protocol version");
|
|
415
|
-
const
|
|
416
|
-
const
|
|
163
|
+
const storageVersion = requireVersion(value.storageVersion, "Storage version");
|
|
164
|
+
const minimumStorageVersion = requireVersion(value.minimumStorageVersion, "Minimum storage migration version");
|
|
165
|
+
if (minimumStorageVersion > storageVersion) {
|
|
166
|
+
throw new Error("Minimum storage migration version cannot exceed the current storage version.");
|
|
167
|
+
}
|
|
417
168
|
return {
|
|
418
169
|
version,
|
|
419
170
|
controllerProtocolVersion,
|
|
420
|
-
|
|
421
|
-
|
|
171
|
+
storageVersion,
|
|
172
|
+
minimumStorageVersion
|
|
422
173
|
};
|
|
423
174
|
}
|
|
424
175
|
/** Managed continuity is a protocol/storage contract, not a package pin. */
|
|
425
176
|
function assertContinuityIdentity(label, expected, actual) {
|
|
426
177
|
for (const field of [
|
|
427
178
|
"controllerProtocolVersion",
|
|
428
|
-
"
|
|
429
|
-
"aggregateSchemaVersion"
|
|
179
|
+
"storageVersion"
|
|
430
180
|
]) {
|
|
431
181
|
if (expected[field] !== actual[field]) {
|
|
432
182
|
throw new Error(`${label} ${field} does not match the frozen control plane `
|
|
@@ -442,8 +192,7 @@ function assertControllerContinuityIdentity(status, expected) {
|
|
|
442
192
|
throw new Error("Controller version is invalid at the managed continuity gate.");
|
|
443
193
|
}
|
|
444
194
|
assertControllerField(status.protocolVersion, expected.controllerProtocolVersion, "protocol");
|
|
445
|
-
assertControllerField(status.
|
|
446
|
-
assertControllerField(status.aggregateSchemaVersion, expected.aggregateSchemaVersion, "aggregate schema");
|
|
195
|
+
assertControllerField(status.storageVersion, expected.storageVersion, "storage version");
|
|
447
196
|
}
|
|
448
197
|
function assertControllerField(actual, expected, label) {
|
|
449
198
|
if (actual !== expected) {
|
|
@@ -459,16 +208,6 @@ function assertSamePath(expected, actual, label) {
|
|
|
459
208
|
throw new Error(`${label} does not match the frozen control plane (expected ${expected}, found ${normalized}).`);
|
|
460
209
|
}
|
|
461
210
|
}
|
|
462
|
-
function assertEnvironment(expected, actual, label) {
|
|
463
|
-
if (actual !== expected) {
|
|
464
|
-
throw new Error(`${label} does not match the exact Task runtime descriptor.`);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
function assertOptionalEnvironment(expected, actual, label) {
|
|
468
|
-
if (actual !== expected) {
|
|
469
|
-
throw new Error(`${label} does not match the exact Task runtime descriptor.`);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
211
|
function parseDescriptorRecord(value) {
|
|
473
212
|
let parsed;
|
|
474
213
|
try {
|
|
@@ -487,19 +226,6 @@ function assertDescriptorKind(value, expected) {
|
|
|
487
226
|
+ `${typeof value.kind === "string" ? value.kind : "unknown"}.`);
|
|
488
227
|
}
|
|
489
228
|
}
|
|
490
|
-
function requireAdapter(value) {
|
|
491
|
-
if (value !== "codex" && value !== "claude") {
|
|
492
|
-
throw new Error("Agent adapter id is invalid.");
|
|
493
|
-
}
|
|
494
|
-
return value;
|
|
495
|
-
}
|
|
496
|
-
function requireIdentity(value, label) {
|
|
497
|
-
const text = requireText(value, label).trim();
|
|
498
|
-
if (text.length === 0 || text.length > 1_024 || text.includes("\0")) {
|
|
499
|
-
throw new Error(`${label} is invalid.`);
|
|
500
|
-
}
|
|
501
|
-
return text;
|
|
502
|
-
}
|
|
503
229
|
function requireText(value, label) {
|
|
504
230
|
if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
|
|
505
231
|
throw new Error(`${label} is invalid.`);
|
|
@@ -4,7 +4,7 @@ export function projectFirstProgressAdvisory(input) {
|
|
|
4
4
|
: [...(input.sessions.history ?? []), ...Object.values(input.sessions.sessions)]
|
|
5
5
|
.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
6
6
|
const unique = [...new Map(sessions.map((session) => [
|
|
7
|
-
`${session.nativeSessionId}\0${session.
|
|
7
|
+
`${session.nativeSessionId}\0${session.runtimeGenerationId ?? ""}`,
|
|
8
8
|
session
|
|
9
9
|
])).values()];
|
|
10
10
|
const firstGenerationAt = unique[0]?.createdAt;
|
package/dist/runtime/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { createRuntimeBinding } from "./runtimeBinding.js";
|
|
|
5
5
|
export { normalizeRuntimeOwner } from "./runtimeOwner.js";
|
|
6
6
|
export { createSessionLaunchRequest } from "./sessionLaunchRequest.js";
|
|
7
7
|
export { DEFAULT_RECENT_TURN_ID_LIMIT, hasRecentTurnId, rememberRecentTurnId, validateRecentTurnIds } from "./recentTurnIds.js";
|
|
8
|
-
export { RuntimeHostContentionError, RuntimeLaunchError } from "./ports.js";
|
|
8
|
+
export { RuntimeGenerationMismatchError, RuntimeHostContentionError, RuntimeLaunchError } from "./ports.js";
|
|
9
9
|
export { AgentHostPromptPushAdapter, TmuxSessionHost } from "./tmuxAdapters.js";
|
|
10
10
|
export { FileTaskRuntimeIsolation, YUI_TASK_RUNTIME_ISOLATION_DESCRIPTOR, YUI_TASK_RUNTIME_SERVICE_NAMESPACE, assertTaskRuntimeIsolationPreflight, createTaskRuntimeIsolationDescriptor, parseTaskRuntimeIsolationDescriptor, planTaskRuntimeCleanup, taskRuntimeIsolationEnvironment, taskRuntimeIsolationFingerprint } from "./taskRuntimeIsolation.js";
|
|
11
11
|
export { createSessionOwnerIdentity, discoverProviderRootByLaunchEnv, isLinuxProcessLive, listLaunchFencedProcesses, listOwnedProcessTree, readLinuxProcessIdentity } from "./sessionOwnerIdentity.js";
|
|
@@ -17,30 +17,30 @@ export class LaunchBroker {
|
|
|
17
17
|
#reservations = new Map();
|
|
18
18
|
reserve(payload) {
|
|
19
19
|
validatePayload(payload);
|
|
20
|
-
if (this.#reservations.has(payload.
|
|
21
|
-
throw new Error(`Launch payload is already reserved: ${payload.
|
|
20
|
+
if (this.#reservations.has(payload.runtimeGenerationId)) {
|
|
21
|
+
throw new Error(`Launch payload is already reserved: ${payload.runtimeGenerationId}.`);
|
|
22
22
|
}
|
|
23
23
|
const ticket = randomBytes(32).toString("hex");
|
|
24
|
-
this.#reservations.set(payload.
|
|
24
|
+
this.#reservations.set(payload.runtimeGenerationId, Object.freeze({
|
|
25
25
|
ticket,
|
|
26
26
|
payload,
|
|
27
27
|
createdAt: Date.now()
|
|
28
28
|
}));
|
|
29
|
-
return Object.freeze({
|
|
29
|
+
return Object.freeze({ runtimeGenerationId: payload.runtimeGenerationId, ticket });
|
|
30
30
|
}
|
|
31
|
-
redeem(
|
|
32
|
-
const reservation = this.#reservations.get(
|
|
31
|
+
redeem(runtimeGenerationId, ticket) {
|
|
32
|
+
const reservation = this.#reservations.get(runtimeGenerationId);
|
|
33
33
|
if (reservation === undefined || reservation.ticket !== ticket) {
|
|
34
34
|
throw new Error("Launch ticket is invalid or already consumed.");
|
|
35
35
|
}
|
|
36
|
-
this.#reservations.delete(
|
|
36
|
+
this.#reservations.delete(runtimeGenerationId);
|
|
37
37
|
if (Date.now() - reservation.createdAt > AGENT_HOST_LAUNCH_TICKET_TTL_MS) {
|
|
38
38
|
throw new Error("Launch ticket expired before redemption.");
|
|
39
39
|
}
|
|
40
40
|
return reservation.payload;
|
|
41
41
|
}
|
|
42
|
-
revoke(
|
|
43
|
-
this.#reservations.delete(
|
|
42
|
+
revoke(runtimeGenerationId) {
|
|
43
|
+
this.#reservations.delete(runtimeGenerationId);
|
|
44
44
|
}
|
|
45
45
|
pendingCount() {
|
|
46
46
|
return this.#reservations.size;
|
|
@@ -53,9 +53,9 @@ export function validateAgentHostLaunchPayload(value) {
|
|
|
53
53
|
return validatePayload(value);
|
|
54
54
|
}
|
|
55
55
|
function validatePayload(payload) {
|
|
56
|
-
if (payload.schemaVersion !==
|
|
56
|
+
if (payload.schemaVersion !== 2)
|
|
57
57
|
throw new Error("Agent Host launch payload version is invalid.");
|
|
58
|
-
text(payload.
|
|
58
|
+
text(payload.runtimeGenerationId, "runtimeGenerationId");
|
|
59
59
|
text(payload.command, "command");
|
|
60
60
|
text(payload.cwd, "cwd");
|
|
61
61
|
if (!Array.isArray(payload.args))
|
|
@@ -33,11 +33,11 @@ export function runtimeLifecycleSignalKey(owner) {
|
|
|
33
33
|
? `role:${encodeURIComponent(owner.taskId)}/${encodeURIComponent(owner.roleName)}`
|
|
34
34
|
: `global-role:${encodeURIComponent(owner.roleName)}`;
|
|
35
35
|
}
|
|
36
|
-
export function isRuntimeLaunchReservation(processing,
|
|
36
|
+
export function isRuntimeLaunchReservation(processing, runtimeGenerationId) {
|
|
37
37
|
return processing?.owner === RUNTIME_LIFECYCLE_OWNER
|
|
38
38
|
&& processing.batch.reasons.length === 1
|
|
39
39
|
&& processing.batch.reasons[0] === RUNTIME_LAUNCH_RESERVED_REASON
|
|
40
|
-
&& (
|
|
40
|
+
&& (runtimeGenerationId === undefined || processing.batchId === runtimeGenerationId);
|
|
41
41
|
}
|
|
42
42
|
export function hasRuntimeLaunchReservation(mailbox) {
|
|
43
43
|
return isRuntimeLaunchReservation(mailbox?.processing);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { activeRoleAgentBinding } from "../role/role.js";
|
|
3
|
+
/** Per-process credential injected at native Session launch. */
|
|
4
|
+
export const MANAGED_CALLER_KEY_ENV = "YUI_JOB_CALLER_KEY";
|
|
5
|
+
/**
|
|
6
|
+
* A managed Session presented valid self-identity but is no longer the current
|
|
7
|
+
* runtime of its Task Role. This is a bounded diagnosis rather than a bare
|
|
8
|
+
* denial: the Agent can still read current state and decide whether to re-read,
|
|
9
|
+
* hand back, or stop.
|
|
10
|
+
*/
|
|
11
|
+
export class ManagedRuntimeDriftError extends Error {
|
|
12
|
+
name = "ManagedRuntimeDriftError";
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Reads the process's own immutable managed identity, or undefined when unmanaged. */
|
|
18
|
+
export function managedTaskSessionIdentity(environment) {
|
|
19
|
+
const env = environment ?? {};
|
|
20
|
+
if (env.YUI_SESSION_SCOPE !== "task")
|
|
21
|
+
return undefined;
|
|
22
|
+
const taskId = identity(env.YUI_TASK_ID);
|
|
23
|
+
const roleName = identity(env.YUI_ROLE);
|
|
24
|
+
if (taskId === undefined || roleName === undefined) {
|
|
25
|
+
throw new ManagedRuntimeDriftError("Managed Task Session identity is incomplete: YUI_TASK_ID and YUI_ROLE are required.");
|
|
26
|
+
}
|
|
27
|
+
const workspace = identity(env.YUI_WORKSPACE);
|
|
28
|
+
const callerKey = identity(env[MANAGED_CALLER_KEY_ENV]);
|
|
29
|
+
return Object.freeze({
|
|
30
|
+
taskId,
|
|
31
|
+
roleName,
|
|
32
|
+
...(workspace === undefined ? {} : { workspace }),
|
|
33
|
+
...(callerKey === undefined ? {} : { callerKey })
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolves the current runtime authority for a managed Task Session, or
|
|
38
|
+
* undefined for an unmanaged (plain user) invocation. Throws only when the
|
|
39
|
+
* process claims managed identity that durable state no longer recognizes.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveManagedTaskCaller(store, environment) {
|
|
42
|
+
const self = managedTaskSessionIdentity(environment);
|
|
43
|
+
if (self === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
return requireCurrentRuntime(store, self);
|
|
46
|
+
}
|
|
47
|
+
/** Same as resolveManagedTaskCaller, but requires a managed Task Session. */
|
|
48
|
+
export function requireManagedTaskCaller(store, environment) {
|
|
49
|
+
const caller = resolveManagedTaskCaller(store, environment);
|
|
50
|
+
if (caller === undefined) {
|
|
51
|
+
throw new ManagedRuntimeDriftError("This command requires a managed Task Session.");
|
|
52
|
+
}
|
|
53
|
+
return caller;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The current runtime authority for one Task Role, or undefined. Used by
|
|
57
|
+
* command authorization that must not fail the whole invocation, only decline
|
|
58
|
+
* one privileged effect.
|
|
59
|
+
*/
|
|
60
|
+
export function currentManagedRuntime(store, environment, taskId, roleName) {
|
|
61
|
+
let caller;
|
|
62
|
+
try {
|
|
63
|
+
caller = resolveManagedTaskCaller(store, environment);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
if (caller === undefined || caller.taskId !== taskId)
|
|
69
|
+
return undefined;
|
|
70
|
+
if (roleName !== undefined && caller.roleName !== roleName)
|
|
71
|
+
return undefined;
|
|
72
|
+
return caller;
|
|
73
|
+
}
|
|
74
|
+
function requireCurrentRuntime(store, self) {
|
|
75
|
+
const role = store.getRole(self.taskId, self.roleName);
|
|
76
|
+
if (role === null) {
|
|
77
|
+
throw new ManagedRuntimeDriftError(`This managed Session belongs to ${self.taskId}/${self.roleName}, which no longer exists. `
|
|
78
|
+
+ "A new Session must be launched to act on this Task.");
|
|
79
|
+
}
|
|
80
|
+
const agentId = role.activeAgentId;
|
|
81
|
+
if (self.callerKey === undefined) {
|
|
82
|
+
throw new ManagedRuntimeDriftError(`This managed Session carries no ${MANAGED_CALLER_KEY_ENV}, so it cannot be recognized `
|
|
83
|
+
+ `as the current runtime of ${self.taskId}/${self.roleName}.`);
|
|
84
|
+
}
|
|
85
|
+
const expectedHash = store.getJobCallerKeyHash(self.taskId, self.roleName, agentId);
|
|
86
|
+
if (expectedHash === null
|
|
87
|
+
|| createHash("sha256").update(self.callerKey).digest("hex") !== expectedHash) {
|
|
88
|
+
throw new ManagedRuntimeDriftError(`This managed Session is no longer the current runtime of ${self.taskId}/${self.roleName} `
|
|
89
|
+
+ `(the Role now runs Agent ${agentId}). Its native Session was replaced or its Role was `
|
|
90
|
+
+ "rebound, so its durable caller key no longer matches. Nothing was changed. Read the "
|
|
91
|
+
+ `current state with \`yui task show ${self.taskId}\`; acting requires the Session Yui `
|
|
92
|
+
+ "launched for the current runtime.");
|
|
93
|
+
}
|
|
94
|
+
const activeTurn = store.getActiveTurn(self.taskId, self.roleName);
|
|
95
|
+
return Object.freeze({
|
|
96
|
+
taskId: self.taskId,
|
|
97
|
+
roleName: self.roleName,
|
|
98
|
+
agentId,
|
|
99
|
+
adapterId: activeRoleAgentBinding(role).adapterId,
|
|
100
|
+
...(self.workspace === undefined ? {} : { workspace: self.workspace }),
|
|
101
|
+
...(activeTurn === null || activeTurn.status !== "active"
|
|
102
|
+
? {}
|
|
103
|
+
: { currentTurnId: activeTurn.id })
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function identity(value) {
|
|
107
|
+
if (typeof value !== "string" || value.includes("\0"))
|
|
108
|
+
return undefined;
|
|
109
|
+
const normalized = value.trim();
|
|
110
|
+
return normalized.length === 0 || normalized !== value ? undefined : normalized;
|
|
111
|
+
}
|
package/dist/runtime/ports.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/** A persisted launch is either temporarily unavailable or permanently lost. */
|
|
2
2
|
export class RuntimeLaunchError extends Error {
|
|
3
3
|
retryable;
|
|
4
|
-
|
|
4
|
+
runtimeGenerationId;
|
|
5
5
|
reason;
|
|
6
6
|
name = "RuntimeLaunchError";
|
|
7
|
-
constructor(retryable,
|
|
7
|
+
constructor(retryable, runtimeGenerationId, message, reason) {
|
|
8
8
|
super(message);
|
|
9
9
|
this.retryable = retryable;
|
|
10
|
-
this.
|
|
10
|
+
this.runtimeGenerationId = runtimeGenerationId;
|
|
11
11
|
this.reason = reason;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -20,3 +20,16 @@ export class RuntimeHostContentionError extends Error {
|
|
|
20
20
|
this.reason = reason;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
/** A reused Agent Host acknowledged a different runtime generation. */
|
|
24
|
+
export class RuntimeGenerationMismatchError extends Error {
|
|
25
|
+
expectedRuntimeGenerationId;
|
|
26
|
+
observedRuntimeGenerationId;
|
|
27
|
+
hostState;
|
|
28
|
+
name = "RuntimeGenerationMismatchError";
|
|
29
|
+
constructor(expectedRuntimeGenerationId, observedRuntimeGenerationId, hostState, message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.expectedRuntimeGenerationId = expectedRuntimeGenerationId;
|
|
32
|
+
this.observedRuntimeGenerationId = observedRuntimeGenerationId;
|
|
33
|
+
this.hostState = hostState;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -2,10 +2,10 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { requireText } from "./validation.js";
|
|
4
4
|
/** Deterministic native identity preallocated for one exact provider launch. */
|
|
5
|
-
export function nativeSessionIdForLaunch(home,
|
|
5
|
+
export function nativeSessionIdForLaunch(home, runtimeGenerationId, agentId, adapterId) {
|
|
6
6
|
const hex = createHash("sha256").update(JSON.stringify([
|
|
7
7
|
resolve(requireText(home, "YUI_HOME")),
|
|
8
|
-
requireText(
|
|
8
|
+
requireText(runtimeGenerationId, "Runtime generation id"),
|
|
9
9
|
requireText(agentId, "Agent id"),
|
|
10
10
|
requireText(adapterId, "Agent adapter id")
|
|
11
11
|
])).digest("hex");
|