@agimon-ai/po-workflow 0.0.0-alpha.79 → 0.0.0-alpha.80

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.
@@ -608,8 +608,50 @@ const WorkflowLauncherMetadataSchema = zod.z.discriminatedUnion("type", [
608
608
  TmuxWorkflowLauncherMetadataSchema,
609
609
  NativeWorkflowLauncherMetadataSchema
610
610
  ]);
611
+ /** Only trusted in-process launchers set this metadata, never workflow environment. */
612
+ const WorkflowAdmissionMetadataSchema = zod.z.object({
613
+ attemptId: zod.z.string().min(1),
614
+ admissionId: zod.z.string().uuid(),
615
+ ownerSessionId: zod.z.string().min(1),
616
+ launcherSessionId: zod.z.string().min(1),
617
+ reason: zod.z.enum([
618
+ "launch",
619
+ "recover",
620
+ "startup",
621
+ "resume"
622
+ ]),
623
+ previousRun: zod.z.object({
624
+ runId: zod.z.string(),
625
+ ownerSessionId: zod.z.string(),
626
+ runKey: zod.z.string(),
627
+ registryWorkspace: zod.z.string()
628
+ }).optional(),
629
+ environment: zod.z.record(zod.z.string(), zod.z.string()).optional(),
630
+ ready: zod.z.boolean().optional(),
631
+ binding: zod.z.object({
632
+ association: zod.z.object({
633
+ coordinator: zod.z.string().min(1),
634
+ key: zod.z.string().min(1)
635
+ }),
636
+ epoch: zod.z.object({
637
+ admissionId: zod.z.string().uuid(),
638
+ operationId: zod.z.string().uuid()
639
+ })
640
+ }).optional()
641
+ });
642
+ const WorkflowAdmissionIntentSchema = zod.z.object({
643
+ metadata: WorkflowAdmissionMetadataSchema,
644
+ requestHash: zod.z.string(),
645
+ rejected: zod.z.array(zod.z.string()).optional()
646
+ });
611
647
  const WorkflowRunRecordSchema = zod.z.object({
612
648
  activeRepairId: zod.z.string().regex(/^\d+$/).optional(),
649
+ admission: WorkflowAdmissionMetadataSchema.optional(),
650
+ archivedProgress: zod.z.array(WorkflowProgressEventSchema).optional(),
651
+ configuration: zod.z.object({
652
+ revision: zod.z.string(),
653
+ workflow: zod.z.record(zod.z.string(), zod.z.unknown())
654
+ }).optional(),
613
655
  /** Read-only legacy metadata. New runs only write runner. */
614
656
  cliAgent: zod.z.string().optional(),
615
657
  continueOnError: zod.z.boolean().optional(),
@@ -1016,6 +1058,193 @@ var WorkflowRegistryService = class {
1016
1058
  resolveWorkspace(explicitWorkspace, workflowWorkspace) {
1017
1059
  return this.slugifySegment(explicitWorkspace || workflowWorkspace || DEFAULT_WORKSPACE$1, DEFAULT_WORKSPACE$1);
1018
1060
  }
1061
+ /** The journal is outside reusable and movable run directories. Exclusive publish is the CAS. */
1062
+ async reserveAdmission(intent) {
1063
+ intent = WorkflowAdmissionIntentSchema.parse(intent);
1064
+ const directory = (0, node_path.resolve)(this.homeDir, "admissions");
1065
+ (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.createDir(directory, { recursive: true }, this.runtime.ioContext));
1066
+ const file = this.admissionPath(intent.metadata.launcherSessionId, intent.metadata.attemptId);
1067
+ try {
1068
+ await this.writeExclusiveJson(file, intent);
1069
+ return {
1070
+ intent,
1071
+ fresh: true
1072
+ };
1073
+ } catch (error) {
1074
+ if (error.code !== "EEXIST") throw error;
1075
+ const existing = await this.readAdmission(intent.metadata.launcherSessionId, intent.metadata.attemptId);
1076
+ if (!existing || existing.requestHash !== intent.requestHash) throw new Error("Workflow attempt ID was reused with a different request.");
1077
+ return {
1078
+ intent: existing,
1079
+ fresh: false
1080
+ };
1081
+ }
1082
+ }
1083
+ async readAdmission(launcher, attempt) {
1084
+ const file = this.admissionPath(launcher, attempt);
1085
+ try {
1086
+ return WorkflowAdmissionIntentSchema.parse(JSON.parse((0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.readTextFile(file, this.runtime.ioContext))));
1087
+ } catch (error) {
1088
+ if (["ENOENT", "not_found"].includes(String(error.code))) return void 0;
1089
+ throw error;
1090
+ }
1091
+ }
1092
+ async rejectAdmission(intent, errors) {
1093
+ const file = this.admissionPath(intent.metadata.launcherSessionId, intent.metadata.attemptId);
1094
+ await this.writeAtomic(file, {
1095
+ ...intent,
1096
+ rejected: errors
1097
+ });
1098
+ }
1099
+ admissionPath(launcher, attempt) {
1100
+ return (0, node_path.resolve)(this.homeDir, "admissions", `${(0, node_crypto.createHash)("sha256").update(JSON.stringify([launcher, attempt])).digest("hex")}.json`);
1101
+ }
1102
+ runRef(record) {
1103
+ const ownerSessionId = record.admission?.ownerSessionId ?? record.env?.PO_SESSION_ID;
1104
+ return record.runId && ownerSessionId ? {
1105
+ runId: record.runId,
1106
+ runKey: record.runKey,
1107
+ ownerSessionId,
1108
+ registryWorkspace: record.workspace
1109
+ } : void 0;
1110
+ }
1111
+ /** Exact-ID evidence survives replay, deletion and a lost secondary admission index write. */
1112
+ async readRunEvidence(runId) {
1113
+ const current = (await this.listRuns()).find((record) => record.runId === runId);
1114
+ if (current) return current;
1115
+ const file = (0, node_path.resolve)(this.homeDir, "receipts", `${(0, node_crypto.createHash)("sha256").update(runId).digest("hex")}.json`);
1116
+ if (!await this.pathExists(file)) return void 0;
1117
+ return this.readRunRecord(file);
1118
+ }
1119
+ async listRunEvidence() {
1120
+ const directory = (0, node_path.resolve)(this.homeDir, "receipts");
1121
+ if (!await this.pathExists(directory)) return this.listRuns();
1122
+ const entries = (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.listDir(directory, this.runtime.ioContext));
1123
+ const archived = await Promise.all(entries.filter((entry) => entry.name.endsWith(".json")).map((entry) => this.readRunRecord((0, node_path.resolve)(directory, entry.name))));
1124
+ return [...new Map([...archived, ...await this.listRuns()].map((record) => [record.runId, record])).values()];
1125
+ }
1126
+ async preserveRunEvidence(record, runDir = this.runDirectoryFor(record)) {
1127
+ if (!record.runId || record.stage === RUNNING_STAGE) return;
1128
+ const directory = (0, node_path.resolve)(this.homeDir, "receipts");
1129
+ (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.createDir(directory, { recursive: true }, this.runtime.ioContext));
1130
+ const file = (0, node_path.resolve)(directory, `${(0, node_crypto.createHash)("sha256").update(record.runId).digest("hex")}.json`);
1131
+ if (await this.pathExists(file)) return;
1132
+ const progress = await readWorkflowProgress(this.runtime, runDir);
1133
+ try {
1134
+ await this.writeExclusiveJson(file, {
1135
+ ...record,
1136
+ archivedProgress: progress.filter((event) => Date.parse(event.at) >= Date.parse(record.startedAt))
1137
+ });
1138
+ } catch (error) {
1139
+ if (error.code !== "EEXIST") throw error;
1140
+ }
1141
+ }
1142
+ /** All registry instances use the same stable workspace/run-key lock, including while stage dirs move. */
1143
+ async withRunLock(recordPath, action) {
1144
+ return this.writeCoordinator.forKey(recordPath)(async () => {
1145
+ const runDir = (0, node_path.dirname)(recordPath);
1146
+ const workspaceDir = (0, node_path.dirname)((0, node_path.dirname)(runDir));
1147
+ const lockPath = (0, node_path.resolve)(workspaceDir, `${(0, node_crypto.createHash)("sha256").update(runDir.split("/").at(-1)).digest("hex")}.operation.lock`);
1148
+ const release = await this.runtime.workflowHost.acquireRecoveryLock(lockPath, {
1149
+ lockfilePath: lockPath,
1150
+ realpath: false,
1151
+ retries: {
1152
+ retries: 100,
1153
+ minTimeout: 10,
1154
+ maxTimeout: 10
1155
+ },
1156
+ stale: DEFAULT_RECOVERY_CLAIM_TTL_MS
1157
+ });
1158
+ try {
1159
+ return await action();
1160
+ } finally {
1161
+ await release();
1162
+ }
1163
+ });
1164
+ }
1165
+ /** Persist the epoch under the authoritative writer, await policy unlocked, then fence and acknowledge atomically. */
1166
+ async authorizeExecution(handle, resume = false) {
1167
+ const prepared = await this.withRunLock(handle.recordPath, async () => {
1168
+ const current = await this.readRunRecord(handle.recordPath);
1169
+ if (current.runId !== handle.runId || current.stage !== RUNNING_STAGE) throw new Error("Workflow generation changed before policy admission.");
1170
+ const request = resume ? await this.readPauseRequest(handle) : void 0;
1171
+ if (resume && (request?.action !== "resume" || current.executionState !== "resume_requested" && current.executionState !== "paused")) throw new Error("Workflow resume request changed.");
1172
+ if (await this.hasStopRequest(handle)) throw new Error("Workflow was stopped before policy admission.");
1173
+ if (!current.admission?.binding) return {
1174
+ record: current,
1175
+ request
1176
+ };
1177
+ const admission = structuredClone(current.admission);
1178
+ if (resume) {
1179
+ admission.reason = "resume";
1180
+ admission.binding.epoch.operationId = (0, node_crypto.randomUUID)();
1181
+ }
1182
+ admission.ready = false;
1183
+ const record = {
1184
+ ...current,
1185
+ admission
1186
+ };
1187
+ await this.writeRunRecord(handle.recordPath, record);
1188
+ return {
1189
+ record,
1190
+ request
1191
+ };
1192
+ });
1193
+ const admission = prepared.record.admission;
1194
+ let environment = {};
1195
+ if (admission?.binding) {
1196
+ const before = this.runtime.beforeWorkflowExecution;
1197
+ if (!before) throw new Error(`Workflow policy coordinator is unavailable: ${admission.binding.association.coordinator}`);
1198
+ const signal = this.runtime.workflowSignal ?? new AbortController().signal;
1199
+ signal.throwIfAborted();
1200
+ const result = await before({
1201
+ binding: structuredClone(admission.binding),
1202
+ reason: admission.reason,
1203
+ ownerSessionId: admission.ownerSessionId,
1204
+ launcherSessionId: admission.launcherSessionId,
1205
+ run: this.runRef(prepared.record),
1206
+ previousRun: admission.previousRun,
1207
+ signal
1208
+ });
1209
+ signal.throwIfAborted();
1210
+ environment = result?.environment ?? {};
1211
+ for (const [key, value] of Object.entries(environment)) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || /^(PO_|WORKFLOW_)/.test(key) || typeof value !== "string") throw new Error("Invalid workflow policy environment patch.");
1212
+ } else if (!resume) return prepared.record;
1213
+ return this.withRunLock(handle.recordPath, async () => {
1214
+ const current = await this.readRunRecord(handle.recordPath);
1215
+ const request = resume ? await this.readPauseRequest(handle) : void 0;
1216
+ if (current.runId !== handle.runId || current.stage !== RUNNING_STAGE || current.admission?.binding?.epoch.operationId !== admission?.binding?.epoch.operationId || resume && (current.executionState !== prepared.record.executionState || request?.action !== "resume" || request.requestId !== prepared.request?.requestId) || await this.hasStopRequest(handle)) throw new Error("Workflow policy operation changed or stopped.");
1217
+ const updated = { ...current };
1218
+ if (admission?.binding) {
1219
+ updated.admission = {
1220
+ ...current.admission,
1221
+ ready: true,
1222
+ environment: {
1223
+ ...current.admission?.environment,
1224
+ ...environment
1225
+ }
1226
+ };
1227
+ updated.env = {
1228
+ ...current.env,
1229
+ ...environment
1230
+ };
1231
+ }
1232
+ if (resume) {
1233
+ const cursor = current.executionCursor;
1234
+ updated.executionState = "running";
1235
+ updated.executionCursor = {
1236
+ ...cursor,
1237
+ phase: cursor?.phase ?? "step",
1238
+ resumeMode: cursor?.resumeMode ?? "replay-current",
1239
+ executionGeneration: (cursor?.executionGeneration ?? 1) + (cursor?.resumeMode === "continue-next" ? 0 : 1),
1240
+ requestId: request.requestId,
1241
+ revision: (cursor?.revision ?? 0) + 1
1242
+ };
1243
+ }
1244
+ await this.writeRunRecord(handle.recordPath, updated);
1245
+ return updated;
1246
+ });
1247
+ }
1019
1248
  /**
1020
1249
  * Create or resume a workflow run in the `running` stage for its workspace.
1021
1250
  */
@@ -1023,97 +1252,104 @@ var WorkflowRegistryService = class {
1023
1252
  const workspace = this.resolveWorkspace(input.workspace, input.workflowWorkspace);
1024
1253
  const runKey = this.slugifySegment(input.displayName, FALLBACK_RUN_KEY);
1025
1254
  await this.ensureWorkspaceStructure(workspace);
1026
- const existingStage = await this.findReusableRunStage(workspace, runKey);
1027
- if (existingStage === RUNNING_STAGE) throw new WorkflowRunConflictError(workspace, runKey);
1028
- const runningDir = this.getRunDirectory(workspace, RUNNING_STAGE, runKey);
1029
- if (existingStage) {
1030
- const existingDir = this.getRunDirectory(workspace, existingStage, runKey);
1031
- let releaseRecoveryLock;
1032
- try {
1033
- if (existingStage === ERROR_STAGE$2) {
1034
- releaseRecoveryLock = await this.acquireRecoveryClaimLock(existingDir);
1035
- await this.assertRecoveryPromotion(existingDir, workspace, runKey, input.recoveryClaimId);
1036
- } else if (input.recoveryClaimId) throw new Error(`Workflow recovery claim does not target a failed run: ${workspace}/${runKey}`);
1037
- (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.remove(runningDir, {
1038
- recursive: true,
1039
- force: true
1040
- }, this.runtime.ioContext));
1041
- (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.renameFile(existingDir, runningDir, this.runtime.ioContext));
1042
- } finally {
1043
- await releaseRecoveryLock?.();
1044
- }
1045
- await this.removeFileIfExists((0, node_path.resolve)(runningDir, FIX_FILENAME$2));
1046
- await this.removeFileIfExists((0, node_path.resolve)(runningDir, PROCEED_FILENAME$1));
1047
- await this.removeFileIfExists((0, node_path.resolve)(runningDir, STOP_REQUEST_FILENAME));
1048
- await this.removeFileIfExists((0, node_path.resolve)(runningDir, PAUSE_REQUEST_FILENAME$1));
1049
- await this.removeFileIfExists((0, node_path.resolve)(runningDir, FINALIZING_FILENAME));
1050
- await this.removeFileIfExists((0, node_path.resolve)(runningDir, RECOVERY_CLAIM_FILENAME));
1051
- await this.removeFileIfExists((0, node_path.resolve)(runningDir, RECOVERY_ADOPTION_FILENAME));
1052
- } else {
1053
- if (input.recoveryClaimId) throw new Error(`Workflow recovery claim target no longer exists: ${workspace}/${runKey}`);
1054
- (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.createDir(runningDir, { recursive: true }, this.runtime.ioContext));
1055
- }
1056
- const runId = (0, node_crypto.randomUUID)();
1057
- const runRecord = {
1058
- activeRepairId: input.activeRepairId,
1059
- continueOnError: input.continueOnError,
1060
- displayName: input.displayName,
1061
- dryRun: input.dryRun,
1062
- env: input.env,
1063
- inputs: input.inputs,
1064
- job: input.job,
1065
- keepWorktree: input.keepWorktree,
1066
- launcher: input.launcher,
1067
- runKey,
1068
- prompt: input.prompt,
1069
- runner: input.runner,
1070
- ...input.command === void 0 ? {} : { command: input.command },
1071
- ...input.choice === void 0 ? {} : { choice: input.choice },
1072
- ...input.profile === void 0 ? {} : { profile: input.profile },
1073
- runDirectory: input.runDirectory,
1074
- runId,
1075
- secretFile: input.secretFile,
1076
- skipLaunch: input.skipLaunch,
1077
- statusFile: input.statusFile,
1078
- terminalSocket: input.terminalSocket,
1079
- executionState: "running",
1080
- executionCursor: {
1081
- executionGeneration: 1,
1082
- phase: "starting",
1083
- resumeMode: "continue-next",
1084
- revision: 0
1085
- },
1086
- stage: RUNNING_STAGE,
1087
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1088
- pid: input.pid ?? this.runtime.workflowHost.processId,
1089
- workflowPath: input.workflowPath,
1090
- workflowId: input.workflowId,
1091
- workflowName: input.workflowName,
1092
- workspace
1093
- };
1094
- const recordPath = (0, node_path.resolve)(runningDir, RUN_RECORD_FILENAME$1);
1095
- await this.writeRunRecord(recordPath, runRecord);
1096
- return {
1097
- changelogPath: (0, node_path.resolve)(runningDir, CHANGELOG_FILENAME),
1098
- contextPath: (0, node_path.resolve)(runningDir, CONTEXT_FILENAME),
1099
- displayName: input.displayName,
1100
- pauseRequestPath: (0, node_path.resolve)(runningDir, PAUSE_REQUEST_FILENAME$1),
1101
- recordPath,
1102
- runDir: runningDir,
1103
- runId,
1104
- runKey,
1105
- stopRequestPath: (0, node_path.resolve)(runningDir, STOP_REQUEST_FILENAME),
1106
- workspace
1107
- };
1255
+ if (await this.findRunStage(workspace, runKey) === RUNNING_STAGE) await this.reconcileStaleRunningRecord(workspace, runKey);
1256
+ return this.withRunLock((0, node_path.resolve)(this.getRunDirectory(workspace, RUNNING_STAGE, runKey), RUN_RECORD_FILENAME$1), async () => {
1257
+ const existingStage = await this.findRunStage(workspace, runKey);
1258
+ if (existingStage === RUNNING_STAGE) throw new WorkflowRunConflictError(workspace, runKey);
1259
+ const runningDir = this.getRunDirectory(workspace, RUNNING_STAGE, runKey);
1260
+ if (existingStage) {
1261
+ const existingDir = this.getRunDirectory(workspace, existingStage, runKey);
1262
+ await this.preserveRunEvidence(await this.readRunRecord((0, node_path.resolve)(existingDir, RUN_RECORD_FILENAME$1)));
1263
+ let releaseRecoveryLock;
1264
+ try {
1265
+ if (existingStage === ERROR_STAGE$2) {
1266
+ releaseRecoveryLock = await this.acquireRecoveryClaimLock(existingDir);
1267
+ await this.assertRecoveryPromotion(existingDir, workspace, runKey, input.recoveryClaimId);
1268
+ } else if (input.recoveryClaimId) throw new Error(`Workflow recovery claim does not target a failed run: ${workspace}/${runKey}`);
1269
+ (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.remove(runningDir, {
1270
+ recursive: true,
1271
+ force: true
1272
+ }, this.runtime.ioContext));
1273
+ (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.renameFile(existingDir, runningDir, this.runtime.ioContext));
1274
+ } finally {
1275
+ await releaseRecoveryLock?.();
1276
+ }
1277
+ await this.removeFileIfExists((0, node_path.resolve)(runningDir, FIX_FILENAME$2));
1278
+ await this.removeFileIfExists((0, node_path.resolve)(runningDir, PROCEED_FILENAME$1));
1279
+ await this.removeFileIfExists((0, node_path.resolve)(runningDir, STOP_REQUEST_FILENAME));
1280
+ await this.removeFileIfExists((0, node_path.resolve)(runningDir, PAUSE_REQUEST_FILENAME$1));
1281
+ await this.removeFileIfExists((0, node_path.resolve)(runningDir, FINALIZING_FILENAME));
1282
+ await this.removeFileIfExists((0, node_path.resolve)(runningDir, RECOVERY_CLAIM_FILENAME));
1283
+ await this.removeFileIfExists((0, node_path.resolve)(runningDir, RECOVERY_ADOPTION_FILENAME));
1284
+ } else {
1285
+ if (input.recoveryClaimId) throw new Error(`Workflow recovery claim target no longer exists: ${workspace}/${runKey}`);
1286
+ (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.createDir(runningDir, { recursive: true }, this.runtime.ioContext));
1287
+ }
1288
+ const runId = (0, node_crypto.randomUUID)();
1289
+ const runRecord = {
1290
+ activeRepairId: input.activeRepairId,
1291
+ admission: input.admission,
1292
+ configuration: input.configuration,
1293
+ continueOnError: input.continueOnError,
1294
+ displayName: input.displayName,
1295
+ dryRun: input.dryRun,
1296
+ env: input.env,
1297
+ inputs: input.inputs,
1298
+ job: input.job,
1299
+ keepWorktree: input.keepWorktree,
1300
+ launcher: input.launcher,
1301
+ runKey,
1302
+ prompt: input.prompt,
1303
+ runner: input.runner,
1304
+ ...input.command === void 0 ? {} : { command: input.command },
1305
+ ...input.choice === void 0 ? {} : { choice: input.choice },
1306
+ ...input.profile === void 0 ? {} : { profile: input.profile },
1307
+ runDirectory: input.runDirectory,
1308
+ runId,
1309
+ secretFile: input.secretFile,
1310
+ skipLaunch: input.skipLaunch,
1311
+ statusFile: input.statusFile,
1312
+ terminalSocket: input.terminalSocket,
1313
+ executionState: "running",
1314
+ executionCursor: {
1315
+ executionGeneration: 1,
1316
+ phase: "starting",
1317
+ resumeMode: "continue-next",
1318
+ revision: 0
1319
+ },
1320
+ stage: RUNNING_STAGE,
1321
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1322
+ pid: input.pid ?? this.runtime.workflowHost.processId,
1323
+ workflowPath: input.workflowPath,
1324
+ workflowId: input.workflowId,
1325
+ workflowName: input.workflowName,
1326
+ workspace
1327
+ };
1328
+ const recordPath = (0, node_path.resolve)(runningDir, RUN_RECORD_FILENAME$1);
1329
+ await this.writeRunRecord(recordPath, runRecord);
1330
+ return {
1331
+ changelogPath: (0, node_path.resolve)(runningDir, CHANGELOG_FILENAME),
1332
+ contextPath: (0, node_path.resolve)(runningDir, CONTEXT_FILENAME),
1333
+ displayName: input.displayName,
1334
+ pauseRequestPath: (0, node_path.resolve)(runningDir, PAUSE_REQUEST_FILENAME$1),
1335
+ recordPath,
1336
+ runDir: runningDir,
1337
+ runId,
1338
+ runKey,
1339
+ stopRequestPath: (0, node_path.resolve)(runningDir, STOP_REQUEST_FILENAME),
1340
+ workspace
1341
+ };
1342
+ });
1108
1343
  }
1109
1344
  /**
1110
1345
  * Finalize a workflow run by moving it into a terminal stage and updating metadata.
1111
1346
  */
1112
1347
  async finalizeRun(handle, input) {
1113
- return this.writeCoordinator.forKey(handle.recordPath)(async () => {
1348
+ return this.withRunLock(handle.recordPath, async () => {
1114
1349
  await this.ensureWorkspaceStructure(handle.workspace);
1115
1350
  const nextDir = this.getRunDirectory(handle.workspace, input.stage, handle.runKey);
1116
1351
  const currentRecord = await this.readRunRecord(handle.recordPath);
1352
+ if (currentRecord.runId !== handle.runId) throw new Error("Workflow generation changed before finalization.");
1117
1353
  const finalRecord = {
1118
1354
  ...currentRecord,
1119
1355
  errorMessage: input.errorMessage,
@@ -1246,7 +1482,7 @@ var WorkflowRegistryService = class {
1246
1482
  resumeMode: "replay-current"
1247
1483
  },
1248
1484
  requestId: request.requestId,
1249
- resumeMode: "replay-current",
1485
+ resumeMode: record.executionCursor?.resumeMode ?? "replay-current",
1250
1486
  revision: revision + 1
1251
1487
  }, revision);
1252
1488
  await this.writeControlRequest(handle, handle.pauseRequestPath ?? (0, node_path.resolve)(handle.runDir, PAUSE_REQUEST_FILENAME$1), request);
@@ -1265,7 +1501,7 @@ var WorkflowRegistryService = class {
1265
1501
  }
1266
1502
  }
1267
1503
  async updateExecutionState(handle, executionState, executionCursor, expectedRevision) {
1268
- return this.writeCoordinator.forKey(handle.recordPath)(async () => {
1504
+ return this.withRunLock(handle.recordPath, async () => {
1269
1505
  const current = await this.readRunRecord(handle.recordPath);
1270
1506
  if (current.stage !== RUNNING_STAGE || current.runId && current.runId !== handle.runId) throw new Error(`Workflow run is no longer controllable: ${handle.workspace}/${handle.runKey}`);
1271
1507
  const currentRevision = current.executionCursor?.revision ?? 0;
@@ -1280,7 +1516,7 @@ var WorkflowRegistryService = class {
1280
1516
  });
1281
1517
  }
1282
1518
  async updateExecutionCursor(handle, patch) {
1283
- return this.writeCoordinator.forKey(handle.recordPath)(async () => {
1519
+ return this.withRunLock(handle.recordPath, async () => {
1284
1520
  const current = await this.readRunRecord(handle.recordPath);
1285
1521
  if (current.stage !== RUNNING_STAGE || current.runId && current.runId !== handle.runId) throw new Error(`Workflow run is no longer controllable: ${handle.workspace}/${handle.runKey}`);
1286
1522
  const currentCursor = current.executionCursor ?? {
@@ -1305,7 +1541,7 @@ var WorkflowRegistryService = class {
1305
1541
  async markResumable(run, reason) {
1306
1542
  const runDir = this.getRunDirectory(run.workspace, RUNNING_STAGE, run.runKey);
1307
1543
  const recordPath = (0, node_path.resolve)(runDir, RUN_RECORD_FILENAME$1);
1308
- return this.writeCoordinator.forKey(recordPath)(async () => {
1544
+ return this.withRunLock(recordPath, async () => {
1309
1545
  const current = await this.readRunRecord(recordPath);
1310
1546
  if (current.stage !== RUNNING_STAGE || current.runId !== run.runId) throw new Error(`Workflow run is no longer controllable: ${run.workspace}/${run.runKey}`);
1311
1547
  if (current.executionState === "paused" || await this.hasStopRequest({
@@ -1328,8 +1564,18 @@ var WorkflowRegistryService = class {
1328
1564
  });
1329
1565
  }
1330
1566
  /** Save worktree identity as soon as it exists, including for a later interrupted run. */
1567
+ async updateConfiguration(handle, configuration) {
1568
+ await this.withRunLock(handle.recordPath, async () => {
1569
+ const record = await this.readRunRecord(handle.recordPath);
1570
+ if (record.runId !== handle.runId || record.stage !== RUNNING_STAGE) throw new Error("Workflow configuration generation changed.");
1571
+ await this.writeRunRecord(handle.recordPath, {
1572
+ ...record,
1573
+ configuration
1574
+ });
1575
+ });
1576
+ }
1331
1577
  async updateWorktree(handle, metadata) {
1332
- return this.writeCoordinator.forKey(handle.recordPath)(async () => {
1578
+ return this.withRunLock(handle.recordPath, async () => {
1333
1579
  const current = await this.readRunRecord(handle.recordPath);
1334
1580
  if (current.stage !== RUNNING_STAGE || current.runId !== handle.runId) throw new Error(`Workflow run is no longer controllable: ${handle.workspace}/${handle.runKey}`);
1335
1581
  await this.writeRunRecord(handle.recordPath, {
@@ -1408,8 +1654,10 @@ var WorkflowRegistryService = class {
1408
1654
  const tempPath = `${path}.${this.runtime.workflowHost.processId}.${(0, node_crypto.randomUUID)()}.tmp`;
1409
1655
  try {
1410
1656
  (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`, this.runtime.ioContext));
1411
- await this.assertCurrentRun(handle);
1412
- (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.renameFile(tempPath, path, this.runtime.ioContext));
1657
+ await this.withRunLock(handle.recordPath, async () => {
1658
+ await this.assertCurrentRun(handle);
1659
+ (0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.renameFile(tempPath, path, this.runtime.ioContext));
1660
+ });
1413
1661
  } catch (error) {
1414
1662
  await this.removeFileIfExists(tempPath);
1415
1663
  throw error;
@@ -1630,7 +1878,7 @@ var WorkflowRegistryService = class {
1630
1878
  * Persist the active repair pointer and fix-cycle progress on a running record.
1631
1879
  */
1632
1880
  async updateRepairState(handle, input) {
1633
- return this.writeCoordinator.forKey(handle.recordPath)(async () => {
1881
+ return this.withRunLock(handle.recordPath, async () => {
1634
1882
  const current = await this.readRunRecord(handle.recordPath);
1635
1883
  const updated = {
1636
1884
  ...current,
@@ -1671,7 +1919,7 @@ var WorkflowRegistryService = class {
1671
1919
  };
1672
1920
  const runDir = this.getRunDirectory(input.workspace, input.stage, input.runKey);
1673
1921
  const errorStage = input.stage === ERROR_STAGE$2;
1674
- return this.writeCoordinator.forKey((0, node_path.resolve)(runDir, RUN_RECORD_FILENAME$1))(async () => {
1922
+ return this.withRunLock((0, node_path.resolve)(runDir, RUN_RECORD_FILENAME$1), async () => {
1675
1923
  const releaseLock = errorStage ? await this.acquireRecoveryClaimLock(runDir) : void 0;
1676
1924
  try {
1677
1925
  if (!await this.pathExists(runDir)) throw new Error(`Workflow run not found: ${input.workspace}/${input.stage}/${input.runKey}`);
@@ -1881,16 +2129,6 @@ var WorkflowRegistryService = class {
1881
2129
  return [...records.values()];
1882
2130
  }
1883
2131
  /**
1884
- * Resolve the run stage, first converting dead running records into
1885
- * interrupted terminal records so reruns are unblocked.
1886
- */
1887
- async findReusableRunStage(workspace, runKey) {
1888
- const existingStage = await this.findRunStage(workspace, runKey);
1889
- if (existingStage !== RUNNING_STAGE) return existingStage;
1890
- await this.reconcileStaleRunningRecord(workspace, runKey);
1891
- return this.findRunStage(workspace, runKey);
1892
- }
1893
- /**
1894
2132
  * Read a running record for status displays without mutating it on disk.
1895
2133
  * If the recorded PID is gone, mark the returned record as stale but keep
1896
2134
  * the persisted state untouched so the original failure metadata is not
@@ -1949,7 +2187,7 @@ var WorkflowRegistryService = class {
1949
2187
  async reconcileStaleRunningRecord(workspace, runKey, expected) {
1950
2188
  const runningDir = this.getRunDirectory(workspace, RUNNING_STAGE, runKey);
1951
2189
  const runningRecordPath = (0, node_path.resolve)(runningDir, RUN_RECORD_FILENAME$1);
1952
- return this.writeCoordinator.forKey(runningRecordPath)(async () => {
2190
+ return this.withRunLock(runningRecordPath, async () => {
1953
2191
  const currentRecord = await this.readRunRecord(runningRecordPath);
1954
2192
  if (expected !== void 0 && (currentRecord.runId !== expected.runId || currentRecord.env?.PO_SESSION_ID !== expected.ownerSessionId)) {
1955
2193
  if (expected.stop) throw new Error(`Workflow run ownership changed: ${workspace}/${runKey}`);
@@ -2017,6 +2255,7 @@ var WorkflowRegistryService = class {
2017
2255
  */
2018
2256
  async writeRunRecord(recordPath, record) {
2019
2257
  await this.writeAtomic(recordPath, record);
2258
+ if (record.stage !== RUNNING_STAGE) await this.preserveRunEvidence(record, (0, node_path.dirname)(recordPath));
2020
2259
  }
2021
2260
  /**
2022
2261
  * Validate the persisted run record shape before returning it to callers.
@@ -4451,6 +4690,19 @@ var StepRunnerService = class StepRunnerService {
4451
4690
  * progress log and telemetry instead of existing only as a log line.
4452
4691
  */
4453
4692
  async executeStep(step, context, options, report) {
4693
+ let policyEnvironment = {};
4694
+ const runDir = context.env[WORKFLOW_RUN_DIR_ENV_KEY];
4695
+ if (!options.dryRun && runDir) {
4696
+ const file = (0, node_path.join)(runDir, RUN_RECORD_FILENAME);
4697
+ if (await (0, _agimon_ai_po_core_platform.pathExists)(this.runtime.fs, file, this.runtime.ioContext)) {
4698
+ const record = await new WorkflowRegistryService(this.runtime).readRunRecord(file);
4699
+ if (record.admission?.binding) {
4700
+ if (record.runId !== context.env.WORKFLOW_RUN_ID || !record.admission.ready) throw new Error("Managed workflow execution is not admitted for this generation.");
4701
+ policyEnvironment = record.admission.environment ?? {};
4702
+ Object.assign(context.env, policyEnvironment);
4703
+ }
4704
+ }
4705
+ }
4454
4706
  const fail = (reason) => {
4455
4707
  this.logger.error(` ${RED$3}${STATUS_ERROR}${RESET$4} ${reason}`);
4456
4708
  report.reason = reason;
@@ -4530,6 +4782,7 @@ var StepRunnerService = class StepRunnerService {
4530
4782
  ...stepContext.env
4531
4783
  };
4532
4784
  if (step.env) for (const [key, val] of Object.entries(step.env)) stepEnv[key] = this.parser.interpolate(String(val), stepContext);
4785
+ Object.assign(stepEnv, policyEnvironment);
4533
4786
  for (const key of [
4534
4787
  "WORKFLOW_RUN_ID",
4535
4788
  "WORKFLOW_RUN_KEY",
@@ -4619,8 +4872,9 @@ var StepRunnerService = class StepRunnerService {
4619
4872
  });
4620
4873
  await this.markPaused(options.stopRequestPath, stepName, pausePolicy);
4621
4874
  await options.onExecutionStateChange?.("paused", stepName);
4622
- await this.waitForResume(options.stopRequestPath, stepName);
4875
+ Object.assign(stepContext.env, await this.waitForResume(options.stopRequestPath, stepName, stepContext.env.WORKFLOW_RUN_ID));
4623
4876
  await options.onExecutionStateChange?.("resumed", stepName);
4877
+ return this.executeStep(step, stepContext, options, report);
4624
4878
  }
4625
4879
  let maxTimeoutRetries;
4626
4880
  try {
@@ -4693,12 +4947,11 @@ var StepRunnerService = class StepRunnerService {
4693
4947
  });
4694
4948
  await this.markPaused(options.stopRequestPath, stepName, pausePolicy, executionResult.survivors);
4695
4949
  await options.onExecutionStateChange?.("paused", stepName);
4696
- await this.waitForResume(options.stopRequestPath, stepName);
4950
+ Object.assign(stepContext.env, await this.waitForResume(options.stopRequestPath, stepName, stepContext.env.WORKFLOW_RUN_ID));
4697
4951
  await options.onExecutionStateChange?.("resumed", stepName);
4698
4952
  if (pausePolicy === "protected") return true;
4699
4953
  await this.logStepLifecycle("info", "workflow.step.replay", stepEnv, { "workflow.step.outcome": "replay-current" });
4700
- attempt -= 1;
4701
- continue;
4954
+ return this.executeStep(step, stepContext, options, report);
4702
4955
  }
4703
4956
  if (executionResult.status === "stop_requested") {
4704
4957
  this.logger.info(`\n ${YELLOW$2}${STATUS_INTERRUPTED}${RESET$4} ${stepName} (${STATUS_STOP})`);
@@ -6207,8 +6460,8 @@ var StepRunnerService = class StepRunnerService {
6207
6460
  throw error;
6208
6461
  }
6209
6462
  }
6210
- async waitForResume(stopRequestPath, stepName) {
6211
- if (!stopRequestPath) return;
6463
+ async waitForResume(stopRequestPath, stepName, expectedRunId) {
6464
+ if (!stopRequestPath) return {};
6212
6465
  const pauseRequestPath = (0, node_path.join)((0, node_path.dirname)(stopRequestPath), PAUSE_REQUEST_FILENAME);
6213
6466
  const recordPath = (0, node_path.join)((0, node_path.dirname)(stopRequestPath), RUN_RECORD_FILENAME);
6214
6467
  this.logger.info(` ${YELLOW$2}${STATUS_WAIT}${RESET$4} ${stepName} paused; waiting for resume`);
@@ -6222,26 +6475,34 @@ var StepRunnerService = class StepRunnerService {
6222
6475
  context: this.createInterruptContext(stepName, "")
6223
6476
  });
6224
6477
  if (await (0, _agimon_ai_po_core_platform.pathExists)(this.runtime.fs, pauseRequestPath, this.runtime.ioContext) && await this.readControlAction(pauseRequestPath) === "resume") {
6225
- const record = JSON.parse((0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.readTextFile(recordPath, this.runtime.ioContext)));
6226
- record.executionState = "running";
6227
- const cursor = record.executionCursor && typeof record.executionCursor === "object" ? record.executionCursor : {};
6228
- const request = JSON.parse((0, _agimon_ai_po_core_platform.getOrThrow)(await this.runtime.fs.readTextFile(pauseRequestPath, this.runtime.ioContext)));
6229
- const resumeMode = cursor.resumeMode === "continue-next" ? "continue-next" : "replay-current";
6230
- record.executionCursor = {
6231
- ...cursor,
6232
- executionGeneration: resumeMode === "replay-current" && typeof cursor.executionGeneration === "number" ? cursor.executionGeneration + 1 : typeof cursor.executionGeneration === "number" ? cursor.executionGeneration : 1,
6233
- requestId: typeof request.requestId === "string" ? request.requestId : cursor.requestId,
6234
- revision: typeof cursor.revision === "number" ? cursor.revision + 1 : 1,
6235
- resumeMode
6478
+ const registry = new WorkflowRegistryService(this.runtime);
6479
+ const current = await registry.readRunRecord(recordPath);
6480
+ if (expectedRunId !== void 0 && current.runId !== expectedRunId) throw new Error("Workflow generation changed while paused.");
6481
+ const handle = {
6482
+ runId: current.runId,
6483
+ workspace: current.workspace,
6484
+ runKey: current.runKey,
6485
+ runDir: (0, node_path.dirname)(recordPath),
6486
+ recordPath,
6487
+ displayName: current.displayName,
6488
+ stopRequestPath,
6489
+ changelogPath: (0, node_path.join)((0, node_path.dirname)(recordPath), "changelog.md"),
6490
+ contextPath: (0, node_path.join)((0, node_path.dirname)(recordPath), "context.md")
6236
6491
  };
6237
- await this.writeControlRecord(recordPath, record);
6238
- return;
6492
+ return (await registry.authorizeExecution(handle, true)).admission?.environment ?? {};
6239
6493
  }
6240
6494
  await new Promise((resolvePromise) => setTimeout(resolvePromise, STOP_REQUEST_POLL_INTERVAL_MS));
6241
6495
  }
6242
6496
  }
6243
6497
  async writeControlRecord(recordPath, record) {
6244
- await (0, _agimon_ai_po_core_platform.writeFileAtomic)(this.runtime.fs, recordPath, `${JSON.stringify(record, null, 2)}\n`, this.runtime.ioContext);
6498
+ const parsed = WorkflowRunRecordSchema.parse(record);
6499
+ if (!parsed.runId) throw new Error("Workflow pause record has no generation.");
6500
+ await new WorkflowRegistryService(this.runtime).updateExecutionState({
6501
+ recordPath,
6502
+ runId: parsed.runId,
6503
+ workspace: parsed.workspace,
6504
+ runKey: parsed.runKey
6505
+ }, "paused", parsed.executionCursor, (parsed.executionCursor?.revision ?? 1) - 1);
6245
6506
  }
6246
6507
  captureTerminalState() {
6247
6508
  if (!this.ownsTerminal) return null;
@@ -11567,6 +11828,7 @@ var RunWorkflowService = class {
11567
11828
  * hooks. `post:` is the exception, and it is not a harmless one.
11568
11829
  */
11569
11830
  let delegated = false;
11831
+ let policyAuthorized = input.admission?.binding === void 0;
11570
11832
  let progress;
11571
11833
  let result = null;
11572
11834
  let runError = null;
@@ -11671,6 +11933,8 @@ var RunWorkflowService = class {
11671
11933
  if (dryRun && input.recovery) registryRun = await this.createRecoveryDryRunHandle(input, resolvedWorkspace);
11672
11934
  else registryRun = await this.registry.createRun({
11673
11935
  activeRepairId: input.recovery?.activeRepairId,
11936
+ admission: input.admission,
11937
+ configuration: configuration(),
11674
11938
  continueOnError: input.continueOnError,
11675
11939
  displayName: workflowName,
11676
11940
  dryRun,
@@ -11696,6 +11960,11 @@ var RunWorkflowService = class {
11696
11960
  workflowWorkspace: workflow.workspace,
11697
11961
  workspace: input.workspace
11698
11962
  });
11963
+ if (!dryRun && input.admission?.binding) {
11964
+ const admitted = await this.registry.authorizeExecution(registryRun);
11965
+ Object.assign(workflowEnv, admitted.admission?.environment);
11966
+ policyAuthorized = true;
11967
+ }
11699
11968
  if (input.recovery?.activeRepairId && !dryRun) activeRepair = await this.repairService.readRepair(registryRun.runDir, input.recovery.activeRepairId);
11700
11969
  workflowEnv.WORKFLOW_RUN_DIR = registryRun.runDir;
11701
11970
  workflowEnv.WORKFLOW_RUN_ID = registryRun.runId;
@@ -11906,6 +12175,7 @@ var RunWorkflowService = class {
11906
12175
  while (!result) {
11907
12176
  workflow = this.parser.selectRunner(await this.parser.parseWorkflowFile(await currentWorkflowPath()), runner);
11908
12177
  this.useCommandSelection(workflow, input);
12178
+ if (!dryRun) await this.registry.updateConfiguration(registryRun, configuration());
11909
12179
  input.onConfigChanged?.({
11910
12180
  runKey: registryRun.runKey,
11911
12181
  runId: registryRun.runId,
@@ -12275,7 +12545,7 @@ var RunWorkflowService = class {
12275
12545
  }
12276
12546
  }
12277
12547
  }
12278
- if (workflow.post && !delegated) {
12548
+ if (workflow.post && !delegated && policyAuthorized) {
12279
12549
  this.stepRunner.resetInterruption();
12280
12550
  const settled = this.resolveRunOutcome(result, runError);
12281
12551
  workflowEnv[WORKFLOW_PHASE_ENV] = "post";
@@ -12962,12 +13232,29 @@ var RecoverWorkflowService = class {
12962
13232
  async recoverForSession(input, ownerSessionId) {
12963
13233
  return await this.recoverWithOwner(input, ownerSessionId);
12964
13234
  }
13235
+ /** Read-only reconstruction reused by admission validation; no claim, process cleanup, or runner call. */
13236
+ async prepareRecovery(input) {
13237
+ const record = await this.registry.readRunByKey(input.workspace, ERROR_STAGE, input.runKey);
13238
+ if (this.hooks.expectedRunId !== void 0 && record.runId !== this.hooks.expectedRunId) throw new Error("Workflow recovery generation changed.");
13239
+ return this.buildReplay(input, void 0, record.admission?.ownerSessionId, record);
13240
+ }
13241
+ async recoverClaimed(input, claimId, owner, record) {
13242
+ const replay = await this.buildReplay(input, claimId, owner, record);
13243
+ return this.runner.run({
13244
+ ...replay,
13245
+ onRegistered: (registration) => this.hooks.onRegistered?.(registration, replay),
13246
+ onConfigChanged: (registration) => this.hooks.onConfigChanged?.(registration, replay)
13247
+ });
13248
+ }
12965
13249
  async recoverWithOwner(input, ownerSessionId) {
12966
13250
  const record = await this.registry.readRunByKey(input.workspace, ERROR_STAGE, input.runKey);
13251
+ if (this.hooks.expectedRunId !== void 0 && record.runId !== this.hooks.expectedRunId) throw new Error("Workflow recovery generation changed.");
13252
+ if (record.admission && ownerSessionId !== record.admission.ownerSessionId) throw new Error("Managed workflow recovery must run in its original owner session.");
12967
13253
  if (input.dryRun || record.dryRun) return await this.recoverClaimed(input, void 0, void 0, record);
12968
13254
  const claim = input.recoveryClaimId ? await this.registry.adoptRunRecoveryClaim(record.workspace, record.runKey, input.recoveryClaimId) : await this.registry.claimRunRecovery(record.workspace, record.runKey, ownerSessionId);
12969
13255
  try {
12970
13256
  if (!input.recoveryClaimId) await this.registry.adoptRunRecoveryClaim(record.workspace, record.runKey, claim.claimId);
13257
+ if ((await this.registry.readRunByKey(record.workspace, ERROR_STAGE, record.runKey)).runId !== record.runId) throw new Error("Workflow recovery generation changed after claiming.");
12971
13258
  const runDir = this.registry.runDirectoryFor(record);
12972
13259
  const paneRefs = (await readWorkflowProgress(this.runtime, runDir)).flatMap((event) => event.ref?.kind === "pane" ? [event.ref.id] : []);
12973
13260
  await new NativeTerminalService$1(this.runtime).killRunPanes(record.runId, paneRefs);
@@ -12978,7 +13265,7 @@ var RecoverWorkflowService = class {
12978
13265
  throw error;
12979
13266
  }
12980
13267
  }
12981
- async recoverClaimed(input, recoveryClaimId, claimedOwnerSessionId, failedRecord) {
13268
+ async buildReplay(input, recoveryClaimId, claimedOwnerSessionId, failedRecord) {
12982
13269
  const record = failedRecord ?? await this.registry.readRunByKey(input.workspace, ERROR_STAGE, input.runKey);
12983
13270
  const activeRepair = record.activeRepairId ? await this.registry.readRunRepair(record.workspace, ERROR_STAGE, record.runKey, record.activeRepairId) : void 0;
12984
13271
  if (activeRepair && !["pending", "active"].includes(activeRepair.status)) throw new Error(`Workflow run "${record.workspace}/${record.runKey}" points to non-active repair ${activeRepair.repairId} (${activeRepair.status})`);
@@ -13002,9 +13289,26 @@ var RecoverWorkflowService = class {
13002
13289
  ].includes(value))) throw new Error("Retired workflow execution cannot be replayed. Select a supported runner explicitly.");
13003
13290
  const originalJob = fixRestartFrom && !activeRepair ? void 0 : record.job;
13004
13291
  if (!this.parser.resolveJobOrder(workflow.jobs, originalJob ?? null).includes(recoveredJob)) throw new Error(`Workflow run "${record.workspace}/${record.runKey}" recovery target "${recoveredJob}" is not in the workflow job order`);
13005
- const ownerSessionId = claimedOwnerSessionId?.trim();
13292
+ const ownerSessionId = record.admission?.ownerSessionId ?? claimedOwnerSessionId?.trim();
13293
+ const admissionId = (0, node_crypto.randomUUID)();
13294
+ const admission = this.hooks.admission ?? (record.admission ? {
13295
+ ...record.admission,
13296
+ attemptId: `recover:${admissionId}`,
13297
+ admissionId,
13298
+ reason: this.hooks.reason ?? "recover",
13299
+ previousRun: this.registry.runRef(record),
13300
+ binding: record.admission.binding ? {
13301
+ ...record.admission.binding,
13302
+ epoch: {
13303
+ admissionId,
13304
+ operationId: (0, node_crypto.randomUUID)()
13305
+ }
13306
+ } : void 0
13307
+ } : void 0);
13308
+ if (record.admission && admission && (admission.ownerSessionId !== record.admission.ownerSessionId || admission.launcherSessionId !== record.admission.launcherSessionId || JSON.stringify(admission.binding?.association) !== JSON.stringify(record.admission.binding?.association))) throw new Error("Workflow recovery policy ownership changed.");
13006
13309
  const envOverrides = ownerSessionId ? { [PO_SESSION_ENV]: ownerSessionId } : void 0;
13007
- const replay = {
13310
+ return {
13311
+ admission,
13008
13312
  runner,
13009
13313
  command: record.command,
13010
13314
  choice: record.choice,
@@ -13035,11 +13339,6 @@ var RecoverWorkflowService = class {
13035
13339
  worktreePath: record.worktreePath
13036
13340
  }
13037
13341
  };
13038
- return await this.runner.run({
13039
- ...replay,
13040
- onRegistered: (registration) => this.hooks.onRegistered?.(registration, replay),
13041
- onConfigChanged: (registration) => this.hooks.onConfigChanged?.(registration, replay)
13042
- });
13043
13342
  }
13044
13343
  parseTerminalFix(fixContent, allowTerminalStop) {
13045
13344
  if (!fixContent.startsWith(FIX_FRONTMATTER_DELIMITER)) return {};
@@ -13343,4 +13642,4 @@ Object.defineProperty(exports, "workflowShellOutput", {
13343
13642
  }
13344
13643
  });
13345
13644
 
13346
- //# sourceMappingURL=RecoverWorkflowService-lWxTE3o6.cjs.map
13645
+ //# sourceMappingURL=RecoverWorkflowService-C-iiu-ku.cjs.map