@osolmaz/pi-workflows 0.13.4 → 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/README.md +136 -118
- package/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +34 -31
- package/dist/controllers/sqlite.js +116 -77
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/index.js +721 -202
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/restart-policy.d.ts +38 -0
- package/dist/extension/restart-policy.js +116 -0
- package/dist/extension/restart-policy.js.map +1 -0
- package/dist/extension/terminal-decision.d.ts +51 -0
- package/dist/extension/terminal-decision.js +110 -0
- package/dist/extension/terminal-decision.js.map +1 -0
- package/dist/state/prune.js +36 -10
- package/dist/state/prune.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +4 -0
- package/dist/workflows/tool-input.js +6 -1
- package/dist/workflows/tool-input.js.map +1 -1
- package/docs/2026-08-25-workflow-follow-ups.md +8 -6
- package/docs/DEFERRED_TURNS.md +39 -26
- package/docs/HUMAN_DECISIONS.md +12 -4
- package/docs/SQLITE_STATE.md +24 -0
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +34 -8
- package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +357 -0
- package/docs/workflows.md +85 -29
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/skills/autodoc/SKILL.md +1 -1
- package/skills/autoimplement/SKILL.md +1 -1
- package/skills/autoplan/SKILL.md +1 -1
- package/skills/pi-workflows/SKILL.md +2 -0
- package/src/controllers/index.ts +3 -0
- package/src/controllers/sqlite.ts +226 -155
- package/src/extension/index.ts +881 -220
- package/src/extension/restart-policy.ts +163 -0
- package/src/extension/terminal-decision.ts +172 -0
- package/src/state/prune.ts +35 -9
- package/src/workflows/tool-input.ts +9 -1
|
@@ -2,6 +2,7 @@ import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { StateDatabase, workflowStatePath } from "../state/database.js";
|
|
5
|
+
import { canonicalJson } from "../state/json.js";
|
|
5
6
|
import { resourceIdFor, tokenHash } from "../state/mutation.js";
|
|
6
7
|
import {
|
|
7
8
|
EffectRequestConflictError,
|
|
@@ -39,6 +40,26 @@ export type WorkflowRunLaunchStatus =
|
|
|
39
40
|
| "failed"
|
|
40
41
|
| "cancelled";
|
|
41
42
|
|
|
43
|
+
export type WorkflowRunReservationOptions = {
|
|
44
|
+
runId: string;
|
|
45
|
+
workflowName: string;
|
|
46
|
+
workflowSourceRef: string;
|
|
47
|
+
workflowSource: unknown;
|
|
48
|
+
definitionDigest: string;
|
|
49
|
+
definitionSnapshot: unknown;
|
|
50
|
+
input: unknown;
|
|
51
|
+
launchOptions?: unknown;
|
|
52
|
+
runnerId: string;
|
|
53
|
+
originSessionId: string;
|
|
54
|
+
parentRunId?: string;
|
|
55
|
+
now?: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type WorkflowRunClaimOptions = WorkflowRunReservationOptions & {
|
|
59
|
+
claimToken: string;
|
|
60
|
+
leaseMs: number;
|
|
61
|
+
};
|
|
62
|
+
|
|
42
63
|
export type WorkflowRunQueueRecord = {
|
|
43
64
|
runId: string;
|
|
44
65
|
workflowName: string;
|
|
@@ -64,6 +85,16 @@ export type WorkflowRunQueueRecord = {
|
|
|
64
85
|
finishedAt: string | null;
|
|
65
86
|
};
|
|
66
87
|
|
|
88
|
+
export type WorkflowRunPreparationResult =
|
|
89
|
+
| {
|
|
90
|
+
state: "claimed";
|
|
91
|
+
run: WorkflowRunQueueRecord & { claimToken: string };
|
|
92
|
+
}
|
|
93
|
+
| {
|
|
94
|
+
state: "adopted";
|
|
95
|
+
run: WorkflowRunQueueRecord;
|
|
96
|
+
};
|
|
97
|
+
|
|
67
98
|
export type WorkflowNotificationRecord = {
|
|
68
99
|
notificationId: string;
|
|
69
100
|
runId: string;
|
|
@@ -84,7 +115,9 @@ export type WorkflowTurnIntentCause =
|
|
|
84
115
|
| "failed"
|
|
85
116
|
| "launchFailed"
|
|
86
117
|
| "controllerInterrupted"
|
|
87
|
-
| "claimLost"
|
|
118
|
+
| "claimLost"
|
|
119
|
+
| "terminal"
|
|
120
|
+
| "cancelled";
|
|
88
121
|
|
|
89
122
|
export type WorkflowTurnIntentResolution = "workflowPrompt" | "presentation" | "fallback";
|
|
90
123
|
|
|
@@ -996,139 +1029,147 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
996
1029
|
}));
|
|
997
1030
|
}
|
|
998
1031
|
|
|
999
|
-
reserveWorkflowRun(options: {
|
|
1000
|
-
runId: string;
|
|
1001
|
-
workflowName: string;
|
|
1002
|
-
workflowSourceRef: string;
|
|
1003
|
-
workflowSource: unknown;
|
|
1004
|
-
definitionDigest: string;
|
|
1005
|
-
definitionSnapshot: unknown;
|
|
1006
|
-
input: unknown;
|
|
1007
|
-
launchOptions?: unknown;
|
|
1008
|
-
runnerId: string;
|
|
1009
|
-
originSessionId: string;
|
|
1010
|
-
parentRunId?: string;
|
|
1011
|
-
now?: string;
|
|
1012
|
-
}): WorkflowRunQueueRecord {
|
|
1032
|
+
reserveWorkflowRun(options: WorkflowRunReservationOptions): WorkflowRunQueueRecord {
|
|
1013
1033
|
validateRunId(options.runId);
|
|
1014
1034
|
const now = epoch(validTimestamp(options.now));
|
|
1015
1035
|
const definitionDigest = digestBuffer(options.definitionDigest);
|
|
1016
|
-
return this.state.transaction(() =>
|
|
1017
|
-
|
|
1018
|
-
|
|
1036
|
+
return this.state.transaction(() =>
|
|
1037
|
+
this.reserveWorkflowRunInTransaction(options, now, definitionDigest),
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
private reserveWorkflowRunInTransaction(
|
|
1042
|
+
options: WorkflowRunReservationOptions,
|
|
1043
|
+
now: number,
|
|
1044
|
+
definitionDigest: Buffer,
|
|
1045
|
+
): WorkflowRunQueueRecord {
|
|
1046
|
+
if (this.getWorkflowRun(options.runId) !== undefined) {
|
|
1047
|
+
throw new Error(`Workflow run already reserved: ${options.runId}`);
|
|
1048
|
+
}
|
|
1049
|
+
if (options.parentRunId !== undefined) {
|
|
1050
|
+
const parent = this.requireWorkflowRunRow(options.parentRunId);
|
|
1051
|
+
if (parent.originSessionId !== options.originSessionId) {
|
|
1052
|
+
throw new Error("Continuation parent belongs to another Pi session");
|
|
1019
1053
|
}
|
|
1020
|
-
if (
|
|
1021
|
-
const
|
|
1022
|
-
if (
|
|
1023
|
-
throw new Error("Continuation parent
|
|
1054
|
+
if (parent.status === "parked") {
|
|
1055
|
+
const parentLease = this.requireLease(parent.resourceId);
|
|
1056
|
+
if (parentLease.ownerId !== null) {
|
|
1057
|
+
throw new Error("Continuation parent still has an active owner");
|
|
1024
1058
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
throw new Error("Continuation parent still has an active owner");
|
|
1029
|
-
}
|
|
1030
|
-
this.state.connection
|
|
1031
|
-
.prepare(
|
|
1032
|
-
`UPDATE run_queue
|
|
1059
|
+
this.state.connection
|
|
1060
|
+
.prepare(
|
|
1061
|
+
`UPDATE run_queue
|
|
1033
1062
|
SET status = 'done', updated_at = ?, finished_at = ?
|
|
1034
1063
|
WHERE run_id = ? AND status = 'parked'`,
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
}
|
|
1064
|
+
)
|
|
1065
|
+
.run(now, now, parent.runId);
|
|
1066
|
+
const parentRevision = this.resourceRevision(parent.resourceId);
|
|
1067
|
+
this.bumpResource(parent.resourceId, parentRevision, now);
|
|
1068
|
+
this.insertEvent(
|
|
1069
|
+
parent.resourceId,
|
|
1070
|
+
parentRevision + 1,
|
|
1071
|
+
"run.queue_done_for_continuation",
|
|
1072
|
+
"session",
|
|
1073
|
+
options.originSessionId,
|
|
1074
|
+
{ continuationRunId: options.runId },
|
|
1075
|
+
now,
|
|
1076
|
+
);
|
|
1077
|
+
} else if (parent.status === "done") {
|
|
1078
|
+
throw new Error("Continuation parent already has a reserved continuation");
|
|
1079
|
+
} else {
|
|
1080
|
+
throw new Error(`Continuation parent queue is ${parent.status}`);
|
|
1053
1081
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1082
|
+
}
|
|
1083
|
+
const resourceId = resourceIdFor("run", options.runId);
|
|
1084
|
+
const definitionHash = this.state.putJson(options.definitionSnapshot, now);
|
|
1085
|
+
const queuedSource = queuedWorkflowSource(options.workflowSource);
|
|
1086
|
+
const inputHash = this.state.putJson(options.input ?? null, now);
|
|
1087
|
+
const launchHash = this.state.putJson(options.launchOptions ?? {}, now);
|
|
1088
|
+
this.state.connection
|
|
1089
|
+
.prepare(
|
|
1090
|
+
`INSERT INTO workflow_definitions(
|
|
1062
1091
|
definition_digest, workflow_name, definition_hash, created_at
|
|
1063
1092
|
) VALUES (?, ?, ?, ?)
|
|
1064
1093
|
ON CONFLICT(definition_digest) DO NOTHING`,
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1094
|
+
)
|
|
1095
|
+
.run(definitionDigest, options.workflowName, definitionHash, now);
|
|
1096
|
+
this.state.connection
|
|
1097
|
+
.prepare(
|
|
1098
|
+
`INSERT INTO resources(resource_id, resource_type, aggregate_key, revision, created_at, updated_at)
|
|
1070
1099
|
VALUES (?, 'run', ?, 1, ?, ?)`,
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1100
|
+
)
|
|
1101
|
+
.run(resourceId, options.runId, now, now);
|
|
1102
|
+
this.state.connection
|
|
1103
|
+
.prepare("INSERT INTO leases(resource_id, generation) VALUES (?, 0)")
|
|
1104
|
+
.run(resourceId);
|
|
1105
|
+
this.state.connection
|
|
1106
|
+
.prepare(
|
|
1107
|
+
`INSERT INTO runs(
|
|
1079
1108
|
run_id, resource_id, project_id, parent_run_id, definition_digest,
|
|
1080
1109
|
workflow_ref, launch_options_hash, status, paused,
|
|
1081
1110
|
input_hash, created_at, updated_at
|
|
1082
1111
|
) VALUES (?, ?, ?, ?, ?, ?, ?, 'queued', 0, ?, ?, ?)`,
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1112
|
+
)
|
|
1113
|
+
.run(
|
|
1114
|
+
options.runId,
|
|
1115
|
+
resourceId,
|
|
1116
|
+
this.requireProjectId(),
|
|
1117
|
+
options.parentRunId ?? null,
|
|
1118
|
+
definitionDigest,
|
|
1119
|
+
options.workflowSourceRef,
|
|
1120
|
+
launchHash,
|
|
1121
|
+
inputHash,
|
|
1122
|
+
now,
|
|
1123
|
+
now,
|
|
1124
|
+
);
|
|
1125
|
+
insertQueuedRunSources(this.state, options.runId, queuedSource);
|
|
1126
|
+
this.state.connection
|
|
1127
|
+
.prepare(
|
|
1128
|
+
`INSERT INTO run_bindings(run_id, origin_session_id, execution_mode, created_at)
|
|
1100
1129
|
VALUES (?, ?, 'interactive', ?)`,
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1130
|
+
)
|
|
1131
|
+
.run(options.runId, options.originSessionId, now);
|
|
1132
|
+
this.state.connection
|
|
1133
|
+
.prepare(
|
|
1134
|
+
`INSERT INTO run_queue(
|
|
1106
1135
|
run_id, status, available_at, affinity_runner_id, origin_session_id,
|
|
1107
1136
|
consecutive_errors, created_at, updated_at
|
|
1108
1137
|
) VALUES (?, 'queued', ?, ?, ?, 0, ?, ?)`,
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1138
|
+
)
|
|
1139
|
+
.run(options.runId, now, options.runnerId, options.originSessionId, now, now);
|
|
1140
|
+
this.insertEvent(resourceId, 1, "run.queued", "session", options.originSessionId, {}, now);
|
|
1141
|
+
return this.requireWorkflowRun(options.runId);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
prepareOrAdoptWorkflowRun(options: WorkflowRunClaimOptions): WorkflowRunPreparationResult {
|
|
1145
|
+
validateRunId(options.runId);
|
|
1146
|
+
const now = epoch(validTimestamp(options.now));
|
|
1147
|
+
const definitionDigest = digestBuffer(options.definitionDigest);
|
|
1148
|
+
return this.state.transaction(() => {
|
|
1149
|
+
const existing = this.workflowRunRow(options.runId);
|
|
1150
|
+
if (existing !== undefined) {
|
|
1151
|
+
this.assertWorkflowRunPreparationCompatible(existing, options, definitionDigest);
|
|
1152
|
+
return { state: "adopted", run: this.mapWorkflowRun(existing) };
|
|
1153
|
+
}
|
|
1154
|
+
this.reserveWorkflowRunInTransaction(options, now, definitionDigest);
|
|
1155
|
+
const claimed = this.claimRunInTransaction(
|
|
1156
|
+
options.runId,
|
|
1157
|
+
options.runnerId,
|
|
1158
|
+
options.claimToken,
|
|
1159
|
+
options.leaseMs,
|
|
1160
|
+
now,
|
|
1161
|
+
);
|
|
1162
|
+
if (claimed === undefined) {
|
|
1163
|
+
throw new Error(`Workflow run could not be claimed: ${options.runId}`);
|
|
1164
|
+
}
|
|
1165
|
+
return {
|
|
1166
|
+
state: "claimed",
|
|
1167
|
+
run: claimed as WorkflowRunQueueRecord & { claimToken: string },
|
|
1168
|
+
};
|
|
1113
1169
|
});
|
|
1114
1170
|
}
|
|
1115
1171
|
|
|
1116
|
-
enqueueWorkflowRun(options: {
|
|
1117
|
-
runId: string;
|
|
1118
|
-
workflowName: string;
|
|
1119
|
-
workflowSourceRef: string;
|
|
1120
|
-
workflowSource: unknown;
|
|
1121
|
-
definitionDigest: string;
|
|
1122
|
-
definitionSnapshot: unknown;
|
|
1123
|
-
input: unknown;
|
|
1124
|
-
launchOptions?: unknown;
|
|
1125
|
-
runnerId: string;
|
|
1126
|
-
claimToken: string;
|
|
1127
|
-
leaseMs: number;
|
|
1128
|
-
originSessionId: string;
|
|
1129
|
-
parentRunId?: string;
|
|
1130
|
-
now?: string;
|
|
1131
|
-
}): WorkflowRunQueueRecord {
|
|
1172
|
+
enqueueWorkflowRun(options: WorkflowRunClaimOptions): WorkflowRunQueueRecord {
|
|
1132
1173
|
if (this.getWorkflowRun(options.runId) === undefined) {
|
|
1133
1174
|
this.reserveWorkflowRun({
|
|
1134
1175
|
runId: options.runId,
|
|
@@ -2228,6 +2269,27 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2228
2269
|
return this.mapWorkflowRun(this.requireWorkflowRunRow(runId));
|
|
2229
2270
|
}
|
|
2230
2271
|
|
|
2272
|
+
private assertWorkflowRunPreparationCompatible(
|
|
2273
|
+
row: RunRow,
|
|
2274
|
+
options: WorkflowRunReservationOptions,
|
|
2275
|
+
definitionDigest: Buffer,
|
|
2276
|
+
): void {
|
|
2277
|
+
const compatible =
|
|
2278
|
+
row.workflowName === options.workflowName &&
|
|
2279
|
+
row.workflowRef === options.workflowSourceRef &&
|
|
2280
|
+
row.definitionDigest.equals(definitionDigest) &&
|
|
2281
|
+
canonicalJson(readQueuedRunSources(this.state, row)) ===
|
|
2282
|
+
canonicalJson(queuedWorkflowSource(options.workflowSource)) &&
|
|
2283
|
+
canonicalJson(this.state.readJson(row.inputHash)) === canonicalJson(options.input ?? null) &&
|
|
2284
|
+
canonicalJson(this.state.readJson(row.launchOptionsHash)) ===
|
|
2285
|
+
canonicalJson(options.launchOptions ?? {}) &&
|
|
2286
|
+
row.originSessionId === options.originSessionId &&
|
|
2287
|
+
row.parentRunId === (options.parentRunId ?? null);
|
|
2288
|
+
if (!compatible) {
|
|
2289
|
+
throw new Error(`Workflow run preparation conflicts: ${options.runId}`);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2231
2293
|
/* istanbul ignore next -- pure projection covered by integration tests */
|
|
2232
2294
|
private mapWorkflowRun(row: RunRow): WorkflowRunQueueRecord {
|
|
2233
2295
|
return {
|
|
@@ -2267,56 +2329,65 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2267
2329
|
leaseMs: number,
|
|
2268
2330
|
now: number,
|
|
2269
2331
|
): WorkflowRunQueueRecord | undefined {
|
|
2270
|
-
return this.state.transaction(() =>
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2332
|
+
return this.state.transaction(() =>
|
|
2333
|
+
this.claimRunInTransaction(runId, runnerId, claimToken, leaseMs, now),
|
|
2334
|
+
);
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
private claimRunInTransaction(
|
|
2338
|
+
runId: string,
|
|
2339
|
+
runnerId: string,
|
|
2340
|
+
claimToken: string,
|
|
2341
|
+
leaseMs: number,
|
|
2342
|
+
now: number,
|
|
2343
|
+
): WorkflowRunQueueRecord | undefined {
|
|
2344
|
+
const row = this.workflowRunRow(runId);
|
|
2345
|
+
if (row === undefined || ["done", "failed", "cancelled"].includes(row.status)) return undefined;
|
|
2346
|
+
const lease = this.requireLease(row.resourceId);
|
|
2347
|
+
if (
|
|
2348
|
+
lease.ownerId !== null &&
|
|
2349
|
+
lease.expiresAt !== null &&
|
|
2350
|
+
lease.expiresAt > now &&
|
|
2351
|
+
lease.ownerId !== runnerId
|
|
2352
|
+
)
|
|
2353
|
+
return undefined;
|
|
2354
|
+
const generation = lease.generation + 1;
|
|
2355
|
+
const expiresAt = now + leaseMs;
|
|
2356
|
+
const result = this.state.connection
|
|
2357
|
+
.prepare(
|
|
2358
|
+
`UPDATE leases SET generation = ?, owner_type = ?, owner_id = ?, token_hash = ?,
|
|
2287
2359
|
acquired_at = ?, heartbeat_at = ?, expires_at = ?
|
|
2288
2360
|
WHERE resource_id = ? AND generation = ?`,
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
runnerId.startsWith("host-") ? "host" : "session",
|
|
2293
|
-
runnerId,
|
|
2294
|
-
tokenHash(claimToken),
|
|
2295
|
-
now,
|
|
2296
|
-
now,
|
|
2297
|
-
expiresAt,
|
|
2298
|
-
row.resourceId,
|
|
2299
|
-
lease.generation,
|
|
2300
|
-
);
|
|
2301
|
-
/* istanbul ignore if -- impossible after exact schema and transaction checks */
|
|
2302
|
-
if (result.changes !== 1) return undefined;
|
|
2303
|
-
this.state.connection
|
|
2304
|
-
.prepare("UPDATE run_queue SET status = 'starting', updated_at = ? WHERE run_id = ?")
|
|
2305
|
-
.run(now, runId);
|
|
2306
|
-
const revision = this.resourceRevision(row.resourceId);
|
|
2307
|
-
this.bumpResource(row.resourceId, revision, now);
|
|
2308
|
-
this.insertEvent(
|
|
2309
|
-
row.resourceId,
|
|
2310
|
-
revision + 1,
|
|
2311
|
-
"lease.claimed",
|
|
2361
|
+
)
|
|
2362
|
+
.run(
|
|
2363
|
+
generation,
|
|
2312
2364
|
runnerId.startsWith("host-") ? "host" : "session",
|
|
2313
2365
|
runnerId,
|
|
2314
|
-
|
|
2366
|
+
tokenHash(claimToken),
|
|
2315
2367
|
now,
|
|
2316
|
-
|
|
2368
|
+
now,
|
|
2369
|
+
expiresAt,
|
|
2370
|
+
row.resourceId,
|
|
2371
|
+
lease.generation,
|
|
2317
2372
|
);
|
|
2318
|
-
|
|
2319
|
-
|
|
2373
|
+
/* istanbul ignore if -- impossible after exact schema and transaction checks */
|
|
2374
|
+
if (result.changes !== 1) return undefined;
|
|
2375
|
+
this.state.connection
|
|
2376
|
+
.prepare("UPDATE run_queue SET status = 'starting', updated_at = ? WHERE run_id = ?")
|
|
2377
|
+
.run(now, runId);
|
|
2378
|
+
const revision = this.resourceRevision(row.resourceId);
|
|
2379
|
+
this.bumpResource(row.resourceId, revision, now);
|
|
2380
|
+
this.insertEvent(
|
|
2381
|
+
row.resourceId,
|
|
2382
|
+
revision + 1,
|
|
2383
|
+
"lease.claimed",
|
|
2384
|
+
runnerId.startsWith("host-") ? "host" : "session",
|
|
2385
|
+
runnerId,
|
|
2386
|
+
{ expiresAt },
|
|
2387
|
+
now,
|
|
2388
|
+
generation,
|
|
2389
|
+
);
|
|
2390
|
+
return { ...this.requireWorkflowRun(runId), claimToken };
|
|
2320
2391
|
}
|
|
2321
2392
|
|
|
2322
2393
|
private updateClaimedRunStatus(
|