acpx 0.18.0 → 0.19.1
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/{agent-registry-DdKB-zia.js → agent-registry-DWlx5BQ4.js} +5 -3
- package/dist/agent-registry-DWlx5BQ4.js.map +1 -0
- package/dist/agent-registry.js +1 -1
- package/dist/{cli-8mVHeUXh.js → cli-BerngYEz.js} +5 -5
- package/dist/{cli-8mVHeUXh.js.map → cli-BerngYEz.js.map} +1 -1
- package/dist/cli.js +77 -53
- package/dist/cli.js.map +1 -1
- package/dist/{client-DeNKZZcu.d.ts → client-BchvQRpz.d.ts} +17 -4
- package/dist/client-BchvQRpz.d.ts.map +1 -0
- package/dist/{flows-WOQNzCMC.js → flows-DimX0it5.js} +693 -475
- package/dist/flows-DimX0it5.js.map +1 -0
- package/dist/flows.d.ts +20 -7
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{invocation-options-B6d7TI-y.js → invocation-options-vobVApfO.js} +32 -11
- package/dist/invocation-options-vobVApfO.js.map +1 -0
- package/dist/{ipc-BJrvfNXr.js → ipc-DAApJ5mi.js} +1041 -263
- package/dist/ipc-DAApJ5mi.js.map +1 -0
- package/dist/{output-CL18YmsG.js → output-Cnuji8Bo.js} +41 -20
- package/dist/output-Cnuji8Bo.js.map +1 -0
- package/dist/{queue-owner-runtime-Cr0dWJuL.js → queue-owner-runtime-C4OOosbo.js} +732 -507
- package/dist/queue-owner-runtime-C4OOosbo.js.map +1 -0
- package/dist/runtime.d.ts +19 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +48 -29
- package/dist/runtime.js.map +1 -1
- package/dist/session-options-Dp6mzNSy.d.ts.map +1 -1
- package/dist/{session-watch-B8Q_TsGf.js → session-watch-Bj9LoYUq.js} +5 -5
- package/dist/session-watch-Bj9LoYUq.js.map +1 -0
- package/dist/{watch-Dp73ph8h.js → watch-Cz6jbrRs.js} +2 -2
- package/dist/{watch-Dp73ph8h.js.map → watch-Cz6jbrRs.js.map} +1 -1
- package/package.json +1 -1
- package/skills/acpx/SKILL.md +45 -23
- package/dist/agent-registry-DdKB-zia.js.map +0 -1
- package/dist/client-DeNKZZcu.d.ts.map +0 -1
- package/dist/flows-WOQNzCMC.js.map +0 -1
- package/dist/invocation-options-B6d7TI-y.js.map +0 -1
- package/dist/ipc-BJrvfNXr.js.map +0 -1
- package/dist/output-CL18YmsG.js.map +0 -1
- package/dist/queue-owner-runtime-Cr0dWJuL.js.map +0 -1
- package/dist/session-watch-B8Q_TsGf.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as createOutputFormatter } from "./output-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { t as createOutputFormatter } from "./output-Cnuji8Bo.js";
|
|
2
|
+
import { D as textPrompt, E as promptToDisplayText, _t as writePrivateFile, ft as defaultSessionEventLog, jt as SESSION_RECORD_SCHEMA, kt as PERMISSION_MODES, vt as writePrivateJsonFile, z as resolveSessionRecord } from "./ipc-DAApJ5mi.js";
|
|
3
|
+
import { G as recordPromptSubmission, K as recordSessionUpdate, U as createSessionConversation, V as cloneSessionAcpxState, W as recordClientOperation, i as runOnce, lt as withInterrupt, m as createSessionWithClient, o as sendSessionDirect, ot as InterruptedError, st as TimeoutError, ut as withTimeout } from "./queue-owner-runtime-C4OOosbo.js";
|
|
4
|
+
import { N as runTimedExecFile } from "./agent-registry-DWlx5BQ4.js";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import fs from "node:fs/promises";
|
|
@@ -10,6 +10,131 @@ import { appendRegularFile } from "@openclaw/fs-safe/advanced";
|
|
|
10
10
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
11
11
|
import { ZodError, z } from "zod";
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
|
+
import { performance } from "node:perf_hooks";
|
|
14
|
+
//#region src/flows/attempt.ts
|
|
15
|
+
/** One node attempt owns admission and completion of its managed operations. */
|
|
16
|
+
var FlowAttempt = class {
|
|
17
|
+
signal;
|
|
18
|
+
nodeId;
|
|
19
|
+
attemptId;
|
|
20
|
+
startedAt;
|
|
21
|
+
terminationSignal = "SIGTERM";
|
|
22
|
+
controller = new AbortController();
|
|
23
|
+
pending = /* @__PURE__ */ new Set();
|
|
24
|
+
cancellations = /* @__PURE__ */ new Set();
|
|
25
|
+
cleanupFailures = [];
|
|
26
|
+
deadlineAt;
|
|
27
|
+
timeoutMs;
|
|
28
|
+
timer;
|
|
29
|
+
accepting = true;
|
|
30
|
+
finished = false;
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this.nodeId = options.nodeId;
|
|
33
|
+
this.attemptId = options.attemptId;
|
|
34
|
+
this.startedAt = options.startedAt;
|
|
35
|
+
this.signal = this.controller.signal;
|
|
36
|
+
if (options.timeoutMs != null && options.timeoutMs > 0) {
|
|
37
|
+
this.timeoutMs = options.timeoutMs;
|
|
38
|
+
this.deadlineAt = performance.now() + options.timeoutMs;
|
|
39
|
+
this.timer = setTimeout(() => this.cancel(new TimeoutError(options.timeoutMs)), options.timeoutMs);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
get active() {
|
|
43
|
+
return this.accepting && !this.signal.aborted;
|
|
44
|
+
}
|
|
45
|
+
assertActive() {
|
|
46
|
+
this.checkDeadline();
|
|
47
|
+
this.signal.throwIfAborted();
|
|
48
|
+
if (!this.accepting) throw new Error("Flow attempt has finished accepting work");
|
|
49
|
+
}
|
|
50
|
+
remainingTimeoutMs() {
|
|
51
|
+
this.assertActive();
|
|
52
|
+
return this.deadlineAt === void 0 ? void 0 : Math.max(1, Math.ceil(this.deadlineAt - performance.now()));
|
|
53
|
+
}
|
|
54
|
+
cancel(reason, signal = "SIGTERM") {
|
|
55
|
+
if (this.finished || this.signal.aborted) return;
|
|
56
|
+
this.accepting = false;
|
|
57
|
+
this.terminationSignal = signal;
|
|
58
|
+
this.controller.abort(reason);
|
|
59
|
+
for (const cancel of this.cancellations) this.trackCancellation(cancel);
|
|
60
|
+
}
|
|
61
|
+
registerCancellation(cancel) {
|
|
62
|
+
if (!this.active) {
|
|
63
|
+
this.trackCancellation(cancel);
|
|
64
|
+
return () => {};
|
|
65
|
+
}
|
|
66
|
+
this.cancellations.add(cancel);
|
|
67
|
+
return () => {
|
|
68
|
+
this.cancellations.delete(cancel);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Track runtime work, never an arbitrary user callback that may ignore cancellation. */
|
|
72
|
+
own(run, options = {}) {
|
|
73
|
+
this.assertActive();
|
|
74
|
+
const operation = run().then((value) => {
|
|
75
|
+
this.checkDeadline();
|
|
76
|
+
if (this.signal.aborted) this.signal.throwIfAborted();
|
|
77
|
+
return value;
|
|
78
|
+
});
|
|
79
|
+
const outcome = operation.then(() => ({ ok: true }), (error) => options.bestEffort ? { ok: true } : {
|
|
80
|
+
ok: false,
|
|
81
|
+
error
|
|
82
|
+
});
|
|
83
|
+
this.pending.add(outcome);
|
|
84
|
+
outcome.then(() => this.pending.delete(outcome));
|
|
85
|
+
return operation;
|
|
86
|
+
}
|
|
87
|
+
async run(run) {
|
|
88
|
+
let rejectAbort = () => {};
|
|
89
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
90
|
+
rejectAbort = reject;
|
|
91
|
+
});
|
|
92
|
+
const onAbort = () => rejectAbort(this.signal.reason);
|
|
93
|
+
this.signal.addEventListener("abort", onAbort, { once: true });
|
|
94
|
+
try {
|
|
95
|
+
this.assertActive();
|
|
96
|
+
const value = await Promise.race([run(), aborted]);
|
|
97
|
+
this.accepting = false;
|
|
98
|
+
const errors = await this.drain();
|
|
99
|
+
if (errors.length > 0) throw this.cleanupError(errors);
|
|
100
|
+
this.checkDeadline();
|
|
101
|
+
this.signal.throwIfAborted();
|
|
102
|
+
return value;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
this.cancel(error);
|
|
105
|
+
const errors = await this.drain();
|
|
106
|
+
if (errors.length > 0) throw this.cleanupError([error, ...errors]);
|
|
107
|
+
throw error;
|
|
108
|
+
} finally {
|
|
109
|
+
this.accepting = false;
|
|
110
|
+
this.finished = true;
|
|
111
|
+
clearTimeout(this.timer);
|
|
112
|
+
this.signal.removeEventListener("abort", onAbort);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
checkDeadline() {
|
|
116
|
+
if (this.deadlineAt !== void 0 && performance.now() >= this.deadlineAt) this.cancel(new TimeoutError(this.timeoutMs));
|
|
117
|
+
}
|
|
118
|
+
async drain() {
|
|
119
|
+
const errors = [];
|
|
120
|
+
while (this.pending.size > 0) for (const outcome of await Promise.all(this.pending)) if (!outcome.ok && outcome.error !== this.signal.reason) errors.push(outcome.error);
|
|
121
|
+
return [.../* @__PURE__ */ new Set([...errors, ...this.cleanupFailures.splice(0)])];
|
|
122
|
+
}
|
|
123
|
+
trackCancellation(cancel) {
|
|
124
|
+
const outcome = Promise.resolve().then(() => cancel(this.terminationSignal)).then(() => ({ ok: true }), (error) => {
|
|
125
|
+
this.cleanupFailures.push(error);
|
|
126
|
+
return { ok: true };
|
|
127
|
+
});
|
|
128
|
+
this.pending.add(outcome);
|
|
129
|
+
outcome.then(() => this.pending.delete(outcome));
|
|
130
|
+
}
|
|
131
|
+
cleanupError(errors) {
|
|
132
|
+
const reason = this.signal.reason;
|
|
133
|
+
const failures = [.../* @__PURE__ */ new Set([...this.signal.aborted ? [reason] : [], ...errors])];
|
|
134
|
+
return new AggregateError(failures, "Flow attempt cleanup failed", { cause: failures[0] });
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
//#endregion
|
|
13
138
|
//#region src/flows/authoring.ts
|
|
14
139
|
const FLOW_DEFINITION_BRAND = Symbol.for("acpx.flow.definition");
|
|
15
140
|
function markDefinedFlow(definition) {
|
|
@@ -109,7 +234,7 @@ const flowDefinitionSchema = extensibleObject({
|
|
|
109
234
|
const flowNodeTypeSchema = z.object({ nodeType: z.enum(FLOW_NODE_TYPES) });
|
|
110
235
|
function assertValidFlowDefinitionShape(flow) {
|
|
111
236
|
const parsed = parseWithSchema("flow definition", flowDefinitionSchema, flow);
|
|
112
|
-
for (const [nodeId, node] of Object.entries(
|
|
237
|
+
for (const [nodeId, node] of Object.entries(flow.nodes)) assertValidFlowNodeDefinitionShape(node, `flow node "${nodeId}"`);
|
|
113
238
|
parsed.edges.forEach((edge, index) => {
|
|
114
239
|
assertValidFlowEdgeShape(edge, `flow definition: edges.${index}`);
|
|
115
240
|
});
|
|
@@ -164,6 +289,8 @@ function assertValidFlowEdgeShape(edge, label) {
|
|
|
164
289
|
return;
|
|
165
290
|
}
|
|
166
291
|
parseWithSchema(label, switchFlowEdgeSchema, edge);
|
|
292
|
+
const cases = edge.switch.cases;
|
|
293
|
+
for (const [key, target] of Object.entries(cases)) parseWithSchema(`${label}: switch.cases.${key}`, z.string(), target);
|
|
167
294
|
}
|
|
168
295
|
function parseWithSchema(label, schema, value) {
|
|
169
296
|
try {
|
|
@@ -414,25 +541,11 @@ function resolveShellActionTimeoutMs(timeoutMs) {
|
|
|
414
541
|
if (timeoutMs == null || !(timeoutMs > 0)) return;
|
|
415
542
|
return timeoutMs;
|
|
416
543
|
}
|
|
417
|
-
async function withShellAbort(run, signal) {
|
|
418
|
-
signal.throwIfAborted();
|
|
419
|
-
let rejectAbort = () => {};
|
|
420
|
-
const aborted = new Promise((_resolve, reject) => {
|
|
421
|
-
rejectAbort = reject;
|
|
422
|
-
});
|
|
423
|
-
const onAbort = () => rejectAbort(signal.reason);
|
|
424
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
425
|
-
try {
|
|
426
|
-
return await Promise.race([run(), aborted]);
|
|
427
|
-
} finally {
|
|
428
|
-
signal.removeEventListener("abort", onAbort);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
544
|
function rejectIfShellFailed(spec, args, result, timedOut, timeoutMs) {
|
|
432
545
|
if (timedOut) return new TimeoutError(timeoutMs ?? spec.timeoutMs ?? 0);
|
|
433
546
|
if (((result.exitCode ?? 0) !== 0 || result.signal != null) && spec.allowNonZeroExit !== true) return createShellFailureError(spec, args, result.exitCode, result.signal, result.stderr);
|
|
434
547
|
}
|
|
435
|
-
function
|
|
548
|
+
function waitForShellResult(child, spec, args, cwd, startMs, termination, mode) {
|
|
436
549
|
const stdoutStream = child.stdout;
|
|
437
550
|
const stderrStream = child.stderr;
|
|
438
551
|
if (!stdoutStream || !stderrStream) throw new Error("Shell action child is missing stdio pipes");
|
|
@@ -442,7 +555,8 @@ function waitForShellExit(child, spec, args, cwd, startMs, timeoutMs, terminatio
|
|
|
442
555
|
settled = true;
|
|
443
556
|
reject(error);
|
|
444
557
|
};
|
|
445
|
-
const capture = createShellOutputCapture(spec.maxBufferBytes, () => settled || termination.
|
|
558
|
+
const capture = createShellOutputCapture(spec.maxBufferBytes, () => settled || mode === "node" && termination.cancelled(), (error) => {
|
|
559
|
+
if (termination.cancelled()) return;
|
|
446
560
|
fail(error);
|
|
447
561
|
termination.cancel("SIGTERM").catch(fail);
|
|
448
562
|
});
|
|
@@ -455,10 +569,10 @@ function waitForShellExit(child, spec, args, cwd, startMs, timeoutMs, terminatio
|
|
|
455
569
|
capture.append("stderr", chunk);
|
|
456
570
|
});
|
|
457
571
|
child.once("error", fail);
|
|
458
|
-
child.once("exit", (exitCode, signal) => {
|
|
572
|
+
child.once(mode === "node" ? "exit" : "close", (exitCode, signal) => {
|
|
459
573
|
settled = true;
|
|
460
574
|
const { stdout, stderr } = capture.output;
|
|
461
|
-
|
|
575
|
+
resolve({
|
|
462
576
|
command: spec.command,
|
|
463
577
|
args,
|
|
464
578
|
cwd,
|
|
@@ -467,14 +581,9 @@ function waitForShellExit(child, spec, args, cwd, startMs, timeoutMs, terminatio
|
|
|
467
581
|
combinedOutput: `${stdout}${stderr}`,
|
|
468
582
|
exitCode,
|
|
469
583
|
signal,
|
|
470
|
-
durationMs: Date.now() - startMs
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (error) {
|
|
474
|
-
reject(error);
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
resolve(result);
|
|
584
|
+
durationMs: Date.now() - startMs,
|
|
585
|
+
timedOut: mode === "node" ? termination.cancelled() : termination.timedOut()
|
|
586
|
+
});
|
|
478
587
|
});
|
|
479
588
|
});
|
|
480
589
|
}
|
|
@@ -486,6 +595,7 @@ function createShellTermination(child, timeoutMs, options) {
|
|
|
486
595
|
});
|
|
487
596
|
let termination;
|
|
488
597
|
let timedOut = false;
|
|
598
|
+
let cancelled = false;
|
|
489
599
|
let released = false;
|
|
490
600
|
let deadline;
|
|
491
601
|
let monitor;
|
|
@@ -504,7 +614,8 @@ function createShellTermination(child, timeoutMs, options) {
|
|
|
504
614
|
const cancel = (signal) => {
|
|
505
615
|
if (termination) return termination;
|
|
506
616
|
if (released) return Promise.resolve();
|
|
507
|
-
|
|
617
|
+
clearDeadline();
|
|
618
|
+
cancelled = true;
|
|
508
619
|
termination = stopShellProcess(child, closed, signal).finally(release);
|
|
509
620
|
termination.catch(rejectCleanup);
|
|
510
621
|
return termination;
|
|
@@ -514,6 +625,7 @@ function createShellTermination(child, timeoutMs, options) {
|
|
|
514
625
|
};
|
|
515
626
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
516
627
|
if (timeoutMs != null) deadline = setTimeout(() => {
|
|
628
|
+
timedOut = true;
|
|
517
629
|
cancel("SIGTERM");
|
|
518
630
|
}, timeoutMs);
|
|
519
631
|
unregister = options.registerOwner?.({
|
|
@@ -532,6 +644,7 @@ function createShellTermination(child, timeoutMs, options) {
|
|
|
532
644
|
return {
|
|
533
645
|
cancel,
|
|
534
646
|
timedOut: () => timedOut,
|
|
647
|
+
cancelled: () => cancelled,
|
|
535
648
|
cleanupFailure,
|
|
536
649
|
async dispose() {
|
|
537
650
|
clearDeadline();
|
|
@@ -544,7 +657,16 @@ function createShellTermination(child, timeoutMs, options) {
|
|
|
544
657
|
};
|
|
545
658
|
}
|
|
546
659
|
async function runShellAction(spec, options = {}) {
|
|
547
|
-
|
|
660
|
+
const { timedOut, ...result } = await runShellProcess(spec, options, "node");
|
|
661
|
+
const failure = rejectIfShellFailed(spec, result.args, result, timedOut, resolveShellActionTimeoutMs(spec.timeoutMs));
|
|
662
|
+
if (failure) throw failure;
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
665
|
+
async function runShellCommand(spec, options) {
|
|
666
|
+
return await runShellProcess(spec, options, "command");
|
|
667
|
+
}
|
|
668
|
+
async function runShellProcess(spec, options, mode) {
|
|
669
|
+
options.signal?.throwIfAborted();
|
|
548
670
|
const cwd = spec.cwd ?? process.cwd();
|
|
549
671
|
const args = spec.args ?? [];
|
|
550
672
|
const startMs = Date.now();
|
|
@@ -566,47 +688,57 @@ async function runShellAction(spec, options = {}) {
|
|
|
566
688
|
detached: process.platform !== "win32"
|
|
567
689
|
});
|
|
568
690
|
const termination = createShellTermination(child, timeoutMs, options);
|
|
569
|
-
const finish =
|
|
691
|
+
const finish = waitForShellResult(child, spec, args, cwd, startMs, termination, mode);
|
|
570
692
|
writeShellStdin(child, spec.stdin);
|
|
571
693
|
try {
|
|
572
|
-
|
|
694
|
+
const result = await Promise.race([finish, termination.cleanupFailure]);
|
|
695
|
+
throwIfShellCancelled(options.signal, mode);
|
|
696
|
+
return result;
|
|
573
697
|
} finally {
|
|
574
698
|
await termination.dispose();
|
|
575
699
|
}
|
|
576
700
|
}
|
|
701
|
+
function throwIfShellCancelled(signal, mode) {
|
|
702
|
+
if (!signal?.aborted) return;
|
|
703
|
+
const reason = signal.reason;
|
|
704
|
+
if (mode === "command" || reason instanceof TimeoutError || reason instanceof InterruptedError) throw reason;
|
|
705
|
+
}
|
|
577
706
|
//#endregion
|
|
578
707
|
//#region src/flows/graph.ts
|
|
579
708
|
function validateFlowDefinition(flow) {
|
|
580
709
|
assertValidFlowDefinitionShape(flow);
|
|
581
|
-
|
|
710
|
+
assertKnownFlowNode(flow, flow.startAt, "Flow start node is missing");
|
|
582
711
|
const outgoingEdges = /* @__PURE__ */ new Set();
|
|
583
712
|
for (const edge of flow.edges) validateFlowEdge(flow, edge, outgoingEdges);
|
|
584
713
|
}
|
|
585
714
|
function assertKnownFlowNode(flow, nodeId, description) {
|
|
586
|
-
if (!flow.nodes
|
|
715
|
+
if (!Object.hasOwn(flow.nodes, nodeId)) throw new Error(`${description}: ${nodeId}`);
|
|
716
|
+
}
|
|
717
|
+
function isDirectEdge(edge) {
|
|
718
|
+
return Object.hasOwn(edge, "to");
|
|
587
719
|
}
|
|
588
720
|
function validateFlowEdge(flow, edge, outgoingEdges) {
|
|
589
721
|
assertKnownFlowNode(flow, edge.from, "Flow edge references unknown from-node");
|
|
590
722
|
if (outgoingEdges.has(edge.from)) throw new Error(`Flow node must not declare multiple outgoing edges: ${edge.from}`);
|
|
591
723
|
outgoingEdges.add(edge.from);
|
|
592
|
-
if (
|
|
724
|
+
if (isDirectEdge(edge)) {
|
|
593
725
|
assertKnownFlowNode(flow, edge.to, "Flow edge references unknown to-node");
|
|
594
726
|
return;
|
|
595
727
|
}
|
|
596
728
|
for (const target of Object.values(edge.switch.cases)) assertKnownFlowNode(flow, target, "Flow switch references unknown to-node");
|
|
597
729
|
}
|
|
598
730
|
function canFollowEdge(edge, result) {
|
|
599
|
-
return !result || result.outcome === "ok" ||
|
|
731
|
+
return !result || result.outcome === "ok" || !isDirectEdge(edge) && edge.switch.on.startsWith("$result.");
|
|
600
732
|
}
|
|
601
733
|
function resolveNext(edges, from, output, result) {
|
|
602
734
|
const edge = edges.find((candidate) => candidate.from === from);
|
|
603
735
|
if (!edge || !canFollowEdge(edge, result)) return null;
|
|
604
|
-
if (
|
|
736
|
+
if (isDirectEdge(edge)) return edge.to;
|
|
605
737
|
const value = getBySwitchPath(output, result, edge.switch.on);
|
|
606
738
|
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") throw new Error(`Flow switch value must be scalar for ${edge.switch.on}`);
|
|
607
|
-
const
|
|
608
|
-
if (!
|
|
609
|
-
return
|
|
739
|
+
const key = String(value);
|
|
740
|
+
if (!Object.hasOwn(edge.switch.cases, key)) throw new Error(`No flow switch case for ${edge.switch.on}=${JSON.stringify(value)}`);
|
|
741
|
+
return edge.switch.cases[key];
|
|
610
742
|
}
|
|
611
743
|
function getBySwitchPath(output, result, jsonPath) {
|
|
612
744
|
if (jsonPath.startsWith("$result.")) return getByPath(result, `$.${jsonPath.slice(8)}`);
|
|
@@ -707,11 +839,19 @@ function summarizePrompt(promptText, explicitDetail) {
|
|
|
707
839
|
}
|
|
708
840
|
function createQuietCaptureOutput() {
|
|
709
841
|
const chunks = [];
|
|
842
|
+
let sessionId;
|
|
843
|
+
const formatter = createOutputFormatter("quiet", { stdout: { write(chunk) {
|
|
844
|
+
chunks.push(chunk);
|
|
845
|
+
} } });
|
|
846
|
+
const setContext = formatter.setContext.bind(formatter);
|
|
847
|
+
formatter.setContext = (context) => {
|
|
848
|
+
sessionId = context.sessionId ?? sessionId;
|
|
849
|
+
setContext(context);
|
|
850
|
+
};
|
|
710
851
|
return {
|
|
711
|
-
formatter
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
read: () => chunks.join("").trim()
|
|
852
|
+
formatter,
|
|
853
|
+
read: () => chunks.join("").trim(),
|
|
854
|
+
sessionId: () => sessionId
|
|
715
855
|
};
|
|
716
856
|
}
|
|
717
857
|
async function resolveFlowRunTitle(flow, input, flowPath) {
|
|
@@ -730,8 +870,13 @@ function normalizeFlowRunTitle(value) {
|
|
|
730
870
|
function createRunId(flowName) {
|
|
731
871
|
return `${isoNow$1().replaceAll(":", "").replaceAll(".", "")}-${slugifyAsciiIdPart(flowName)}-${randomUUID().slice(0, 8)}`;
|
|
732
872
|
}
|
|
733
|
-
function createSessionBindingKey(agentCommand, cwd, handle) {
|
|
734
|
-
return
|
|
873
|
+
function createSessionBindingKey(agentCommand, cwd, handle, agentArgv) {
|
|
874
|
+
return JSON.stringify([
|
|
875
|
+
agentCommand,
|
|
876
|
+
agentArgv ?? null,
|
|
877
|
+
cwd,
|
|
878
|
+
handle
|
|
879
|
+
]);
|
|
735
880
|
}
|
|
736
881
|
function createSessionName(flowName, handle, cwd, runId) {
|
|
737
882
|
return `${flowName}-${handle}-${stableShortHash(cwd)}-${runId.slice(-8)}`;
|
|
@@ -909,10 +1054,15 @@ var FlowRunStore = class {
|
|
|
909
1054
|
traceSeqByRun = /* @__PURE__ */ new Map();
|
|
910
1055
|
sessionSeqByBundle = /* @__PURE__ */ new Map();
|
|
911
1056
|
manifestByRun = /* @__PURE__ */ new Map();
|
|
912
|
-
|
|
1057
|
+
writeChainByPath = /* @__PURE__ */ new Map();
|
|
913
1058
|
constructor(outputRoot = flowRunsBaseDir()) {
|
|
914
1059
|
this.outputRoot = outputRoot;
|
|
915
1060
|
}
|
|
1061
|
+
releaseRun(runDir) {
|
|
1062
|
+
this.traceSeqByRun.delete(runDir);
|
|
1063
|
+
this.manifestByRun.delete(runDir);
|
|
1064
|
+
for (const key of this.sessionSeqByBundle.keys()) if (key.startsWith(`${runDir}::`)) this.sessionSeqByBundle.delete(key);
|
|
1065
|
+
}
|
|
916
1066
|
async createRunDir(runId) {
|
|
917
1067
|
const runDir = path.join(this.outputRoot, runId);
|
|
918
1068
|
await fs.mkdir(path.join(runDir, PROJECTIONS_DIR), {
|
|
@@ -1002,57 +1152,65 @@ var FlowRunStore = class {
|
|
|
1002
1152
|
});
|
|
1003
1153
|
return artifact;
|
|
1004
1154
|
}
|
|
1005
|
-
async ensureSessionBundle(runDir, state,
|
|
1155
|
+
async ensureSessionBundle(runDir, state, inputBinding, record) {
|
|
1156
|
+
const binding = structuredClone(inputBinding);
|
|
1006
1157
|
const sessionDir = this.resolveRunPath(runDir, sessionDirPath(binding.bundleId));
|
|
1007
|
-
await
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
id: binding.bundleId,
|
|
1019
|
-
handle: binding.handle,
|
|
1020
|
-
bindingPath: path.posix.join(sessionDirPath(binding.bundleId), "binding.json"),
|
|
1021
|
-
recordPath: path.posix.join(sessionDirPath(binding.bundleId), "record.json"),
|
|
1022
|
-
eventsPath: path.posix.join(sessionDirPath(binding.bundleId), "events.ndjson")
|
|
1023
|
-
};
|
|
1024
|
-
manifest.sessions.push(entry);
|
|
1025
|
-
await writePrivateJsonFile(this.resolveRunPath(runDir, MANIFEST_PATH), manifest);
|
|
1026
|
-
}
|
|
1027
|
-
if (isNew) await this.appendTrace(runDir, state, {
|
|
1028
|
-
scope: "session",
|
|
1029
|
-
type: "session_bound",
|
|
1030
|
-
sessionId: binding.bundleId,
|
|
1031
|
-
payload: {
|
|
1032
|
-
sessionId: binding.bundleId,
|
|
1033
|
-
handle: binding.handle,
|
|
1034
|
-
bindingArtifact: {
|
|
1035
|
-
path: path.posix.join(sessionDirPath(binding.bundleId), "binding.json"),
|
|
1158
|
+
await this.serializeWrite(path.join(sessionDir, "binding.json"), async () => {
|
|
1159
|
+
await fs.mkdir(sessionDir, {
|
|
1160
|
+
recursive: true,
|
|
1161
|
+
mode: 448
|
|
1162
|
+
});
|
|
1163
|
+
await writePrivateJsonFile(path.join(sessionDir, "binding.json"), binding);
|
|
1164
|
+
await ensureFile(path.join(sessionDir, "events.ndjson"));
|
|
1165
|
+
if (record) await this.writeSessionRecord(runDir, state, binding, record);
|
|
1166
|
+
const manifest = this.getManifest(runDir, state);
|
|
1167
|
+
if (!manifest.sessions.find((entry) => entry.id === binding.bundleId)) {
|
|
1168
|
+
const bindingArtifact = await this.writeArtifact(runDir, state, binding, {
|
|
1036
1169
|
mediaType: "application/json",
|
|
1037
|
-
|
|
1038
|
-
|
|
1170
|
+
extension: "json",
|
|
1171
|
+
emitTrace: false
|
|
1172
|
+
});
|
|
1173
|
+
const entry = {
|
|
1174
|
+
id: binding.bundleId,
|
|
1175
|
+
handle: binding.handle,
|
|
1176
|
+
bindingPath: path.posix.join(sessionDirPath(binding.bundleId), "binding.json"),
|
|
1177
|
+
recordPath: path.posix.join(sessionDirPath(binding.bundleId), "record.json"),
|
|
1178
|
+
eventsPath: path.posix.join(sessionDirPath(binding.bundleId), "events.ndjson")
|
|
1179
|
+
};
|
|
1180
|
+
manifest.sessions.push(entry);
|
|
1181
|
+
await writePrivateJsonFile(this.resolveRunPath(runDir, MANIFEST_PATH), manifest);
|
|
1182
|
+
await this.appendTrace(runDir, state, {
|
|
1183
|
+
scope: "session",
|
|
1184
|
+
type: "session_bound",
|
|
1185
|
+
sessionId: binding.bundleId,
|
|
1186
|
+
payload: {
|
|
1187
|
+
sessionId: binding.bundleId,
|
|
1188
|
+
handle: binding.handle,
|
|
1189
|
+
bindingArtifact
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1039
1192
|
}
|
|
1040
1193
|
});
|
|
1041
1194
|
}
|
|
1042
1195
|
async writeSessionRecord(runDir, _state, binding, record) {
|
|
1043
|
-
const bundledRecord = createBundledSessionRecord(binding, record, this.sessionSeqByBundle.get(`${runDir}::${binding.bundleId}`) ?? 0);
|
|
1196
|
+
const bundledRecord = createBundledSessionRecord(binding, record, this.sessionSeqByBundle.get(`${runDir}::${binding.bundleId}`)?.persisted ?? 0);
|
|
1044
1197
|
await writePrivateJsonFile(this.resolveRunPath(runDir, path.posix.join(sessionDirPath(binding.bundleId), "record.json")), bundledRecord);
|
|
1045
1198
|
}
|
|
1046
1199
|
async appendSessionEvent(runDir, binding, direction, message) {
|
|
1047
1200
|
const sessionKey = `${runDir}::${binding.bundleId}`;
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1201
|
+
const sequence = this.sessionSeqByBundle.get(sessionKey) ?? {
|
|
1202
|
+
allocated: 0,
|
|
1203
|
+
persisted: 0
|
|
1204
|
+
};
|
|
1205
|
+
this.sessionSeqByBundle.set(sessionKey, sequence);
|
|
1206
|
+
const seq = ++sequence.allocated;
|
|
1050
1207
|
await this.appendJsonLine(this.resolveRunPath(runDir, path.posix.join(sessionDirPath(binding.bundleId), "events.ndjson")), {
|
|
1051
1208
|
seq,
|
|
1052
1209
|
at: isoNow(),
|
|
1053
1210
|
direction,
|
|
1054
1211
|
message
|
|
1055
1212
|
});
|
|
1213
|
+
sequence.persisted = Math.max(sequence.persisted, seq);
|
|
1056
1214
|
return seq;
|
|
1057
1215
|
}
|
|
1058
1216
|
getManifest(runDir, state) {
|
|
@@ -1083,7 +1241,7 @@ var FlowRunStore = class {
|
|
|
1083
1241
|
return path.join(runDir, ...relativePath.split("/"));
|
|
1084
1242
|
}
|
|
1085
1243
|
async appendJsonLine(filePath, value) {
|
|
1086
|
-
|
|
1244
|
+
await this.serializeWrite(filePath, async () => {
|
|
1087
1245
|
await fs.mkdir(path.dirname(filePath), {
|
|
1088
1246
|
recursive: true,
|
|
1089
1247
|
mode: 448
|
|
@@ -1093,10 +1251,13 @@ var FlowRunStore = class {
|
|
|
1093
1251
|
content: `${JSON.stringify(value)}\n`,
|
|
1094
1252
|
mode: 384
|
|
1095
1253
|
});
|
|
1096
|
-
}).finally(() => {
|
|
1097
|
-
if (this.appendChainByPath.get(filePath) === tracked) this.appendChainByPath.delete(filePath);
|
|
1098
1254
|
});
|
|
1099
|
-
|
|
1255
|
+
}
|
|
1256
|
+
async serializeWrite(filePath, write) {
|
|
1257
|
+
const tracked = (this.writeChainByPath.get(filePath) ?? Promise.resolve()).then(write).finally(() => {
|
|
1258
|
+
if (this.writeChainByPath.get(filePath) === tracked) this.writeChainByPath.delete(filePath);
|
|
1259
|
+
});
|
|
1260
|
+
this.writeChainByPath.set(filePath, tracked);
|
|
1100
1261
|
await tracked;
|
|
1101
1262
|
}
|
|
1102
1263
|
};
|
|
@@ -1229,10 +1390,6 @@ async function ensureFile(filePath) {
|
|
|
1229
1390
|
mode: 384
|
|
1230
1391
|
});
|
|
1231
1392
|
}
|
|
1232
|
-
async function fileSha256(filePath) {
|
|
1233
|
-
const payload = await fs.readFile(filePath);
|
|
1234
|
-
return createHash("sha256").update(payload).digest("hex");
|
|
1235
|
-
}
|
|
1236
1393
|
function toArtifactBuffer(content, mediaType) {
|
|
1237
1394
|
if (typeof content === "string") return Buffer.from(content, "utf8");
|
|
1238
1395
|
if (Buffer.isBuffer(content)) return content;
|
|
@@ -1254,6 +1411,14 @@ function isoNow() {
|
|
|
1254
1411
|
//#region src/flows/runtime.ts
|
|
1255
1412
|
const DEFAULT_FLOW_HEARTBEAT_MS = 5e3;
|
|
1256
1413
|
const DEFAULT_FLOW_STEP_TIMEOUT_MS = 9e5;
|
|
1414
|
+
function setNodeValue(values, nodeId, value) {
|
|
1415
|
+
Object.defineProperty(values, nodeId, {
|
|
1416
|
+
value,
|
|
1417
|
+
enumerable: true,
|
|
1418
|
+
configurable: true,
|
|
1419
|
+
writable: true
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1257
1422
|
var FlowRunner = class {
|
|
1258
1423
|
resolveAgent;
|
|
1259
1424
|
defaultCwd;
|
|
@@ -1264,7 +1429,8 @@ var FlowRunner = class {
|
|
|
1264
1429
|
services;
|
|
1265
1430
|
store;
|
|
1266
1431
|
pendingPersistentSessionClients = /* @__PURE__ */ new Map();
|
|
1267
|
-
|
|
1432
|
+
attempts = /* @__PURE__ */ new Map();
|
|
1433
|
+
pendingClientReleases = /* @__PURE__ */ new WeakMap();
|
|
1268
1434
|
runInterruptions = /* @__PURE__ */ new Map();
|
|
1269
1435
|
shellOwners = /* @__PURE__ */ new Map();
|
|
1270
1436
|
constructor(options) {
|
|
@@ -1305,23 +1471,27 @@ var FlowRunner = class {
|
|
|
1305
1471
|
steps: [],
|
|
1306
1472
|
sessionBindings: {}
|
|
1307
1473
|
};
|
|
1308
|
-
const inputArtifact = await this.store.writeArtifact(runDir, state, input, {
|
|
1309
|
-
mediaType: "application/json",
|
|
1310
|
-
extension: "json",
|
|
1311
|
-
emitTrace: false
|
|
1312
|
-
});
|
|
1313
|
-
await this.store.initializeRunBundle(runDir, {
|
|
1314
|
-
flow,
|
|
1315
|
-
state,
|
|
1316
|
-
inputArtifact
|
|
1317
|
-
});
|
|
1318
1474
|
try {
|
|
1319
|
-
|
|
1475
|
+
const inputArtifact = await this.store.writeArtifact(runDir, state, input, {
|
|
1476
|
+
mediaType: "application/json",
|
|
1477
|
+
extension: "json",
|
|
1478
|
+
emitTrace: false
|
|
1479
|
+
});
|
|
1480
|
+
await this.store.initializeRunBundle(runDir, {
|
|
1481
|
+
flow,
|
|
1482
|
+
state,
|
|
1483
|
+
inputArtifact
|
|
1484
|
+
});
|
|
1485
|
+
return await this.runWithOwnership(flow, input, runDir, state);
|
|
1320
1486
|
} finally {
|
|
1321
|
-
|
|
1487
|
+
try {
|
|
1488
|
+
await this.closePendingPersistentSessionClients(runDir);
|
|
1489
|
+
} finally {
|
|
1490
|
+
this.store.releaseRun(runDir);
|
|
1491
|
+
}
|
|
1322
1492
|
}
|
|
1323
1493
|
}
|
|
1324
|
-
async
|
|
1494
|
+
async runWithOwnership(flow, input, runDir, state) {
|
|
1325
1495
|
let execution;
|
|
1326
1496
|
let cancellation;
|
|
1327
1497
|
let interruption;
|
|
@@ -1334,11 +1504,8 @@ var FlowRunner = class {
|
|
|
1334
1504
|
this.runInterruptions.set(runDir, reason);
|
|
1335
1505
|
cancellation ??= this.cancelShellOwners(runDir, signal);
|
|
1336
1506
|
cancellation.catch(() => {});
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
interruptShell(reason, signal);
|
|
1340
|
-
await execution?.catch(() => {});
|
|
1341
|
-
}
|
|
1507
|
+
this.attempts.get(runDir)?.cancel(reason, signal);
|
|
1508
|
+
await execution?.catch(() => {});
|
|
1342
1509
|
}).then((value) => ({
|
|
1343
1510
|
ok: true,
|
|
1344
1511
|
value
|
|
@@ -1390,7 +1557,7 @@ var FlowRunner = class {
|
|
|
1390
1557
|
let current = flow.startAt;
|
|
1391
1558
|
const attemptCounts = /* @__PURE__ */ new Map();
|
|
1392
1559
|
try {
|
|
1393
|
-
while (current) {
|
|
1560
|
+
while (current !== null) {
|
|
1394
1561
|
this.throwIfRunInterrupted(runDir);
|
|
1395
1562
|
const step = await this.executeFlowStep(flow, input, runDir, state, current, attemptCounts);
|
|
1396
1563
|
this.throwIfRunInterrupted(runDir, step.executionError);
|
|
@@ -1412,21 +1579,33 @@ var FlowRunner = class {
|
|
|
1412
1579
|
if (interrupted) throw error ?? interrupted;
|
|
1413
1580
|
}
|
|
1414
1581
|
async executeFlowStep(flow, input, runDir, state, nodeId, attemptCounts) {
|
|
1582
|
+
if (!Object.hasOwn(flow.nodes, nodeId)) throw new Error(`Unknown flow node: ${nodeId}`);
|
|
1415
1583
|
const node = flow.nodes[nodeId];
|
|
1416
|
-
if (!node) throw new Error(`Unknown flow node: ${nodeId}`);
|
|
1417
1584
|
const attemptId = nextAttemptId(attemptCounts, nodeId);
|
|
1418
1585
|
const startedAt = isoNow$1();
|
|
1419
1586
|
markNodeStarted(state, nodeId, attemptId, node.nodeType, startedAt, node.statusDetail);
|
|
1420
|
-
|
|
1421
|
-
|
|
1587
|
+
const attempt = new FlowAttempt({
|
|
1588
|
+
nodeId,
|
|
1589
|
+
attemptId,
|
|
1590
|
+
startedAt,
|
|
1591
|
+
timeoutMs: node.timeoutMs ?? this.defaultNodeTimeoutMs
|
|
1592
|
+
});
|
|
1593
|
+
this.attempts.set(runDir, attempt);
|
|
1594
|
+
const context = this.makeAttemptContext(runDir, state, input, node, attempt);
|
|
1595
|
+
let stopHeartbeat = () => {};
|
|
1422
1596
|
let executed;
|
|
1423
1597
|
let outcome = "ok";
|
|
1424
1598
|
let executionError;
|
|
1425
1599
|
try {
|
|
1426
1600
|
this.throwIfRunInterrupted(runDir);
|
|
1427
|
-
executed = await
|
|
1601
|
+
executed = await attempt.run(async () => {
|
|
1602
|
+
await attempt.own(() => this.writeNodeStartedSnapshot(runDir, state, nodeId, attemptId, node));
|
|
1603
|
+
stopHeartbeat = this.startHeartbeat(runDir, state, node, attempt);
|
|
1604
|
+
const result = await this.executeNode(runDir, state, flow, nodeId, node, context);
|
|
1605
|
+
result.trace = await attempt.own(() => finalizeStepTrace(this.store, runDir, state, nodeId, attemptId, result.output, result.trace));
|
|
1606
|
+
return result;
|
|
1607
|
+
});
|
|
1428
1608
|
this.throwIfRunInterrupted(runDir);
|
|
1429
|
-
executed.trace = await finalizeStepTrace(this.store, runDir, state, nodeId, attemptId, executed.output, executed.trace);
|
|
1430
1609
|
} catch (error) {
|
|
1431
1610
|
outcome = outcomeForError(error);
|
|
1432
1611
|
executionError = error;
|
|
@@ -1436,8 +1615,12 @@ var FlowRunner = class {
|
|
|
1436
1615
|
rawText: null,
|
|
1437
1616
|
sessionInfo: null,
|
|
1438
1617
|
agentInfo: null,
|
|
1439
|
-
|
|
1618
|
+
...context.acpResult,
|
|
1619
|
+
trace: await finalizeStepTrace(this.store, runDir, state, nodeId, attemptId, void 0, context.acpResult ? context.acpResult.trace : extractAttachedStepTrace(error) ?? null)
|
|
1440
1620
|
};
|
|
1621
|
+
} finally {
|
|
1622
|
+
stopHeartbeat();
|
|
1623
|
+
this.attempts.delete(runDir);
|
|
1441
1624
|
}
|
|
1442
1625
|
const nodeResult = createNodeResult({
|
|
1443
1626
|
attemptId,
|
|
@@ -1448,7 +1631,7 @@ var FlowRunner = class {
|
|
|
1448
1631
|
finishedAt: isoNow$1(),
|
|
1449
1632
|
...outcome === "ok" ? { output: executed.output } : { error: executionError instanceof Error ? executionError.message : String(executionError) }
|
|
1450
1633
|
});
|
|
1451
|
-
state.results
|
|
1634
|
+
setNodeValue(state.results, nodeId, nodeResult);
|
|
1452
1635
|
return {
|
|
1453
1636
|
...executed,
|
|
1454
1637
|
nodeResult,
|
|
@@ -1460,6 +1643,17 @@ var FlowRunner = class {
|
|
|
1460
1643
|
state
|
|
1461
1644
|
};
|
|
1462
1645
|
}
|
|
1646
|
+
makeAttemptContext(runDir, state, input, node, attempt) {
|
|
1647
|
+
const context = {
|
|
1648
|
+
nodeContext: {
|
|
1649
|
+
...makeFlowNodeContext(state, input, this.services),
|
|
1650
|
+
signal: attempt.signal
|
|
1651
|
+
},
|
|
1652
|
+
attempt
|
|
1653
|
+
};
|
|
1654
|
+
if (node.nodeType === "action" && "run" in node) context.nodeContext.runShell = (execution) => this.runCallbackShell(runDir, attempt, execution);
|
|
1655
|
+
return context;
|
|
1656
|
+
}
|
|
1463
1657
|
async writeNodeStartedSnapshot(runDir, state, nodeId, attemptId, node) {
|
|
1464
1658
|
await this.store.writeSnapshot(runDir, state, {
|
|
1465
1659
|
scope: "node",
|
|
@@ -1475,7 +1669,7 @@ var FlowRunner = class {
|
|
|
1475
1669
|
}
|
|
1476
1670
|
async maybeCompleteCheckpointStep(runDir, state, step) {
|
|
1477
1671
|
if (step.nodeResult.outcome !== "ok" || step.node.nodeType !== "checkpoint") return;
|
|
1478
|
-
state.outputs
|
|
1672
|
+
setNodeValue(state.outputs, step.nodeId, step.output);
|
|
1479
1673
|
state.waitingOn = step.nodeId;
|
|
1480
1674
|
state.updatedAt = isoNow$1();
|
|
1481
1675
|
state.status = "waiting";
|
|
@@ -1487,11 +1681,11 @@ var FlowRunner = class {
|
|
|
1487
1681
|
}
|
|
1488
1682
|
resolveNextNode(flow, step) {
|
|
1489
1683
|
if (step.nodeResult.outcome === "ok") {
|
|
1490
|
-
step.state.outputs
|
|
1684
|
+
setNodeValue(step.state.outputs, step.nodeId, step.output);
|
|
1491
1685
|
return resolveNext(flow.edges, step.nodeId, step.output, step.nodeResult);
|
|
1492
1686
|
}
|
|
1493
1687
|
const next = resolveNext(flow.edges, step.nodeId, void 0, step.nodeResult);
|
|
1494
|
-
if (next) return next;
|
|
1688
|
+
if (next !== null) return next;
|
|
1495
1689
|
throw step.executionError;
|
|
1496
1690
|
}
|
|
1497
1691
|
async completeFlowRun(runDir, state) {
|
|
@@ -1537,19 +1731,22 @@ var FlowRunner = class {
|
|
|
1537
1731
|
}
|
|
1538
1732
|
async executeNode(runDir, state, flow, nodeId, node, context) {
|
|
1539
1733
|
switch (node.nodeType) {
|
|
1540
|
-
case "compute": return await this.executeCallbackNode(
|
|
1541
|
-
case "action": return "run" in node ? await this.executeCallbackNode(
|
|
1542
|
-
case "checkpoint": return await this.executeCallbackNode(
|
|
1734
|
+
case "compute": return await this.executeCallbackNode(nodeId, node, context);
|
|
1735
|
+
case "action": return "run" in node ? await this.executeCallbackNode(nodeId, node, context) : await this.executeShellNode(runDir, state, node, context);
|
|
1736
|
+
case "checkpoint": return await this.executeCallbackNode(nodeId, node, context);
|
|
1543
1737
|
case "acp": return await this.executeAcpNode(runDir, state, flow, node, context);
|
|
1544
1738
|
default: throw new Error(`Unsupported flow node: ${String(node)}`);
|
|
1545
1739
|
}
|
|
1546
1740
|
}
|
|
1547
|
-
async executeCallbackNode(
|
|
1741
|
+
async executeCallbackNode(nodeId, node, context) {
|
|
1742
|
+
context.attempt.assertActive();
|
|
1743
|
+
const output = node.nodeType === "checkpoint" && typeof node.run !== "function" ? {
|
|
1744
|
+
checkpoint: nodeId,
|
|
1745
|
+
summary: node.summary ?? nodeId
|
|
1746
|
+
} : await node.run?.(context.nodeContext);
|
|
1747
|
+
context.attempt.assertActive();
|
|
1548
1748
|
return {
|
|
1549
|
-
output
|
|
1550
|
-
checkpoint: nodeId,
|
|
1551
|
-
summary: node.summary ?? nodeId
|
|
1552
|
-
} : await this.runWithHeartbeat(runDir, state, nodeId, node, node.timeoutMs ?? this.defaultNodeTimeoutMs, async () => await node.run?.(context)),
|
|
1749
|
+
output,
|
|
1553
1750
|
promptText: null,
|
|
1554
1751
|
rawText: null,
|
|
1555
1752
|
sessionInfo: null,
|
|
@@ -1558,186 +1755,173 @@ var FlowRunner = class {
|
|
|
1558
1755
|
};
|
|
1559
1756
|
}
|
|
1560
1757
|
async executeShellNode(runDir, state, node, context) {
|
|
1561
|
-
const
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
return this.registerShellOwner(runDir, owner);
|
|
1569
|
-
}
|
|
1758
|
+
const attempt = context.attempt;
|
|
1759
|
+
const execution = await node.exec(context.nodeContext);
|
|
1760
|
+
attempt.assertActive();
|
|
1761
|
+
const effectiveExecution = {
|
|
1762
|
+
...execution,
|
|
1763
|
+
cwd: resolveShellActionCwd(this.defaultCwd, execution.cwd),
|
|
1764
|
+
timeoutMs: resolveShellActionTimeoutMs(execution.timeoutMs ?? attempt.remainingTimeoutMs())
|
|
1570
1765
|
};
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
shellAbort.signal.throwIfAborted();
|
|
1621
|
-
await this.store.appendTrace(runDir, state, {
|
|
1622
|
-
scope: "action",
|
|
1623
|
-
type: "action_completed",
|
|
1624
|
-
nodeId: state.currentNode,
|
|
1625
|
-
attemptId: state.currentAttemptId,
|
|
1626
|
-
payload: {
|
|
1627
|
-
action: {
|
|
1628
|
-
actionType: "shell",
|
|
1629
|
-
command: result.command,
|
|
1630
|
-
args: result.args,
|
|
1631
|
-
cwd: result.cwd,
|
|
1632
|
-
exitCode: result.exitCode,
|
|
1633
|
-
signal: result.signal,
|
|
1634
|
-
durationMs: result.durationMs
|
|
1635
|
-
},
|
|
1636
|
-
stdoutArtifact,
|
|
1637
|
-
stderrArtifact
|
|
1638
|
-
}
|
|
1639
|
-
});
|
|
1640
|
-
const trace = {
|
|
1641
|
-
action: {
|
|
1642
|
-
actionType: "shell",
|
|
1643
|
-
command: result.command,
|
|
1644
|
-
args: result.args,
|
|
1645
|
-
cwd: result.cwd,
|
|
1646
|
-
exitCode: result.exitCode,
|
|
1647
|
-
signal: result.signal,
|
|
1648
|
-
durationMs: result.durationMs
|
|
1649
|
-
},
|
|
1766
|
+
updateStatusDetail(state, formatShellActionSummary(effectiveExecution));
|
|
1767
|
+
await attempt.own(() => this.store.writeLive(runDir, state, {
|
|
1768
|
+
scope: "node",
|
|
1769
|
+
type: "node_heartbeat",
|
|
1770
|
+
nodeId: attempt.nodeId,
|
|
1771
|
+
attemptId: attempt.attemptId,
|
|
1772
|
+
payload: { statusDetail: state.statusDetail }
|
|
1773
|
+
}));
|
|
1774
|
+
await attempt.own(() => this.store.appendTrace(runDir, state, {
|
|
1775
|
+
scope: "action",
|
|
1776
|
+
type: "action_prepared",
|
|
1777
|
+
nodeId: attempt.nodeId,
|
|
1778
|
+
attemptId: attempt.attemptId,
|
|
1779
|
+
payload: { action: {
|
|
1780
|
+
actionType: "shell",
|
|
1781
|
+
command: effectiveExecution.command,
|
|
1782
|
+
args: effectiveExecution.args ?? [],
|
|
1783
|
+
cwd: effectiveExecution.cwd
|
|
1784
|
+
} }
|
|
1785
|
+
}));
|
|
1786
|
+
const result = await attempt.own(() => runShellAction(effectiveExecution, this.shellControl(runDir, attempt)));
|
|
1787
|
+
const stdoutArtifact = await attempt.own(() => this.store.writeArtifact(runDir, state, result.stdout, {
|
|
1788
|
+
mediaType: "text/plain",
|
|
1789
|
+
extension: "txt",
|
|
1790
|
+
nodeId: attempt.nodeId,
|
|
1791
|
+
attemptId: attempt.attemptId
|
|
1792
|
+
}));
|
|
1793
|
+
const stderrArtifact = await attempt.own(() => this.store.writeArtifact(runDir, state, result.stderr, {
|
|
1794
|
+
mediaType: "text/plain",
|
|
1795
|
+
extension: "txt",
|
|
1796
|
+
nodeId: attempt.nodeId,
|
|
1797
|
+
attemptId: attempt.attemptId
|
|
1798
|
+
}));
|
|
1799
|
+
const actionTrace = {
|
|
1800
|
+
actionType: "shell",
|
|
1801
|
+
command: result.command,
|
|
1802
|
+
args: result.args,
|
|
1803
|
+
cwd: result.cwd,
|
|
1804
|
+
exitCode: result.exitCode,
|
|
1805
|
+
signal: result.signal,
|
|
1806
|
+
durationMs: result.durationMs
|
|
1807
|
+
};
|
|
1808
|
+
await attempt.own(() => this.store.appendTrace(runDir, state, {
|
|
1809
|
+
scope: "action",
|
|
1810
|
+
type: "action_completed",
|
|
1811
|
+
nodeId: attempt.nodeId,
|
|
1812
|
+
attemptId: attempt.attemptId,
|
|
1813
|
+
payload: {
|
|
1814
|
+
action: actionTrace,
|
|
1650
1815
|
stdoutArtifact,
|
|
1651
1816
|
stderrArtifact
|
|
1652
|
-
};
|
|
1653
|
-
shellAbort.signal.throwIfAborted();
|
|
1654
|
-
let parsedOutput;
|
|
1655
|
-
try {
|
|
1656
|
-
parsedOutput = node.parse ? await node.parse(result, context) : result;
|
|
1657
|
-
shellAbort.signal.throwIfAborted();
|
|
1658
|
-
} catch (error) {
|
|
1659
|
-
throw attachStepTrace(error, trace);
|
|
1660
|
-
}
|
|
1661
|
-
return {
|
|
1662
|
-
output: parsedOutput,
|
|
1663
|
-
rawText: result.combinedOutput,
|
|
1664
|
-
trace
|
|
1665
|
-
};
|
|
1666
|
-
}, shellAbort.signal)).catch(async (error) => {
|
|
1667
|
-
shellAbort.abort(error);
|
|
1668
|
-
try {
|
|
1669
|
-
await runningOwner?.cancel(shellControl.terminationSignal ?? "SIGTERM");
|
|
1670
|
-
await runningShell;
|
|
1671
|
-
} catch (cleanupError) {
|
|
1672
|
-
if (!(cleanupError instanceof TimeoutError) && cleanupError !== error) throw new AggregateError([error, cleanupError], "Shell action cleanup failed", { cause: cleanupError });
|
|
1673
1817
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1818
|
+
}));
|
|
1819
|
+
const trace = {
|
|
1820
|
+
action: actionTrace,
|
|
1821
|
+
stdoutArtifact,
|
|
1822
|
+
stderrArtifact
|
|
1823
|
+
};
|
|
1824
|
+
let output;
|
|
1825
|
+
try {
|
|
1826
|
+
output = node.parse ? await node.parse(result, context.nodeContext) : result;
|
|
1827
|
+
attempt.assertActive();
|
|
1828
|
+
} catch (error) {
|
|
1829
|
+
throw attachStepTrace(error, trace);
|
|
1830
|
+
}
|
|
1678
1831
|
return {
|
|
1679
1832
|
output,
|
|
1680
1833
|
promptText: null,
|
|
1681
|
-
rawText,
|
|
1834
|
+
rawText: result.combinedOutput,
|
|
1682
1835
|
sessionInfo: null,
|
|
1683
1836
|
agentInfo: null,
|
|
1684
1837
|
trace
|
|
1685
1838
|
};
|
|
1686
1839
|
}
|
|
1840
|
+
shellControl(runDir, attempt) {
|
|
1841
|
+
return {
|
|
1842
|
+
signal: attempt.signal,
|
|
1843
|
+
get terminationSignal() {
|
|
1844
|
+
return attempt.terminationSignal;
|
|
1845
|
+
},
|
|
1846
|
+
registerOwner: (owner) => {
|
|
1847
|
+
const releaseAttempt = attempt.registerCancellation((signal) => owner.cancel(signal));
|
|
1848
|
+
const releaseRun = this.registerShellOwner(runDir, owner);
|
|
1849
|
+
return () => {
|
|
1850
|
+
releaseAttempt();
|
|
1851
|
+
releaseRun();
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
runCallbackShell(runDir, attempt, execution) {
|
|
1857
|
+
return attempt.own(() => runShellCommand({
|
|
1858
|
+
...execution,
|
|
1859
|
+
cwd: resolveShellActionCwd(this.defaultCwd, execution.cwd)
|
|
1860
|
+
}, this.shellControl(runDir, attempt)));
|
|
1861
|
+
}
|
|
1687
1862
|
async executeAcpNode(runDir, state, flow, node, context) {
|
|
1688
|
-
const
|
|
1689
|
-
|
|
1690
|
-
return await this.
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
boundSession = await this.ensureSessionBinding(runDir, state, flow, node, prepared.agentInfo, nodeTimeoutMs);
|
|
1694
|
-
return await this.executePersistentAcpPrompt(runDir, state, node, context, prepared, boundSession);
|
|
1695
|
-
}, async () => {
|
|
1696
|
-
if (!boundSession) return;
|
|
1697
|
-
await cancelSessionPrompt({ sessionId: boundSession.acpxRecordId });
|
|
1698
|
-
});
|
|
1863
|
+
const attempt = context.attempt;
|
|
1864
|
+
const prepared = await this.prepareAcpPrompt(runDir, state, node, context);
|
|
1865
|
+
if (node.session?.isolated) return await this.executeIsolatedAcpPrompt(runDir, state, flow, node, context, prepared);
|
|
1866
|
+
const boundSession = await this.ensureSessionBinding(runDir, state, flow, node, prepared.agentInfo, attempt);
|
|
1867
|
+
return await this.executePersistentAcpPrompt(runDir, state, node, context, prepared, boundSession);
|
|
1699
1868
|
}
|
|
1700
|
-
async prepareAcpPrompt(runDir, state, node, context
|
|
1869
|
+
async prepareAcpPrompt(runDir, state, node, context) {
|
|
1701
1870
|
const resolvedAgent = this.resolveAgent(node.profile);
|
|
1702
1871
|
const agentInfo = {
|
|
1703
1872
|
...resolvedAgent,
|
|
1704
|
-
|
|
1873
|
+
agentArgv: resolvedAgent.agentArgv?.slice(),
|
|
1874
|
+
cwd: await resolveNodeCwd(resolvedAgent.cwd, node.cwd, context.nodeContext)
|
|
1705
1875
|
};
|
|
1706
|
-
|
|
1876
|
+
context.attempt.assertActive();
|
|
1877
|
+
const prompt = normalizePromptInput(await Promise.resolve(node.prompt(context.nodeContext)));
|
|
1878
|
+
context.attempt.assertActive();
|
|
1707
1879
|
const promptText = promptToDisplayText(prompt);
|
|
1880
|
+
const result = {
|
|
1881
|
+
output: void 0,
|
|
1882
|
+
promptText,
|
|
1883
|
+
rawText: null,
|
|
1884
|
+
sessionInfo: null,
|
|
1885
|
+
agentInfo,
|
|
1886
|
+
trace: null
|
|
1887
|
+
};
|
|
1888
|
+
context.acpResult = result;
|
|
1708
1889
|
updateStatusDetail(state, summarizePrompt(promptText, node.statusDetail));
|
|
1709
|
-
await this.writeAcpPromptHeartbeat(runDir, state);
|
|
1890
|
+
await context.attempt.own(() => this.writeAcpPromptHeartbeat(runDir, state, context.attempt));
|
|
1891
|
+
const promptArtifact = await context.attempt.own(() => this.store.writeArtifact(runDir, state, promptText, {
|
|
1892
|
+
mediaType: "text/plain",
|
|
1893
|
+
extension: "txt",
|
|
1894
|
+
nodeId: context.attempt.nodeId,
|
|
1895
|
+
attemptId: context.attempt.attemptId
|
|
1896
|
+
}));
|
|
1897
|
+
result.trace = { promptArtifact };
|
|
1710
1898
|
return {
|
|
1711
1899
|
agentInfo,
|
|
1712
1900
|
prompt,
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
extension: "txt",
|
|
1717
|
-
nodeId: state.currentNode,
|
|
1718
|
-
attemptId: state.currentAttemptId
|
|
1719
|
-
}),
|
|
1720
|
-
nodeTimeoutMs
|
|
1901
|
+
promptArtifact,
|
|
1902
|
+
attempt: context.attempt,
|
|
1903
|
+
result
|
|
1721
1904
|
};
|
|
1722
1905
|
}
|
|
1723
|
-
async writeAcpPromptHeartbeat(runDir, state) {
|
|
1906
|
+
async writeAcpPromptHeartbeat(runDir, state, attempt) {
|
|
1724
1907
|
await this.store.writeLive(runDir, state, {
|
|
1725
1908
|
scope: "node",
|
|
1726
1909
|
type: "node_heartbeat",
|
|
1727
|
-
nodeId:
|
|
1728
|
-
attemptId:
|
|
1910
|
+
nodeId: attempt.nodeId,
|
|
1911
|
+
attemptId: attempt.attemptId,
|
|
1729
1912
|
payload: { statusDetail: state.statusDetail }
|
|
1730
1913
|
});
|
|
1731
1914
|
}
|
|
1732
1915
|
async executeIsolatedAcpPrompt(runDir, state, flow, node, context, prepared) {
|
|
1733
|
-
const binding = createIsolatedSessionBinding(flow.name, state.runId,
|
|
1734
|
-
|
|
1735
|
-
await this.
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1916
|
+
const binding = createIsolatedSessionBinding(flow.name, state.runId, prepared.attempt.attemptId, node.profile, prepared.agentInfo);
|
|
1917
|
+
prepared.result.sessionInfo = binding;
|
|
1918
|
+
await prepared.attempt.own(() => this.initializeIsolatedSessionBundle(runDir, state, binding, prepared.attempt));
|
|
1919
|
+
await prepared.attempt.own(() => this.appendAcpPromptPreparedTrace(runDir, state, binding, prepared.promptArtifact, prepared.attempt));
|
|
1920
|
+
const prompt = await prepared.attempt.own(() => this.runIsolatedPrompt(runDir, state, binding, prepared));
|
|
1921
|
+
return await this.finishAcpPrompt(runDir, state, node, context, prepared, prompt);
|
|
1922
|
+
}
|
|
1923
|
+
async initializeIsolatedSessionBundle(runDir, state, binding, attempt) {
|
|
1924
|
+
const timestamp = attempt.startedAt;
|
|
1741
1925
|
const initialRecord = createSyntheticSessionRecord({
|
|
1742
1926
|
binding,
|
|
1743
1927
|
createdAt: timestamp,
|
|
@@ -1749,16 +1933,17 @@ var FlowRunner = class {
|
|
|
1749
1933
|
await this.store.ensureSessionBundle(runDir, state, binding, initialRecord);
|
|
1750
1934
|
}
|
|
1751
1935
|
async executePersistentAcpPrompt(runDir, state, node, context, prepared, binding) {
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1936
|
+
prepared.result.sessionInfo = binding;
|
|
1937
|
+
await prepared.attempt.own(() => this.appendAcpPromptPreparedTrace(runDir, state, binding, prepared.promptArtifact, prepared.attempt));
|
|
1938
|
+
const prompt = await prepared.attempt.own(() => this.runPersistentPrompt(runDir, state, binding, prepared));
|
|
1939
|
+
return await this.finishAcpPrompt(runDir, state, node, context, prepared, prompt);
|
|
1755
1940
|
}
|
|
1756
|
-
async appendAcpPromptPreparedTrace(runDir, state, binding, promptArtifact) {
|
|
1941
|
+
async appendAcpPromptPreparedTrace(runDir, state, binding, promptArtifact, attempt) {
|
|
1757
1942
|
await this.store.appendTrace(runDir, state, {
|
|
1758
1943
|
scope: "acp",
|
|
1759
1944
|
type: "acp_prompt_prepared",
|
|
1760
|
-
nodeId:
|
|
1761
|
-
attemptId:
|
|
1945
|
+
nodeId: attempt.nodeId,
|
|
1946
|
+
attemptId: attempt.attemptId,
|
|
1762
1947
|
sessionId: binding.bundleId,
|
|
1763
1948
|
payload: {
|
|
1764
1949
|
sessionId: binding.bundleId,
|
|
@@ -1766,107 +1951,121 @@ var FlowRunner = class {
|
|
|
1766
1951
|
}
|
|
1767
1952
|
});
|
|
1768
1953
|
}
|
|
1769
|
-
async finishAcpPrompt(runDir, state, node, context, prepared, prompt
|
|
1770
|
-
|
|
1771
|
-
await this.
|
|
1954
|
+
async finishAcpPrompt(runDir, state, node, context, prepared, prompt) {
|
|
1955
|
+
await prepared.attempt.own(() => this.appendAcpResponseParsedTrace(runDir, state, prompt, prepared.attempt));
|
|
1956
|
+
const output = await this.parseAcpOutput(node, context, prompt.rawText);
|
|
1957
|
+
return {
|
|
1958
|
+
...prepared.result,
|
|
1959
|
+
output
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
async publishAcpCapture(runDir, state, prepared, sessionInfo, record, messageStart, rawText, events) {
|
|
1963
|
+
const result = prepared.result;
|
|
1964
|
+
result.sessionInfo = sessionInfo;
|
|
1772
1965
|
const trace = {
|
|
1773
1966
|
sessionId: sessionInfo.bundleId,
|
|
1774
|
-
promptArtifact: prepared.promptArtifact
|
|
1775
|
-
rawResponseArtifact,
|
|
1776
|
-
conversation: prompt.conversation
|
|
1967
|
+
promptArtifact: prepared.promptArtifact
|
|
1777
1968
|
};
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
sessionInfo,
|
|
1783
|
-
|
|
1784
|
-
|
|
1969
|
+
result.trace = trace;
|
|
1970
|
+
await this.store.ensureSessionBundle(runDir, state, sessionInfo);
|
|
1971
|
+
await this.store.writeSessionRecord(runDir, state, sessionInfo, record);
|
|
1972
|
+
if (events) trace.conversation = {
|
|
1973
|
+
sessionId: sessionInfo.bundleId,
|
|
1974
|
+
messageStart,
|
|
1975
|
+
messageEnd: Math.max(messageStart, record.messages.length - 1),
|
|
1976
|
+
...events
|
|
1785
1977
|
};
|
|
1786
|
-
|
|
1787
|
-
async writeAcpRawResponseArtifact(runDir, state, prompt, sessionInfo) {
|
|
1788
|
-
return await this.store.writeArtifact(runDir, state, prompt.rawText, {
|
|
1978
|
+
const rawResponseArtifact = await this.store.writeArtifact(runDir, state, rawText, {
|
|
1789
1979
|
mediaType: "text/plain",
|
|
1790
1980
|
extension: "txt",
|
|
1791
|
-
nodeId:
|
|
1792
|
-
attemptId:
|
|
1981
|
+
nodeId: prepared.attempt.nodeId,
|
|
1982
|
+
attemptId: prepared.attempt.attemptId,
|
|
1793
1983
|
sessionId: sessionInfo.bundleId
|
|
1794
1984
|
});
|
|
1985
|
+
trace.rawResponseArtifact = rawResponseArtifact;
|
|
1986
|
+
return {
|
|
1987
|
+
rawText,
|
|
1988
|
+
sessionInfo,
|
|
1989
|
+
conversation: trace.conversation,
|
|
1990
|
+
rawResponseArtifact
|
|
1991
|
+
};
|
|
1795
1992
|
}
|
|
1796
|
-
async appendAcpResponseParsedTrace(runDir, state, prompt,
|
|
1993
|
+
async appendAcpResponseParsedTrace(runDir, state, prompt, attempt) {
|
|
1797
1994
|
await this.store.appendTrace(runDir, state, {
|
|
1798
1995
|
scope: "acp",
|
|
1799
1996
|
type: "acp_response_parsed",
|
|
1800
|
-
nodeId:
|
|
1801
|
-
attemptId:
|
|
1802
|
-
sessionId: sessionInfo.bundleId,
|
|
1997
|
+
nodeId: attempt.nodeId,
|
|
1998
|
+
attemptId: attempt.attemptId,
|
|
1999
|
+
sessionId: prompt.sessionInfo.bundleId,
|
|
1803
2000
|
payload: {
|
|
1804
|
-
sessionId: sessionInfo.bundleId,
|
|
2001
|
+
sessionId: prompt.sessionInfo.bundleId,
|
|
1805
2002
|
conversation: prompt.conversation,
|
|
1806
|
-
rawResponseArtifact
|
|
2003
|
+
rawResponseArtifact: prompt.rawResponseArtifact
|
|
1807
2004
|
}
|
|
1808
2005
|
});
|
|
1809
2006
|
}
|
|
1810
|
-
async parseAcpOutput(node, context, rawText
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
}
|
|
2007
|
+
async parseAcpOutput(node, context, rawText) {
|
|
2008
|
+
context.attempt.assertActive();
|
|
2009
|
+
const output = node.parse ? await node.parse(rawText, context.nodeContext) : rawText;
|
|
2010
|
+
context.attempt.assertActive();
|
|
2011
|
+
return output;
|
|
1816
2012
|
}
|
|
1817
|
-
|
|
2013
|
+
startHeartbeat(runDir, state, node, attempt) {
|
|
1818
2014
|
const heartbeatMs = Math.max(0, Math.round(node.heartbeatMs ?? DEFAULT_FLOW_HEARTBEAT_MS));
|
|
1819
|
-
|
|
1820
|
-
let
|
|
1821
|
-
let heartbeatPending = false;
|
|
2015
|
+
if (heartbeatMs === 0) return () => {};
|
|
2016
|
+
let pending = false;
|
|
1822
2017
|
const heartbeat = async () => {
|
|
1823
|
-
if (!active ||
|
|
1824
|
-
|
|
2018
|
+
if (!attempt.active || pending) return;
|
|
2019
|
+
pending = true;
|
|
1825
2020
|
try {
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
2021
|
+
await attempt.own(async () => {
|
|
2022
|
+
state.lastHeartbeatAt = isoNow$1();
|
|
2023
|
+
state.updatedAt = state.lastHeartbeatAt;
|
|
2024
|
+
await this.store.writeLive(runDir, state, {
|
|
2025
|
+
scope: "node",
|
|
2026
|
+
type: "node_heartbeat",
|
|
2027
|
+
nodeId: attempt.nodeId,
|
|
2028
|
+
attemptId: attempt.attemptId,
|
|
2029
|
+
payload: { statusDetail: state.statusDetail }
|
|
2030
|
+
});
|
|
2031
|
+
}, { bestEffort: true });
|
|
2032
|
+
} catch {} finally {
|
|
2033
|
+
pending = false;
|
|
1837
2034
|
}
|
|
1838
2035
|
};
|
|
1839
|
-
|
|
1840
|
-
heartbeat()
|
|
2036
|
+
const timer = setInterval(() => {
|
|
2037
|
+
heartbeat();
|
|
1841
2038
|
}, heartbeatMs);
|
|
1842
|
-
|
|
1843
|
-
return await withTimeout(run(), timeoutMs);
|
|
1844
|
-
} catch (error) {
|
|
1845
|
-
if (error instanceof TimeoutError && onTimeout) await onTimeout().catch(() => {});
|
|
1846
|
-
throw error;
|
|
1847
|
-
} finally {
|
|
1848
|
-
active = false;
|
|
1849
|
-
if (timer) clearInterval(timer);
|
|
1850
|
-
}
|
|
2039
|
+
return () => clearInterval(timer);
|
|
1851
2040
|
}
|
|
1852
|
-
async ensureSessionBinding(runDir, state, flow, node, agent,
|
|
2041
|
+
async ensureSessionBinding(runDir, state, flow, node, agent, attempt) {
|
|
1853
2042
|
const handle = node.session?.handle ?? "main";
|
|
1854
|
-
const key = createSessionBindingKey(agent.agentCommand, agent.cwd, handle);
|
|
2043
|
+
const key = createSessionBindingKey(agent.agentCommand, agent.cwd, handle, agent.agentArgv);
|
|
1855
2044
|
const existing = state.sessionBindings[key];
|
|
1856
2045
|
if (existing) {
|
|
1857
|
-
await this.store.ensureSessionBundle(runDir, state, existing);
|
|
2046
|
+
await attempt.own(() => this.store.ensureSessionBundle(runDir, state, existing));
|
|
1858
2047
|
return existing;
|
|
1859
2048
|
}
|
|
1860
2049
|
const name = createSessionName(flow.name, handle, agent.cwd, state.runId);
|
|
1861
|
-
const created = await
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
2050
|
+
const created = await attempt.own(async () => {
|
|
2051
|
+
const acquired = await createSessionWithClient({
|
|
2052
|
+
agentCommand: agent.agentCommand,
|
|
2053
|
+
agentArgv: agent.agentArgv,
|
|
2054
|
+
cwd: agent.cwd,
|
|
2055
|
+
name,
|
|
2056
|
+
...this.connectionOptions,
|
|
2057
|
+
signal: attempt.signal,
|
|
2058
|
+
handleProcessInterrupts: false,
|
|
2059
|
+
sessionOptions: this.sessionOptions
|
|
2060
|
+
});
|
|
2061
|
+
this.pendingClientReleases.set(acquired.client, attempt.registerCancellation(async () => {
|
|
2062
|
+
const clients = this.pendingPersistentSessionClients.get(runDir);
|
|
2063
|
+
if (clients?.get(key) === acquired.client) clients.delete(key);
|
|
2064
|
+
await acquired.client.close();
|
|
2065
|
+
}));
|
|
2066
|
+
return acquired;
|
|
1869
2067
|
});
|
|
2068
|
+
attempt.assertActive();
|
|
1870
2069
|
const binding = {
|
|
1871
2070
|
key,
|
|
1872
2071
|
handle,
|
|
@@ -1882,31 +2081,44 @@ var FlowRunner = class {
|
|
|
1882
2081
|
agentSessionId: created.record.agentSessionId
|
|
1883
2082
|
};
|
|
1884
2083
|
state.sessionBindings[key] = binding;
|
|
1885
|
-
this.pendingPersistentSessionClients.
|
|
1886
|
-
|
|
2084
|
+
let clients = this.pendingPersistentSessionClients.get(runDir);
|
|
2085
|
+
if (!clients) {
|
|
2086
|
+
clients = /* @__PURE__ */ new Map();
|
|
2087
|
+
this.pendingPersistentSessionClients.set(runDir, clients);
|
|
2088
|
+
}
|
|
2089
|
+
clients.set(binding.key, created.client);
|
|
2090
|
+
await attempt.own(() => this.store.ensureSessionBundle(runDir, state, binding, created.record));
|
|
1887
2091
|
return binding;
|
|
1888
2092
|
}
|
|
1889
|
-
async refreshSessionBinding(binding) {
|
|
1890
|
-
const record = await resolveSessionRecord(binding.acpxRecordId);
|
|
1891
|
-
return {
|
|
1892
|
-
...binding,
|
|
1893
|
-
acpSessionId: record.acpSessionId,
|
|
1894
|
-
agentSessionId: record.agentSessionId
|
|
1895
|
-
};
|
|
1896
|
-
}
|
|
1897
2093
|
createPromptEventCapture(runDir, binding) {
|
|
1898
|
-
const pending =
|
|
2094
|
+
const pending = /* @__PURE__ */ new Set();
|
|
2095
|
+
let ordinal = 0;
|
|
2096
|
+
let failure;
|
|
2097
|
+
let eventStartSeq;
|
|
2098
|
+
let eventEndSeq = 0;
|
|
2099
|
+
const snapshot = (outcome) => ({
|
|
2100
|
+
outcome,
|
|
2101
|
+
events: !failure && eventStartSeq !== void 0 ? {
|
|
2102
|
+
eventStartSeq,
|
|
2103
|
+
eventEndSeq
|
|
2104
|
+
} : void 0,
|
|
2105
|
+
lastSeq: eventEndSeq
|
|
2106
|
+
});
|
|
1899
2107
|
return {
|
|
1900
2108
|
onAcpMessage: (direction, message) => {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
2109
|
+
const index = ordinal++;
|
|
2110
|
+
const write = this.store.appendSessionEvent(runDir, binding, direction, message).then((seq) => {
|
|
2111
|
+
eventStartSeq = Math.min(eventStartSeq ?? seq, seq);
|
|
2112
|
+
eventEndSeq = Math.max(eventEndSeq, seq);
|
|
2113
|
+
}, (reason) => {
|
|
2114
|
+
if (!failure || index < failure.ordinal) failure = {
|
|
2115
|
+
ordinal: index,
|
|
2116
|
+
reason
|
|
2117
|
+
};
|
|
2118
|
+
}).finally(() => pending.delete(write));
|
|
2119
|
+
pending.add(write);
|
|
1908
2120
|
},
|
|
1909
|
-
async run(operation) {
|
|
2121
|
+
async run(operation, finalize) {
|
|
1910
2122
|
let result;
|
|
1911
2123
|
try {
|
|
1912
2124
|
result = {
|
|
@@ -1919,78 +2131,84 @@ var FlowRunner = class {
|
|
|
1919
2131
|
reason
|
|
1920
2132
|
};
|
|
1921
2133
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
2134
|
+
await Promise.all(pending);
|
|
2135
|
+
let finalized;
|
|
2136
|
+
try {
|
|
2137
|
+
finalized = {
|
|
2138
|
+
status: "fulfilled",
|
|
2139
|
+
value: await finalize(snapshot(result))
|
|
2140
|
+
};
|
|
2141
|
+
} catch (reason) {
|
|
2142
|
+
finalized = {
|
|
2143
|
+
status: "rejected",
|
|
2144
|
+
reason
|
|
2145
|
+
};
|
|
1930
2146
|
}
|
|
2147
|
+
if (result.status === "rejected") throw result.reason;
|
|
2148
|
+
if (failure) throw failure.reason;
|
|
1931
2149
|
if (eventStartSeq === void 0) throw new Error(`Missing ACP event capture for session ${binding.bundleId}`);
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
eventStartSeq,
|
|
1935
|
-
eventEndSeq
|
|
1936
|
-
};
|
|
2150
|
+
if (finalized.status === "rejected") throw finalized.reason;
|
|
2151
|
+
return finalized.value;
|
|
1937
2152
|
}
|
|
1938
2153
|
};
|
|
1939
2154
|
}
|
|
1940
|
-
async runPersistentPrompt(runDir, state, binding,
|
|
2155
|
+
async runPersistentPrompt(runDir, state, binding, prepared) {
|
|
2156
|
+
const { attempt, prompt } = prepared;
|
|
1941
2157
|
const capture = createQuietCaptureOutput();
|
|
1942
2158
|
const beforeRecord = await resolveSessionRecord(binding.acpxRecordId);
|
|
2159
|
+
attempt.assertActive();
|
|
1943
2160
|
const events = this.createPromptEventCapture(runDir, binding);
|
|
1944
|
-
const
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
resumePolicy: "same-session-only",
|
|
1951
|
-
...this.connectionOptions,
|
|
1952
|
-
outputFormatter: capture.formatter,
|
|
1953
|
-
errorEmissionPolicy: { queueErrorAlreadyEmitted: false },
|
|
1954
|
-
onAcpMessage: events.onAcpMessage,
|
|
1955
|
-
suppressSdkConsoleErrors: this.suppressSdkConsoleErrors,
|
|
1956
|
-
timeoutMs,
|
|
1957
|
-
client: initialClient
|
|
1958
|
-
}));
|
|
1959
|
-
const sessionInfo = await this.refreshSessionBinding(binding);
|
|
1960
|
-
state.sessionBindings[sessionInfo.key] = sessionInfo;
|
|
1961
|
-
await this.store.ensureSessionBundle(runDir, state, sessionInfo);
|
|
1962
|
-
const afterRecord = await resolveSessionRecord(sessionInfo.acpxRecordId);
|
|
1963
|
-
await this.store.writeSessionRecord(runDir, state, sessionInfo, afterRecord);
|
|
1964
|
-
const messageStartResolved = findConversationDeltaStart(beforeRecord.messages, afterRecord.messages);
|
|
1965
|
-
return {
|
|
1966
|
-
rawText: capture.read(),
|
|
1967
|
-
sessionInfo,
|
|
1968
|
-
conversation: {
|
|
1969
|
-
sessionId: sessionInfo.bundleId,
|
|
1970
|
-
messageStart: messageStartResolved,
|
|
1971
|
-
messageEnd: Math.max(messageStartResolved, afterRecord.messages.length - 1),
|
|
1972
|
-
eventStartSeq,
|
|
1973
|
-
eventEndSeq
|
|
1974
|
-
}
|
|
1975
|
-
};
|
|
1976
|
-
} finally {
|
|
1977
|
-
if (initialClient) await initialClient.close().catch(() => {});
|
|
2161
|
+
const clients = this.pendingPersistentSessionClients.get(runDir);
|
|
2162
|
+
const initialClient = clients?.get(binding.key);
|
|
2163
|
+
if (initialClient) {
|
|
2164
|
+
clients?.delete(binding.key);
|
|
2165
|
+
this.pendingClientReleases.get(initialClient)?.();
|
|
2166
|
+
this.pendingClientReleases.delete(initialClient);
|
|
1978
2167
|
}
|
|
2168
|
+
return await events.run(() => sendSessionDirect({
|
|
2169
|
+
sessionId: binding.acpxRecordId,
|
|
2170
|
+
prompt,
|
|
2171
|
+
resumePolicy: "same-session-only",
|
|
2172
|
+
...this.connectionOptions,
|
|
2173
|
+
outputFormatter: capture.formatter,
|
|
2174
|
+
errorEmissionPolicy: { queueErrorAlreadyEmitted: false },
|
|
2175
|
+
onAcpMessage: events.onAcpMessage,
|
|
2176
|
+
suppressSdkConsoleErrors: this.suppressSdkConsoleErrors,
|
|
2177
|
+
client: initialClient
|
|
2178
|
+
}, {
|
|
2179
|
+
signal: attempt.signal,
|
|
2180
|
+
handleProcessInterrupts: false
|
|
2181
|
+
}), async (receipt) => {
|
|
2182
|
+
const rawText = capture.read();
|
|
2183
|
+
prepared.result.rawText = rawText;
|
|
2184
|
+
const afterRecord = await resolveSessionRecord(binding.acpxRecordId);
|
|
2185
|
+
const sessionInfo = {
|
|
2186
|
+
...binding,
|
|
2187
|
+
acpSessionId: afterRecord.acpSessionId,
|
|
2188
|
+
agentSessionId: afterRecord.agentSessionId
|
|
2189
|
+
};
|
|
2190
|
+
state.sessionBindings[sessionInfo.key] = sessionInfo;
|
|
2191
|
+
return await this.publishAcpCapture(runDir, state, prepared, sessionInfo, afterRecord, findConversationDeltaStart(beforeRecord.messages, afterRecord.messages), rawText, receipt.events);
|
|
2192
|
+
});
|
|
1979
2193
|
}
|
|
1980
|
-
async closePendingPersistentSessionClients() {
|
|
1981
|
-
const pendingClients = [...this.pendingPersistentSessionClients.values()];
|
|
1982
|
-
this.pendingPersistentSessionClients.
|
|
1983
|
-
await Promise.
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2194
|
+
async closePendingPersistentSessionClients(runDir) {
|
|
2195
|
+
const pendingClients = [...this.pendingPersistentSessionClients.get(runDir)?.values() ?? []];
|
|
2196
|
+
this.pendingPersistentSessionClients.delete(runDir);
|
|
2197
|
+
const failure = (await Promise.allSettled(pendingClients.map(async (client) => {
|
|
2198
|
+
this.pendingClientReleases.get(client)?.();
|
|
2199
|
+
this.pendingClientReleases.delete(client);
|
|
2200
|
+
await client.close();
|
|
2201
|
+
}))).find((result) => result.status === "rejected");
|
|
2202
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
2203
|
+
}
|
|
2204
|
+
async runIsolatedPrompt(runDir, state, binding, prepared) {
|
|
2205
|
+
const { agentInfo: agent, prompt, attempt } = prepared;
|
|
1988
2206
|
const capture = createQuietCaptureOutput();
|
|
1989
|
-
const conversation = createSessionConversation(
|
|
2207
|
+
const conversation = createSessionConversation(attempt.startedAt);
|
|
1990
2208
|
let acpxState;
|
|
1991
|
-
recordPromptSubmission(conversation, prompt,
|
|
2209
|
+
recordPromptSubmission(conversation, prompt, attempt.startedAt);
|
|
1992
2210
|
const events = this.createPromptEventCapture(runDir, binding);
|
|
1993
|
-
|
|
2211
|
+
return await events.run(() => runOnce({
|
|
1994
2212
|
agentCommand: agent.agentCommand,
|
|
1995
2213
|
agentArgv: agent.agentArgv,
|
|
1996
2214
|
cwd: agent.cwd,
|
|
@@ -2006,35 +2224,29 @@ var FlowRunner = class {
|
|
|
2006
2224
|
acpxState = recordClientOperation(conversation, acpxState, operation);
|
|
2007
2225
|
},
|
|
2008
2226
|
suppressSdkConsoleErrors: this.suppressSdkConsoleErrors,
|
|
2009
|
-
timeoutMs,
|
|
2010
2227
|
sessionOptions: this.sessionOptions
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2228
|
+
}, {
|
|
2229
|
+
signal: attempt.signal,
|
|
2230
|
+
handleProcessInterrupts: false
|
|
2231
|
+
}), async (receipt) => {
|
|
2232
|
+
const rawText = capture.read();
|
|
2233
|
+
prepared.result.rawText = rawText;
|
|
2234
|
+
const sessionId = receipt.outcome.status === "fulfilled" ? receipt.outcome.value.sessionId : capture.sessionId();
|
|
2235
|
+
const sessionInfo = sessionId === void 0 ? binding : {
|
|
2236
|
+
...binding,
|
|
2237
|
+
acpxRecordId: sessionId,
|
|
2238
|
+
acpSessionId: sessionId
|
|
2239
|
+
};
|
|
2240
|
+
const record = createSyntheticSessionRecord({
|
|
2241
|
+
binding: sessionInfo,
|
|
2242
|
+
createdAt: attempt.startedAt,
|
|
2243
|
+
updatedAt: conversation.updated_at,
|
|
2244
|
+
conversation,
|
|
2245
|
+
acpxState: cloneSessionAcpxState(acpxState),
|
|
2246
|
+
lastSeq: receipt.lastSeq
|
|
2247
|
+
});
|
|
2248
|
+
return await this.publishAcpCapture(runDir, state, prepared, sessionInfo, record, 0, rawText, receipt.events);
|
|
2025
2249
|
});
|
|
2026
|
-
await this.store.writeSessionRecord(runDir, state, sessionInfo, syntheticRecord);
|
|
2027
|
-
return {
|
|
2028
|
-
rawText: capture.read(),
|
|
2029
|
-
sessionInfo,
|
|
2030
|
-
conversation: {
|
|
2031
|
-
sessionId: sessionInfo.bundleId,
|
|
2032
|
-
messageStart: 0,
|
|
2033
|
-
messageEnd: Math.max(0, conversation.messages.length - 1),
|
|
2034
|
-
eventStartSeq,
|
|
2035
|
-
eventEndSeq
|
|
2036
|
-
}
|
|
2037
|
-
};
|
|
2038
2250
|
}
|
|
2039
2251
|
};
|
|
2040
2252
|
//#endregion
|
|
@@ -2064,9 +2276,23 @@ function parseFencedJsonIfAllowed(text, mode) {
|
|
|
2064
2276
|
const fencedText = extractFencedJsonText(text);
|
|
2065
2277
|
return fencedText === null ? { ok: false } : tryParse(fencedText);
|
|
2066
2278
|
}
|
|
2279
|
+
const COMPAT_JSON_WORK_MULTIPLIER = 8;
|
|
2280
|
+
function consumeRecoveryWork(budget, units) {
|
|
2281
|
+
if (units > budget.remaining) {
|
|
2282
|
+
budget.remaining = 0;
|
|
2283
|
+
return false;
|
|
2284
|
+
}
|
|
2285
|
+
budget.remaining -= units;
|
|
2286
|
+
return true;
|
|
2287
|
+
}
|
|
2067
2288
|
function parseBalancedJsonCandidate(text) {
|
|
2068
|
-
|
|
2069
|
-
|
|
2289
|
+
const budget = { remaining: text.length * COMPAT_JSON_WORK_MULTIPLIER };
|
|
2290
|
+
for (let index = 0; index < text.length && budget.remaining > 0; index += 1) {
|
|
2291
|
+
if (text[index] !== "{" && text[index] !== "[") continue;
|
|
2292
|
+
const end = scanBalanced(text, index, budget);
|
|
2293
|
+
if (end === null) continue;
|
|
2294
|
+
if (!consumeRecoveryWork(budget, end - index)) return { ok: false };
|
|
2295
|
+
const parsed = tryParse(text.slice(index, end));
|
|
2070
2296
|
if (parsed.ok) return parsed;
|
|
2071
2297
|
}
|
|
2072
2298
|
return { ok: false };
|
|
@@ -2100,20 +2326,12 @@ function extractFencedJsonText(text) {
|
|
|
2100
2326
|
function isFenceWhitespace(char) {
|
|
2101
2327
|
return char === " " || char === "\n" || char === "\r" || char === " ";
|
|
2102
2328
|
}
|
|
2103
|
-
function
|
|
2104
|
-
const candidates = [];
|
|
2105
|
-
for (let index = 0; index < text.length; index += 1) {
|
|
2106
|
-
if (text[index] !== "{" && text[index] !== "[") continue;
|
|
2107
|
-
const result = scanBalanced(text, index);
|
|
2108
|
-
if (result) candidates.push(result);
|
|
2109
|
-
}
|
|
2110
|
-
return candidates;
|
|
2111
|
-
}
|
|
2112
|
-
function scanBalanced(text, startIndex) {
|
|
2329
|
+
function scanBalanced(text, startIndex, budget) {
|
|
2113
2330
|
const stack = [];
|
|
2114
2331
|
let inString = false;
|
|
2115
2332
|
let escaped = false;
|
|
2116
2333
|
for (let index = startIndex; index < text.length; index += 1) {
|
|
2334
|
+
if (!consumeRecoveryWork(budget, 1)) return null;
|
|
2117
2335
|
const char = text[index];
|
|
2118
2336
|
if (inString) {
|
|
2119
2337
|
const next = scanStringChar(char, escaped);
|
|
@@ -2125,13 +2343,13 @@ function scanBalanced(text, startIndex) {
|
|
|
2125
2343
|
inString = true;
|
|
2126
2344
|
continue;
|
|
2127
2345
|
}
|
|
2128
|
-
const result = scanBalancedToken(
|
|
2346
|
+
const result = scanBalancedToken(index, char, stack);
|
|
2129
2347
|
if (result !== SCAN_CONTINUE) return result;
|
|
2130
2348
|
}
|
|
2131
2349
|
return null;
|
|
2132
2350
|
}
|
|
2133
2351
|
const SCAN_CONTINUE = Symbol("scan-continue");
|
|
2134
|
-
function scanBalancedToken(
|
|
2352
|
+
function scanBalancedToken(index, char, stack) {
|
|
2135
2353
|
if (char === "{" || char === "[") {
|
|
2136
2354
|
stack.push(char);
|
|
2137
2355
|
return SCAN_CONTINUE;
|
|
@@ -2139,7 +2357,7 @@ function scanBalancedToken(text, startIndex, index, char, stack) {
|
|
|
2139
2357
|
if (char !== "}" && char !== "]") return SCAN_CONTINUE;
|
|
2140
2358
|
if (!balancedClosingTokenMatches(stack.at(-1), char)) return null;
|
|
2141
2359
|
stack.pop();
|
|
2142
|
-
return stack.length === 0 ?
|
|
2360
|
+
return stack.length === 0 ? index + 1 : SCAN_CONTINUE;
|
|
2143
2361
|
}
|
|
2144
2362
|
function balancedClosingTokenMatches(open, close) {
|
|
2145
2363
|
if (open === "{") return close === "}";
|
|
@@ -2229,4 +2447,4 @@ function formatDecisionPrompt(question, choices, field) {
|
|
|
2229
2447
|
//#endregion
|
|
2230
2448
|
export { parseStrictJsonObject as a, validateFlowDefinition as c, checkpoint as d, compute as f, isDefinedFlow as h, parseJsonObject as i, acp as l, shell as m, decisionEdge as n, FlowRunner as o, defineFlow as p, extractJsonObject as r, flowRunsBaseDir as s, decision as t, action as u };
|
|
2231
2449
|
|
|
2232
|
-
//# sourceMappingURL=flows-
|
|
2450
|
+
//# sourceMappingURL=flows-DimX0it5.js.map
|