@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
|
@@ -97,7 +97,7 @@ export class FileRoleLaunchPlanner {
|
|
|
97
97
|
|| role === null) {
|
|
98
98
|
throw new Error(`Task runtime is not current: ${input.taskId}/${input.roleName}.`);
|
|
99
99
|
}
|
|
100
|
-
const run = this.store.
|
|
100
|
+
const run = this.store.getActiveTurn(input.taskId, input.roleName);
|
|
101
101
|
const sessions = this.store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
102
102
|
const session = sessions?.sessions[role.activeAgentId];
|
|
103
103
|
if (session === undefined || session.launchId === undefined) {
|
|
@@ -111,11 +111,11 @@ export class FileRoleLaunchPlanner {
|
|
|
111
111
|
agentId: session.agentId,
|
|
112
112
|
adapterId: session.adapterId,
|
|
113
113
|
workspace: effective.workspace.root,
|
|
114
|
-
...(run === null ? {} : {
|
|
114
|
+
...(run === null ? {} : { turnId: run.id }),
|
|
115
115
|
launchId: session.launchId,
|
|
116
116
|
nativeSessionId: session.nativeSessionId
|
|
117
117
|
});
|
|
118
|
-
if (descriptor.
|
|
118
|
+
if (descriptor.turnId !== input.turnId
|
|
119
119
|
|| descriptor.launchId !== input.launchId
|
|
120
120
|
|| descriptor.nativeSessionId !== input.nativeSessionId
|
|
121
121
|
|| descriptor.agentId !== input.agentId
|
|
@@ -140,22 +140,22 @@ export class FileRoleLaunchPlanner {
|
|
|
140
140
|
const role = this.store.getRole(input.taskId, input.roleName);
|
|
141
141
|
if (role === null)
|
|
142
142
|
throw new Error(`Role not found: ${input.taskId}/${input.roleName}.`);
|
|
143
|
-
const
|
|
144
|
-
if (input.
|
|
145
|
-
throw new Error(`Role
|
|
143
|
+
const activeTurn = this.store.getActiveTurn(task.id, role.name);
|
|
144
|
+
if (input.turnId !== undefined && activeTurn?.id !== input.turnId) {
|
|
145
|
+
throw new Error(`Role Turn is no longer current: ${input.turnId}.`);
|
|
146
146
|
}
|
|
147
|
-
const runWorkspace =
|
|
147
|
+
const runWorkspace = activeTurn?.workspace;
|
|
148
148
|
const main = this.store.getTaskWorkspace(task.id);
|
|
149
149
|
// Quick Win (EXE-04/EXE-08): classify workspace preflight failures so
|
|
150
150
|
// split-brain state is never reported as a transient Provider failure.
|
|
151
|
-
const preflight = classifyWorkspacePreflight(this.store, task, input.roleName,
|
|
151
|
+
const preflight = classifyWorkspacePreflight(this.store, task, input.roleName, activeTurn === null ? null : { id: activeTurn.id, workspace: activeTurn.workspace }, this.#inspectWorkspacePhysicalState);
|
|
152
152
|
if (preflight !== null) {
|
|
153
153
|
throw new Error(formatWorkspacePreflightError(preflight));
|
|
154
154
|
}
|
|
155
155
|
const assignedWorkItem = this.store.listWorkItems(task.id).find((item) => item.assignee === role.name
|
|
156
156
|
&& !["completed", "failed", "retired"].includes(item.status));
|
|
157
|
-
// A
|
|
158
|
-
// a
|
|
157
|
+
// A Turn snapshot is authoritative for the live launch. In particular,
|
|
158
|
+
// a Reviewer Turn must launch from its ReviewRound-owned workspace rather
|
|
159
159
|
// than falling back to the WorkItem Develop workspace. Without an
|
|
160
160
|
// active snapshot, resolve the normal Role/WorkItem assignment.
|
|
161
161
|
const workspace = runWorkspace !== undefined
|
|
@@ -183,8 +183,8 @@ export class FileRoleLaunchPlanner {
|
|
|
183
183
|
&& isolatedWorkItem !== null
|
|
184
184
|
&& (isolatedWorkItem.assignee === undefined || isolatedWorkItem.assignee === role.name)
|
|
185
185
|
&& !["completed", "failed", "retired"].includes(isolatedWorkItem.status)
|
|
186
|
-
&& (
|
|
187
|
-
||
|
|
186
|
+
&& (activeTurn === null
|
|
187
|
+
|| activeTurn.workItemId === workspace.owner.workItemId)
|
|
188
188
|
&& sameWorkspaceProjects(workspace, task.projectBindings.map(({ projectId }) => projectId))
|
|
189
189
|
&& sameWritableProjects(workspace, isolatedWorkItem.writeProjectIds);
|
|
190
190
|
const runScoped = runWorkspace !== undefined
|
|
@@ -192,29 +192,29 @@ export class FileRoleLaunchPlanner {
|
|
|
192
192
|
&& sameWorkspaceProjects(runWorkspace, task.projectBindings.map(({ projectId }) => projectId))
|
|
193
193
|
&& (runWorkspace.owner.type === "task"
|
|
194
194
|
|| (runWorkspace.owner.type === "work-item"
|
|
195
|
-
&& runWorkspace.owner.workItemId ===
|
|
195
|
+
&& runWorkspace.owner.workItemId === activeTurn?.workItemId)
|
|
196
196
|
|| (runWorkspace.owner.type === "review-round"
|
|
197
|
-
&&
|
|
198
|
-
&& runWorkspace.owner.reviewRoundId ===
|
|
197
|
+
&& activeTurn?.purpose === "review"
|
|
198
|
+
&& runWorkspace.owner.reviewRoundId === activeTurn.reviewRoundId)
|
|
199
199
|
|| (runWorkspace.owner.type === "execution-lane"
|
|
200
|
-
&& runWorkspace.owner.executionGroupId ===
|
|
201
|
-
&& runWorkspace.owner.executionLaneId ===
|
|
202
|
-
&& ((runWorkspace.owner.purpose === "review" &&
|
|
203
|
-
|| (runWorkspace.owner.purpose === "execution" &&
|
|
200
|
+
&& runWorkspace.owner.executionGroupId === activeTurn?.executionGroupId
|
|
201
|
+
&& runWorkspace.owner.executionLaneId === activeTurn?.executionLaneId
|
|
202
|
+
&& ((runWorkspace.owner.purpose === "review" && activeTurn?.purpose === "review")
|
|
203
|
+
|| (runWorkspace.owner.purpose === "execution" && activeTurn?.purpose === "execution"))));
|
|
204
204
|
if (!runScoped && !sharedMain && !isolated) {
|
|
205
205
|
throw new Error(`Role workspace is not ready: ${input.taskId}/${input.roleName}.`);
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
const sessionSet = this.store.getTaskRoleSessionSet(task.id, role.name);
|
|
209
|
-
const resolvedEffective =
|
|
209
|
+
const resolvedEffective = activeTurn?.effective
|
|
210
210
|
?? activeLiveRoleAgentSession(sessionSet)?.effective
|
|
211
211
|
?? resolveTaskRoleEffectiveLaunch(this.store, role);
|
|
212
212
|
if (input.effective !== undefined
|
|
213
213
|
&& !isDeepStrictEqual(resolvedEffective, input.effective)) {
|
|
214
|
-
throw new Error(`Role launch effective
|
|
214
|
+
throw new Error(`Role launch effective Turn snapshot changed: ${input.taskId}/${input.roleName}.`);
|
|
215
215
|
}
|
|
216
|
-
if (
|
|
217
|
-
throw new Error(`Role launch is missing the effective
|
|
216
|
+
if (activeTurn !== null && input.effective === undefined) {
|
|
217
|
+
throw new Error(`Role launch is missing the effective Turn snapshot: ${input.taskId}/${input.roleName}.`);
|
|
218
218
|
}
|
|
219
219
|
const effective = input.effective ?? resolvedEffective;
|
|
220
220
|
const existing = sessionSet?.sessions[effective.agentId];
|
|
@@ -230,7 +230,7 @@ export class FileRoleLaunchPlanner {
|
|
|
230
230
|
throw new Error(`Task Role still has a live Session: ${task.id}/${role.name}.`);
|
|
231
231
|
}
|
|
232
232
|
return this.#compile(role, input, { scope: "task", taskId: task.id }, resolveTaskRoleSessionTitle(input.mode === "resume" ? existing?.title : undefined, task, role.name), input.mode === "resume" && compatibleExisting ? existing.nativeSessionId : undefined, runWorkspace, effective, {
|
|
233
|
-
purpose:
|
|
233
|
+
purpose: activeTurn?.purpose ?? "execution"
|
|
234
234
|
});
|
|
235
235
|
}
|
|
236
236
|
planGlobalRole(input) {
|
|
@@ -280,7 +280,7 @@ export class FileRoleLaunchPlanner {
|
|
|
280
280
|
const adapter = resolveAgentAdapter(binding.adapterId);
|
|
281
281
|
const effectiveWorkspace = effective.workspace.root;
|
|
282
282
|
const agentWorkspace = nativeAgentWorkspace(effective.workspace);
|
|
283
|
-
if (adapter.id === "codex" && (owner.scope !== "task" || input.
|
|
283
|
+
if (adapter.id === "codex" && (owner.scope !== "task" || input.turnId === undefined)) {
|
|
284
284
|
const codexConfig = inspectCodexLaunchConfig({
|
|
285
285
|
environment: launchEnvironment,
|
|
286
286
|
workspace: agentWorkspace,
|
|
@@ -325,21 +325,21 @@ export class FileRoleLaunchPlanner {
|
|
|
325
325
|
sessionManifestDigest: bootstrap.manifest.digest,
|
|
326
326
|
sessionCliPath: bootstrap.sessionCliPath
|
|
327
327
|
};
|
|
328
|
-
const managedRun = owner.scope === "task" && input.
|
|
329
|
-
? this.store.
|
|
328
|
+
const managedRun = owner.scope === "task" && input.turnId !== undefined
|
|
329
|
+
? this.store.getTurn(owner.taskId, input.turnId)
|
|
330
330
|
: null;
|
|
331
331
|
const driver = builtinAgentDriverRegistry().require(builtinDriverIdForAdapter(configured.adapterId));
|
|
332
|
-
if (owner.scope === "task" && input.
|
|
332
|
+
if (owner.scope === "task" && input.turnId !== undefined) {
|
|
333
333
|
const admission = managedRuntimeAdmission(driver.capabilities);
|
|
334
334
|
if (!admission.admitted) {
|
|
335
|
-
throw new Error(`Agent Driver ${driver.id} cannot host managed
|
|
335
|
+
throw new Error(`Agent Driver ${driver.id} cannot host managed Turns; missing capabilities: `
|
|
336
336
|
+ admission.missing.join(", "));
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
339
|
const roleConfig = binding.config.adapterId === "claude"
|
|
340
340
|
&& owner.scope === "task"
|
|
341
|
-
&& input.
|
|
342
|
-
? managedClaudeControlPlaneConfig(binding.config, owner.taskId, managedRun?.workItemId, input.
|
|
341
|
+
&& input.turnId !== undefined
|
|
342
|
+
? managedClaudeControlPlaneConfig(binding.config, owner.taskId, managedRun?.workItemId, input.turnId)
|
|
343
343
|
: binding.config;
|
|
344
344
|
const effectiveConfig = withNativeProjectDirectories(roleConfig, nativeAdditionalDirectories(effective.workspace, agentWorkspace));
|
|
345
345
|
const compileInput = {
|
|
@@ -356,18 +356,18 @@ export class FileRoleLaunchPlanner {
|
|
|
356
356
|
}
|
|
357
357
|
// A previous attempt may have persisted a preallocated/discovered ID
|
|
358
358
|
// before its receipt was committed. Reuse that fixed session rather than
|
|
359
|
-
// allocating a second native session for the same durable
|
|
359
|
+
// allocating a second native session for the same durable Turn.
|
|
360
360
|
const resumeNativeSessionId = input.mode === "resume"
|
|
361
361
|
? requireText(input.nativeSessionId, "Native session id")
|
|
362
362
|
: knownNativeSessionId;
|
|
363
363
|
const launchMode = resumeNativeSessionId === undefined
|
|
364
364
|
? "new"
|
|
365
365
|
: "resume";
|
|
366
|
-
const managedControl = owner.scope === "task" && input.
|
|
366
|
+
const managedControl = owner.scope === "task" && input.turnId !== undefined;
|
|
367
367
|
const managedProviderEnvironment = managedControl
|
|
368
368
|
&& configured.adapterId === "codex"
|
|
369
369
|
? {
|
|
370
|
-
// Managed Codex
|
|
370
|
+
// Managed Codex Turns are non-interactive. Use the Codex execution
|
|
371
371
|
// identity for provider requests while clientInfo still identifies Yui.
|
|
372
372
|
CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "codex_exec"
|
|
373
373
|
}
|
|
@@ -409,17 +409,17 @@ export class FileRoleLaunchPlanner {
|
|
|
409
409
|
}
|
|
410
410
|
if (binding.adapterId === "codex") {
|
|
411
411
|
// Global/interactive Codex sessions still use notify for presentation.
|
|
412
|
-
// Managed
|
|
412
|
+
// Managed Turns receive lifecycle facts through their ordinary App Server
|
|
413
413
|
// subscription, avoiding a second Hook channel for the same Turn.
|
|
414
|
-
if (owner.scope !== "task" || input.
|
|
414
|
+
if (owner.scope !== "task" || input.turnId === undefined) {
|
|
415
415
|
args = addCodexSessionNotify(args, launchMode, this.#cliPath);
|
|
416
416
|
}
|
|
417
|
-
// Managed Codex
|
|
417
|
+
// Managed Codex Turns use disposable proxy clients against the shared
|
|
418
418
|
// native App Server. Their Session Manifest points at the Yui Skills;
|
|
419
419
|
// no Yui Hook config is installed.
|
|
420
|
-
if (owner.scope === "task" && input.
|
|
420
|
+
if (owner.scope === "task" && input.turnId !== undefined) {
|
|
421
421
|
if (managedRun === null || managedRun.status !== "active") {
|
|
422
|
-
throw new Error(`Managed Codex
|
|
422
|
+
throw new Error(`Managed Codex Turn is no longer active: ${input.turnId}.`);
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
425
|
session = launchMode === "resume"
|
|
@@ -439,9 +439,9 @@ export class FileRoleLaunchPlanner {
|
|
|
439
439
|
}
|
|
440
440
|
const managedClaudeRun = binding.adapterId === "claude"
|
|
441
441
|
&& owner.scope === "task"
|
|
442
|
-
&& input.
|
|
442
|
+
&& input.turnId !== undefined;
|
|
443
443
|
if (managedClaudeRun && (managedRun === null || managedRun.status !== "active")) {
|
|
444
|
-
throw new Error(`Managed Claude
|
|
444
|
+
throw new Error(`Managed Claude Turn is no longer active: ${input.turnId}.`);
|
|
445
445
|
}
|
|
446
446
|
const runtimeDescriptor = owner.scope === "task"
|
|
447
447
|
? createExactTaskRuntimeDescriptor({
|
|
@@ -451,7 +451,7 @@ export class FileRoleLaunchPlanner {
|
|
|
451
451
|
agentId: configured.id,
|
|
452
452
|
adapterId: configured.adapterId,
|
|
453
453
|
workspace: effectiveWorkspace,
|
|
454
|
-
...(input.
|
|
454
|
+
...(input.turnId === undefined ? {} : { turnId: input.turnId }),
|
|
455
455
|
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
456
456
|
...(session === null
|
|
457
457
|
? {}
|
|
@@ -474,15 +474,15 @@ export class FileRoleLaunchPlanner {
|
|
|
474
474
|
}
|
|
475
475
|
// Session lifecycle and Turn submission are separate atomic operations.
|
|
476
476
|
// Planning only starts or restores the exact native Session; delivery
|
|
477
|
-
// submits the
|
|
477
|
+
// submits the Turn input after that Session fact is durable.
|
|
478
478
|
if (managedControl && (managedRun === null || managedRun.status !== "active")) {
|
|
479
|
-
throw new Error(`Managed
|
|
479
|
+
throw new Error(`Managed Turn is no longer active: ${input.turnId}.`);
|
|
480
480
|
}
|
|
481
481
|
const providerAuthority = managedControl
|
|
482
482
|
? this.#providerAuthorityForLaunch(owner.taskId, role.name, input.launchId, input.mode)
|
|
483
483
|
: undefined;
|
|
484
484
|
const providerOwnedTurn = managedControl && input.mode === "resume"
|
|
485
|
-
? this.#providerOwnedTurnForLaunch(owner.taskId, role.name, input.
|
|
485
|
+
? this.#providerOwnedTurnForLaunch(owner.taskId, role.name, input.turnId)
|
|
486
486
|
: undefined;
|
|
487
487
|
const providerNativeSessionId = binding.adapterId === "claude"
|
|
488
488
|
? preallocatedNativeSessionId
|
|
@@ -557,7 +557,7 @@ export class FileRoleLaunchPlanner {
|
|
|
557
557
|
...(input.launchId === undefined
|
|
558
558
|
? {}
|
|
559
559
|
: { YUI_LAUNCH_ID: input.launchId }),
|
|
560
|
-
...(input.
|
|
560
|
+
...(input.turnId === undefined ? {} : { YUI_TURN_ID: input.turnId }),
|
|
561
561
|
...(session === null
|
|
562
562
|
? {}
|
|
563
563
|
: { YUI_NATIVE_SESSION_ID: session.nativeSessionId })
|
|
@@ -617,17 +617,18 @@ export class FileRoleLaunchPlanner {
|
|
|
617
617
|
}
|
|
618
618
|
throw new Error(`Provider writer authority is unknown: ${taskId}/${roleName}.`);
|
|
619
619
|
}
|
|
620
|
-
#providerOwnedTurnForLaunch(taskId, roleName,
|
|
620
|
+
#providerOwnedTurnForLaunch(taskId, roleName, turnId) {
|
|
621
621
|
const binding = this.store.getTaskRoleSessionSet(taskId, roleName)?.providerBinding;
|
|
622
|
-
if (binding === null || binding === undefined
|
|
622
|
+
if (binding === null || binding === undefined
|
|
623
|
+
|| binding.turn?.turnId !== turnId)
|
|
623
624
|
return undefined;
|
|
624
625
|
const turn = binding.turn;
|
|
625
|
-
if (turn === null ||
|
|
626
|
+
if (turn === null || turn.status !== "accepted")
|
|
626
627
|
return undefined;
|
|
627
|
-
if (turn.
|
|
628
|
+
if (turn.nativeTurnId === undefined) {
|
|
628
629
|
throw new Error(`Active Provider Turn has no native identity: ${taskId}/${roleName}.`);
|
|
629
630
|
}
|
|
630
|
-
return { attemptId: turn.attemptId, turnId: turn.
|
|
631
|
+
return { attemptId: turn.attemptId, turnId: turn.nativeTurnId };
|
|
631
632
|
}
|
|
632
633
|
#applyWorkspaceScope(taskId, role, launch, workspaceOverride) {
|
|
633
634
|
const workspace = workspaceOverride
|
|
@@ -748,17 +749,16 @@ function ensureClaudeLifecyclePlugin(home, cliPath) {
|
|
|
748
749
|
}, null, 2)}\n`);
|
|
749
750
|
return root;
|
|
750
751
|
}
|
|
751
|
-
function managedClaudeControlPlaneConfig(config, taskId, workItemId,
|
|
752
|
+
function managedClaudeControlPlaneConfig(config, taskId, workItemId, turnId) {
|
|
752
753
|
if (config.permission.strategy !== "configured")
|
|
753
754
|
return config;
|
|
754
755
|
const managed = [
|
|
755
|
-
`Bash(yui task
|
|
756
|
+
`Bash(yui task turn context ${taskId}/${turnId}:*)`,
|
|
756
757
|
`Bash(yui --json task context ${taskId})`,
|
|
757
758
|
`Bash(yui --json task work list ${taskId})`,
|
|
758
759
|
...(workItemId === undefined
|
|
759
760
|
? []
|
|
760
|
-
: [`Bash(yui --json task work show ${workItemId})`])
|
|
761
|
-
`Bash(yui task run yield ${runId} --summary-file -:*)`
|
|
761
|
+
: [`Bash(yui --json task work show ${workItemId})`])
|
|
762
762
|
];
|
|
763
763
|
const existing = (config.permission.allowedTools ?? [])
|
|
764
764
|
.filter((rule) => !isManagedYuiBashRule(rule));
|
|
@@ -833,7 +833,7 @@ function addCodexSessionNotify(args, mode, cliPath) {
|
|
|
833
833
|
/**
|
|
834
834
|
* Codex 0.145 discovers lifecycle hooks from its effective config. Keep Yui's
|
|
835
835
|
* two handlers invocation-local: this avoids mutating CODEX_HOME or the Task
|
|
836
|
-
* workspace, while the exact launch environment supplies the durable
|
|
836
|
+
* workspace, while the exact launch environment supplies the durable Turn fence.
|
|
837
837
|
*/
|
|
838
838
|
function codexLifecycleHooksConfig(cliPath) {
|
|
839
839
|
const command = [
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function withYuiTurnEnvironment(launch, yuiHome, role, turn, nativeSessionId, nativeSessionRoot) {
|
|
2
2
|
return {
|
|
3
3
|
...launch,
|
|
4
4
|
env: {
|
|
5
5
|
...launch.env,
|
|
6
6
|
YUI_HOME: yuiHome,
|
|
7
|
-
YUI_TASK_ID:
|
|
7
|
+
YUI_TASK_ID: turn.taskId,
|
|
8
8
|
YUI_ROLE: role.name,
|
|
9
9
|
YUI_AGENT_ID: role.activeAgentId ?? role.agent ?? "",
|
|
10
|
-
|
|
10
|
+
YUI_TURN_ID: turn.id,
|
|
11
11
|
YUI_WORKSPACE: role.workspace,
|
|
12
12
|
...(nativeSessionRoot === undefined ? {} : { YUI_NATIVE_SESSION_ROOT: nativeSessionRoot }),
|
|
13
13
|
...(nativeSessionId === undefined ? {} : { YUI_NATIVE_SESSION_ID: nativeSessionId })
|
|
@@ -20,7 +20,7 @@ function ownerLabel(owner) {
|
|
|
20
20
|
* This is a read-only check: it never mutates Git state, creates Sessions, or
|
|
21
21
|
* writes recovery records.
|
|
22
22
|
*/
|
|
23
|
-
export function classifyWorkspacePreflight(store, task, roleName,
|
|
23
|
+
export function classifyWorkspacePreflight(store, task, roleName, activeTurn, inspectPhysical) {
|
|
24
24
|
const main = store.getTaskWorkspace(task.id);
|
|
25
25
|
const bindings = task.projectBindings.map(({ projectId, directory }) => ({ projectId, directory }));
|
|
26
26
|
// 1. Owner validation: the Task main workspace must be a durable,
|
|
@@ -41,26 +41,26 @@ export function classifyWorkspacePreflight(store, task, roleName, activeRun, ins
|
|
|
41
41
|
actual
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
// 2.
|
|
44
|
+
// 2. Turn snapshot validation: when the active Turn carries a workspace
|
|
45
45
|
// snapshot its launch-stable identity must match the durable
|
|
46
46
|
// ManagedWorkspace. Audit timestamps may change without changing the
|
|
47
|
-
// owner, root, or Project entries authorized for the
|
|
48
|
-
if (
|
|
49
|
-
const durableRunWorkspace = store.getManagedWorkspace(
|
|
47
|
+
// owner, root, or Project entries authorized for the Turn.
|
|
48
|
+
if (activeTurn?.workspace !== undefined) {
|
|
49
|
+
const durableRunWorkspace = store.getManagedWorkspace(activeTurn.workspace.owner);
|
|
50
50
|
if (durableRunWorkspace === null
|
|
51
|
-
|| !sameManagedWorkspaceIdentity(durableRunWorkspace,
|
|
51
|
+
|| !sameManagedWorkspaceIdentity(durableRunWorkspace, activeTurn.workspace)) {
|
|
52
52
|
const diff = [];
|
|
53
53
|
if (durableRunWorkspace === null) {
|
|
54
|
-
diff.push(`durable ManagedWorkspace for owner ${JSON.stringify(
|
|
54
|
+
diff.push(`durable ManagedWorkspace for owner ${JSON.stringify(activeTurn.workspace.owner)} is missing`);
|
|
55
55
|
}
|
|
56
56
|
else {
|
|
57
|
-
if (durableRunWorkspace.root !==
|
|
58
|
-
diff.push(`root: durable=${durableRunWorkspace.root} run=${
|
|
57
|
+
if (durableRunWorkspace.root !== activeTurn.workspace.root) {
|
|
58
|
+
diff.push(`root: durable=${durableRunWorkspace.root} run=${activeTurn.workspace.root}`);
|
|
59
59
|
}
|
|
60
|
-
if (durableRunWorkspace.entries.length !==
|
|
61
|
-
diff.push(`entries: durable=${durableRunWorkspace.entries.length} run=${
|
|
60
|
+
if (durableRunWorkspace.entries.length !== activeTurn.workspace.entries.length) {
|
|
61
|
+
diff.push(`entries: durable=${durableRunWorkspace.entries.length} run=${activeTurn.workspace.entries.length}`);
|
|
62
62
|
}
|
|
63
|
-
for (const [index, entry] of
|
|
63
|
+
for (const [index, entry] of activeTurn.workspace.entries.entries()) {
|
|
64
64
|
const durableEntry = durableRunWorkspace.entries[index];
|
|
65
65
|
if (durableEntry === undefined) {
|
|
66
66
|
diff.push(`entry[${index}] ${entry.projectId}: missing from durable`);
|
|
@@ -76,10 +76,10 @@ export function classifyWorkspacePreflight(store, task, roleName, activeRun, ins
|
|
|
76
76
|
}
|
|
77
77
|
return {
|
|
78
78
|
kind: "workspace-stale",
|
|
79
|
-
reason: `Role
|
|
79
|
+
reason: `Role Turn workspace is not the durable owner: ${task.id}/${roleName}.`,
|
|
80
80
|
taskId: task.id,
|
|
81
81
|
roleName,
|
|
82
|
-
|
|
82
|
+
turnId: activeTurn.id,
|
|
83
83
|
diff
|
|
84
84
|
};
|
|
85
85
|
}
|
|
@@ -89,7 +89,7 @@ export function classifyWorkspacePreflight(store, task, roleName, activeRun, ins
|
|
|
89
89
|
// from that immutable boundary. A reset/repoint outside the lineage is
|
|
90
90
|
// physical drift and must fail before Provider launch.
|
|
91
91
|
if (inspectPhysical !== undefined) {
|
|
92
|
-
const effective =
|
|
92
|
+
const effective = activeTurn?.workspace ?? main;
|
|
93
93
|
for (const entry of effective.entries) {
|
|
94
94
|
const physical = inspectPhysical(entry);
|
|
95
95
|
if (!physical.recordedBaseIsAncestor) {
|
|
@@ -121,7 +121,7 @@ export function formatWorkspacePreflightError(classification) {
|
|
|
121
121
|
+ "` to inspect the binding, ManagedWorkspace, and physical HEAD.");
|
|
122
122
|
break;
|
|
123
123
|
case "workspace-stale":
|
|
124
|
-
lines.push(`
|
|
124
|
+
lines.push(` Turn ${classification.turnId} snapshot differs from the durable ManagedWorkspace:`);
|
|
125
125
|
for (const entry of classification.diff) {
|
|
126
126
|
lines.push(` - ${entry}`);
|
|
127
127
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { requireIdentity, requireText, requireTimestamp, requirePositiveInteger } from "../domain/validation.js";
|
|
2
2
|
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
3
|
-
export const CAPABILITY_GRANT_SCHEMA_VERSION =
|
|
3
|
+
export const CAPABILITY_GRANT_SCHEMA_VERSION = 2;
|
|
4
4
|
const IRREVERSIBILITY_RANK = {
|
|
5
5
|
none: 0,
|
|
6
6
|
reversible: 1,
|
|
@@ -19,6 +19,7 @@ export function createCapabilityGrant(id, taskId, input, now) {
|
|
|
19
19
|
...(input.expiresAt === undefined ? {} : { expiresAt: requireTimestamp(input.expiresAt, "Capability grant expiresAt") }),
|
|
20
20
|
...(input.maxUses === undefined ? {} : { maxUses: requirePositiveInteger(input.maxUses, "Capability grant maxUses") }),
|
|
21
21
|
usesUsed: 0,
|
|
22
|
+
useReservations: Object.freeze([]),
|
|
22
23
|
irreversibilityCeiling: normalizeCeiling(input.irreversibilityCeiling),
|
|
23
24
|
createdAt: timestamp,
|
|
24
25
|
updatedAt: timestamp
|
|
@@ -61,16 +62,14 @@ export function checkGrant(grant, request, now, options) {
|
|
|
61
62
|
}
|
|
62
63
|
return { allowed: true };
|
|
63
64
|
}
|
|
64
|
-
/** The reservation keys recorded on a grant
|
|
65
|
+
/** The reservation keys recorded on a grant. */
|
|
65
66
|
export function grantUseReservations(grant) {
|
|
66
|
-
return grant.useReservations
|
|
67
|
+
return grant.useReservations;
|
|
67
68
|
}
|
|
68
69
|
/**
|
|
69
70
|
* Record one use against the grant, failing closed once the limit is reached.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* double charge. A use recorded without a key (legacy callers) leaves the
|
|
73
|
-
* reservation list untouched.
|
|
71
|
+
* The reservation key is appended to the grant's durable reservations so a
|
|
72
|
+
* later resume recognizes this exact attempt and avoids a double charge.
|
|
74
73
|
*/
|
|
75
74
|
export function recordGrantUse(grant, now, reservationKey) {
|
|
76
75
|
validateCapabilityGrant(grant);
|
|
@@ -80,9 +79,7 @@ export function recordGrantUse(grant, now, reservationKey) {
|
|
|
80
79
|
return validateCapabilityGrant({
|
|
81
80
|
...grant,
|
|
82
81
|
usesUsed: grant.usesUsed + 1,
|
|
83
|
-
...(reservationKey
|
|
84
|
-
? {}
|
|
85
|
-
: { useReservations: Object.freeze([...grantUseReservations(grant), reservationKey]) }),
|
|
82
|
+
useReservations: Object.freeze([...grantUseReservations(grant), reservationKey]),
|
|
86
83
|
updatedAt: now.toISOString()
|
|
87
84
|
});
|
|
88
85
|
}
|
|
@@ -105,7 +102,7 @@ export function revokeGrant(grant, revokedBy, now) {
|
|
|
105
102
|
}
|
|
106
103
|
export function validateCapabilityGrant(grant) {
|
|
107
104
|
if (grant.schemaVersion !== CAPABILITY_GRANT_SCHEMA_VERSION) {
|
|
108
|
-
throw new Error("Capability grant must use schemaVersion
|
|
105
|
+
throw new Error("Capability grant must use schemaVersion 2.");
|
|
109
106
|
}
|
|
110
107
|
validateTaskRecordReference({ taskId: grant.taskId, localId: grant.id }, "capabilityGrant");
|
|
111
108
|
requireIdentity(grant.taskId, "Task id");
|
|
@@ -125,22 +122,20 @@ export function validateCapabilityGrant(grant) {
|
|
|
125
122
|
if (grant.maxUses !== undefined && grant.usesUsed > grant.maxUses) {
|
|
126
123
|
throw new Error("Capability grant usesUsed cannot exceed maxUses.");
|
|
127
124
|
}
|
|
128
|
-
if (grant.useReservations
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
throw new Error("Capability grant useReservations must be non-empty strings.");
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (new Set(grant.useReservations).size !== grant.useReservations.length) {
|
|
138
|
-
throw new Error("Capability grant useReservations must be unique.");
|
|
139
|
-
}
|
|
140
|
-
if (grant.useReservations.length > grant.usesUsed) {
|
|
141
|
-
throw new Error("Capability grant useReservations cannot outnumber usesUsed.");
|
|
125
|
+
if (!Array.isArray(grant.useReservations)) {
|
|
126
|
+
throw new Error("Capability grant useReservations must be an array.");
|
|
127
|
+
}
|
|
128
|
+
for (const key of grant.useReservations) {
|
|
129
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
130
|
+
throw new Error("Capability grant useReservations must be non-empty strings.");
|
|
142
131
|
}
|
|
143
132
|
}
|
|
133
|
+
if (new Set(grant.useReservations).size !== grant.useReservations.length) {
|
|
134
|
+
throw new Error("Capability grant useReservations must be unique.");
|
|
135
|
+
}
|
|
136
|
+
if (grant.useReservations.length > grant.usesUsed) {
|
|
137
|
+
throw new Error("Capability grant useReservations cannot outnumber usesUsed.");
|
|
138
|
+
}
|
|
144
139
|
normalizeCeiling(grant.irreversibilityCeiling);
|
|
145
140
|
if ((grant.revokedAt === undefined) !== (grant.revokedBy === undefined)) {
|
|
146
141
|
throw new Error("Capability grant revocation requires both revokedAt and revokedBy.");
|
|
@@ -14,7 +14,7 @@ export function createInputRequest(id, taskId, requester, input, now) {
|
|
|
14
14
|
const timestamp = requireDate(now, "Input request creation time");
|
|
15
15
|
const choices = normalizeChoices(input.choices);
|
|
16
16
|
return validateInputRequest({
|
|
17
|
-
schemaVersion:
|
|
17
|
+
schemaVersion: 3,
|
|
18
18
|
id: requireIdentity(id, "Input request id"),
|
|
19
19
|
taskId: requireIdentity(taskId, "Input request Task id"),
|
|
20
20
|
requester: normalizeRequester(requester),
|
|
@@ -72,12 +72,12 @@ export function validateInputRequest(value) {
|
|
|
72
72
|
"schemaVersion", "id", "taskId", "requester", "question", "choices",
|
|
73
73
|
"blockedRefs", "policy", "status", "createdAt", "updatedAt", ...terminalField
|
|
74
74
|
], "Input request");
|
|
75
|
-
if (request.schemaVersion !==
|
|
76
|
-
throw new Error("Input request must use schemaVersion
|
|
75
|
+
if (request.schemaVersion !== 3)
|
|
76
|
+
throw new Error("Input request must use schemaVersion 3.");
|
|
77
77
|
const choices = validateChoices(request.choices);
|
|
78
78
|
const createdAt = requireTimestamp(request.createdAt, "Input request createdAt");
|
|
79
79
|
const base = {
|
|
80
|
-
schemaVersion:
|
|
80
|
+
schemaVersion: 3,
|
|
81
81
|
id: requireIdentity(request.id, "Input request id"),
|
|
82
82
|
taskId: requireIdentity(request.taskId, "Input request Task id"),
|
|
83
83
|
requester: normalizeRequester(request.requester),
|
|
@@ -91,10 +91,10 @@ export function validateInputRequest(value) {
|
|
|
91
91
|
validateTaskRecordReference({ taskId: base.taskId, localId: base.id }, "inputRequest");
|
|
92
92
|
validateTaskRecordReference({
|
|
93
93
|
taskId: base.requester.taskId,
|
|
94
|
-
localId: base.requester.
|
|
95
|
-
}, "
|
|
94
|
+
localId: base.requester.turnId
|
|
95
|
+
}, "turn");
|
|
96
96
|
for (const reference of base.blockedRefs) {
|
|
97
|
-
validateTaskRecordReference({ taskId: reference.taskId, localId: reference.id }, reference.type === "
|
|
97
|
+
validateTaskRecordReference({ taskId: reference.taskId, localId: reference.id }, reference.type === "turn" ? "turn" : "workItem");
|
|
98
98
|
}
|
|
99
99
|
if (base.requester.taskId !== base.taskId
|
|
100
100
|
|| base.blockedRefs.some(({ taskId }) => taskId !== base.taskId)) {
|
|
@@ -175,8 +175,8 @@ function normalizeBlockedRefs(value) {
|
|
|
175
175
|
const references = value.map((reference) => {
|
|
176
176
|
const item = record(reference, "Input blocked reference");
|
|
177
177
|
exact(item, ["type", "taskId", "id"], "Input blocked reference");
|
|
178
|
-
if (item.type !== "work-item" && item.type !== "
|
|
179
|
-
throw new Error("Input blocked reference type must be work-item or
|
|
178
|
+
if (item.type !== "work-item" && item.type !== "turn") {
|
|
179
|
+
throw new Error("Input blocked reference type must be work-item or turn.");
|
|
180
180
|
}
|
|
181
181
|
return {
|
|
182
182
|
type: item.type,
|
|
@@ -224,15 +224,15 @@ function validateBlockedRefs(value) {
|
|
|
224
224
|
function normalizeRequester(value) {
|
|
225
225
|
const requester = record(value, "Input requester");
|
|
226
226
|
exact(requester, requester.nativeSessionId === undefined
|
|
227
|
-
? ["taskId", "roleName", "agentId", "
|
|
228
|
-
: ["taskId", "roleName", "agentId", "
|
|
227
|
+
? ["taskId", "roleName", "agentId", "turnId"]
|
|
228
|
+
: ["taskId", "roleName", "agentId", "turnId", "nativeSessionId"], "Input requester");
|
|
229
229
|
if (requester.roleName !== "leader")
|
|
230
230
|
throw new Error("Input requester must be the Task Leader.");
|
|
231
231
|
return {
|
|
232
232
|
taskId: requireIdentity(requester.taskId, "Input requester Task id"),
|
|
233
233
|
roleName: "leader",
|
|
234
234
|
agentId: requireIdentity(requester.agentId, "Input requester Agent id"),
|
|
235
|
-
|
|
235
|
+
turnId: requireIdentity(requester.turnId, "Input requester Turn id"),
|
|
236
236
|
...(requester.nativeSessionId === undefined
|
|
237
237
|
? {}
|
|
238
238
|
: { nativeSessionId: requireIdentity(requester.nativeSessionId, "Input requester native session id") })
|
|
@@ -3,18 +3,16 @@ import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
|
3
3
|
import { validateChangeSetManifest } from "./changeSetManifest.js";
|
|
4
4
|
export function createWorkItemChangeSet(input, now) {
|
|
5
5
|
return validateChangeSet({
|
|
6
|
-
schemaVersion:
|
|
6
|
+
schemaVersion: 4,
|
|
7
7
|
...input,
|
|
8
8
|
changedPaths: [...input.changedPaths],
|
|
9
|
-
|
|
10
|
-
? {}
|
|
11
|
-
: { manifest: validateChangeSetManifest(input.manifest) }),
|
|
9
|
+
manifest: validateChangeSetManifest(input.manifest),
|
|
12
10
|
createdAt: now.toISOString()
|
|
13
11
|
});
|
|
14
12
|
}
|
|
15
13
|
export function validateChangeSet(changeSet) {
|
|
16
|
-
if (changeSet.schemaVersion !==
|
|
17
|
-
throw new Error("ChangeSet must use schemaVersion
|
|
14
|
+
if (changeSet.schemaVersion !== 4)
|
|
15
|
+
throw new Error("ChangeSet must use schemaVersion 4.");
|
|
18
16
|
validateTaskRecordReference({ taskId: changeSet.taskId, localId: changeSet.id }, "changeSet");
|
|
19
17
|
validateTaskRecordReference({
|
|
20
18
|
taskId: changeSet.taskId,
|
|
@@ -28,13 +26,11 @@ export function validateChangeSet(changeSet) {
|
|
|
28
26
|
}
|
|
29
27
|
requireText(changeSet.branch, "ChangeSet branch");
|
|
30
28
|
normalizedUniqueText(changeSet.changedPaths, "ChangeSet path");
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
throw new Error(`ChangeSet manifest deleted path is not a changed path: ${deleted}.`);
|
|
37
|
-
}
|
|
29
|
+
const manifest = validateChangeSetManifest(changeSet.manifest);
|
|
30
|
+
const changed = new Set(changeSet.changedPaths);
|
|
31
|
+
for (const deleted of manifest.deletedPaths) {
|
|
32
|
+
if (!changed.has(deleted)) {
|
|
33
|
+
throw new Error(`ChangeSet manifest deleted path is not a changed path: ${deleted}.`);
|
|
38
34
|
}
|
|
39
35
|
}
|
|
40
36
|
requireTimestamp(changeSet.createdAt, "ChangeSet createdAt");
|
|
@@ -33,7 +33,7 @@ export function validateChangeSetManifest(manifest) {
|
|
|
33
33
|
}
|
|
34
34
|
const tags = normalizedUniqueText(manifest.tags, "ChangeSet manifest tag");
|
|
35
35
|
const deletedPaths = normalizedUniqueText(manifest.deletedPaths, "ChangeSet manifest deleted path");
|
|
36
|
-
const evidenceRefs = normalizedUniqueText(manifest.evidenceRefs
|
|
36
|
+
const evidenceRefs = normalizedUniqueText(manifest.evidenceRefs, "ChangeSet manifest evidence reference");
|
|
37
37
|
return {
|
|
38
38
|
schemaVersion: CHANGE_SET_MANIFEST_SCHEMA_VERSION,
|
|
39
39
|
tags,
|
|
@@ -933,7 +933,7 @@ function integrationRuntimeLaunchId(home, integrationId) {
|
|
|
933
933
|
}
|
|
934
934
|
/**
|
|
935
935
|
* Reuse is enabled only for an immutable installed release. Development
|
|
936
|
-
* checkouts have no content-addressed release identity, so they
|
|
936
|
+
* checkouts have no content-addressed release identity, so they return.
|
|
937
937
|
*/
|
|
938
938
|
function integrationRuntimeReleaseIdentity(home) {
|
|
939
939
|
try {
|
|
@@ -1050,7 +1050,7 @@ async function resolveRef(repositoryPath, ref) {
|
|
|
1050
1050
|
/**
|
|
1051
1051
|
* Static preflight before the (potentially expensive) checks: the target ref
|
|
1052
1052
|
* must still equal the expected head and a checked-out target worktree must be
|
|
1053
|
-
* clean. A failure here means the check commands must not
|
|
1053
|
+
* clean. A failure here means the check commands must not turn. The post-check
|
|
1054
1054
|
* {@link advanceTargetRef} re-verifies both before the CAS, so a target that
|
|
1055
1055
|
* moves during the gate is still fenced.
|
|
1056
1056
|
*/
|