acpx 0.17.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-registry-evUoJINz.js → agent-registry-Ct2yWPW7.js} +18 -18
- package/dist/agent-registry-Ct2yWPW7.js.map +1 -0
- package/dist/agent-registry.d.ts.map +1 -1
- package/dist/agent-registry.js +1 -1
- package/dist/{cli-B2ycKzaO.js → cli-Bmki9cj5.js} +5 -5
- package/dist/{cli-B2ycKzaO.js.map → cli-Bmki9cj5.js.map} +1 -1
- package/dist/cli.js +167 -131
- package/dist/cli.js.map +1 -1
- package/dist/{client-CkbBoSGd.d.ts → client-DzaUKazo.d.ts} +23 -15
- package/dist/client-DzaUKazo.d.ts.map +1 -0
- package/dist/{flows-xGyhmoHO.js → flows--Ewxn_fQ.js} +665 -460
- package/dist/flows--Ewxn_fQ.js.map +1 -0
- package/dist/flows.d.ts +19 -7
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{invocation-options-eZEBh4vd.js → invocation-options-DRzxHZmr.js} +34 -12
- package/dist/invocation-options-DRzxHZmr.js.map +1 -0
- package/dist/{ipc-D2rstexT.js → ipc-fMgj5vrO.js} +818 -302
- package/dist/ipc-fMgj5vrO.js.map +1 -0
- package/dist/{output-D2xL6wdp.js → output-D6kDs8BN.js} +30 -14
- package/dist/output-D6kDs8BN.js.map +1 -0
- package/dist/{queue-owner-runtime-Dgv3e2Tp.js → queue-owner-runtime-CLwjMYNL.js} +1448 -717
- package/dist/queue-owner-runtime-CLwjMYNL.js.map +1 -0
- package/dist/runtime.d.ts +57 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +303 -166
- package/dist/runtime.js.map +1 -1
- package/dist/session-options-Dp6mzNSy.d.ts.map +1 -1
- package/dist/{session-watch-CTe9Bnbc.js → session-watch-BfdIo-n4.js} +5 -5
- package/dist/session-watch-BfdIo-n4.js.map +1 -0
- package/dist/{watch-DxuXEc8n.js → watch-DH-zXQH2.js} +2 -2
- package/dist/{watch-DxuXEc8n.js.map → watch-DH-zXQH2.js.map} +1 -1
- package/package.json +1 -1
- package/skills/acpx/SKILL.md +69 -30
- package/dist/agent-registry-evUoJINz.js.map +0 -1
- package/dist/client-CkbBoSGd.d.ts.map +0 -1
- package/dist/flows-xGyhmoHO.js.map +0 -1
- package/dist/invocation-options-eZEBh4vd.js.map +0 -1
- package/dist/ipc-D2rstexT.js.map +0 -1
- package/dist/output-D2xL6wdp.js.map +0 -1
- package/dist/queue-owner-runtime-Dgv3e2Tp.js.map +0 -1
- package/dist/session-watch-CTe9Bnbc.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as createOutputFormatter } from "./output-
|
|
2
|
-
import {
|
|
3
|
-
import { B as
|
|
4
|
-
import { M as runTimedExecFile } from "./agent-registry-
|
|
1
|
+
import { t as createOutputFormatter } from "./output-D6kDs8BN.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-fMgj5vrO.js";
|
|
3
|
+
import { B as cloneSessionAcpxState, G as recordSessionUpdate, H as createSessionConversation, U as recordClientOperation, W as recordPromptSubmission, at as InterruptedError, ct as withInterrupt, i as runOnce, lt as withTimeout, m as createSessionWithClient, o as sendSessionDirect, ot as TimeoutError } from "./queue-owner-runtime-CLwjMYNL.js";
|
|
4
|
+
import { M as runTimedExecFile } from "./agent-registry-Ct2yWPW7.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) {
|
|
@@ -414,25 +539,11 @@ function resolveShellActionTimeoutMs(timeoutMs) {
|
|
|
414
539
|
if (timeoutMs == null || !(timeoutMs > 0)) return;
|
|
415
540
|
return timeoutMs;
|
|
416
541
|
}
|
|
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
542
|
function rejectIfShellFailed(spec, args, result, timedOut, timeoutMs) {
|
|
432
543
|
if (timedOut) return new TimeoutError(timeoutMs ?? spec.timeoutMs ?? 0);
|
|
433
544
|
if (((result.exitCode ?? 0) !== 0 || result.signal != null) && spec.allowNonZeroExit !== true) return createShellFailureError(spec, args, result.exitCode, result.signal, result.stderr);
|
|
434
545
|
}
|
|
435
|
-
function
|
|
546
|
+
function waitForShellResult(child, spec, args, cwd, startMs, termination, mode) {
|
|
436
547
|
const stdoutStream = child.stdout;
|
|
437
548
|
const stderrStream = child.stderr;
|
|
438
549
|
if (!stdoutStream || !stderrStream) throw new Error("Shell action child is missing stdio pipes");
|
|
@@ -442,7 +553,8 @@ function waitForShellExit(child, spec, args, cwd, startMs, timeoutMs, terminatio
|
|
|
442
553
|
settled = true;
|
|
443
554
|
reject(error);
|
|
444
555
|
};
|
|
445
|
-
const capture = createShellOutputCapture(spec.maxBufferBytes, () => settled || termination.
|
|
556
|
+
const capture = createShellOutputCapture(spec.maxBufferBytes, () => settled || mode === "node" && termination.cancelled(), (error) => {
|
|
557
|
+
if (termination.cancelled()) return;
|
|
446
558
|
fail(error);
|
|
447
559
|
termination.cancel("SIGTERM").catch(fail);
|
|
448
560
|
});
|
|
@@ -455,10 +567,10 @@ function waitForShellExit(child, spec, args, cwd, startMs, timeoutMs, terminatio
|
|
|
455
567
|
capture.append("stderr", chunk);
|
|
456
568
|
});
|
|
457
569
|
child.once("error", fail);
|
|
458
|
-
child.once("exit", (exitCode, signal) => {
|
|
570
|
+
child.once(mode === "node" ? "exit" : "close", (exitCode, signal) => {
|
|
459
571
|
settled = true;
|
|
460
572
|
const { stdout, stderr } = capture.output;
|
|
461
|
-
|
|
573
|
+
resolve({
|
|
462
574
|
command: spec.command,
|
|
463
575
|
args,
|
|
464
576
|
cwd,
|
|
@@ -467,14 +579,9 @@ function waitForShellExit(child, spec, args, cwd, startMs, timeoutMs, terminatio
|
|
|
467
579
|
combinedOutput: `${stdout}${stderr}`,
|
|
468
580
|
exitCode,
|
|
469
581
|
signal,
|
|
470
|
-
durationMs: Date.now() - startMs
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (error) {
|
|
474
|
-
reject(error);
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
resolve(result);
|
|
582
|
+
durationMs: Date.now() - startMs,
|
|
583
|
+
timedOut: mode === "node" ? termination.cancelled() : termination.timedOut()
|
|
584
|
+
});
|
|
478
585
|
});
|
|
479
586
|
});
|
|
480
587
|
}
|
|
@@ -486,6 +593,7 @@ function createShellTermination(child, timeoutMs, options) {
|
|
|
486
593
|
});
|
|
487
594
|
let termination;
|
|
488
595
|
let timedOut = false;
|
|
596
|
+
let cancelled = false;
|
|
489
597
|
let released = false;
|
|
490
598
|
let deadline;
|
|
491
599
|
let monitor;
|
|
@@ -504,7 +612,8 @@ function createShellTermination(child, timeoutMs, options) {
|
|
|
504
612
|
const cancel = (signal) => {
|
|
505
613
|
if (termination) return termination;
|
|
506
614
|
if (released) return Promise.resolve();
|
|
507
|
-
|
|
615
|
+
clearDeadline();
|
|
616
|
+
cancelled = true;
|
|
508
617
|
termination = stopShellProcess(child, closed, signal).finally(release);
|
|
509
618
|
termination.catch(rejectCleanup);
|
|
510
619
|
return termination;
|
|
@@ -514,6 +623,7 @@ function createShellTermination(child, timeoutMs, options) {
|
|
|
514
623
|
};
|
|
515
624
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
516
625
|
if (timeoutMs != null) deadline = setTimeout(() => {
|
|
626
|
+
timedOut = true;
|
|
517
627
|
cancel("SIGTERM");
|
|
518
628
|
}, timeoutMs);
|
|
519
629
|
unregister = options.registerOwner?.({
|
|
@@ -532,6 +642,7 @@ function createShellTermination(child, timeoutMs, options) {
|
|
|
532
642
|
return {
|
|
533
643
|
cancel,
|
|
534
644
|
timedOut: () => timedOut,
|
|
645
|
+
cancelled: () => cancelled,
|
|
535
646
|
cleanupFailure,
|
|
536
647
|
async dispose() {
|
|
537
648
|
clearDeadline();
|
|
@@ -544,7 +655,16 @@ function createShellTermination(child, timeoutMs, options) {
|
|
|
544
655
|
};
|
|
545
656
|
}
|
|
546
657
|
async function runShellAction(spec, options = {}) {
|
|
547
|
-
|
|
658
|
+
const { timedOut, ...result } = await runShellProcess(spec, options, "node");
|
|
659
|
+
const failure = rejectIfShellFailed(spec, result.args, result, timedOut, resolveShellActionTimeoutMs(spec.timeoutMs));
|
|
660
|
+
if (failure) throw failure;
|
|
661
|
+
return result;
|
|
662
|
+
}
|
|
663
|
+
async function runShellCommand(spec, options) {
|
|
664
|
+
return await runShellProcess(spec, options, "command");
|
|
665
|
+
}
|
|
666
|
+
async function runShellProcess(spec, options, mode) {
|
|
667
|
+
options.signal?.throwIfAborted();
|
|
548
668
|
const cwd = spec.cwd ?? process.cwd();
|
|
549
669
|
const args = spec.args ?? [];
|
|
550
670
|
const startMs = Date.now();
|
|
@@ -566,14 +686,21 @@ async function runShellAction(spec, options = {}) {
|
|
|
566
686
|
detached: process.platform !== "win32"
|
|
567
687
|
});
|
|
568
688
|
const termination = createShellTermination(child, timeoutMs, options);
|
|
569
|
-
const finish =
|
|
689
|
+
const finish = waitForShellResult(child, spec, args, cwd, startMs, termination, mode);
|
|
570
690
|
writeShellStdin(child, spec.stdin);
|
|
571
691
|
try {
|
|
572
|
-
|
|
692
|
+
const result = await Promise.race([finish, termination.cleanupFailure]);
|
|
693
|
+
throwIfShellCancelled(options.signal, mode);
|
|
694
|
+
return result;
|
|
573
695
|
} finally {
|
|
574
696
|
await termination.dispose();
|
|
575
697
|
}
|
|
576
698
|
}
|
|
699
|
+
function throwIfShellCancelled(signal, mode) {
|
|
700
|
+
if (!signal?.aborted) return;
|
|
701
|
+
const reason = signal.reason;
|
|
702
|
+
if (mode === "command" || reason instanceof TimeoutError || reason instanceof InterruptedError) throw reason;
|
|
703
|
+
}
|
|
577
704
|
//#endregion
|
|
578
705
|
//#region src/flows/graph.ts
|
|
579
706
|
function validateFlowDefinition(flow) {
|
|
@@ -707,11 +834,19 @@ function summarizePrompt(promptText, explicitDetail) {
|
|
|
707
834
|
}
|
|
708
835
|
function createQuietCaptureOutput() {
|
|
709
836
|
const chunks = [];
|
|
837
|
+
let sessionId;
|
|
838
|
+
const formatter = createOutputFormatter("quiet", { stdout: { write(chunk) {
|
|
839
|
+
chunks.push(chunk);
|
|
840
|
+
} } });
|
|
841
|
+
const setContext = formatter.setContext.bind(formatter);
|
|
842
|
+
formatter.setContext = (context) => {
|
|
843
|
+
sessionId = context.sessionId ?? sessionId;
|
|
844
|
+
setContext(context);
|
|
845
|
+
};
|
|
710
846
|
return {
|
|
711
|
-
formatter
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
read: () => chunks.join("").trim()
|
|
847
|
+
formatter,
|
|
848
|
+
read: () => chunks.join("").trim(),
|
|
849
|
+
sessionId: () => sessionId
|
|
715
850
|
};
|
|
716
851
|
}
|
|
717
852
|
async function resolveFlowRunTitle(flow, input, flowPath) {
|
|
@@ -730,8 +865,13 @@ function normalizeFlowRunTitle(value) {
|
|
|
730
865
|
function createRunId(flowName) {
|
|
731
866
|
return `${isoNow$1().replaceAll(":", "").replaceAll(".", "")}-${slugifyAsciiIdPart(flowName)}-${randomUUID().slice(0, 8)}`;
|
|
732
867
|
}
|
|
733
|
-
function createSessionBindingKey(agentCommand, cwd, handle) {
|
|
734
|
-
return
|
|
868
|
+
function createSessionBindingKey(agentCommand, cwd, handle, agentArgv) {
|
|
869
|
+
return JSON.stringify([
|
|
870
|
+
agentCommand,
|
|
871
|
+
agentArgv ?? null,
|
|
872
|
+
cwd,
|
|
873
|
+
handle
|
|
874
|
+
]);
|
|
735
875
|
}
|
|
736
876
|
function createSessionName(flowName, handle, cwd, runId) {
|
|
737
877
|
return `${flowName}-${handle}-${stableShortHash(cwd)}-${runId.slice(-8)}`;
|
|
@@ -909,10 +1049,15 @@ var FlowRunStore = class {
|
|
|
909
1049
|
traceSeqByRun = /* @__PURE__ */ new Map();
|
|
910
1050
|
sessionSeqByBundle = /* @__PURE__ */ new Map();
|
|
911
1051
|
manifestByRun = /* @__PURE__ */ new Map();
|
|
912
|
-
|
|
1052
|
+
writeChainByPath = /* @__PURE__ */ new Map();
|
|
913
1053
|
constructor(outputRoot = flowRunsBaseDir()) {
|
|
914
1054
|
this.outputRoot = outputRoot;
|
|
915
1055
|
}
|
|
1056
|
+
releaseRun(runDir) {
|
|
1057
|
+
this.traceSeqByRun.delete(runDir);
|
|
1058
|
+
this.manifestByRun.delete(runDir);
|
|
1059
|
+
for (const key of this.sessionSeqByBundle.keys()) if (key.startsWith(`${runDir}::`)) this.sessionSeqByBundle.delete(key);
|
|
1060
|
+
}
|
|
916
1061
|
async createRunDir(runId) {
|
|
917
1062
|
const runDir = path.join(this.outputRoot, runId);
|
|
918
1063
|
await fs.mkdir(path.join(runDir, PROJECTIONS_DIR), {
|
|
@@ -1002,57 +1147,65 @@ var FlowRunStore = class {
|
|
|
1002
1147
|
});
|
|
1003
1148
|
return artifact;
|
|
1004
1149
|
}
|
|
1005
|
-
async ensureSessionBundle(runDir, state,
|
|
1150
|
+
async ensureSessionBundle(runDir, state, inputBinding, record) {
|
|
1151
|
+
const binding = structuredClone(inputBinding);
|
|
1006
1152
|
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"),
|
|
1153
|
+
await this.serializeWrite(path.join(sessionDir, "binding.json"), async () => {
|
|
1154
|
+
await fs.mkdir(sessionDir, {
|
|
1155
|
+
recursive: true,
|
|
1156
|
+
mode: 448
|
|
1157
|
+
});
|
|
1158
|
+
await writePrivateJsonFile(path.join(sessionDir, "binding.json"), binding);
|
|
1159
|
+
await ensureFile(path.join(sessionDir, "events.ndjson"));
|
|
1160
|
+
if (record) await this.writeSessionRecord(runDir, state, binding, record);
|
|
1161
|
+
const manifest = this.getManifest(runDir, state);
|
|
1162
|
+
if (!manifest.sessions.find((entry) => entry.id === binding.bundleId)) {
|
|
1163
|
+
const bindingArtifact = await this.writeArtifact(runDir, state, binding, {
|
|
1036
1164
|
mediaType: "application/json",
|
|
1037
|
-
|
|
1038
|
-
|
|
1165
|
+
extension: "json",
|
|
1166
|
+
emitTrace: false
|
|
1167
|
+
});
|
|
1168
|
+
const entry = {
|
|
1169
|
+
id: binding.bundleId,
|
|
1170
|
+
handle: binding.handle,
|
|
1171
|
+
bindingPath: path.posix.join(sessionDirPath(binding.bundleId), "binding.json"),
|
|
1172
|
+
recordPath: path.posix.join(sessionDirPath(binding.bundleId), "record.json"),
|
|
1173
|
+
eventsPath: path.posix.join(sessionDirPath(binding.bundleId), "events.ndjson")
|
|
1174
|
+
};
|
|
1175
|
+
manifest.sessions.push(entry);
|
|
1176
|
+
await writePrivateJsonFile(this.resolveRunPath(runDir, MANIFEST_PATH), manifest);
|
|
1177
|
+
await this.appendTrace(runDir, state, {
|
|
1178
|
+
scope: "session",
|
|
1179
|
+
type: "session_bound",
|
|
1180
|
+
sessionId: binding.bundleId,
|
|
1181
|
+
payload: {
|
|
1182
|
+
sessionId: binding.bundleId,
|
|
1183
|
+
handle: binding.handle,
|
|
1184
|
+
bindingArtifact
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1039
1187
|
}
|
|
1040
1188
|
});
|
|
1041
1189
|
}
|
|
1042
1190
|
async writeSessionRecord(runDir, _state, binding, record) {
|
|
1043
|
-
const bundledRecord = createBundledSessionRecord(binding, record, this.sessionSeqByBundle.get(`${runDir}::${binding.bundleId}`) ?? 0);
|
|
1191
|
+
const bundledRecord = createBundledSessionRecord(binding, record, this.sessionSeqByBundle.get(`${runDir}::${binding.bundleId}`)?.persisted ?? 0);
|
|
1044
1192
|
await writePrivateJsonFile(this.resolveRunPath(runDir, path.posix.join(sessionDirPath(binding.bundleId), "record.json")), bundledRecord);
|
|
1045
1193
|
}
|
|
1046
1194
|
async appendSessionEvent(runDir, binding, direction, message) {
|
|
1047
1195
|
const sessionKey = `${runDir}::${binding.bundleId}`;
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1196
|
+
const sequence = this.sessionSeqByBundle.get(sessionKey) ?? {
|
|
1197
|
+
allocated: 0,
|
|
1198
|
+
persisted: 0
|
|
1199
|
+
};
|
|
1200
|
+
this.sessionSeqByBundle.set(sessionKey, sequence);
|
|
1201
|
+
const seq = ++sequence.allocated;
|
|
1050
1202
|
await this.appendJsonLine(this.resolveRunPath(runDir, path.posix.join(sessionDirPath(binding.bundleId), "events.ndjson")), {
|
|
1051
1203
|
seq,
|
|
1052
1204
|
at: isoNow(),
|
|
1053
1205
|
direction,
|
|
1054
1206
|
message
|
|
1055
1207
|
});
|
|
1208
|
+
sequence.persisted = Math.max(sequence.persisted, seq);
|
|
1056
1209
|
return seq;
|
|
1057
1210
|
}
|
|
1058
1211
|
getManifest(runDir, state) {
|
|
@@ -1083,7 +1236,7 @@ var FlowRunStore = class {
|
|
|
1083
1236
|
return path.join(runDir, ...relativePath.split("/"));
|
|
1084
1237
|
}
|
|
1085
1238
|
async appendJsonLine(filePath, value) {
|
|
1086
|
-
|
|
1239
|
+
await this.serializeWrite(filePath, async () => {
|
|
1087
1240
|
await fs.mkdir(path.dirname(filePath), {
|
|
1088
1241
|
recursive: true,
|
|
1089
1242
|
mode: 448
|
|
@@ -1093,10 +1246,13 @@ var FlowRunStore = class {
|
|
|
1093
1246
|
content: `${JSON.stringify(value)}\n`,
|
|
1094
1247
|
mode: 384
|
|
1095
1248
|
});
|
|
1096
|
-
}).finally(() => {
|
|
1097
|
-
if (this.appendChainByPath.get(filePath) === tracked) this.appendChainByPath.delete(filePath);
|
|
1098
1249
|
});
|
|
1099
|
-
|
|
1250
|
+
}
|
|
1251
|
+
async serializeWrite(filePath, write) {
|
|
1252
|
+
const tracked = (this.writeChainByPath.get(filePath) ?? Promise.resolve()).then(write).finally(() => {
|
|
1253
|
+
if (this.writeChainByPath.get(filePath) === tracked) this.writeChainByPath.delete(filePath);
|
|
1254
|
+
});
|
|
1255
|
+
this.writeChainByPath.set(filePath, tracked);
|
|
1100
1256
|
await tracked;
|
|
1101
1257
|
}
|
|
1102
1258
|
};
|
|
@@ -1229,10 +1385,6 @@ async function ensureFile(filePath) {
|
|
|
1229
1385
|
mode: 384
|
|
1230
1386
|
});
|
|
1231
1387
|
}
|
|
1232
|
-
async function fileSha256(filePath) {
|
|
1233
|
-
const payload = await fs.readFile(filePath);
|
|
1234
|
-
return createHash("sha256").update(payload).digest("hex");
|
|
1235
|
-
}
|
|
1236
1388
|
function toArtifactBuffer(content, mediaType) {
|
|
1237
1389
|
if (typeof content === "string") return Buffer.from(content, "utf8");
|
|
1238
1390
|
if (Buffer.isBuffer(content)) return content;
|
|
@@ -1264,7 +1416,8 @@ var FlowRunner = class {
|
|
|
1264
1416
|
services;
|
|
1265
1417
|
store;
|
|
1266
1418
|
pendingPersistentSessionClients = /* @__PURE__ */ new Map();
|
|
1267
|
-
|
|
1419
|
+
attempts = /* @__PURE__ */ new Map();
|
|
1420
|
+
pendingClientReleases = /* @__PURE__ */ new WeakMap();
|
|
1268
1421
|
runInterruptions = /* @__PURE__ */ new Map();
|
|
1269
1422
|
shellOwners = /* @__PURE__ */ new Map();
|
|
1270
1423
|
constructor(options) {
|
|
@@ -1305,23 +1458,27 @@ var FlowRunner = class {
|
|
|
1305
1458
|
steps: [],
|
|
1306
1459
|
sessionBindings: {}
|
|
1307
1460
|
};
|
|
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
1461
|
try {
|
|
1319
|
-
|
|
1462
|
+
const inputArtifact = await this.store.writeArtifact(runDir, state, input, {
|
|
1463
|
+
mediaType: "application/json",
|
|
1464
|
+
extension: "json",
|
|
1465
|
+
emitTrace: false
|
|
1466
|
+
});
|
|
1467
|
+
await this.store.initializeRunBundle(runDir, {
|
|
1468
|
+
flow,
|
|
1469
|
+
state,
|
|
1470
|
+
inputArtifact
|
|
1471
|
+
});
|
|
1472
|
+
return await this.runWithOwnership(flow, input, runDir, state);
|
|
1320
1473
|
} finally {
|
|
1321
|
-
|
|
1474
|
+
try {
|
|
1475
|
+
await this.closePendingPersistentSessionClients(runDir);
|
|
1476
|
+
} finally {
|
|
1477
|
+
this.store.releaseRun(runDir);
|
|
1478
|
+
}
|
|
1322
1479
|
}
|
|
1323
1480
|
}
|
|
1324
|
-
async
|
|
1481
|
+
async runWithOwnership(flow, input, runDir, state) {
|
|
1325
1482
|
let execution;
|
|
1326
1483
|
let cancellation;
|
|
1327
1484
|
let interruption;
|
|
@@ -1334,11 +1491,8 @@ var FlowRunner = class {
|
|
|
1334
1491
|
this.runInterruptions.set(runDir, reason);
|
|
1335
1492
|
cancellation ??= this.cancelShellOwners(runDir, signal);
|
|
1336
1493
|
cancellation.catch(() => {});
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
interruptShell(reason, signal);
|
|
1340
|
-
await execution?.catch(() => {});
|
|
1341
|
-
}
|
|
1494
|
+
this.attempts.get(runDir)?.cancel(reason, signal);
|
|
1495
|
+
await execution?.catch(() => {});
|
|
1342
1496
|
}).then((value) => ({
|
|
1343
1497
|
ok: true,
|
|
1344
1498
|
value
|
|
@@ -1417,16 +1571,28 @@ var FlowRunner = class {
|
|
|
1417
1571
|
const attemptId = nextAttemptId(attemptCounts, nodeId);
|
|
1418
1572
|
const startedAt = isoNow$1();
|
|
1419
1573
|
markNodeStarted(state, nodeId, attemptId, node.nodeType, startedAt, node.statusDetail);
|
|
1420
|
-
|
|
1421
|
-
|
|
1574
|
+
const attempt = new FlowAttempt({
|
|
1575
|
+
nodeId,
|
|
1576
|
+
attemptId,
|
|
1577
|
+
startedAt,
|
|
1578
|
+
timeoutMs: node.timeoutMs ?? this.defaultNodeTimeoutMs
|
|
1579
|
+
});
|
|
1580
|
+
this.attempts.set(runDir, attempt);
|
|
1581
|
+
const context = this.makeAttemptContext(runDir, state, input, node, attempt);
|
|
1582
|
+
let stopHeartbeat = () => {};
|
|
1422
1583
|
let executed;
|
|
1423
1584
|
let outcome = "ok";
|
|
1424
1585
|
let executionError;
|
|
1425
1586
|
try {
|
|
1426
1587
|
this.throwIfRunInterrupted(runDir);
|
|
1427
|
-
executed = await
|
|
1588
|
+
executed = await attempt.run(async () => {
|
|
1589
|
+
await attempt.own(() => this.writeNodeStartedSnapshot(runDir, state, nodeId, attemptId, node));
|
|
1590
|
+
stopHeartbeat = this.startHeartbeat(runDir, state, node, attempt);
|
|
1591
|
+
const result = await this.executeNode(runDir, state, flow, nodeId, node, context);
|
|
1592
|
+
result.trace = await attempt.own(() => finalizeStepTrace(this.store, runDir, state, nodeId, attemptId, result.output, result.trace));
|
|
1593
|
+
return result;
|
|
1594
|
+
});
|
|
1428
1595
|
this.throwIfRunInterrupted(runDir);
|
|
1429
|
-
executed.trace = await finalizeStepTrace(this.store, runDir, state, nodeId, attemptId, executed.output, executed.trace);
|
|
1430
1596
|
} catch (error) {
|
|
1431
1597
|
outcome = outcomeForError(error);
|
|
1432
1598
|
executionError = error;
|
|
@@ -1436,8 +1602,12 @@ var FlowRunner = class {
|
|
|
1436
1602
|
rawText: null,
|
|
1437
1603
|
sessionInfo: null,
|
|
1438
1604
|
agentInfo: null,
|
|
1439
|
-
|
|
1605
|
+
...context.acpResult,
|
|
1606
|
+
trace: await finalizeStepTrace(this.store, runDir, state, nodeId, attemptId, void 0, context.acpResult ? context.acpResult.trace : extractAttachedStepTrace(error) ?? null)
|
|
1440
1607
|
};
|
|
1608
|
+
} finally {
|
|
1609
|
+
stopHeartbeat();
|
|
1610
|
+
this.attempts.delete(runDir);
|
|
1441
1611
|
}
|
|
1442
1612
|
const nodeResult = createNodeResult({
|
|
1443
1613
|
attemptId,
|
|
@@ -1460,6 +1630,17 @@ var FlowRunner = class {
|
|
|
1460
1630
|
state
|
|
1461
1631
|
};
|
|
1462
1632
|
}
|
|
1633
|
+
makeAttemptContext(runDir, state, input, node, attempt) {
|
|
1634
|
+
const context = {
|
|
1635
|
+
nodeContext: {
|
|
1636
|
+
...makeFlowNodeContext(state, input, this.services),
|
|
1637
|
+
signal: attempt.signal
|
|
1638
|
+
},
|
|
1639
|
+
attempt
|
|
1640
|
+
};
|
|
1641
|
+
if (node.nodeType === "action" && "run" in node) context.nodeContext.runShell = (execution) => this.runCallbackShell(runDir, attempt, execution);
|
|
1642
|
+
return context;
|
|
1643
|
+
}
|
|
1463
1644
|
async writeNodeStartedSnapshot(runDir, state, nodeId, attemptId, node) {
|
|
1464
1645
|
await this.store.writeSnapshot(runDir, state, {
|
|
1465
1646
|
scope: "node",
|
|
@@ -1537,19 +1718,22 @@ var FlowRunner = class {
|
|
|
1537
1718
|
}
|
|
1538
1719
|
async executeNode(runDir, state, flow, nodeId, node, context) {
|
|
1539
1720
|
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(
|
|
1721
|
+
case "compute": return await this.executeCallbackNode(nodeId, node, context);
|
|
1722
|
+
case "action": return "run" in node ? await this.executeCallbackNode(nodeId, node, context) : await this.executeShellNode(runDir, state, node, context);
|
|
1723
|
+
case "checkpoint": return await this.executeCallbackNode(nodeId, node, context);
|
|
1543
1724
|
case "acp": return await this.executeAcpNode(runDir, state, flow, node, context);
|
|
1544
1725
|
default: throw new Error(`Unsupported flow node: ${String(node)}`);
|
|
1545
1726
|
}
|
|
1546
1727
|
}
|
|
1547
|
-
async executeCallbackNode(
|
|
1728
|
+
async executeCallbackNode(nodeId, node, context) {
|
|
1729
|
+
context.attempt.assertActive();
|
|
1730
|
+
const output = node.nodeType === "checkpoint" && typeof node.run !== "function" ? {
|
|
1731
|
+
checkpoint: nodeId,
|
|
1732
|
+
summary: node.summary ?? nodeId
|
|
1733
|
+
} : await node.run?.(context.nodeContext);
|
|
1734
|
+
context.attempt.assertActive();
|
|
1548
1735
|
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)),
|
|
1736
|
+
output,
|
|
1553
1737
|
promptText: null,
|
|
1554
1738
|
rawText: null,
|
|
1555
1739
|
sessionInfo: null,
|
|
@@ -1558,186 +1742,173 @@ var FlowRunner = class {
|
|
|
1558
1742
|
};
|
|
1559
1743
|
}
|
|
1560
1744
|
async executeShellNode(runDir, state, node, context) {
|
|
1561
|
-
const
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
return this.registerShellOwner(runDir, owner);
|
|
1569
|
-
}
|
|
1745
|
+
const attempt = context.attempt;
|
|
1746
|
+
const execution = await node.exec(context.nodeContext);
|
|
1747
|
+
attempt.assertActive();
|
|
1748
|
+
const effectiveExecution = {
|
|
1749
|
+
...execution,
|
|
1750
|
+
cwd: resolveShellActionCwd(this.defaultCwd, execution.cwd),
|
|
1751
|
+
timeoutMs: resolveShellActionTimeoutMs(execution.timeoutMs ?? attempt.remainingTimeoutMs())
|
|
1570
1752
|
};
|
|
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
|
-
},
|
|
1753
|
+
updateStatusDetail(state, formatShellActionSummary(effectiveExecution));
|
|
1754
|
+
await attempt.own(() => this.store.writeLive(runDir, state, {
|
|
1755
|
+
scope: "node",
|
|
1756
|
+
type: "node_heartbeat",
|
|
1757
|
+
nodeId: attempt.nodeId,
|
|
1758
|
+
attemptId: attempt.attemptId,
|
|
1759
|
+
payload: { statusDetail: state.statusDetail }
|
|
1760
|
+
}));
|
|
1761
|
+
await attempt.own(() => this.store.appendTrace(runDir, state, {
|
|
1762
|
+
scope: "action",
|
|
1763
|
+
type: "action_prepared",
|
|
1764
|
+
nodeId: attempt.nodeId,
|
|
1765
|
+
attemptId: attempt.attemptId,
|
|
1766
|
+
payload: { action: {
|
|
1767
|
+
actionType: "shell",
|
|
1768
|
+
command: effectiveExecution.command,
|
|
1769
|
+
args: effectiveExecution.args ?? [],
|
|
1770
|
+
cwd: effectiveExecution.cwd
|
|
1771
|
+
} }
|
|
1772
|
+
}));
|
|
1773
|
+
const result = await attempt.own(() => runShellAction(effectiveExecution, this.shellControl(runDir, attempt)));
|
|
1774
|
+
const stdoutArtifact = await attempt.own(() => this.store.writeArtifact(runDir, state, result.stdout, {
|
|
1775
|
+
mediaType: "text/plain",
|
|
1776
|
+
extension: "txt",
|
|
1777
|
+
nodeId: attempt.nodeId,
|
|
1778
|
+
attemptId: attempt.attemptId
|
|
1779
|
+
}));
|
|
1780
|
+
const stderrArtifact = await attempt.own(() => this.store.writeArtifact(runDir, state, result.stderr, {
|
|
1781
|
+
mediaType: "text/plain",
|
|
1782
|
+
extension: "txt",
|
|
1783
|
+
nodeId: attempt.nodeId,
|
|
1784
|
+
attemptId: attempt.attemptId
|
|
1785
|
+
}));
|
|
1786
|
+
const actionTrace = {
|
|
1787
|
+
actionType: "shell",
|
|
1788
|
+
command: result.command,
|
|
1789
|
+
args: result.args,
|
|
1790
|
+
cwd: result.cwd,
|
|
1791
|
+
exitCode: result.exitCode,
|
|
1792
|
+
signal: result.signal,
|
|
1793
|
+
durationMs: result.durationMs
|
|
1794
|
+
};
|
|
1795
|
+
await attempt.own(() => this.store.appendTrace(runDir, state, {
|
|
1796
|
+
scope: "action",
|
|
1797
|
+
type: "action_completed",
|
|
1798
|
+
nodeId: attempt.nodeId,
|
|
1799
|
+
attemptId: attempt.attemptId,
|
|
1800
|
+
payload: {
|
|
1801
|
+
action: actionTrace,
|
|
1650
1802
|
stdoutArtifact,
|
|
1651
1803
|
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
1804
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1805
|
+
}));
|
|
1806
|
+
const trace = {
|
|
1807
|
+
action: actionTrace,
|
|
1808
|
+
stdoutArtifact,
|
|
1809
|
+
stderrArtifact
|
|
1810
|
+
};
|
|
1811
|
+
let output;
|
|
1812
|
+
try {
|
|
1813
|
+
output = node.parse ? await node.parse(result, context.nodeContext) : result;
|
|
1814
|
+
attempt.assertActive();
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
throw attachStepTrace(error, trace);
|
|
1817
|
+
}
|
|
1678
1818
|
return {
|
|
1679
1819
|
output,
|
|
1680
1820
|
promptText: null,
|
|
1681
|
-
rawText,
|
|
1821
|
+
rawText: result.combinedOutput,
|
|
1682
1822
|
sessionInfo: null,
|
|
1683
1823
|
agentInfo: null,
|
|
1684
1824
|
trace
|
|
1685
1825
|
};
|
|
1686
1826
|
}
|
|
1827
|
+
shellControl(runDir, attempt) {
|
|
1828
|
+
return {
|
|
1829
|
+
signal: attempt.signal,
|
|
1830
|
+
get terminationSignal() {
|
|
1831
|
+
return attempt.terminationSignal;
|
|
1832
|
+
},
|
|
1833
|
+
registerOwner: (owner) => {
|
|
1834
|
+
const releaseAttempt = attempt.registerCancellation((signal) => owner.cancel(signal));
|
|
1835
|
+
const releaseRun = this.registerShellOwner(runDir, owner);
|
|
1836
|
+
return () => {
|
|
1837
|
+
releaseAttempt();
|
|
1838
|
+
releaseRun();
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
runCallbackShell(runDir, attempt, execution) {
|
|
1844
|
+
return attempt.own(() => runShellCommand({
|
|
1845
|
+
...execution,
|
|
1846
|
+
cwd: resolveShellActionCwd(this.defaultCwd, execution.cwd)
|
|
1847
|
+
}, this.shellControl(runDir, attempt)));
|
|
1848
|
+
}
|
|
1687
1849
|
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
|
-
});
|
|
1850
|
+
const attempt = context.attempt;
|
|
1851
|
+
const prepared = await this.prepareAcpPrompt(runDir, state, node, context);
|
|
1852
|
+
if (node.session?.isolated) return await this.executeIsolatedAcpPrompt(runDir, state, flow, node, context, prepared);
|
|
1853
|
+
const boundSession = await this.ensureSessionBinding(runDir, state, flow, node, prepared.agentInfo, attempt);
|
|
1854
|
+
return await this.executePersistentAcpPrompt(runDir, state, node, context, prepared, boundSession);
|
|
1699
1855
|
}
|
|
1700
|
-
async prepareAcpPrompt(runDir, state, node, context
|
|
1856
|
+
async prepareAcpPrompt(runDir, state, node, context) {
|
|
1701
1857
|
const resolvedAgent = this.resolveAgent(node.profile);
|
|
1702
1858
|
const agentInfo = {
|
|
1703
1859
|
...resolvedAgent,
|
|
1704
|
-
|
|
1860
|
+
agentArgv: resolvedAgent.agentArgv?.slice(),
|
|
1861
|
+
cwd: await resolveNodeCwd(resolvedAgent.cwd, node.cwd, context.nodeContext)
|
|
1705
1862
|
};
|
|
1706
|
-
|
|
1863
|
+
context.attempt.assertActive();
|
|
1864
|
+
const prompt = normalizePromptInput(await Promise.resolve(node.prompt(context.nodeContext)));
|
|
1865
|
+
context.attempt.assertActive();
|
|
1707
1866
|
const promptText = promptToDisplayText(prompt);
|
|
1867
|
+
const result = {
|
|
1868
|
+
output: void 0,
|
|
1869
|
+
promptText,
|
|
1870
|
+
rawText: null,
|
|
1871
|
+
sessionInfo: null,
|
|
1872
|
+
agentInfo,
|
|
1873
|
+
trace: null
|
|
1874
|
+
};
|
|
1875
|
+
context.acpResult = result;
|
|
1708
1876
|
updateStatusDetail(state, summarizePrompt(promptText, node.statusDetail));
|
|
1709
|
-
await this.writeAcpPromptHeartbeat(runDir, state);
|
|
1877
|
+
await context.attempt.own(() => this.writeAcpPromptHeartbeat(runDir, state, context.attempt));
|
|
1878
|
+
const promptArtifact = await context.attempt.own(() => this.store.writeArtifact(runDir, state, promptText, {
|
|
1879
|
+
mediaType: "text/plain",
|
|
1880
|
+
extension: "txt",
|
|
1881
|
+
nodeId: context.attempt.nodeId,
|
|
1882
|
+
attemptId: context.attempt.attemptId
|
|
1883
|
+
}));
|
|
1884
|
+
result.trace = { promptArtifact };
|
|
1710
1885
|
return {
|
|
1711
1886
|
agentInfo,
|
|
1712
1887
|
prompt,
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
extension: "txt",
|
|
1717
|
-
nodeId: state.currentNode,
|
|
1718
|
-
attemptId: state.currentAttemptId
|
|
1719
|
-
}),
|
|
1720
|
-
nodeTimeoutMs
|
|
1888
|
+
promptArtifact,
|
|
1889
|
+
attempt: context.attempt,
|
|
1890
|
+
result
|
|
1721
1891
|
};
|
|
1722
1892
|
}
|
|
1723
|
-
async writeAcpPromptHeartbeat(runDir, state) {
|
|
1893
|
+
async writeAcpPromptHeartbeat(runDir, state, attempt) {
|
|
1724
1894
|
await this.store.writeLive(runDir, state, {
|
|
1725
1895
|
scope: "node",
|
|
1726
1896
|
type: "node_heartbeat",
|
|
1727
|
-
nodeId:
|
|
1728
|
-
attemptId:
|
|
1897
|
+
nodeId: attempt.nodeId,
|
|
1898
|
+
attemptId: attempt.attemptId,
|
|
1729
1899
|
payload: { statusDetail: state.statusDetail }
|
|
1730
1900
|
});
|
|
1731
1901
|
}
|
|
1732
1902
|
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
|
-
|
|
1903
|
+
const binding = createIsolatedSessionBinding(flow.name, state.runId, prepared.attempt.attemptId, node.profile, prepared.agentInfo);
|
|
1904
|
+
prepared.result.sessionInfo = binding;
|
|
1905
|
+
await prepared.attempt.own(() => this.initializeIsolatedSessionBundle(runDir, state, binding, prepared.attempt));
|
|
1906
|
+
await prepared.attempt.own(() => this.appendAcpPromptPreparedTrace(runDir, state, binding, prepared.promptArtifact, prepared.attempt));
|
|
1907
|
+
const prompt = await prepared.attempt.own(() => this.runIsolatedPrompt(runDir, state, binding, prepared));
|
|
1908
|
+
return await this.finishAcpPrompt(runDir, state, node, context, prepared, prompt);
|
|
1909
|
+
}
|
|
1910
|
+
async initializeIsolatedSessionBundle(runDir, state, binding, attempt) {
|
|
1911
|
+
const timestamp = attempt.startedAt;
|
|
1741
1912
|
const initialRecord = createSyntheticSessionRecord({
|
|
1742
1913
|
binding,
|
|
1743
1914
|
createdAt: timestamp,
|
|
@@ -1749,16 +1920,17 @@ var FlowRunner = class {
|
|
|
1749
1920
|
await this.store.ensureSessionBundle(runDir, state, binding, initialRecord);
|
|
1750
1921
|
}
|
|
1751
1922
|
async executePersistentAcpPrompt(runDir, state, node, context, prepared, binding) {
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1923
|
+
prepared.result.sessionInfo = binding;
|
|
1924
|
+
await prepared.attempt.own(() => this.appendAcpPromptPreparedTrace(runDir, state, binding, prepared.promptArtifact, prepared.attempt));
|
|
1925
|
+
const prompt = await prepared.attempt.own(() => this.runPersistentPrompt(runDir, state, binding, prepared));
|
|
1926
|
+
return await this.finishAcpPrompt(runDir, state, node, context, prepared, prompt);
|
|
1755
1927
|
}
|
|
1756
|
-
async appendAcpPromptPreparedTrace(runDir, state, binding, promptArtifact) {
|
|
1928
|
+
async appendAcpPromptPreparedTrace(runDir, state, binding, promptArtifact, attempt) {
|
|
1757
1929
|
await this.store.appendTrace(runDir, state, {
|
|
1758
1930
|
scope: "acp",
|
|
1759
1931
|
type: "acp_prompt_prepared",
|
|
1760
|
-
nodeId:
|
|
1761
|
-
attemptId:
|
|
1932
|
+
nodeId: attempt.nodeId,
|
|
1933
|
+
attemptId: attempt.attemptId,
|
|
1762
1934
|
sessionId: binding.bundleId,
|
|
1763
1935
|
payload: {
|
|
1764
1936
|
sessionId: binding.bundleId,
|
|
@@ -1766,107 +1938,121 @@ var FlowRunner = class {
|
|
|
1766
1938
|
}
|
|
1767
1939
|
});
|
|
1768
1940
|
}
|
|
1769
|
-
async finishAcpPrompt(runDir, state, node, context, prepared, prompt
|
|
1770
|
-
|
|
1771
|
-
await this.
|
|
1941
|
+
async finishAcpPrompt(runDir, state, node, context, prepared, prompt) {
|
|
1942
|
+
await prepared.attempt.own(() => this.appendAcpResponseParsedTrace(runDir, state, prompt, prepared.attempt));
|
|
1943
|
+
const output = await this.parseAcpOutput(node, context, prompt.rawText);
|
|
1944
|
+
return {
|
|
1945
|
+
...prepared.result,
|
|
1946
|
+
output
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
async publishAcpCapture(runDir, state, prepared, sessionInfo, record, messageStart, rawText, events) {
|
|
1950
|
+
const result = prepared.result;
|
|
1951
|
+
result.sessionInfo = sessionInfo;
|
|
1772
1952
|
const trace = {
|
|
1773
1953
|
sessionId: sessionInfo.bundleId,
|
|
1774
|
-
promptArtifact: prepared.promptArtifact
|
|
1775
|
-
rawResponseArtifact,
|
|
1776
|
-
conversation: prompt.conversation
|
|
1954
|
+
promptArtifact: prepared.promptArtifact
|
|
1777
1955
|
};
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
sessionInfo,
|
|
1783
|
-
|
|
1784
|
-
|
|
1956
|
+
result.trace = trace;
|
|
1957
|
+
await this.store.ensureSessionBundle(runDir, state, sessionInfo);
|
|
1958
|
+
await this.store.writeSessionRecord(runDir, state, sessionInfo, record);
|
|
1959
|
+
if (events) trace.conversation = {
|
|
1960
|
+
sessionId: sessionInfo.bundleId,
|
|
1961
|
+
messageStart,
|
|
1962
|
+
messageEnd: Math.max(messageStart, record.messages.length - 1),
|
|
1963
|
+
...events
|
|
1785
1964
|
};
|
|
1786
|
-
|
|
1787
|
-
async writeAcpRawResponseArtifact(runDir, state, prompt, sessionInfo) {
|
|
1788
|
-
return await this.store.writeArtifact(runDir, state, prompt.rawText, {
|
|
1965
|
+
const rawResponseArtifact = await this.store.writeArtifact(runDir, state, rawText, {
|
|
1789
1966
|
mediaType: "text/plain",
|
|
1790
1967
|
extension: "txt",
|
|
1791
|
-
nodeId:
|
|
1792
|
-
attemptId:
|
|
1968
|
+
nodeId: prepared.attempt.nodeId,
|
|
1969
|
+
attemptId: prepared.attempt.attemptId,
|
|
1793
1970
|
sessionId: sessionInfo.bundleId
|
|
1794
1971
|
});
|
|
1972
|
+
trace.rawResponseArtifact = rawResponseArtifact;
|
|
1973
|
+
return {
|
|
1974
|
+
rawText,
|
|
1975
|
+
sessionInfo,
|
|
1976
|
+
conversation: trace.conversation,
|
|
1977
|
+
rawResponseArtifact
|
|
1978
|
+
};
|
|
1795
1979
|
}
|
|
1796
|
-
async appendAcpResponseParsedTrace(runDir, state, prompt,
|
|
1980
|
+
async appendAcpResponseParsedTrace(runDir, state, prompt, attempt) {
|
|
1797
1981
|
await this.store.appendTrace(runDir, state, {
|
|
1798
1982
|
scope: "acp",
|
|
1799
1983
|
type: "acp_response_parsed",
|
|
1800
|
-
nodeId:
|
|
1801
|
-
attemptId:
|
|
1802
|
-
sessionId: sessionInfo.bundleId,
|
|
1984
|
+
nodeId: attempt.nodeId,
|
|
1985
|
+
attemptId: attempt.attemptId,
|
|
1986
|
+
sessionId: prompt.sessionInfo.bundleId,
|
|
1803
1987
|
payload: {
|
|
1804
|
-
sessionId: sessionInfo.bundleId,
|
|
1988
|
+
sessionId: prompt.sessionInfo.bundleId,
|
|
1805
1989
|
conversation: prompt.conversation,
|
|
1806
|
-
rawResponseArtifact
|
|
1990
|
+
rawResponseArtifact: prompt.rawResponseArtifact
|
|
1807
1991
|
}
|
|
1808
1992
|
});
|
|
1809
1993
|
}
|
|
1810
|
-
async parseAcpOutput(node, context, rawText
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
}
|
|
1994
|
+
async parseAcpOutput(node, context, rawText) {
|
|
1995
|
+
context.attempt.assertActive();
|
|
1996
|
+
const output = node.parse ? await node.parse(rawText, context.nodeContext) : rawText;
|
|
1997
|
+
context.attempt.assertActive();
|
|
1998
|
+
return output;
|
|
1816
1999
|
}
|
|
1817
|
-
|
|
2000
|
+
startHeartbeat(runDir, state, node, attempt) {
|
|
1818
2001
|
const heartbeatMs = Math.max(0, Math.round(node.heartbeatMs ?? DEFAULT_FLOW_HEARTBEAT_MS));
|
|
1819
|
-
|
|
1820
|
-
let
|
|
1821
|
-
let heartbeatPending = false;
|
|
2002
|
+
if (heartbeatMs === 0) return () => {};
|
|
2003
|
+
let pending = false;
|
|
1822
2004
|
const heartbeat = async () => {
|
|
1823
|
-
if (!active ||
|
|
1824
|
-
|
|
2005
|
+
if (!attempt.active || pending) return;
|
|
2006
|
+
pending = true;
|
|
1825
2007
|
try {
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
2008
|
+
await attempt.own(async () => {
|
|
2009
|
+
state.lastHeartbeatAt = isoNow$1();
|
|
2010
|
+
state.updatedAt = state.lastHeartbeatAt;
|
|
2011
|
+
await this.store.writeLive(runDir, state, {
|
|
2012
|
+
scope: "node",
|
|
2013
|
+
type: "node_heartbeat",
|
|
2014
|
+
nodeId: attempt.nodeId,
|
|
2015
|
+
attemptId: attempt.attemptId,
|
|
2016
|
+
payload: { statusDetail: state.statusDetail }
|
|
2017
|
+
});
|
|
2018
|
+
}, { bestEffort: true });
|
|
2019
|
+
} catch {} finally {
|
|
2020
|
+
pending = false;
|
|
1837
2021
|
}
|
|
1838
2022
|
};
|
|
1839
|
-
|
|
1840
|
-
heartbeat()
|
|
2023
|
+
const timer = setInterval(() => {
|
|
2024
|
+
heartbeat();
|
|
1841
2025
|
}, 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
|
-
}
|
|
2026
|
+
return () => clearInterval(timer);
|
|
1851
2027
|
}
|
|
1852
|
-
async ensureSessionBinding(runDir, state, flow, node, agent,
|
|
2028
|
+
async ensureSessionBinding(runDir, state, flow, node, agent, attempt) {
|
|
1853
2029
|
const handle = node.session?.handle ?? "main";
|
|
1854
|
-
const key = createSessionBindingKey(agent.agentCommand, agent.cwd, handle);
|
|
2030
|
+
const key = createSessionBindingKey(agent.agentCommand, agent.cwd, handle, agent.agentArgv);
|
|
1855
2031
|
const existing = state.sessionBindings[key];
|
|
1856
2032
|
if (existing) {
|
|
1857
|
-
await this.store.ensureSessionBundle(runDir, state, existing);
|
|
2033
|
+
await attempt.own(() => this.store.ensureSessionBundle(runDir, state, existing));
|
|
1858
2034
|
return existing;
|
|
1859
2035
|
}
|
|
1860
2036
|
const name = createSessionName(flow.name, handle, agent.cwd, state.runId);
|
|
1861
|
-
const created = await
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
2037
|
+
const created = await attempt.own(async () => {
|
|
2038
|
+
const acquired = await createSessionWithClient({
|
|
2039
|
+
agentCommand: agent.agentCommand,
|
|
2040
|
+
agentArgv: agent.agentArgv,
|
|
2041
|
+
cwd: agent.cwd,
|
|
2042
|
+
name,
|
|
2043
|
+
...this.connectionOptions,
|
|
2044
|
+
signal: attempt.signal,
|
|
2045
|
+
handleProcessInterrupts: false,
|
|
2046
|
+
sessionOptions: this.sessionOptions
|
|
2047
|
+
});
|
|
2048
|
+
this.pendingClientReleases.set(acquired.client, attempt.registerCancellation(async () => {
|
|
2049
|
+
const clients = this.pendingPersistentSessionClients.get(runDir);
|
|
2050
|
+
if (clients?.get(key) === acquired.client) clients.delete(key);
|
|
2051
|
+
await acquired.client.close();
|
|
2052
|
+
}));
|
|
2053
|
+
return acquired;
|
|
1869
2054
|
});
|
|
2055
|
+
attempt.assertActive();
|
|
1870
2056
|
const binding = {
|
|
1871
2057
|
key,
|
|
1872
2058
|
handle,
|
|
@@ -1882,31 +2068,44 @@ var FlowRunner = class {
|
|
|
1882
2068
|
agentSessionId: created.record.agentSessionId
|
|
1883
2069
|
};
|
|
1884
2070
|
state.sessionBindings[key] = binding;
|
|
1885
|
-
this.pendingPersistentSessionClients.
|
|
1886
|
-
|
|
2071
|
+
let clients = this.pendingPersistentSessionClients.get(runDir);
|
|
2072
|
+
if (!clients) {
|
|
2073
|
+
clients = /* @__PURE__ */ new Map();
|
|
2074
|
+
this.pendingPersistentSessionClients.set(runDir, clients);
|
|
2075
|
+
}
|
|
2076
|
+
clients.set(binding.key, created.client);
|
|
2077
|
+
await attempt.own(() => this.store.ensureSessionBundle(runDir, state, binding, created.record));
|
|
1887
2078
|
return binding;
|
|
1888
2079
|
}
|
|
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
2080
|
createPromptEventCapture(runDir, binding) {
|
|
1898
|
-
const pending =
|
|
2081
|
+
const pending = /* @__PURE__ */ new Set();
|
|
2082
|
+
let ordinal = 0;
|
|
2083
|
+
let failure;
|
|
2084
|
+
let eventStartSeq;
|
|
2085
|
+
let eventEndSeq = 0;
|
|
2086
|
+
const snapshot = (outcome) => ({
|
|
2087
|
+
outcome,
|
|
2088
|
+
events: !failure && eventStartSeq !== void 0 ? {
|
|
2089
|
+
eventStartSeq,
|
|
2090
|
+
eventEndSeq
|
|
2091
|
+
} : void 0,
|
|
2092
|
+
lastSeq: eventEndSeq
|
|
2093
|
+
});
|
|
1899
2094
|
return {
|
|
1900
2095
|
onAcpMessage: (direction, message) => {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
2096
|
+
const index = ordinal++;
|
|
2097
|
+
const write = this.store.appendSessionEvent(runDir, binding, direction, message).then((seq) => {
|
|
2098
|
+
eventStartSeq = Math.min(eventStartSeq ?? seq, seq);
|
|
2099
|
+
eventEndSeq = Math.max(eventEndSeq, seq);
|
|
2100
|
+
}, (reason) => {
|
|
2101
|
+
if (!failure || index < failure.ordinal) failure = {
|
|
2102
|
+
ordinal: index,
|
|
2103
|
+
reason
|
|
2104
|
+
};
|
|
2105
|
+
}).finally(() => pending.delete(write));
|
|
2106
|
+
pending.add(write);
|
|
1908
2107
|
},
|
|
1909
|
-
async run(operation) {
|
|
2108
|
+
async run(operation, finalize) {
|
|
1910
2109
|
let result;
|
|
1911
2110
|
try {
|
|
1912
2111
|
result = {
|
|
@@ -1919,78 +2118,84 @@ var FlowRunner = class {
|
|
|
1919
2118
|
reason
|
|
1920
2119
|
};
|
|
1921
2120
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
2121
|
+
await Promise.all(pending);
|
|
2122
|
+
let finalized;
|
|
2123
|
+
try {
|
|
2124
|
+
finalized = {
|
|
2125
|
+
status: "fulfilled",
|
|
2126
|
+
value: await finalize(snapshot(result))
|
|
2127
|
+
};
|
|
2128
|
+
} catch (reason) {
|
|
2129
|
+
finalized = {
|
|
2130
|
+
status: "rejected",
|
|
2131
|
+
reason
|
|
2132
|
+
};
|
|
1930
2133
|
}
|
|
2134
|
+
if (result.status === "rejected") throw result.reason;
|
|
2135
|
+
if (failure) throw failure.reason;
|
|
1931
2136
|
if (eventStartSeq === void 0) throw new Error(`Missing ACP event capture for session ${binding.bundleId}`);
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
eventStartSeq,
|
|
1935
|
-
eventEndSeq
|
|
1936
|
-
};
|
|
2137
|
+
if (finalized.status === "rejected") throw finalized.reason;
|
|
2138
|
+
return finalized.value;
|
|
1937
2139
|
}
|
|
1938
2140
|
};
|
|
1939
2141
|
}
|
|
1940
|
-
async runPersistentPrompt(runDir, state, binding,
|
|
2142
|
+
async runPersistentPrompt(runDir, state, binding, prepared) {
|
|
2143
|
+
const { attempt, prompt } = prepared;
|
|
1941
2144
|
const capture = createQuietCaptureOutput();
|
|
1942
2145
|
const beforeRecord = await resolveSessionRecord(binding.acpxRecordId);
|
|
2146
|
+
attempt.assertActive();
|
|
1943
2147
|
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(() => {});
|
|
2148
|
+
const clients = this.pendingPersistentSessionClients.get(runDir);
|
|
2149
|
+
const initialClient = clients?.get(binding.key);
|
|
2150
|
+
if (initialClient) {
|
|
2151
|
+
clients?.delete(binding.key);
|
|
2152
|
+
this.pendingClientReleases.get(initialClient)?.();
|
|
2153
|
+
this.pendingClientReleases.delete(initialClient);
|
|
1978
2154
|
}
|
|
2155
|
+
return await events.run(() => sendSessionDirect({
|
|
2156
|
+
sessionId: binding.acpxRecordId,
|
|
2157
|
+
prompt,
|
|
2158
|
+
resumePolicy: "same-session-only",
|
|
2159
|
+
...this.connectionOptions,
|
|
2160
|
+
outputFormatter: capture.formatter,
|
|
2161
|
+
errorEmissionPolicy: { queueErrorAlreadyEmitted: false },
|
|
2162
|
+
onAcpMessage: events.onAcpMessage,
|
|
2163
|
+
suppressSdkConsoleErrors: this.suppressSdkConsoleErrors,
|
|
2164
|
+
client: initialClient
|
|
2165
|
+
}, {
|
|
2166
|
+
signal: attempt.signal,
|
|
2167
|
+
handleProcessInterrupts: false
|
|
2168
|
+
}), async (receipt) => {
|
|
2169
|
+
const rawText = capture.read();
|
|
2170
|
+
prepared.result.rawText = rawText;
|
|
2171
|
+
const afterRecord = await resolveSessionRecord(binding.acpxRecordId);
|
|
2172
|
+
const sessionInfo = {
|
|
2173
|
+
...binding,
|
|
2174
|
+
acpSessionId: afterRecord.acpSessionId,
|
|
2175
|
+
agentSessionId: afterRecord.agentSessionId
|
|
2176
|
+
};
|
|
2177
|
+
state.sessionBindings[sessionInfo.key] = sessionInfo;
|
|
2178
|
+
return await this.publishAcpCapture(runDir, state, prepared, sessionInfo, afterRecord, findConversationDeltaStart(beforeRecord.messages, afterRecord.messages), rawText, receipt.events);
|
|
2179
|
+
});
|
|
1979
2180
|
}
|
|
1980
|
-
async closePendingPersistentSessionClients() {
|
|
1981
|
-
const pendingClients = [...this.pendingPersistentSessionClients.values()];
|
|
1982
|
-
this.pendingPersistentSessionClients.
|
|
1983
|
-
await Promise.
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2181
|
+
async closePendingPersistentSessionClients(runDir) {
|
|
2182
|
+
const pendingClients = [...this.pendingPersistentSessionClients.get(runDir)?.values() ?? []];
|
|
2183
|
+
this.pendingPersistentSessionClients.delete(runDir);
|
|
2184
|
+
const failure = (await Promise.allSettled(pendingClients.map(async (client) => {
|
|
2185
|
+
this.pendingClientReleases.get(client)?.();
|
|
2186
|
+
this.pendingClientReleases.delete(client);
|
|
2187
|
+
await client.close();
|
|
2188
|
+
}))).find((result) => result.status === "rejected");
|
|
2189
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
2190
|
+
}
|
|
2191
|
+
async runIsolatedPrompt(runDir, state, binding, prepared) {
|
|
2192
|
+
const { agentInfo: agent, prompt, attempt } = prepared;
|
|
1988
2193
|
const capture = createQuietCaptureOutput();
|
|
1989
|
-
const conversation = createSessionConversation(
|
|
2194
|
+
const conversation = createSessionConversation(attempt.startedAt);
|
|
1990
2195
|
let acpxState;
|
|
1991
|
-
recordPromptSubmission(conversation, prompt,
|
|
2196
|
+
recordPromptSubmission(conversation, prompt, attempt.startedAt);
|
|
1992
2197
|
const events = this.createPromptEventCapture(runDir, binding);
|
|
1993
|
-
|
|
2198
|
+
return await events.run(() => runOnce({
|
|
1994
2199
|
agentCommand: agent.agentCommand,
|
|
1995
2200
|
agentArgv: agent.agentArgv,
|
|
1996
2201
|
cwd: agent.cwd,
|
|
@@ -2006,35 +2211,29 @@ var FlowRunner = class {
|
|
|
2006
2211
|
acpxState = recordClientOperation(conversation, acpxState, operation);
|
|
2007
2212
|
},
|
|
2008
2213
|
suppressSdkConsoleErrors: this.suppressSdkConsoleErrors,
|
|
2009
|
-
timeoutMs,
|
|
2010
2214
|
sessionOptions: this.sessionOptions
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2215
|
+
}, {
|
|
2216
|
+
signal: attempt.signal,
|
|
2217
|
+
handleProcessInterrupts: false
|
|
2218
|
+
}), async (receipt) => {
|
|
2219
|
+
const rawText = capture.read();
|
|
2220
|
+
prepared.result.rawText = rawText;
|
|
2221
|
+
const sessionId = receipt.outcome.status === "fulfilled" ? receipt.outcome.value.sessionId : capture.sessionId();
|
|
2222
|
+
const sessionInfo = sessionId === void 0 ? binding : {
|
|
2223
|
+
...binding,
|
|
2224
|
+
acpxRecordId: sessionId,
|
|
2225
|
+
acpSessionId: sessionId
|
|
2226
|
+
};
|
|
2227
|
+
const record = createSyntheticSessionRecord({
|
|
2228
|
+
binding: sessionInfo,
|
|
2229
|
+
createdAt: attempt.startedAt,
|
|
2230
|
+
updatedAt: conversation.updated_at,
|
|
2231
|
+
conversation,
|
|
2232
|
+
acpxState: cloneSessionAcpxState(acpxState),
|
|
2233
|
+
lastSeq: receipt.lastSeq
|
|
2234
|
+
});
|
|
2235
|
+
return await this.publishAcpCapture(runDir, state, prepared, sessionInfo, record, 0, rawText, receipt.events);
|
|
2025
2236
|
});
|
|
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
2237
|
}
|
|
2039
2238
|
};
|
|
2040
2239
|
//#endregion
|
|
@@ -2064,9 +2263,23 @@ function parseFencedJsonIfAllowed(text, mode) {
|
|
|
2064
2263
|
const fencedText = extractFencedJsonText(text);
|
|
2065
2264
|
return fencedText === null ? { ok: false } : tryParse(fencedText);
|
|
2066
2265
|
}
|
|
2266
|
+
const COMPAT_JSON_WORK_MULTIPLIER = 8;
|
|
2267
|
+
function consumeRecoveryWork(budget, units) {
|
|
2268
|
+
if (units > budget.remaining) {
|
|
2269
|
+
budget.remaining = 0;
|
|
2270
|
+
return false;
|
|
2271
|
+
}
|
|
2272
|
+
budget.remaining -= units;
|
|
2273
|
+
return true;
|
|
2274
|
+
}
|
|
2067
2275
|
function parseBalancedJsonCandidate(text) {
|
|
2068
|
-
|
|
2069
|
-
|
|
2276
|
+
const budget = { remaining: text.length * COMPAT_JSON_WORK_MULTIPLIER };
|
|
2277
|
+
for (let index = 0; index < text.length && budget.remaining > 0; index += 1) {
|
|
2278
|
+
if (text[index] !== "{" && text[index] !== "[") continue;
|
|
2279
|
+
const end = scanBalanced(text, index, budget);
|
|
2280
|
+
if (end === null) continue;
|
|
2281
|
+
if (!consumeRecoveryWork(budget, end - index)) return { ok: false };
|
|
2282
|
+
const parsed = tryParse(text.slice(index, end));
|
|
2070
2283
|
if (parsed.ok) return parsed;
|
|
2071
2284
|
}
|
|
2072
2285
|
return { ok: false };
|
|
@@ -2100,20 +2313,12 @@ function extractFencedJsonText(text) {
|
|
|
2100
2313
|
function isFenceWhitespace(char) {
|
|
2101
2314
|
return char === " " || char === "\n" || char === "\r" || char === " ";
|
|
2102
2315
|
}
|
|
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) {
|
|
2316
|
+
function scanBalanced(text, startIndex, budget) {
|
|
2113
2317
|
const stack = [];
|
|
2114
2318
|
let inString = false;
|
|
2115
2319
|
let escaped = false;
|
|
2116
2320
|
for (let index = startIndex; index < text.length; index += 1) {
|
|
2321
|
+
if (!consumeRecoveryWork(budget, 1)) return null;
|
|
2117
2322
|
const char = text[index];
|
|
2118
2323
|
if (inString) {
|
|
2119
2324
|
const next = scanStringChar(char, escaped);
|
|
@@ -2125,13 +2330,13 @@ function scanBalanced(text, startIndex) {
|
|
|
2125
2330
|
inString = true;
|
|
2126
2331
|
continue;
|
|
2127
2332
|
}
|
|
2128
|
-
const result = scanBalancedToken(
|
|
2333
|
+
const result = scanBalancedToken(index, char, stack);
|
|
2129
2334
|
if (result !== SCAN_CONTINUE) return result;
|
|
2130
2335
|
}
|
|
2131
2336
|
return null;
|
|
2132
2337
|
}
|
|
2133
2338
|
const SCAN_CONTINUE = Symbol("scan-continue");
|
|
2134
|
-
function scanBalancedToken(
|
|
2339
|
+
function scanBalancedToken(index, char, stack) {
|
|
2135
2340
|
if (char === "{" || char === "[") {
|
|
2136
2341
|
stack.push(char);
|
|
2137
2342
|
return SCAN_CONTINUE;
|
|
@@ -2139,7 +2344,7 @@ function scanBalancedToken(text, startIndex, index, char, stack) {
|
|
|
2139
2344
|
if (char !== "}" && char !== "]") return SCAN_CONTINUE;
|
|
2140
2345
|
if (!balancedClosingTokenMatches(stack.at(-1), char)) return null;
|
|
2141
2346
|
stack.pop();
|
|
2142
|
-
return stack.length === 0 ?
|
|
2347
|
+
return stack.length === 0 ? index + 1 : SCAN_CONTINUE;
|
|
2143
2348
|
}
|
|
2144
2349
|
function balancedClosingTokenMatches(open, close) {
|
|
2145
2350
|
if (open === "{") return close === "}";
|
|
@@ -2229,4 +2434,4 @@ function formatDecisionPrompt(question, choices, field) {
|
|
|
2229
2434
|
//#endregion
|
|
2230
2435
|
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
2436
|
|
|
2232
|
-
//# sourceMappingURL=flows
|
|
2437
|
+
//# sourceMappingURL=flows--Ewxn_fQ.js.map
|