@zq-silk/yui 0.13.9 → 0.14.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 +69 -428
- package/README.md +108 -298
- package/dist/agent/managedRuntimeEnvironment.js +2 -2
- package/dist/cli/commandCatalog.js +39 -77
- package/dist/cli/interactionCandidates.js +4 -4
- package/dist/cli/interactionPolicy.js +7 -10
- package/dist/cli/invocationRouter.js +1 -1
- package/dist/cli/updateCommand.js +6 -27
- package/dist/cli/updateOrchestrator.js +63 -484
- package/dist/cli/updatePorts.js +15 -333
- package/dist/cli/upgradeCommand.js +8 -65
- package/dist/cli.js +57 -242
- package/dist/commands/configCommands.js +11 -62
- package/dist/commands/executionAuditCommands.js +23 -23
- package/dist/commands/globalRoleCommands.js +2 -7
- package/dist/commands/jobCommands.js +1 -4
- package/dist/commands/projectCommands.js +5 -5
- package/dist/commands/releaseCommands.js +3 -10
- package/dist/commands/resourcesCommands.js +2 -2
- package/dist/commands/sessionCommands.js +2 -2
- package/dist/commands/taskActor.js +31 -39
- package/dist/commands/taskChangeSetCommands.js +5 -12
- package/dist/commands/taskCommands.js +464 -1249
- package/dist/commands/taskContextCommand.js +35 -36
- package/dist/commands/taskExecutionCommands.js +33 -46
- package/dist/commands/taskInputCommands.js +20 -42
- package/dist/commands/taskNextActionCommand.js +9 -9
- package/dist/commands/taskOverviewCommand.js +56 -95
- package/dist/commands/taskRoleRuntimeStatus.js +77 -107
- package/dist/commands/taskUpstreamCommands.js +3 -3
- package/dist/commands/taskWorkspaceCommands.js +7 -79
- package/dist/commands/telemetryCommands.js +32 -124
- package/dist/commands/workflowCommands.js +3 -3
- package/dist/completion/fileCompletionManager.js +1 -1
- package/dist/config/configCatalog.js +4 -6
- package/dist/config/yuiConfig.js +6 -43
- package/dist/context/dispatchContext.js +14 -30
- package/dist/context/roleSessionContext.js +2 -2
- package/dist/context/sessionBootstrapManifest.js +2 -2
- package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
- package/dist/context/turnInputContract.js +204 -0
- package/dist/context/wakeNotification.js +2 -2
- package/dist/controller/agentRuntimeObserver.js +18 -18
- package/dist/controller/clientRuntime.js +13 -20
- package/dist/controller/controller.js +89 -116
- package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
- package/dist/controller/jobControl.js +17 -17
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +38 -59
- package/dist/controller/resourceInventoryRpc.js +3 -3
- package/dist/controller/resourceInventoryWorker.js +1 -1
- package/dist/controller/runtime.js +46 -53
- package/dist/controller/runtimeEventInbox.js +23 -31
- package/dist/controller/runtimeEventProcessor.js +15 -15
- package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
- package/dist/controller/runtimeLaunchCoordinator.js +3 -3
- package/dist/controller/runtimeObservationHook.js +11 -8
- package/dist/controller/sessionNotify.js +11 -11
- package/dist/controller/structuredProviderObservation.js +89 -16
- package/dist/coordination/workMailbox.js +309 -579
- package/dist/coordination/workMailboxQueue.js +5 -6
- package/dist/core/controllerClient.js +1 -91
- package/dist/core/controllerServer.js +3 -6
- package/dist/doctor/doctor.js +51 -314
- package/dist/execution/candidateConvergence.js +3 -3
- package/dist/execution/executionGroup.js +32 -33
- package/dist/execution/executionHealth.js +36 -38
- package/dist/execution/resourceBroker.js +12 -12
- package/dist/executor/agentExecutor.js +30 -265
- package/dist/executor/effectiveLaunch.js +9 -19
- package/dist/executor/executorRegistry.js +43 -17
- package/dist/executor/fileRoleLaunchPlanner.js +56 -56
- package/dist/executor/launchPlan.js +3 -3
- package/dist/executor/workspacePreflightClassification.js +16 -16
- package/dist/grant/capabilityGrant.js +20 -25
- package/dist/input/inputRequest.js +12 -12
- package/dist/integration/changeSet.js +9 -13
- package/dist/integration/changeSetManifest.js +1 -1
- package/dist/integration/gitIntegrationService.js +2 -2
- package/dist/integration/integrationQueueService.js +5 -21
- package/dist/integration/overlapDiagnostics.js +2 -2
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/job/jobRunner.js +1 -1
- package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
- package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
- package/dist/message/message.js +6 -6
- package/dist/observability/executionAudit.js +74 -86
- package/dist/observability/faultClassification.js +7 -24
- package/dist/observability/orchestrationMetrics.js +5 -5
- package/dist/observability/runtimeIdentity.js +34 -101
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +3 -3
- package/dist/release/releaseWorkflowEngine.js +6 -10
- package/dist/release/releaseWorkflowPorts.js +2 -2
- package/dist/release/workflowFileLock.js +1 -1
- package/dist/repository/checkoutSwap.js +1 -1
- package/dist/repository/gitWorkspace.js +0 -13
- package/dist/repository/project.js +1 -1
- package/dist/repository/projectMaintenanceLock.js +1 -2
- package/dist/repository/taskBaseFreshness.js +13 -13
- package/dist/repository/taskWorkspaceCoordinator.js +21 -25
- package/dist/repository/taskWorkspaceIdentity.js +8 -21
- package/dist/repository/taskWorkspacePreparer.js +63 -620
- package/dist/resources/autoResourceGc.js +3 -3
- package/dist/resources/liveReferences.js +1 -1
- package/dist/resources/resourceGc.js +1 -1
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/reviewDecision.js +7 -7
- package/dist/review/reviewFindingLedger.js +1 -1
- package/dist/review/reviewOutcomeClassifier.js +35 -50
- package/dist/review/reviewRound.js +14 -25
- package/dist/review/reviewerAvailability.js +4 -4
- package/dist/review/taskFinalReviewContractResolution.js +41 -0
- package/dist/role/role.js +6 -3
- package/dist/runtime/agentDriver.js +4 -0
- package/dist/runtime/agentHost.js +151 -36
- package/dist/runtime/builtinAgentDrivers.js +79 -2
- package/dist/runtime/codexAppServerRuntime.js +122 -0
- package/dist/runtime/continuationManager.js +3 -5
- package/dist/runtime/exactControlPlane.js +17 -42
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/launchDiagnostics.js +2 -2
- package/dist/runtime/lifecycleReservation.js +4 -4
- package/dist/runtime/processExitObservation.js +1 -1
- package/dist/runtime/processExitOutbox.js +0 -32
- package/dist/runtime/promptEnvelope.js +9 -9
- package/dist/runtime/providerContinuation.js +2 -2
- package/dist/runtime/providerContinuationReconciliationService.js +2 -2
- package/dist/runtime/providerRuntimeIdentity.js +76 -24
- package/dist/runtime/recentTurnIds.js +38 -0
- package/dist/runtime/runtimeContinuationProjection.js +2 -2
- package/dist/runtime/runtimeHealthPolicy.js +1 -1
- package/dist/runtime/runtimeObservation.js +58 -21
- package/dist/runtime/runtimeProjection.js +19 -27
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/structuredProviderHost.js +177 -12
- package/dist/runtime/tmuxAdapters.js +46 -11
- package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
- package/dist/scheduler/leaderWakeupProcessor.js +147 -445
- package/dist/scheduler/operatorEvent.js +4 -6
- package/dist/scheduler/resourceQueueProjection.js +71 -0
- package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
- package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
- package/dist/scheduler/taskExecutionProjection.js +99 -99
- package/dist/scheduler/taskObservabilityProjection.js +10 -10
- package/dist/scheduler/taskWake.js +4 -4
- package/dist/scheduler/wakeReason.js +6 -10
- package/dist/scheduler/wakeupQueue.js +0 -9
- package/dist/setup/setupCommand.js +2 -2
- package/dist/storage/currentTaskStore.js +46 -0
- package/dist/storage/persistenceWorker.js +1 -1
- package/dist/storage/sqliteSchema.js +81 -485
- package/dist/storage/sqliteStore.js +206 -365
- package/dist/storage/storageSchema.js +1 -15
- package/dist/storage/storageVersions.js +8 -9
- package/dist/storage/storeRpc.js +10 -34
- package/dist/storage/taskStore.js +63 -3415
- package/dist/storage/upgrade/recordVersions.js +55 -149
- package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
- package/dist/task/completionReadiness.js +5 -5
- package/dist/task/deliveryGuard.js +10 -10
- package/dist/task/nextAction.js +74 -77
- package/dist/task/task.js +2 -4
- package/dist/task/taskRecordReference.js +3 -3
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/telemetry/sqliteTelemetryStore.js +53 -53
- package/dist/telemetry/telemetryConfig.js +14 -14
- package/dist/telemetry/telemetryWiring.js +8 -14
- package/dist/turn/turn.js +356 -0
- package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
- package/dist/verification/gateArtifactStore.js +2 -2
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/components.js +29 -54
- package/dist/web/assets/client/i18n.js +17 -37
- package/dist/web/assets/client/view.js +37 -37
- package/dist/web/assets/shell.js +1 -1
- package/dist/web/assets/styles/cards.js +5 -5
- package/dist/web/assets/styles/widgets.js +1 -2
- package/dist/web/webSnapshot.js +65 -60
- package/dist/workItem/workItem.js +11 -11
- package/dist/workspace/workItemChangeSetManager.js +1 -1
- package/docs/task-local-identity.md +5 -5
- package/i18n/README.zh-CN.md +41 -54
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +85 -83
- package/skills/yui-operator/SKILL.md +16 -16
- package/skills/yui-reviewer/SKILL.md +10 -10
- package/skills/yui-runtime/SKILL.md +17 -18
- package/skills/yui-worker/SKILL.md +16 -18
- package/dist/context/runContextContract.js +0 -162
- package/dist/executor/turnCompletion.js +0 -126
- package/dist/review/taskFinalReviewContractEvent.js +0 -1
- package/dist/review/taskFinalReviewContractRebind.js +0 -367
- package/dist/run/agentRun.js +0 -356
- package/dist/run/rejectedYieldAttempt.js +0 -221
- package/dist/run/runControlRequest.js +0 -50
- package/dist/run/yieldReceipt.js +0 -65
- package/dist/runtime/turnCompletion.js +0 -3
- package/dist/scheduler/actionability.js +0 -323
- package/dist/scheduler/activeRoleRunDelivery.js +0 -868
- package/dist/scheduler/activeTaskProgress.js +0 -126
- package/dist/storage/compatibleTaskStore.js +0 -248
- package/dist/storage/migration/baseline.js +0 -78
- package/dist/storage/migration/classifier.js +0 -51
- package/dist/storage/migration/compatibleCodec.js +0 -53
- package/dist/storage/migration/engine.js +0 -147
- package/dist/storage/migration/index.js +0 -33
- package/dist/storage/migration/planner.js +0 -154
- package/dist/storage/migration/productionRegistry.js +0 -3931
- package/dist/storage/migration/registry.js +0 -169
- package/dist/storage/migration/report.js +0 -54
- package/dist/storage/migration/types.js +0 -31
- package/dist/storage/upgrade/homeClassification.js +0 -353
- package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
- package/dist/storage/upgrade/migrationReceipt.js +0 -67
- package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
- package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
- package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
- package/dist/storage/upgrade/recordVersionScan.js +0 -176
- package/dist/storage/upgrade/sqliteFileSet.js +0 -50
- package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
- package/dist/storage/upgrade/sqliteStateMigration.js +0 -918
- package/dist/storage/upgrade/switchProgress.js +0 -80
- package/dist/storage/upgrade/upgradeReceipt.js +0 -161
- package/dist/storage/upgradeCoordination.js +0 -187
- package/dist/storage/upgradeFence.js +0 -366
- package/dist/telemetry/telemetryCompaction.js +0 -252
|
@@ -3,11 +3,9 @@ import { readFileSync, realpathSync } from "node:fs";
|
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { callController as defaultCallController } from "../core/controllerClient.js";
|
|
5
5
|
import { inspectStorageSchema } from "../storage/storageSchema.js";
|
|
6
|
-
import { openCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
|
|
7
6
|
import { yuiVersionIdentity } from "../version.js";
|
|
8
7
|
import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "./lifecycleReservation.js";
|
|
9
8
|
import { nativeSessionIdForLaunch } from "./preallocatedNativeSession.js";
|
|
10
|
-
import { agentRunDeliveryReceiptId } from "../run/agentRun.js";
|
|
11
9
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
12
10
|
export const EXACT_CONTROL_ARGUMENT = "--yui-control";
|
|
13
11
|
export const YUI_CONTROL_PLANE_DESCRIPTOR = "YUI_CONTROL_PLANE_DESCRIPTOR";
|
|
@@ -37,7 +35,7 @@ export function createExactTaskRuntimeDescriptor(input) {
|
|
|
37
35
|
agentId: requireIdentity(input.agentId, "Agent id"),
|
|
38
36
|
adapterId: requireAdapter(input.adapterId),
|
|
39
37
|
workspace: canonicalPath(input.workspace),
|
|
40
|
-
...(input.
|
|
38
|
+
...(input.turnId === undefined ? {} : { turnId: requireIdentity(input.turnId, "Turn id") }),
|
|
41
39
|
...(input.launchId === undefined
|
|
42
40
|
? {}
|
|
43
41
|
: { launchId: requireIdentity(input.launchId, "Launch id") }),
|
|
@@ -79,7 +77,7 @@ export function parseExactTaskRuntimeDescriptor(value) {
|
|
|
79
77
|
agentId: requireText(record.agentId, "Agent id"),
|
|
80
78
|
adapterId: requireAdapter(record.adapterId),
|
|
81
79
|
workspace: requireText(record.workspace, "Task runtime workspace"),
|
|
82
|
-
...(record.
|
|
80
|
+
...(record.turnId === undefined ? {} : { turnId: requireText(record.turnId, "Turn id") }),
|
|
83
81
|
...(record.launchId === undefined
|
|
84
82
|
? {}
|
|
85
83
|
: { launchId: requireText(record.launchId, "Launch id") }),
|
|
@@ -91,7 +89,7 @@ export function parseExactTaskRuntimeDescriptor(value) {
|
|
|
91
89
|
export function exactControlPlaneDigest(descriptor) {
|
|
92
90
|
return createHash("sha256").update(serializeExactDescriptor(descriptor)).digest("hex");
|
|
93
91
|
}
|
|
94
|
-
/** Stable per Task/Role/Agent location; volatile
|
|
92
|
+
/** Stable per Task/Role/Agent location; volatile Turn/launch/native fields are content. */
|
|
95
93
|
export function exactTaskRuntimeDescriptorPath(home, descriptor) {
|
|
96
94
|
const validated = createExactTaskRuntimeDescriptor(descriptor);
|
|
97
95
|
const digest = createHash("sha256").update(JSON.stringify([
|
|
@@ -153,15 +151,7 @@ export async function assertExactControlPlanePreflight(input, options = {}) {
|
|
|
153
151
|
assertContinuityIdentity("Local CLI", descriptor.identity, localIdentity);
|
|
154
152
|
const storage = (options.inspectStorage ?? inspectStorageSchema)(descriptor.yuiHome);
|
|
155
153
|
if (storage.status !== "current") {
|
|
156
|
-
|
|
157
|
-
&& storage.incompatibleComponent === "record"
|
|
158
|
-
&& storage.direction === "older"
|
|
159
|
-
&& storage.currentLayoutVersion === descriptor.identity.storageLayoutVersion
|
|
160
|
-
&& storage.currentAggregateSchemaVersion === descriptor.identity.aggregateSchemaVersion;
|
|
161
|
-
if (!exactRecordOnlyOlder) {
|
|
162
|
-
throw new Error(`Exact control-plane storage is not current: ${storage.status}.`);
|
|
163
|
-
}
|
|
164
|
-
(options.openCompatibleStore ?? openCompatibleFileTaskStore)(descriptor.yuiHome).getConfig();
|
|
154
|
+
throw new Error(`Exact control-plane storage is not current: ${storage.status}.`);
|
|
165
155
|
}
|
|
166
156
|
if (storage.currentLayoutVersion !== descriptor.identity.storageLayoutVersion) {
|
|
167
157
|
throw new Error("Exact control-plane storage layout does not match its frozen descriptor "
|
|
@@ -176,7 +166,7 @@ export async function assertExactControlPlanePreflight(input, options = {}) {
|
|
|
176
166
|
// A frozen descriptor authenticates the command that created it; it no
|
|
177
167
|
// longer pins the Home's deployment pointer for the lifetime of a Session.
|
|
178
168
|
// Continuity is the protocol/storage contract checked above and the durable
|
|
179
|
-
// Task/Role/
|
|
169
|
+
// Task/Role/Turn identity checked below. This lets a compatible Controller or
|
|
180
170
|
// active release advance without invalidating a still-current Session.
|
|
181
171
|
if (options.checkController !== false) {
|
|
182
172
|
const call = options.callController ?? defaultCallController;
|
|
@@ -202,15 +192,7 @@ export async function assertCompatibleControlPlanePreflight(input, options = {})
|
|
|
202
192
|
const identity = validateVersionIdentity(options.identity ?? yuiVersionIdentity());
|
|
203
193
|
const storage = (options.inspectStorage ?? inspectStorageSchema)(home);
|
|
204
194
|
if (storage.status !== "current") {
|
|
205
|
-
|
|
206
|
-
&& storage.incompatibleComponent === "record"
|
|
207
|
-
&& storage.direction === "older"
|
|
208
|
-
&& storage.currentLayoutVersion === identity.storageLayoutVersion
|
|
209
|
-
&& storage.currentAggregateSchemaVersion === identity.aggregateSchemaVersion;
|
|
210
|
-
if (!compatibleRecordOnlyOlder) {
|
|
211
|
-
throw new Error(`Managed control-plane storage is not current: ${storage.status}.`);
|
|
212
|
-
}
|
|
213
|
-
(options.openCompatibleStore ?? openCompatibleFileTaskStore)(home).getConfig();
|
|
195
|
+
throw new Error(`Managed control-plane storage is not current: ${storage.status}.`);
|
|
214
196
|
}
|
|
215
197
|
if (storage.currentLayoutVersion !== identity.storageLayoutVersion) {
|
|
216
198
|
throw new Error("Managed control-plane storage layout is incompatible "
|
|
@@ -257,9 +239,9 @@ export function assertExactTaskRuntimeEnvironment(runtimeSource, environment, ex
|
|
|
257
239
|
assertSamePath(runtime.workspace, requireText(environment.YUI_WORKSPACE, "YUI_WORKSPACE"), "Task runtime workspace");
|
|
258
240
|
// A reused native pane retains its original process environment. Volatile
|
|
259
241
|
// identity therefore comes only from the atomically published descriptor
|
|
260
|
-
// plus durable state, never from stale ambient
|
|
242
|
+
// plus durable state, never from stale ambient Turn/launch/native variables.
|
|
261
243
|
if (!resolved.fromFile) {
|
|
262
|
-
assertOptionalEnvironment(runtime.
|
|
244
|
+
assertOptionalEnvironment(runtime.turnId, environment.YUI_TURN_ID, "Turn id");
|
|
263
245
|
assertOptionalEnvironment(runtime.launchId, environment.YUI_LAUNCH_ID, "Launch id");
|
|
264
246
|
assertOptionalEnvironment(runtime.nativeSessionId, environment.YUI_NATIVE_SESSION_ID, "Native session id");
|
|
265
247
|
}
|
|
@@ -277,18 +259,18 @@ export function assertExactTaskRuntimeState(runtime, store, options = {}) {
|
|
|
277
259
|
if (role === null || role.activeAgentId !== runtime.agentId) {
|
|
278
260
|
throw new Error("Exact Task runtime Role or Agent is not current.");
|
|
279
261
|
}
|
|
280
|
-
const run = store.
|
|
281
|
-
if (runtime.
|
|
262
|
+
const run = store.getActiveTurn(runtime.taskId, runtime.roleName);
|
|
263
|
+
if (runtime.turnId === undefined) {
|
|
282
264
|
if (run !== null)
|
|
283
|
-
throw new Error("Exact Task runtime
|
|
265
|
+
throw new Error("Exact Task runtime Turn is not current.");
|
|
284
266
|
}
|
|
285
267
|
else if (run === null
|
|
286
|
-
|| run.id !== runtime.
|
|
268
|
+
|| run.id !== runtime.turnId
|
|
287
269
|
|| run.status !== "active"
|
|
288
270
|
|| run.effective.agentId !== runtime.agentId
|
|
289
271
|
|| run.effective.adapterId !== runtime.adapterId
|
|
290
272
|
|| canonicalPath(run.effective.workspace.root) !== runtime.workspace) {
|
|
291
|
-
throw new Error("Exact Task runtime
|
|
273
|
+
throw new Error("Exact Task runtime Turn is not current.");
|
|
292
274
|
}
|
|
293
275
|
const sessions = store.getTaskRoleSessionSet(runtime.taskId, runtime.roleName);
|
|
294
276
|
const session = sessions?.sessions[runtime.agentId];
|
|
@@ -317,13 +299,6 @@ export function assertExactTaskRuntimeState(runtime, store, options = {}) {
|
|
|
317
299
|
&& runtime.nativeSessionId !== undefined
|
|
318
300
|
&& (session === undefined || terminalSessionReplacementReservation)
|
|
319
301
|
&& runtime.nativeSessionId === nativeSessionIdForLaunch(preallocated.yuiHome, runtime.launchId, runtime.agentId, runtime.adapterId);
|
|
320
|
-
const preallocatedBeforeInFlightProjection = exactPreallocatedReservation
|
|
321
|
-
&& (sessions?.inFlight === null || sessions?.inFlight === undefined);
|
|
322
|
-
if (runtime.runId !== undefined && (sessions?.inFlight?.agentId !== runtime.agentId
|
|
323
|
-
|| sessions.inFlight.runId !== runtime.runId
|
|
324
|
-
|| sessions.inFlight.receiptId !== agentRunDeliveryReceiptId(run)) && !preallocatedBeforeInFlightProjection) {
|
|
325
|
-
throw new Error("Exact Task runtime in-flight Run fence is not current.");
|
|
326
|
-
}
|
|
327
302
|
if (runtime.launchId === undefined || (!reservation && !sessionLaunch)) {
|
|
328
303
|
throw new Error("Exact Task runtime launch fence is not current.");
|
|
329
304
|
}
|
|
@@ -372,10 +347,10 @@ export function refreshExactTaskRuntimeDescriptorSource(source, home, store) {
|
|
|
372
347
|
return refreshed;
|
|
373
348
|
}
|
|
374
349
|
/**
|
|
375
|
-
* Advances a reused Hook's own exact source to the current durable
|
|
350
|
+
* Advances a reused Hook's own exact source to the current durable Turn/launch
|
|
376
351
|
* generation before the volatile fence. The stable identity (control digest,
|
|
377
352
|
* Task, Role, Agent, adapter, workspace) and the native Session are preserved;
|
|
378
|
-
* only the volatile
|
|
353
|
+
* only the volatile Turn/launch fields advance. A source may never jump to a
|
|
379
354
|
* replacement native Session.
|
|
380
355
|
*/
|
|
381
356
|
export function refreshReusedTaskRuntimeDescriptorSource(source, home, store, current) {
|
|
@@ -385,7 +360,7 @@ export function refreshReusedTaskRuntimeDescriptorSource(source, home, store, cu
|
|
|
385
360
|
}
|
|
386
361
|
const previous = resolved.descriptor;
|
|
387
362
|
if (previous.nativeSessionId === undefined) {
|
|
388
|
-
if (previous.
|
|
363
|
+
if (previous.turnId !== current.turnId || previous.launchId !== current.launchId) {
|
|
389
364
|
throw new Error("Task runtime descriptor source cannot bind a native Session from another generation.");
|
|
390
365
|
}
|
|
391
366
|
return refreshExactTaskRuntimeDescriptorSource(source, home, store);
|
|
@@ -395,7 +370,7 @@ export function refreshReusedTaskRuntimeDescriptorSource(source, home, store, cu
|
|
|
395
370
|
}
|
|
396
371
|
const refreshed = createExactTaskRuntimeDescriptor({
|
|
397
372
|
...previous,
|
|
398
|
-
|
|
373
|
+
turnId: current.turnId,
|
|
399
374
|
launchId: current.launchId,
|
|
400
375
|
nativeSessionId: current.nativeSessionId
|
|
401
376
|
});
|
|
@@ -12,7 +12,7 @@ export function projectFirstProgressAdvisory(input) {
|
|
|
12
12
|
? []
|
|
13
13
|
: [
|
|
14
14
|
...input.events
|
|
15
|
-
.filter((event) => typeof event.payload.
|
|
15
|
+
.filter((event) => typeof event.payload.leaderTurnId === "string")
|
|
16
16
|
.map((event) => ({ at: event.createdAt, ref: `event:${event.id}` })),
|
|
17
17
|
...input.workItems
|
|
18
18
|
.filter((item) => item.status !== "retired")
|
package/dist/runtime/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { projectSessionTokenMetrics, resolveSessionTokenIdentity, unobservedSess
|
|
|
4
4
|
export { createRuntimeBinding } from "./runtimeBinding.js";
|
|
5
5
|
export { normalizeRuntimeOwner } from "./runtimeOwner.js";
|
|
6
6
|
export { createSessionLaunchRequest } from "./sessionLaunchRequest.js";
|
|
7
|
-
export {
|
|
7
|
+
export { DEFAULT_RECENT_TURN_ID_LIMIT, hasRecentTurnId, rememberRecentTurnId, validateRecentTurnIds } from "./recentTurnIds.js";
|
|
8
8
|
export { 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";
|
|
@@ -14,7 +14,7 @@ export { formatRuntimeLaunchDiagnostic, redactLaunchArgument, redactLaunchText,
|
|
|
14
14
|
export { DEFAULT_FORCED_GRACE_MS, DEFAULT_GRACEFUL_GRACE_MS, terminateSessionOwners } from "./sessionTerminationGuard.js";
|
|
15
15
|
export { ProviderContinuationReconciliationService } from "./providerContinuationReconciliationService.js";
|
|
16
16
|
export { codexNotificationBoundary, codexAppServerErrorIsMissing, CodexAppServerRequestError, CodexAppServerRuntime } from "./codexAppServerRuntime.js";
|
|
17
|
-
export { createProviderRuntimeBinding, acceptProviderTurn, beginProviderTurn, currentProviderActivation, currentProviderAuthority, currentProviderConversation, endProviderActivation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurnSubmission, settleProviderTurn, startProviderActivation, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, validateProviderRuntimeBinding } from "./providerRuntimeIdentity.js";
|
|
17
|
+
export { createProviderRuntimeBinding, acceptProviderTurn, beginProviderTurn, currentProviderActivation, currentProviderAuthority, currentProviderConversation, endProviderActivation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurnSubmission, settleProviderTurn, managedProviderTurnId, startProviderActivation, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, validateProviderRuntimeBinding } from "./providerRuntimeIdentity.js";
|
|
18
18
|
export { FencedProviderControl } from "./providerControl.js";
|
|
19
19
|
export { sameProviderAuthorityFence, validateProviderAuthorityFence } from "./providerAuthorityFence.js";
|
|
20
20
|
export { reconcileSessionOwners } from "./sessionReconciliation.js";
|
|
@@ -22,7 +22,7 @@ const MAX_DETAIL_CHARS = 1_000;
|
|
|
22
22
|
const SECRET_VALUE_PATTERN = /(api[_-]?key|access[_-]?token|refresh[_-]?token|secret|password|passwd|cookie|authorization)(\s*[=:]\s*)([^\s,;]+)/gi;
|
|
23
23
|
// Word boundary keeps "task-5-…" workspace paths from being mistaken for keys.
|
|
24
24
|
const OPENAI_KEY_PATTERN = /\bsk-[A-Za-z0-9_-]{6,}/gu;
|
|
25
|
-
/** A bounded, single-line, secret-redacted launch failure for
|
|
25
|
+
/** A bounded, single-line, secret-redacted launch failure for Turn summaries. */
|
|
26
26
|
export class RuntimeLaunchFailure extends Error {
|
|
27
27
|
diagnostic;
|
|
28
28
|
name = "RuntimeLaunchFailure";
|
|
@@ -84,7 +84,7 @@ export function formatRuntimeLaunchDiagnostic(diagnostic) {
|
|
|
84
84
|
}
|
|
85
85
|
if (diagnostic.hint !== undefined)
|
|
86
86
|
fields.push(`hint=${JSON.stringify(diagnostic.hint)}`);
|
|
87
|
-
return `Role
|
|
87
|
+
return `Role Turn could not start: ${fields.join(" ")}`;
|
|
88
88
|
}
|
|
89
89
|
export function redactLaunchArgument(value) {
|
|
90
90
|
return tail(redactLaunchText(value), MAX_ARGUMENT_CHARS);
|
|
@@ -6,9 +6,9 @@ export const RUNTIME_HOST_DETACH_REQUIRED_REASON = "runtime-host-detach-required
|
|
|
6
6
|
/**
|
|
7
7
|
* A Role runtime lifecycle lane already holds an in-flight operation (a
|
|
8
8
|
* launch reservation or a cleanup obligation). This is scheduler
|
|
9
|
-
* backpressure: the equivalent wake/
|
|
10
|
-
* settles. It is never grounds to terminalize a
|
|
11
|
-
* happens before (or outside) any semantic
|
|
9
|
+
* backpressure: the equivalent wake/Turn must be retried after the lane
|
|
10
|
+
* settles. It is never grounds to terminalize a Turn, because the contention
|
|
11
|
+
* happens before (or outside) any semantic Turn launch.
|
|
12
12
|
*/
|
|
13
13
|
export class RuntimeLifecycleBusyError extends Error {
|
|
14
14
|
name = "RuntimeLifecycleBusyError";
|
|
@@ -48,7 +48,7 @@ export function hasRuntimeCleanupObligation(mailbox) {
|
|
|
48
48
|
/** Explicit Session end dominates a coalesced physical Host detach request. */
|
|
49
49
|
export function runtimeCleanupDisposition(mailbox) {
|
|
50
50
|
const reasons = [
|
|
51
|
-
...(mailbox?.pending
|
|
51
|
+
...(mailbox?.pending?.reasons ?? []),
|
|
52
52
|
...(!isRuntimeLaunchReservation(mailbox?.processing)
|
|
53
53
|
? mailbox?.processing?.batch.reasons ?? []
|
|
54
54
|
: [])
|
|
@@ -8,7 +8,7 @@ export function validateRuntimeProcessExitObservation(observation) {
|
|
|
8
8
|
optionalIdentity(observation.providerProcessInstanceId, "providerProcessInstanceId");
|
|
9
9
|
optionalIdentity(observation.taskId, "taskId");
|
|
10
10
|
identity(observation.roleName, "roleName");
|
|
11
|
-
optionalIdentity(observation.
|
|
11
|
+
optionalIdentity(observation.turnId, "turnId");
|
|
12
12
|
identity(observation.launchId, "launchId");
|
|
13
13
|
optionalIdentity(observation.nativeSessionId, "nativeSessionId");
|
|
14
14
|
if (observation.processKind !== "agent-host" && observation.processKind !== "provider-child") {
|
|
@@ -50,39 +50,7 @@ export async function replayRuntimeProcessExitOutbox(home, submit) {
|
|
|
50
50
|
throw error;
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
|
-
// One-generation bridge for observations written by an Agent Host that was
|
|
54
|
-
// already running before this release. New writers use one immutable file
|
|
55
|
-
// per observation above, so they never share an append/unlink boundary.
|
|
56
|
-
for (const name of entries.filter(isLegacyOutboxEntry).sort()) {
|
|
57
|
-
const path = join(directory, name);
|
|
58
|
-
let raw;
|
|
59
|
-
try {
|
|
60
|
-
raw = await readFile(path, "utf8");
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
// The old writer removes its own append log only after the Controller
|
|
64
|
-
// acknowledges delivery. If it won that race, there is nothing to replay.
|
|
65
|
-
if (error.code === "ENOENT")
|
|
66
|
-
continue;
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
69
|
-
// Only consume newline-terminated records. A legacy writer can be between
|
|
70
|
-
// append bytes and its trailing newline while this snapshot is read.
|
|
71
|
-
const completeLength = raw.lastIndexOf("\n") + 1;
|
|
72
|
-
const lines = raw.slice(0, completeLength).split("\n").filter(Boolean);
|
|
73
|
-
for (const line of lines) {
|
|
74
|
-
await submit(validateRuntimeProcessExitObservation(JSON.parse(line)));
|
|
75
|
-
}
|
|
76
|
-
// Deliberately leave the legacy path in place. A pre-upgrade Agent Host may
|
|
77
|
-
// already hold an append descriptor for this inode; renaming/unlinking it
|
|
78
|
-
// would let a later append disappear into an unlinked file. The old writer
|
|
79
|
-
// removes the path after a successful direct submission. Otherwise the
|
|
80
|
-
// durable file remains for the next idempotent replay.
|
|
81
|
-
}
|
|
82
53
|
}
|
|
83
54
|
function outboxDirectory(home) {
|
|
84
55
|
return resolve(join(home, "runtime", "agent-host-outbox"));
|
|
85
56
|
}
|
|
86
|
-
function isLegacyOutboxEntry(name) {
|
|
87
|
-
return name.endsWith(".jsonl");
|
|
88
|
-
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { requireText, requireTimestamp } from "./validation.js";
|
|
2
|
-
import {
|
|
2
|
+
import { formatTurnReceiptId, formatInputRequestReceiptId, validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
3
3
|
export function createPromptEnvelope(input) {
|
|
4
|
-
if (input.source.kind !== "
|
|
5
|
-
&& input.source.kind !== "
|
|
4
|
+
if (input.source.kind !== "turn"
|
|
5
|
+
&& input.source.kind !== "turn-input"
|
|
6
6
|
&& input.source.kind !== "input-request") {
|
|
7
7
|
throw new Error("Prompt source kind is invalid.");
|
|
8
8
|
}
|
|
9
9
|
const source = validateTaskRecordReference({
|
|
10
10
|
taskId: input.source.taskId,
|
|
11
11
|
localId: input.source.localId
|
|
12
|
-
}, input.source.kind === "input-request" ? "inputRequest" : "
|
|
12
|
+
}, input.source.kind === "input-request" ? "inputRequest" : "turn");
|
|
13
13
|
const id = requireQualifiedReceiptId(input.id, input.source.kind, source.taskId, source.localId);
|
|
14
14
|
return {
|
|
15
15
|
id,
|
|
@@ -23,14 +23,14 @@ export function createPromptEnvelope(input) {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
function requireQualifiedReceiptId(value, kind, taskId, localId) {
|
|
26
|
-
const expected = kind === "
|
|
27
|
-
?
|
|
26
|
+
const expected = kind === "turn"
|
|
27
|
+
? formatTurnReceiptId(taskId, localId)
|
|
28
28
|
: kind === "input-request"
|
|
29
29
|
? formatInputRequestReceiptId(taskId, localId)
|
|
30
|
-
: `
|
|
31
|
-
if (kind === "
|
|
30
|
+
: `turn-input:${taskId}/${localId}/`;
|
|
31
|
+
if (kind === "turn-input") {
|
|
32
32
|
if (!value.startsWith(expected)
|
|
33
|
-
|| !/^
|
|
33
|
+
|| !/^[1-9]\d*$/.test(value.slice(expected.length))) {
|
|
34
34
|
throw new Error("Prompt envelope id does not match its source.");
|
|
35
35
|
}
|
|
36
36
|
return value;
|
|
@@ -14,7 +14,7 @@ export function createProviderContinuation(input) {
|
|
|
14
14
|
schemaVersion: 1,
|
|
15
15
|
taskId: text(input.taskId, "Task id"),
|
|
16
16
|
roleName: text(input.roleName, "Role name"),
|
|
17
|
-
|
|
17
|
+
turnId: text(input.turnId, "Turn id"),
|
|
18
18
|
identity: validateProviderContinuationIdentity(input.identity),
|
|
19
19
|
...(input.parentContinuationId === undefined
|
|
20
20
|
? {}
|
|
@@ -152,7 +152,7 @@ export function validateProviderContinuation(value) {
|
|
|
152
152
|
throw new Error("Provider Continuation schemaVersion must be 1.");
|
|
153
153
|
text(value.taskId, "Task id");
|
|
154
154
|
text(value.roleName, "Role name");
|
|
155
|
-
text(value.
|
|
155
|
+
text(value.turnId, "Turn id");
|
|
156
156
|
validateProviderContinuationIdentity(value.identity);
|
|
157
157
|
if (!["active", "quiescent", "unknown"].includes(value.execution)) {
|
|
158
158
|
throw new Error("Provider Continuation execution is invalid.");
|
|
@@ -100,7 +100,7 @@ function groupDetachedContinuations(continuations, observations) {
|
|
|
100
100
|
&& observation.fence.continuationId === continuation.identity.continuationId
|
|
101
101
|
&& observation.fence.continuationGeneration === continuation.identity.generation));
|
|
102
102
|
// A projected continuation without its original durable fence cannot be
|
|
103
|
-
// safely attached to a live
|
|
103
|
+
// safely attached to a live Turn. Keep ownership conservative and let the
|
|
104
104
|
// malformed identity remain visible instead of synthesizing a receipt.
|
|
105
105
|
if (source === undefined)
|
|
106
106
|
continue;
|
|
@@ -149,7 +149,7 @@ function reconciliationObservation(continuation, sourceFence, now) {
|
|
|
149
149
|
...sourceFence,
|
|
150
150
|
taskId: continuation.taskId,
|
|
151
151
|
roleName: continuation.roleName,
|
|
152
|
-
|
|
152
|
+
turnId: continuation.turnId,
|
|
153
153
|
agentId: continuation.identity.accountScope,
|
|
154
154
|
driverId: continuation.identity.providerNamespace,
|
|
155
155
|
conversationId: continuation.identity.conversationId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export function createProviderRuntimeBinding(input) {
|
|
2
2
|
const startedAt = timestamp(input.startedAt, "Provider Activation startedAt");
|
|
3
3
|
return validateProviderRuntimeBinding({
|
|
4
|
-
schemaVersion:
|
|
4
|
+
schemaVersion: 5,
|
|
5
5
|
providerNamespace: identity(input.providerNamespace, "Provider namespace"),
|
|
6
6
|
accountScope: identity(input.accountScope, "Provider account scope"),
|
|
7
7
|
currentConversationEpoch: 1,
|
|
@@ -25,9 +25,25 @@ export function createProviderRuntimeBinding(input) {
|
|
|
25
25
|
holderId: input.activationId,
|
|
26
26
|
changedAt: startedAt
|
|
27
27
|
},
|
|
28
|
-
turn: null
|
|
28
|
+
turn: null,
|
|
29
|
+
goal: null
|
|
29
30
|
});
|
|
30
31
|
}
|
|
32
|
+
export function updateProviderGoal(raw, goal) {
|
|
33
|
+
const binding = validateProviderRuntimeBinding(raw);
|
|
34
|
+
const normalized = validateProviderGoal(goal);
|
|
35
|
+
return validateProviderRuntimeBinding({ ...binding, goal: normalized });
|
|
36
|
+
}
|
|
37
|
+
export function clearProviderGoal(raw) {
|
|
38
|
+
const binding = validateProviderRuntimeBinding(raw);
|
|
39
|
+
return binding.goal === null
|
|
40
|
+
? binding
|
|
41
|
+
: validateProviderRuntimeBinding({ ...binding, goal: null });
|
|
42
|
+
}
|
|
43
|
+
/** Only active means the Provider is expected to continue autonomously. */
|
|
44
|
+
export function providerGoalContinues(goal) {
|
|
45
|
+
return goal?.status === "active";
|
|
46
|
+
}
|
|
31
47
|
export function currentProviderAuthority(binding) {
|
|
32
48
|
return validateProviderRuntimeBinding(binding).authority;
|
|
33
49
|
}
|
|
@@ -146,12 +162,14 @@ export function transferProviderAuthority(raw, input) {
|
|
|
146
162
|
}
|
|
147
163
|
export function beginProviderTurn(raw, input) {
|
|
148
164
|
const binding = validateProviderRuntimeBinding(raw);
|
|
149
|
-
const runId = identity(input.runId, "Run id");
|
|
150
165
|
const attemptId = identity(input.attemptId, "Provider input attempt id");
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
&&
|
|
166
|
+
const turnId = input.turnId === undefined ? undefined : identity(input.turnId, "Turn id");
|
|
167
|
+
const currentTurn = binding.turn;
|
|
168
|
+
if (currentTurn !== null
|
|
169
|
+
&& currentTurn.turnId === turnId
|
|
170
|
+
&& currentTurn.attemptId === attemptId
|
|
171
|
+
&& currentTurn.authorityEpoch === input.authorityEpoch
|
|
172
|
+
&& currentTurn.status === "submitting") {
|
|
155
173
|
return binding;
|
|
156
174
|
}
|
|
157
175
|
if (binding.authority.epoch !== input.authorityEpoch
|
|
@@ -166,7 +184,7 @@ export function beginProviderTurn(raw, input) {
|
|
|
166
184
|
return validateProviderRuntimeBinding({
|
|
167
185
|
...binding,
|
|
168
186
|
turn: {
|
|
169
|
-
|
|
187
|
+
...(turnId === undefined ? {} : { turnId }),
|
|
170
188
|
attemptId,
|
|
171
189
|
authorityEpoch: input.authorityEpoch,
|
|
172
190
|
status: "submitting",
|
|
@@ -189,7 +207,7 @@ export function acceptProviderTurn(raw, input) {
|
|
|
189
207
|
turn: {
|
|
190
208
|
...turn,
|
|
191
209
|
status: "accepted",
|
|
192
|
-
|
|
210
|
+
nativeTurnId: identity(input.nativeTurnId, "Provider native Turn id"),
|
|
193
211
|
updatedAt: acceptedAt
|
|
194
212
|
}
|
|
195
213
|
});
|
|
@@ -255,9 +273,9 @@ export function settleProviderTurnSubmission(raw, input) {
|
|
|
255
273
|
export function settleProviderTurn(raw, input) {
|
|
256
274
|
const binding = validateProviderRuntimeBinding(raw);
|
|
257
275
|
const turn = binding.turn;
|
|
258
|
-
const
|
|
259
|
-
if (turn === null || turn.
|
|
260
|
-
||
|
|
276
|
+
const nativeTurnId = identity(input.nativeTurnId, "Provider native Turn id");
|
|
277
|
+
if (turn === null || turn.nativeTurnId !== nativeTurnId
|
|
278
|
+
|| turn.status !== "accepted") {
|
|
261
279
|
throw new Error("Provider Turn settlement does not match the current Turn.");
|
|
262
280
|
}
|
|
263
281
|
const settledAt = orderedTurnTimestamp(turn, input.settledAt, "Provider Turn settledAt");
|
|
@@ -302,10 +320,10 @@ export function supersedeProviderConversation(raw, input) {
|
|
|
302
320
|
}
|
|
303
321
|
: entry);
|
|
304
322
|
const turn = binding.turn !== null
|
|
305
|
-
&& ["submitting", "accepted", "
|
|
323
|
+
&& ["submitting", "accepted", "delivery-unknown"].includes(binding.turn.status)
|
|
306
324
|
? {
|
|
307
325
|
...binding.turn,
|
|
308
|
-
status: binding.turn.
|
|
326
|
+
status: binding.turn.nativeTurnId === undefined ? "rejected" : "failed",
|
|
309
327
|
updatedAt: switchedAt,
|
|
310
328
|
terminalReason
|
|
311
329
|
}
|
|
@@ -338,12 +356,13 @@ export function supersedeProviderConversation(raw, input) {
|
|
|
338
356
|
holderId: input.activationId,
|
|
339
357
|
changedAt: switchedAt
|
|
340
358
|
},
|
|
341
|
-
turn
|
|
359
|
+
turn,
|
|
360
|
+
goal: null
|
|
342
361
|
});
|
|
343
362
|
}
|
|
344
363
|
export function validateProviderRuntimeBinding(value) {
|
|
345
|
-
if (value.schemaVersion !==
|
|
346
|
-
throw new Error("Provider Runtime Binding schemaVersion must be
|
|
364
|
+
if (value.schemaVersion !== 5)
|
|
365
|
+
throw new Error("Provider Runtime Binding schemaVersion must be 5.");
|
|
347
366
|
identity(value.providerNamespace, "Provider namespace");
|
|
348
367
|
identity(value.accountScope, "Provider account scope");
|
|
349
368
|
integer(value.currentConversationEpoch, 1, "Current Provider Conversation epoch");
|
|
@@ -447,16 +466,45 @@ export function validateProviderRuntimeBinding(value) {
|
|
|
447
466
|
throw new Error("Provider Runtime Binding requires Turn state.");
|
|
448
467
|
if (value.turn !== null)
|
|
449
468
|
validateProviderTurn(value.turn, value.authority.epoch);
|
|
469
|
+
if (!Object.hasOwn(value, "goal"))
|
|
470
|
+
throw new Error("Provider Runtime Binding requires Goal state.");
|
|
471
|
+
if (value.goal !== null)
|
|
472
|
+
validateProviderGoal(value.goal);
|
|
450
473
|
return value;
|
|
451
474
|
}
|
|
475
|
+
function validateProviderGoal(goal) {
|
|
476
|
+
if (![
|
|
477
|
+
"active",
|
|
478
|
+
"paused",
|
|
479
|
+
"blocked",
|
|
480
|
+
"usage-limited",
|
|
481
|
+
"budget-limited",
|
|
482
|
+
"complete"
|
|
483
|
+
].includes(goal.status)) {
|
|
484
|
+
throw new Error("Provider Goal status is invalid.");
|
|
485
|
+
}
|
|
486
|
+
const normalized = {
|
|
487
|
+
status: goal.status,
|
|
488
|
+
objective: identity(goal.objective, "Provider Goal objective"),
|
|
489
|
+
updatedAt: timestamp(goal.updatedAt, "Provider Goal updatedAt"),
|
|
490
|
+
...(goal.nativeTurnId === undefined
|
|
491
|
+
? {}
|
|
492
|
+
: { nativeTurnId: identity(goal.nativeTurnId, "Provider Goal native Turn id") }),
|
|
493
|
+
...(goal.tokenBudget === undefined
|
|
494
|
+
? {}
|
|
495
|
+
: { tokenBudget: integer(goal.tokenBudget, 1, "Provider Goal token budget") })
|
|
496
|
+
};
|
|
497
|
+
return Object.freeze(normalized);
|
|
498
|
+
}
|
|
452
499
|
function validateProviderTurn(turn, currentAuthorityEpoch) {
|
|
453
|
-
|
|
500
|
+
if (turn.turnId !== undefined)
|
|
501
|
+
identity(turn.turnId, "Turn id");
|
|
454
502
|
identity(turn.attemptId, "Provider input attempt id");
|
|
455
503
|
integer(turn.authorityEpoch, 1, "Provider Turn authority epoch");
|
|
456
504
|
if (turn.authorityEpoch > currentAuthorityEpoch) {
|
|
457
505
|
throw new Error("Provider Turn authority epoch is ahead of current authority.");
|
|
458
506
|
}
|
|
459
|
-
if (!["submitting", "accepted", "
|
|
507
|
+
if (!["submitting", "accepted", "completed", "failed", "cancelled", "rejected", "delivery-unknown"]
|
|
460
508
|
.includes(turn.status)) {
|
|
461
509
|
throw new Error("Provider Turn status is invalid.");
|
|
462
510
|
}
|
|
@@ -465,12 +513,16 @@ function validateProviderTurn(turn, currentAuthorityEpoch) {
|
|
|
465
513
|
if (Date.parse(turn.updatedAt) < Date.parse(turn.submittedAt)) {
|
|
466
514
|
throw new Error("Provider Turn updatedAt is earlier than submittedAt.");
|
|
467
515
|
}
|
|
468
|
-
const hasAcceptedIdentity = turn.status === "accepted"
|
|
516
|
+
const hasAcceptedIdentity = turn.status === "accepted"
|
|
469
517
|
|| turn.status === "completed" || turn.status === "failed" || turn.status === "cancelled";
|
|
470
518
|
if (hasAcceptedIdentity)
|
|
471
|
-
identity(turn.
|
|
472
|
-
else if (turn.
|
|
473
|
-
throw new Error("Unaccepted Provider Turn cannot have a Turn id.");
|
|
519
|
+
identity(turn.nativeTurnId, "Provider native Turn id");
|
|
520
|
+
else if (turn.nativeTurnId !== undefined) {
|
|
521
|
+
throw new Error("Unaccepted Provider Turn cannot have a native Turn id.");
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
export function managedProviderTurnId(turn) {
|
|
525
|
+
return turn?.turnId ?? null;
|
|
474
526
|
}
|
|
475
527
|
function requireProviderTurn(binding, attemptId, status) {
|
|
476
528
|
const id = identity(attemptId, "Provider input attempt id");
|
|
@@ -487,7 +539,7 @@ function orderedTurnTimestamp(turn, value, label) {
|
|
|
487
539
|
return normalized;
|
|
488
540
|
}
|
|
489
541
|
function providerTurnIsActive(turn) {
|
|
490
|
-
return turn !== null && ["submitting", "accepted", "
|
|
542
|
+
return turn !== null && ["submitting", "accepted", "delivery-unknown"]
|
|
491
543
|
.includes(turn.status);
|
|
492
544
|
}
|
|
493
545
|
function currentActivationUnchecked(binding) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { requireText } from "./validation.js";
|
|
2
|
+
export const DEFAULT_RECENT_TURN_ID_LIMIT = 32;
|
|
3
|
+
export function validateRecentTurnIds(value, limit = DEFAULT_RECENT_TURN_ID_LIMIT) {
|
|
4
|
+
const normalizedLimit = requireLimit(limit);
|
|
5
|
+
if (!Array.isArray(value))
|
|
6
|
+
throw new Error("Recent Turn ids must be an array.");
|
|
7
|
+
if (value.length > normalizedLimit) {
|
|
8
|
+
throw new Error(`Recent Turn ids must not contain more than ${normalizedLimit} entries.`);
|
|
9
|
+
}
|
|
10
|
+
const result = value.map((turnId) => requireTextValue(turnId, "Recent Turn id"));
|
|
11
|
+
if (new Set(result).size !== result.length) {
|
|
12
|
+
throw new Error("Recent Turn ids must not contain duplicates.");
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
export function rememberRecentTurnId(recentTurnIds, turnId, limit = DEFAULT_RECENT_TURN_ID_LIMIT) {
|
|
17
|
+
const normalizedLimit = requireLimit(limit);
|
|
18
|
+
const existing = validateRecentTurnIds(recentTurnIds, normalizedLimit);
|
|
19
|
+
const normalizedTurnId = requireText(turnId, "Recent Turn id");
|
|
20
|
+
return [
|
|
21
|
+
...existing.filter((candidate) => candidate !== normalizedTurnId),
|
|
22
|
+
normalizedTurnId
|
|
23
|
+
].slice(-normalizedLimit);
|
|
24
|
+
}
|
|
25
|
+
export function hasRecentTurnId(recentTurnIds, turnId) {
|
|
26
|
+
return validateRecentTurnIds(recentTurnIds).includes(requireText(turnId, "Recent Turn id"));
|
|
27
|
+
}
|
|
28
|
+
function requireTextValue(value, label) {
|
|
29
|
+
if (typeof value !== "string")
|
|
30
|
+
throw new Error(`${label} is invalid.`);
|
|
31
|
+
return requireText(value, label);
|
|
32
|
+
}
|
|
33
|
+
function requireLimit(value) {
|
|
34
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
35
|
+
throw new Error("Recent Turn id limit must be a positive integer.");
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
@@ -32,10 +32,10 @@ export function projectProviderContinuations(events) {
|
|
|
32
32
|
export function blockingProviderContinuations(events) {
|
|
33
33
|
return projectProviderContinuations(events).filter((entry) => (continuationOwnsWriterUmbrella(entry) || entry.identityConflict));
|
|
34
34
|
}
|
|
35
|
-
/** Exact
|
|
35
|
+
/** Exact Turn-scoped writer fence shared by terminalization and retry paths. */
|
|
36
36
|
export function runOwnsBlockingProviderContinuation(events, owner) {
|
|
37
37
|
return blockingProviderContinuations(events).some((continuation) => (continuation.taskId === owner.taskId
|
|
38
38
|
&& continuation.roleName === owner.roleName
|
|
39
|
-
&& continuation.
|
|
39
|
+
&& continuation.turnId === owner.turnId
|
|
40
40
|
&& continuation.identity.accountScope === owner.agentId));
|
|
41
41
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* The layers are deliberately time-based and conservative: short silence is
|
|
6
6
|
* normal for high-reasoning-effort turns, large reviews, and tool waits.
|
|
7
7
|
* Time-based layers authorize display hints and coalesced read-only diagnosis
|
|
8
|
-
* only. They never reset a
|
|
8
|
+
* only. They never reset a Turn or replace a Provider Session.
|
|
9
9
|
*/
|
|
10
10
|
/** Runtime silence after which a live turn is surfaced as "quiet" (hint only). */
|
|
11
11
|
export const RUNTIME_QUIET_AFTER_MS = 5 * 60_000;
|