@agimon-ai/po-workflow 0.0.0-alpha.82 → 0.0.0-alpha.83
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/dist/{RecoverWorkflowService-mue4GOSA.cjs → RecoverWorkflowService-3K1OgTtI.cjs} +144 -3
- package/dist/RecoverWorkflowService-3K1OgTtI.cjs.map +1 -0
- package/dist/{RecoverWorkflowService-x5QopQh2.mjs → RecoverWorkflowService-CJLGM3FH.mjs} +139 -4
- package/dist/RecoverWorkflowService-CJLGM3FH.mjs.map +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/extensions/server.cjs +95 -6
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.mjs +95 -6
- package/dist/extensions/server.mjs.map +1 -1
- package/package.json +8 -8
- package/src/prompts/codemode-how-to-workflow/SKILL.md +2 -0
- package/dist/RecoverWorkflowService-mue4GOSA.cjs.map +0 -1
- package/dist/RecoverWorkflowService-x5QopQh2.mjs.map +0 -1
|
@@ -637,6 +637,21 @@ const WorkflowAdmissionMetadataSchema = zod.z.object({
|
|
|
637
637
|
admissionId: zod.z.string().uuid(),
|
|
638
638
|
operationId: zod.z.string().uuid()
|
|
639
639
|
})
|
|
640
|
+
}).optional(),
|
|
641
|
+
/**
|
|
642
|
+
* What the bound coordinator is owed for this terminal generation. Undelivered settlements are
|
|
643
|
+
* retried until the coordinator acknowledges them, so a held lock is never orphaned.
|
|
644
|
+
*/
|
|
645
|
+
settlement: zod.z.object({
|
|
646
|
+
outcome: zod.z.enum([
|
|
647
|
+
"success",
|
|
648
|
+
"skipped",
|
|
649
|
+
"failed",
|
|
650
|
+
"interrupted",
|
|
651
|
+
"abandoned"
|
|
652
|
+
]),
|
|
653
|
+
delivered: zod.z.boolean(),
|
|
654
|
+
at: zod.z.string()
|
|
640
655
|
}).optional()
|
|
641
656
|
});
|
|
642
657
|
const WorkflowAdmissionIntentSchema = zod.z.object({
|
|
@@ -992,6 +1007,93 @@ function summarizeWorkflowProgress(events) {
|
|
|
992
1007
|
return [...jobs.values()];
|
|
993
1008
|
}
|
|
994
1009
|
//#endregion
|
|
1010
|
+
//#region src/services/workflowSettlement/index.ts
|
|
1011
|
+
/** What a terminal bound generation is owed and not yet acknowledged, or undefined when nothing is. */
|
|
1012
|
+
function owedSettlement(record) {
|
|
1013
|
+
const admission = record.admission;
|
|
1014
|
+
if (!admission?.binding || record.stage === "running" || admission.settlement?.delivered === true) return void 0;
|
|
1015
|
+
return admission.settlement?.outcome ?? record.outcome ?? (record.stage === "completed" ? "success" : "interrupted");
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Durable settlement delivery. A held lock is released only through its coordinator, so every owed
|
|
1019
|
+
* settlement is retried (on coordinator registration and whenever a run settles) until acknowledged.
|
|
1020
|
+
*/
|
|
1021
|
+
function createWorkflowSettlement(options) {
|
|
1022
|
+
let sweeping;
|
|
1023
|
+
let again = false;
|
|
1024
|
+
const deliverOne = async (registry, record) => {
|
|
1025
|
+
const outcome = owedSettlement(record);
|
|
1026
|
+
const admission = record.admission;
|
|
1027
|
+
const run = registry.runRef(record);
|
|
1028
|
+
if (!outcome || !admission?.binding || !run || admission.launcherSessionId !== options.sessionId) return;
|
|
1029
|
+
if (await options.settle({
|
|
1030
|
+
binding: structuredClone(admission.binding),
|
|
1031
|
+
run,
|
|
1032
|
+
outcome,
|
|
1033
|
+
...record.failedJob === void 0 ? {} : { failedJob: record.failedJob },
|
|
1034
|
+
ownerSessionId: admission.ownerSessionId,
|
|
1035
|
+
launcherSessionId: admission.launcherSessionId,
|
|
1036
|
+
signal: options.signal
|
|
1037
|
+
}) !== "unavailable") await registry.recordSettlement(record, {
|
|
1038
|
+
outcome,
|
|
1039
|
+
delivered: true
|
|
1040
|
+
}, outcome);
|
|
1041
|
+
};
|
|
1042
|
+
const sweep = async () => {
|
|
1043
|
+
for (const registry of await options.registries()) for (const record of await registry.listRuns()) {
|
|
1044
|
+
if (options.signal.aborted) return;
|
|
1045
|
+
try {
|
|
1046
|
+
await deliverOne(registry, record);
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
options.report(error);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
/** Coalesces concurrent requests into one sweep, plus one more if a request arrived mid-sweep. */
|
|
1053
|
+
const deliver = () => {
|
|
1054
|
+
if (sweeping) {
|
|
1055
|
+
again = true;
|
|
1056
|
+
return sweeping;
|
|
1057
|
+
}
|
|
1058
|
+
sweeping = (async () => {
|
|
1059
|
+
do {
|
|
1060
|
+
again = false;
|
|
1061
|
+
await sweep();
|
|
1062
|
+
} while (again && !options.signal.aborted);
|
|
1063
|
+
})().finally(() => {
|
|
1064
|
+
sweeping = void 0;
|
|
1065
|
+
});
|
|
1066
|
+
return sweeping;
|
|
1067
|
+
};
|
|
1068
|
+
/**
|
|
1069
|
+
* A person gives up a failed bound run: its coordinator releases what it holds and the run is never
|
|
1070
|
+
* recovered. Takes the recovery claim so it cannot race a recovery of the same run.
|
|
1071
|
+
*/
|
|
1072
|
+
const abandon = async (registry, run) => {
|
|
1073
|
+
const record = await registry.readRunByKey(run.registryWorkspace, "error", run.runKey);
|
|
1074
|
+
if (record.runId !== run.runId) throw new Error("Workflow run generation changed.");
|
|
1075
|
+
if (!record.admission?.binding) throw new Error("This run holds no lock, so there is nothing to abandon.");
|
|
1076
|
+
if (record.admission.settlement?.outcome === "abandoned") {
|
|
1077
|
+
deliver();
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
const claim = await registry.claimRunRecovery(record.workspace, record.runKey, options.sessionId);
|
|
1081
|
+
try {
|
|
1082
|
+
if (!await registry.recordSettlement(record, {
|
|
1083
|
+
outcome: "abandoned",
|
|
1084
|
+
delivered: false
|
|
1085
|
+
})) throw new Error("Workflow run changed before it could be abandoned.");
|
|
1086
|
+
} finally {
|
|
1087
|
+
await registry.releaseRunRecoveryClaim(record.workspace, record.runKey, claim.claimId);
|
|
1088
|
+
}
|
|
1089
|
+
await deliver();
|
|
1090
|
+
};
|
|
1091
|
+
return {
|
|
1092
|
+
deliver,
|
|
1093
|
+
abandon
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
//#endregion
|
|
995
1097
|
//#region src/services/WorkflowRegistryService/index.ts
|
|
996
1098
|
/**
|
|
997
1099
|
* WorkflowRegistryService
|
|
@@ -1108,6 +1210,33 @@ var WorkflowRegistryService = class {
|
|
|
1108
1210
|
registryWorkspace: record.workspace
|
|
1109
1211
|
} : void 0;
|
|
1110
1212
|
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Record what a terminal bound generation owes its coordinator, or that the coordinator acknowledged it.
|
|
1215
|
+
* Fenced by run and operation, so a replaced generation is never marked. With `expected`, it writes only
|
|
1216
|
+
* while that outcome is still the one owed, so an abandon that lands mid-delivery is never marked delivered.
|
|
1217
|
+
*/
|
|
1218
|
+
async recordSettlement(record, settlement, expected) {
|
|
1219
|
+
if (record.stage === RUNNING_STAGE || !record.admission?.binding) return false;
|
|
1220
|
+
const recordPath = (0, node_path.resolve)(this.runDirectoryFor(record), RUN_RECORD_FILENAME$1);
|
|
1221
|
+
if (!await this.pathExists(recordPath)) return false;
|
|
1222
|
+
return this.withRunLock(recordPath, async () => {
|
|
1223
|
+
const current = await this.readRunRecord(recordPath);
|
|
1224
|
+
const admission = current.admission;
|
|
1225
|
+
if (current.runId !== record.runId || current.stage === RUNNING_STAGE || admission?.binding?.epoch.operationId !== record.admission?.binding?.epoch.operationId) return false;
|
|
1226
|
+
if (expected !== void 0 && owedSettlement(current) !== expected) return false;
|
|
1227
|
+
await this.writeRunRecord(recordPath, {
|
|
1228
|
+
...current,
|
|
1229
|
+
admission: {
|
|
1230
|
+
...admission,
|
|
1231
|
+
settlement: {
|
|
1232
|
+
...settlement,
|
|
1233
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
return true;
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1111
1240
|
/** Exact-ID evidence survives replay, deletion and a lost secondary admission index write. */
|
|
1112
1241
|
async readRunEvidence(runId) {
|
|
1113
1242
|
const current = (await this.listRuns()).find((record) => record.runId === runId);
|
|
@@ -13254,7 +13383,9 @@ var RecoverWorkflowService = class {
|
|
|
13254
13383
|
const claim = input.recoveryClaimId ? await this.registry.adoptRunRecoveryClaim(record.workspace, record.runKey, input.recoveryClaimId) : await this.registry.claimRunRecovery(record.workspace, record.runKey, ownerSessionId);
|
|
13255
13384
|
try {
|
|
13256
13385
|
if (!input.recoveryClaimId) await this.registry.adoptRunRecoveryClaim(record.workspace, record.runKey, claim.claimId);
|
|
13257
|
-
|
|
13386
|
+
const current = await this.registry.readRunByKey(record.workspace, ERROR_STAGE, record.runKey);
|
|
13387
|
+
if (current.runId !== record.runId) throw new Error("Workflow recovery generation changed after claiming.");
|
|
13388
|
+
if (current.admission?.settlement?.outcome === "abandoned") throw new Error(`Workflow run "${record.workspace}/${record.runKey}" was abandoned and cannot be recovered`);
|
|
13258
13389
|
const runDir = this.registry.runDirectoryFor(record);
|
|
13259
13390
|
const paneRefs = (await readWorkflowProgress(this.runtime, runDir)).flatMap((event) => event.ref?.kind === "pane" ? [event.ref.id] : []);
|
|
13260
13391
|
await new NativeTerminalService$1(this.runtime).killRunPanes(record.runId, paneRefs);
|
|
@@ -13276,6 +13407,7 @@ var RecoverWorkflowService = class {
|
|
|
13276
13407
|
const terminalFixContent = activeRepair && !terminalStopAuthorized ? null : await this.registry.readRunFixContent(record.workspace, ERROR_STAGE, record.runKey);
|
|
13277
13408
|
const terminalFix = terminalFixContent ? this.parseTerminalFix(terminalFixContent, terminalStopAuthorized) : {};
|
|
13278
13409
|
if (terminalFix.terminal) throw new Error(`Workflow run "${record.workspace}/${record.runKey}" has a terminal fix request and cannot be recovered`);
|
|
13410
|
+
if (record.admission?.settlement?.outcome === "abandoned") throw new Error(`Workflow run "${record.workspace}/${record.runKey}" was abandoned and cannot be recovered`);
|
|
13279
13411
|
const fixRestartFrom = terminalFix.restartFrom;
|
|
13280
13412
|
const recoveredJob = activeRepair ? this.repairService.resolveRecoveryStart(activeRepair) : fixRestartFrom ?? record.failedJob;
|
|
13281
13413
|
if (!recoveredJob) throw new Error(`Workflow run "${record.workspace}/${record.runKey}" does not include a recoverable failed job`);
|
|
@@ -13291,8 +13423,11 @@ var RecoverWorkflowService = class {
|
|
|
13291
13423
|
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`);
|
|
13292
13424
|
const ownerSessionId = record.admission?.ownerSessionId ?? claimedOwnerSessionId?.trim();
|
|
13293
13425
|
const admissionId = (0, node_crypto.randomUUID)();
|
|
13426
|
+
const { settlement: _settlement, ...inherited } = record.admission ?? {};
|
|
13294
13427
|
const admission = this.hooks.admission ?? (record.admission ? {
|
|
13295
|
-
...
|
|
13428
|
+
...inherited,
|
|
13429
|
+
ownerSessionId: record.admission.ownerSessionId,
|
|
13430
|
+
launcherSessionId: record.admission.launcherSessionId,
|
|
13296
13431
|
attemptId: `recover:${admissionId}`,
|
|
13297
13432
|
admissionId,
|
|
13298
13433
|
reason: this.hooks.reason ?? "recover",
|
|
@@ -13509,6 +13644,12 @@ Object.defineProperty(exports, "createWorkflowRuntime", {
|
|
|
13509
13644
|
return createWorkflowRuntime;
|
|
13510
13645
|
}
|
|
13511
13646
|
});
|
|
13647
|
+
Object.defineProperty(exports, "createWorkflowSettlement", {
|
|
13648
|
+
enumerable: true,
|
|
13649
|
+
get: function() {
|
|
13650
|
+
return createWorkflowSettlement;
|
|
13651
|
+
}
|
|
13652
|
+
});
|
|
13512
13653
|
Object.defineProperty(exports, "diagnoseWorkflow", {
|
|
13513
13654
|
enumerable: true,
|
|
13514
13655
|
get: function() {
|
|
@@ -13642,4 +13783,4 @@ Object.defineProperty(exports, "workflowShellOutput", {
|
|
|
13642
13783
|
}
|
|
13643
13784
|
});
|
|
13644
13785
|
|
|
13645
|
-
//# sourceMappingURL=RecoverWorkflowService-
|
|
13786
|
+
//# sourceMappingURL=RecoverWorkflowService-3K1OgTtI.cjs.map
|