@evomap/evolver-core 2.0.0-beta.6 → 2.0.0-beta.7
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/algo/conversationSniffer.js +6 -2
- package/dist/events/paths.d.ts +9 -9
- package/dist/events/paths.js +18 -18
- package/dist/exec/claudeBridge.d.ts +3 -0
- package/dist/exec/claudeBridge.js +146 -31
- package/dist/issueReporter/index.js +24 -4
- package/dist/util/fileLock.d.ts +5 -3
- package/dist/util/fileLock.js +131 -50
- package/dist/workflow/dsl.d.ts +24 -3
- package/dist/workflow/dsl.js +4 -0
- package/dist/workflow/engine.d.ts +5 -1
- package/dist/workflow/engine.js +3 -0
- package/dist/workflow/index.d.ts +3 -1
- package/dist/workflow/index.js +3 -1
- package/dist/workflow/runtime.d.ts +110 -0
- package/dist/workflow/runtime.js +1298 -0
- package/dist/workflow/stateStore.d.ts +172 -0
- package/dist/workflow/stateStore.js +1044 -0
- package/package.json +1 -1
package/dist/util/fileLock.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { closeSync, constants, fstatSync, lstatSync, openSync, readSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { closeSync, constants, existsSync, fstatSync, linkSync, lstatSync, openSync, readSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
3
3
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
4
4
|
export function syncSleep(ms) {
|
|
5
5
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
6
6
|
}
|
|
7
|
+
const MALFORMED_LOCK_GRACE_NS = 1000000000n;
|
|
7
8
|
const ownedLocks = new Map();
|
|
9
|
+
const malformedLockObservations = new Map();
|
|
8
10
|
export class LockTimeoutError extends Error {
|
|
9
11
|
code = 'LOCK_TIMEOUT';
|
|
10
12
|
constructor(_lockPath) {
|
|
@@ -60,7 +62,7 @@ function currentOwnerStat(path) {
|
|
|
60
62
|
throw error;
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
|
-
function
|
|
65
|
+
function readOwnerFileBounded(path) {
|
|
64
66
|
const before = currentOwnerStat(path);
|
|
65
67
|
if (before === null)
|
|
66
68
|
return null;
|
|
@@ -95,12 +97,25 @@ function readOwnerBounded(path) {
|
|
|
95
97
|
}
|
|
96
98
|
if (total > MAX_LOCK_OWNER_BYTES)
|
|
97
99
|
throw new UnsafeLockPathError('owner_too_large');
|
|
98
|
-
|
|
100
|
+
const settled = fstatSync(fd, { bigint: true });
|
|
101
|
+
assertRegularOwnerStat(settled);
|
|
102
|
+
const settledPath = currentOwnerStat(path);
|
|
103
|
+
if (settledPath === null
|
|
104
|
+
|| settledPath.dev !== settled.dev
|
|
105
|
+
|| settledPath.ino !== settled.ino
|
|
106
|
+
|| settledPath.size !== settled.size
|
|
107
|
+
|| settledPath.mtimeNs !== settled.mtimeNs) {
|
|
108
|
+
throw new UnsafeLockPathError('path_changed');
|
|
109
|
+
}
|
|
110
|
+
return { raw: buffer.subarray(0, total).toString('utf8'), stat: settled };
|
|
99
111
|
}
|
|
100
112
|
finally {
|
|
101
113
|
closeSync(fd);
|
|
102
114
|
}
|
|
103
115
|
}
|
|
116
|
+
function readOwnerBounded(path) {
|
|
117
|
+
return readOwnerFileBounded(path)?.raw ?? null;
|
|
118
|
+
}
|
|
104
119
|
/** True if pid is a live process (cross-platform via signal 0; EPERM = exists but owned by another user). */
|
|
105
120
|
function pidAlive(pid) {
|
|
106
121
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
@@ -147,11 +162,19 @@ function parseOwner(raw) {
|
|
|
147
162
|
return null;
|
|
148
163
|
return parseJsonOwner(trimmed) ?? parseLegacyOwner(trimmed);
|
|
149
164
|
}
|
|
150
|
-
/**
|
|
151
|
-
function
|
|
165
|
+
/** Snapshot of the lock inode and its owner payload; null if the path disappeared. */
|
|
166
|
+
function lockSnapshot(lockPath) {
|
|
152
167
|
try {
|
|
153
|
-
const
|
|
154
|
-
|
|
168
|
+
const read = readOwnerFileBounded(lockPath);
|
|
169
|
+
if (read === null)
|
|
170
|
+
return null;
|
|
171
|
+
return {
|
|
172
|
+
owner: parseOwner(read.raw),
|
|
173
|
+
dev: read.stat.dev,
|
|
174
|
+
ino: read.stat.ino,
|
|
175
|
+
size: read.stat.size,
|
|
176
|
+
mtimeNs: read.stat.mtimeNs,
|
|
177
|
+
};
|
|
155
178
|
}
|
|
156
179
|
catch (error) {
|
|
157
180
|
// A valid owner can release and another waiter can acquire between lstat/open/fstat. Treat that snapshot as
|
|
@@ -161,6 +184,31 @@ function lockOwner(lockPath) {
|
|
|
161
184
|
throw error;
|
|
162
185
|
}
|
|
163
186
|
}
|
|
187
|
+
function sameInode(left, right) {
|
|
188
|
+
return left !== null && right !== null && left.dev === right.dev && left.ino === right.ino;
|
|
189
|
+
}
|
|
190
|
+
function sameSnapshot(left, right) {
|
|
191
|
+
return sameInode(left, right)
|
|
192
|
+
&& left?.size === right?.size
|
|
193
|
+
&& left?.mtimeNs === right?.mtimeNs;
|
|
194
|
+
}
|
|
195
|
+
function forgetMalformedObservation(lockPath) {
|
|
196
|
+
malformedLockObservations.delete(lockKey(lockPath));
|
|
197
|
+
}
|
|
198
|
+
function reclaimableSnapshot(lockPath, snapshot) {
|
|
199
|
+
if (snapshot.owner !== null) {
|
|
200
|
+
forgetMalformedObservation(lockPath);
|
|
201
|
+
return snapshot.owner.pid !== process.pid && !pidAlive(snapshot.owner.pid);
|
|
202
|
+
}
|
|
203
|
+
const key = lockKey(lockPath);
|
|
204
|
+
const now = process.hrtime.bigint();
|
|
205
|
+
const observed = malformedLockObservations.get(key);
|
|
206
|
+
if (observed === undefined || !sameSnapshot(observed.snapshot, snapshot)) {
|
|
207
|
+
malformedLockObservations.set(key, { snapshot, firstSeenAtNs: now });
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
return now - observed.firstSeenAtNs >= MALFORMED_LOCK_GRACE_NS;
|
|
211
|
+
}
|
|
164
212
|
function sameOwner(parsed, owner) {
|
|
165
213
|
return parsed?.pid === owner.pid && parsed.token === owner.token;
|
|
166
214
|
}
|
|
@@ -179,12 +227,68 @@ function removeFileIfExists(path) {
|
|
|
179
227
|
throw error;
|
|
180
228
|
}
|
|
181
229
|
}
|
|
230
|
+
function pathExists(path) {
|
|
231
|
+
return existsSync(path);
|
|
232
|
+
}
|
|
233
|
+
function restoreMovedFile(lockPath, movedPath, moved) {
|
|
234
|
+
try {
|
|
235
|
+
linkSync(movedPath, lockPath);
|
|
236
|
+
removeFileIfExists(movedPath);
|
|
237
|
+
forgetMalformedObservation(lockPath);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (!isErrno(error, 'EEXIST'))
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
const current = lockSnapshot(lockPath);
|
|
245
|
+
if (sameInode(current, moved)) {
|
|
246
|
+
removeFileIfExists(movedPath);
|
|
247
|
+
forgetMalformedObservation(lockPath);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
throw new Error(`文件锁在安全回收期间被替换,已保留候选文件: ${movedPath}`);
|
|
251
|
+
}
|
|
252
|
+
function removeUnchangedLock(lockPath, expected, kind) {
|
|
253
|
+
const movedPath = sidecarPath(lockPath, kind);
|
|
254
|
+
try {
|
|
255
|
+
renameSync(lockPath, movedPath);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
if (isErrno(error, 'ENOENT'))
|
|
259
|
+
return false;
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
const moved = lockSnapshot(movedPath);
|
|
263
|
+
if (!sameSnapshot(moved, expected)) {
|
|
264
|
+
if (moved !== null)
|
|
265
|
+
restoreMovedFile(lockPath, movedPath, moved);
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
removeFileIfExists(movedPath);
|
|
269
|
+
forgetMalformedObservation(lockPath);
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
function removeFileIfOwned(lockPath, owner) {
|
|
273
|
+
const snapshot = lockSnapshot(lockPath);
|
|
274
|
+
if (snapshot === null || !sameOwner(snapshot.owner, owner))
|
|
275
|
+
return false;
|
|
276
|
+
return removeUnchangedLock(lockPath, snapshot, 'released');
|
|
277
|
+
}
|
|
182
278
|
function createLockFile(lockPath, owner, trackOwnership) {
|
|
279
|
+
const guardPath = mutationGuardPath(lockPath);
|
|
280
|
+
if (trackOwnership && pathExists(guardPath))
|
|
281
|
+
return false;
|
|
183
282
|
for (let permissionAttempt = 0; permissionAttempt < 2; permissionAttempt++) {
|
|
184
283
|
try {
|
|
185
284
|
writeFileSync(lockPath, serializeOwner(owner), { flag: 'wx', mode: 0o600 });
|
|
285
|
+
if (trackOwnership && pathExists(guardPath)) {
|
|
286
|
+
removeFileIfOwned(lockPath, owner);
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
186
289
|
if (trackOwnership)
|
|
187
290
|
ownedLocks.set(lockKey(lockPath), { owner, releasePending: false });
|
|
291
|
+
forgetMalformedObservation(lockPath);
|
|
188
292
|
return true;
|
|
189
293
|
}
|
|
190
294
|
catch (error) {
|
|
@@ -210,55 +314,25 @@ function tryAcquireMutationGuard(lockPath) {
|
|
|
210
314
|
const guard = newOwner();
|
|
211
315
|
if (createLockFile(guardPath, guard, false))
|
|
212
316
|
return guard;
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
317
|
+
const snapshot = lockSnapshot(guardPath);
|
|
318
|
+
if (snapshot === null || !reclaimableSnapshot(guardPath, snapshot))
|
|
319
|
+
return null;
|
|
320
|
+
if (!removeUnchangedLock(guardPath, snapshot, 'stale'))
|
|
215
321
|
return null;
|
|
216
|
-
const stalePath = sidecarPath(guardPath, 'stale');
|
|
217
|
-
try {
|
|
218
|
-
renameSync(guardPath, stalePath);
|
|
219
|
-
}
|
|
220
|
-
catch (error) {
|
|
221
|
-
if (isErrno(error, 'ENOENT'))
|
|
222
|
-
return null;
|
|
223
|
-
throw error;
|
|
224
|
-
}
|
|
225
|
-
removeFileIfExists(stalePath);
|
|
226
322
|
return createLockFile(guardPath, guard, false) ? guard : null;
|
|
227
323
|
}
|
|
228
324
|
function releaseMutationGuard(lockPath, guard) {
|
|
229
|
-
|
|
230
|
-
if (!sameOwner(lockOwner(guardPath), guard))
|
|
231
|
-
return;
|
|
232
|
-
const releasedPath = sidecarPath(guardPath, 'released');
|
|
233
|
-
try {
|
|
234
|
-
renameSync(guardPath, releasedPath);
|
|
235
|
-
}
|
|
236
|
-
catch (error) {
|
|
237
|
-
if (isErrno(error, 'ENOENT'))
|
|
238
|
-
return;
|
|
239
|
-
throw error;
|
|
240
|
-
}
|
|
241
|
-
removeFileIfExists(releasedPath);
|
|
325
|
+
removeFileIfOwned(mutationGuardPath(lockPath), guard);
|
|
242
326
|
}
|
|
243
327
|
function reclaimStaleLock(lockPath) {
|
|
244
328
|
const guard = tryAcquireMutationGuard(lockPath);
|
|
245
329
|
if (guard === null)
|
|
246
330
|
return false;
|
|
247
331
|
try {
|
|
248
|
-
const
|
|
249
|
-
if (
|
|
332
|
+
const snapshot = lockSnapshot(lockPath);
|
|
333
|
+
if (snapshot === null || !reclaimableSnapshot(lockPath, snapshot))
|
|
250
334
|
return false;
|
|
251
|
-
|
|
252
|
-
try {
|
|
253
|
-
renameSync(lockPath, stalePath);
|
|
254
|
-
}
|
|
255
|
-
catch (error) {
|
|
256
|
-
if (isErrno(error, 'ENOENT'))
|
|
257
|
-
return false;
|
|
258
|
-
throw error;
|
|
259
|
-
}
|
|
260
|
-
removeFileIfExists(stalePath);
|
|
261
|
-
return true;
|
|
335
|
+
return removeUnchangedLock(lockPath, snapshot, 'stale');
|
|
262
336
|
}
|
|
263
337
|
finally {
|
|
264
338
|
releaseMutationGuard(lockPath, guard);
|
|
@@ -270,9 +344,11 @@ function reclaimStaleLock(lockPath) {
|
|
|
270
344
|
* The lock file records the owner pid and token. If a waiter finds the lock held by a pid that is no longer
|
|
271
345
|
* alive (the owner crashed without releaseLock), it reclaims the stale lock instead of spinning
|
|
272
346
|
* until timeout — otherwise one crashed process would deadlock every future writer until the file
|
|
273
|
-
* is removed by hand.
|
|
274
|
-
*
|
|
275
|
-
*
|
|
347
|
+
* is removed by hand. Empty or truncated locks are reclaimed only after the same inode and contents
|
|
348
|
+
* remain malformed for a grace period, so a live creator can finish publishing its owner payload.
|
|
349
|
+
* Acquisition stays atomic (O_EXCL), and stale reclaim moves the old lock aside under a mutation
|
|
350
|
+
* guard, then verifies the inode snapshot before deletion. A live owner's lock (including this
|
|
351
|
+
* process's own) is never stolen.
|
|
276
352
|
*
|
|
277
353
|
* NOTE: still synchronous (blocks the event loop while waiting) by design — it guards short
|
|
278
354
|
* synchronous critical sections (append-only writes).
|
|
@@ -291,12 +367,17 @@ export function acquireLock(lockPath, opts = {}) {
|
|
|
291
367
|
if (createLockFile(lockPath, owner, true)) {
|
|
292
368
|
return;
|
|
293
369
|
}
|
|
294
|
-
const current =
|
|
295
|
-
if (current !== null &&
|
|
370
|
+
const current = lockSnapshot(lockPath);
|
|
371
|
+
if (current !== null && reclaimableSnapshot(lockPath, current)) {
|
|
296
372
|
if (!reclaimStaleLock(lockPath))
|
|
297
373
|
syncSleep(waitMs);
|
|
298
374
|
continue;
|
|
299
375
|
}
|
|
376
|
+
if (current === null && pathExists(mutationGuardPath(lockPath))) {
|
|
377
|
+
const guard = tryAcquireMutationGuard(lockPath);
|
|
378
|
+
if (guard !== null)
|
|
379
|
+
releaseMutationGuard(lockPath, guard);
|
|
380
|
+
}
|
|
300
381
|
syncSleep(waitMs);
|
|
301
382
|
}
|
|
302
383
|
throw new LockTimeoutError();
|
package/dist/workflow/dsl.d.ts
CHANGED
|
@@ -14,13 +14,26 @@ export interface CoreCall {
|
|
|
14
14
|
fn: string;
|
|
15
15
|
args: Value[];
|
|
16
16
|
}
|
|
17
|
-
export
|
|
17
|
+
export type WorkflowErrorClass = 'transient' | 'permanent' | 'safety' | 'unknown';
|
|
18
|
+
export declare function containsWorkflowSensitiveText(value: string): boolean;
|
|
19
|
+
export interface WorkflowRetryPolicy {
|
|
20
|
+
maxAttempts: number;
|
|
21
|
+
initialDelayMs?: number;
|
|
22
|
+
maxDelayMs?: number;
|
|
23
|
+
multiplier?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface DurableStepOptions {
|
|
26
|
+
/** Agent steps default to non-idempotent; script steps default to idempotent. */
|
|
27
|
+
idempotency?: 'idempotent' | 'non_idempotent';
|
|
28
|
+
retry?: WorkflowRetryPolicy;
|
|
29
|
+
}
|
|
30
|
+
export interface ScriptStep extends DurableStepOptions {
|
|
18
31
|
id: string;
|
|
19
32
|
kind: 'script';
|
|
20
33
|
setOutput: string;
|
|
21
34
|
call: CoreCall;
|
|
22
35
|
}
|
|
23
|
-
export interface AgentStep {
|
|
36
|
+
export interface AgentStep extends DurableStepOptions {
|
|
24
37
|
id: string;
|
|
25
38
|
kind: 'agent';
|
|
26
39
|
prompt: string;
|
|
@@ -42,8 +55,16 @@ export interface IfStep {
|
|
|
42
55
|
then: WorkflowStep[];
|
|
43
56
|
else?: WorkflowStep[];
|
|
44
57
|
}
|
|
45
|
-
|
|
58
|
+
/** Durable human gate. Execution stops before subsequent steps until an operator approves or rejects it. */
|
|
59
|
+
export interface ApprovalStep {
|
|
60
|
+
id: string;
|
|
61
|
+
kind: 'approval';
|
|
62
|
+
label?: string;
|
|
63
|
+
}
|
|
64
|
+
export type WorkflowStep = ScriptStep | AgentStep | ForeachStep | IfStep | ApprovalStep;
|
|
46
65
|
export interface WorkflowSpec {
|
|
66
|
+
/** Stable caller-supplied identity. If omitted, the durable runtime derives one from the definition. */
|
|
67
|
+
workflowId?: string;
|
|
47
68
|
name: string;
|
|
48
69
|
input?: Record<string, unknown>;
|
|
49
70
|
steps: WorkflowStep[];
|
package/dist/workflow/dsl.js
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
* 表达式表示为**结构化对象**(非字符串解析): 引用 Ref + 白名单 core 调用, 安全 by-construction.
|
|
4
4
|
*/
|
|
5
5
|
export function isRef(v) { return typeof v === 'object' && v !== null && 'ref' in v; }
|
|
6
|
+
const WORKFLOW_SENSITIVE_TEXT_RE = /(?:\bBearer\s+[A-Za-z0-9._~+/=-]{8,}|\bsk-[A-Za-z0-9_-]{12,}|\b(?:gh[oprsu]|npm)_[A-Za-z0-9_-]{8,}|\bgithub_pat_[A-Za-z0-9_]{8,}|\b(?:AKIA|ASIA)[0-9A-Z]{16}\b|\bAIza[0-9A-Za-z_-]{35}\b|-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:password|passwd|secret|token|api[_-]?key|accountkey)\s*[:=]\s*\S+)/i;
|
|
7
|
+
export function containsWorkflowSensitiveText(value) {
|
|
8
|
+
return WORKFLOW_SENSITIVE_TEXT_RE.test(value);
|
|
9
|
+
}
|
|
6
10
|
export const CORE_WHITELIST = {
|
|
7
11
|
list_len: (a) => (Array.isArray(a) ? a.length : 0),
|
|
8
12
|
filter_by_mask: (items, mask) => (Array.isArray(items) && Array.isArray(mask) ? items.filter((_, i) => Boolean(mask[i])) : []),
|
|
@@ -4,8 +4,12 @@ export interface WorkflowContext {
|
|
|
4
4
|
steps: Record<string, unknown>;
|
|
5
5
|
item?: unknown;
|
|
6
6
|
}
|
|
7
|
+
export interface AgentBridgeOptions {
|
|
8
|
+
/** Cooperative cancellation for the current durable attempt. */
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
}
|
|
7
11
|
/** agent step 桥(注入 runtime; 孵化期可注 stub). */
|
|
8
|
-
export type AgentBridge = (prompt: string, ctx: WorkflowContext) => Promise<unknown> | unknown;
|
|
12
|
+
export type AgentBridge = (prompt: string, ctx: WorkflowContext, options?: AgentBridgeOptions) => Promise<unknown> | unknown;
|
|
9
13
|
export interface WorkflowEngineDeps {
|
|
10
14
|
core?: Record<string, CoreFn>;
|
|
11
15
|
agent: AgentBridge;
|
package/dist/workflow/engine.js
CHANGED
|
@@ -68,6 +68,9 @@ export class WorkflowEngine {
|
|
|
68
68
|
await this.runSteps(branch, ctx);
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
+
case 'approval': {
|
|
72
|
+
throw new WorkflowError('approval steps require DurableWorkflowRuntime');
|
|
73
|
+
}
|
|
71
74
|
case 'foreach': {
|
|
72
75
|
const items = resolveRef(step.over.ref, ctx);
|
|
73
76
|
if (!Array.isArray(items))
|
package/dist/workflow/index.d.ts
CHANGED
package/dist/workflow/index.js
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type CoreFn, type WorkflowErrorClass, type WorkflowSpec } from './dsl.js';
|
|
2
|
+
import type { AgentBridge, WorkflowContext } from './engine.js';
|
|
3
|
+
import { WorkflowStateError, WorkflowStateStore, type WorkflowControlState, type WorkflowOperatorOptions, type WorkflowRunState, type WorkflowStateStoreOptions, type WorkflowStepState } from './stateStore.js';
|
|
4
|
+
export declare const MAX_WORKFLOW_DEFINITION_STEPS = 256;
|
|
5
|
+
export declare const MAX_WORKFLOW_NESTING_DEPTH = 16;
|
|
6
|
+
export declare const MAX_WORKFLOW_FOREACH_ITEMS = 1000;
|
|
7
|
+
export interface WorkflowRuntimeDeps {
|
|
8
|
+
agent: AgentBridge;
|
|
9
|
+
stateDir?: string;
|
|
10
|
+
core?: Record<string, CoreFn>;
|
|
11
|
+
now?: () => number;
|
|
12
|
+
sleep?: (ms: number) => Promise<void>;
|
|
13
|
+
classifyError?: (error: unknown) => WorkflowErrorClass;
|
|
14
|
+
generateRunId?: () => string;
|
|
15
|
+
maxConcurrentRuns?: number;
|
|
16
|
+
maxQueuedRuns?: number;
|
|
17
|
+
maxStepExecutions?: number;
|
|
18
|
+
/** Test/embedding seam invoked only after a durable checkpoint has completed. */
|
|
19
|
+
afterCheckpoint?: (state: Readonly<WorkflowRunState>) => void | Promise<void>;
|
|
20
|
+
/** Synchronous fault-injection seam invoked under the commit lock; callbacks must not re-enter the store. */
|
|
21
|
+
onTransactionPhase?: WorkflowStateStoreOptions['onTransactionPhase'];
|
|
22
|
+
/** Bounded startup-recovery diagnostic; raw errors are never exposed. */
|
|
23
|
+
onRecoveryFailure?: (failure: WorkflowRecoveryFailure) => void;
|
|
24
|
+
lock?: {
|
|
25
|
+
maxTries?: number;
|
|
26
|
+
waitMs?: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface WorkflowRecoveryFailure {
|
|
30
|
+
runId: string;
|
|
31
|
+
code: WorkflowStateError['code'] | 'RUN_FAILED' | 'RECOVERY_FAILED';
|
|
32
|
+
}
|
|
33
|
+
export interface WorkflowRunResult {
|
|
34
|
+
runId: string;
|
|
35
|
+
workflowId: string;
|
|
36
|
+
status: WorkflowRunState['status'];
|
|
37
|
+
output: unknown;
|
|
38
|
+
ctx: WorkflowContext;
|
|
39
|
+
}
|
|
40
|
+
export declare class WorkflowRuntimeError extends Error {
|
|
41
|
+
readonly code: 'SENSITIVE_STATE' | 'INVALID_STATE' | 'UNSUPPORTED_PARALLELISM' | 'RUN_NOT_RESUMABLE' | 'INVALID_RETRY' | 'INVALID_CONCURRENCY' | 'RESOURCE_LIMIT';
|
|
42
|
+
constructor(message: string, code: 'SENSITIVE_STATE' | 'INVALID_STATE' | 'UNSUPPORTED_PARALLELISM' | 'RUN_NOT_RESUMABLE' | 'INVALID_RETRY' | 'INVALID_CONCURRENCY' | 'RESOURCE_LIMIT');
|
|
43
|
+
}
|
|
44
|
+
export declare class WorkflowRunFailedError extends Error {
|
|
45
|
+
readonly runId: string;
|
|
46
|
+
readonly status: 'failed' | 'unsafe_to_resume';
|
|
47
|
+
readonly errorClass: WorkflowStepState['lastErrorClass'];
|
|
48
|
+
constructor(runId: string, status: 'failed' | 'unsafe_to_resume', errorClass: WorkflowStepState['lastErrorClass']);
|
|
49
|
+
}
|
|
50
|
+
export declare class ClassifiedWorkflowError extends Error {
|
|
51
|
+
readonly errorClass: WorkflowErrorClass;
|
|
52
|
+
constructor(errorClass: WorkflowErrorClass, message?: string);
|
|
53
|
+
}
|
|
54
|
+
export declare function defaultWorkflowStateDir(): string;
|
|
55
|
+
export declare function deriveWorkflowId(spec: WorkflowSpec): string;
|
|
56
|
+
export declare function assertValidWorkflowSpec(value: unknown): asserts value is WorkflowSpec;
|
|
57
|
+
export declare class DurableWorkflowRuntime {
|
|
58
|
+
private readonly deps;
|
|
59
|
+
readonly store: WorkflowStateStore;
|
|
60
|
+
private readonly core;
|
|
61
|
+
private readonly now;
|
|
62
|
+
private readonly sleep;
|
|
63
|
+
private readonly classifyError;
|
|
64
|
+
private readonly generateRunId;
|
|
65
|
+
private readonly scheduler;
|
|
66
|
+
private readonly maxConcurrentRuns;
|
|
67
|
+
private readonly maxStepExecutions;
|
|
68
|
+
private readonly inFlight;
|
|
69
|
+
constructor(deps: WorkflowRuntimeDeps);
|
|
70
|
+
start(spec: WorkflowSpec, options?: {
|
|
71
|
+
runId?: string;
|
|
72
|
+
} | string): Promise<WorkflowRunResult>;
|
|
73
|
+
resume(runId: string, options?: WorkflowOperatorOptions): Promise<WorkflowRunResult>;
|
|
74
|
+
operatorResume(runId: string, options: WorkflowOperatorOptions): Promise<WorkflowRunResult>;
|
|
75
|
+
pause(runId: string, options: WorkflowOperatorOptions): Promise<WorkflowRunResult>;
|
|
76
|
+
cancel(runId: string, options: WorkflowOperatorOptions): Promise<WorkflowRunResult>;
|
|
77
|
+
approve(runId: string, gateId: string, options: WorkflowOperatorOptions): Promise<WorkflowRunResult>;
|
|
78
|
+
reject(runId: string, gateId: string, options: WorkflowOperatorOptions): Promise<WorkflowRunResult>;
|
|
79
|
+
status(runId: string): WorkflowRunState;
|
|
80
|
+
history(runId: string): ReturnType<WorkflowStateStore['readHistory']>;
|
|
81
|
+
recoverPending(): Promise<WorkflowRunResult[]>;
|
|
82
|
+
protected isRecoveryCandidate(state: WorkflowRunState, control: WorkflowControlState): boolean;
|
|
83
|
+
protected recoverRuns(shouldRecover: (runId: string) => boolean | Promise<boolean>, reportFailure: (runId: string, error: unknown) => void): Promise<WorkflowRunResult[]>;
|
|
84
|
+
private scheduleRecoveryRun;
|
|
85
|
+
private scheduleRun;
|
|
86
|
+
private resumeLocked;
|
|
87
|
+
private withLockedRun;
|
|
88
|
+
private withConcurrencySlot;
|
|
89
|
+
private validateResumeStateOrFail;
|
|
90
|
+
private execute;
|
|
91
|
+
private runSteps;
|
|
92
|
+
private runStep;
|
|
93
|
+
private lastStepOutput;
|
|
94
|
+
private failState;
|
|
95
|
+
private runApproval;
|
|
96
|
+
private runLeaf;
|
|
97
|
+
private runAgentAttempt;
|
|
98
|
+
private observeCancellation;
|
|
99
|
+
private checkpointCompletion;
|
|
100
|
+
private enforceControlBoundary;
|
|
101
|
+
private finalizeCancellation;
|
|
102
|
+
private checkpoint;
|
|
103
|
+
private safetyCheckpoint;
|
|
104
|
+
private ensureApprovalDecisionHistory;
|
|
105
|
+
private appendHistoryUnlessUnavailable;
|
|
106
|
+
private historyEvent;
|
|
107
|
+
private timestamp;
|
|
108
|
+
private result;
|
|
109
|
+
private reportRecoveryFailure;
|
|
110
|
+
}
|