acpx 0.13.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/{cli-DJT3MRRI.js → cli-Cen2Rb6S.js} +4 -4
- package/dist/{cli-DJT3MRRI.js.map → cli-Cen2Rb6S.js.map} +1 -1
- package/dist/cli.d.ts +5 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -8
- package/dist/cli.js.map +1 -1
- package/dist/{client-CxNllqui.d.ts → client-Cvz6msGc.d.ts} +8 -3
- package/dist/client-Cvz6msGc.d.ts.map +1 -0
- package/dist/{flags-CM2rAdBo.js → flags-CRh8BJre.js} +21 -4
- package/dist/flags-CRh8BJre.js.map +1 -0
- package/dist/{flows-BiRKgCnW.js → flows-D-F3Y2o9.js} +436 -63
- package/dist/flows-D-F3Y2o9.js.map +1 -0
- package/dist/flows.d.ts +27 -17
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{live-checkpoint-Gw2oGjhe.js → live-checkpoint-CYRs-D9t.js} +312 -124
- package/dist/live-checkpoint-CYRs-D9t.js.map +1 -0
- package/dist/{output-DiPPprGk.js → output-CIVecnHU.js} +42 -9
- package/dist/output-CIVecnHU.js.map +1 -0
- package/dist/runtime.d.ts +22 -15
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +46 -8
- package/dist/runtime.js.map +1 -1
- package/dist/{session-options-DwRDODlr.d.ts → session-options-DyIGRNfu.d.ts} +48 -2
- package/dist/session-options-DyIGRNfu.d.ts.map +1 -0
- package/package.json +10 -10
- package/skills/acpx/SKILL.md +5 -0
- package/dist/client-CxNllqui.d.ts.map +0 -1
- package/dist/flags-CM2rAdBo.js.map +0 -1
- package/dist/flows-BiRKgCnW.js.map +0 -1
- package/dist/live-checkpoint-Gw2oGjhe.js.map +0 -1
- package/dist/output-DiPPprGk.js.map +0 -1
- package/dist/session-options-DwRDODlr.d.ts.map +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { d as createSessionWithClient, f as cancelSessionPrompt, o as runOnce, s as sendSessionDirect, t as createOutputFormatter } from "./output-
|
|
2
|
-
import { Dt as TimeoutError, Et as InterruptedError, Ot as withInterrupt, Tt as textPrompt, Y as resolveSessionRecord, Yt as PERMISSION_MODES, Zt as SESSION_RECORD_SCHEMA, _ as recordPromptSubmission, dt as defaultSessionEventLog, g as recordClientOperation, h as createSessionConversation, kt as withTimeout, p as cloneSessionAcpxState, v as recordSessionUpdate, wt as promptToDisplayText } from "./live-checkpoint-
|
|
1
|
+
import { d as createSessionWithClient, f as cancelSessionPrompt, o as runOnce, s as sendSessionDirect, t as createOutputFormatter } from "./output-CIVecnHU.js";
|
|
2
|
+
import { Dt as TimeoutError, Et as InterruptedError, F as runTimedExecFile, Ot as withInterrupt, Tt as textPrompt, Y as resolveSessionRecord, Yt as PERMISSION_MODES, Zt as SESSION_RECORD_SCHEMA, _ as recordPromptSubmission, dt as defaultSessionEventLog, g as recordClientOperation, h as createSessionConversation, kt as withTimeout, p as cloneSessionAcpxState, v as recordSessionUpdate, wt as promptToDisplayText } from "./live-checkpoint-CYRs-D9t.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { ZodError, z } from "zod";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
9
10
|
//#region src/flows/authoring.ts
|
|
10
11
|
const FLOW_DEFINITION_BRAND = Symbol.for("acpx.flow.definition");
|
|
11
12
|
function markDefinedFlow(definition) {
|
|
@@ -228,7 +229,167 @@ function checkpoint(definition = {}) {
|
|
|
228
229
|
return node;
|
|
229
230
|
}
|
|
230
231
|
//#endregion
|
|
232
|
+
//#region src/flows/executors/shell-output.ts
|
|
233
|
+
function validateShellActionMaxBufferBytes(value) {
|
|
234
|
+
if (value === void 0) return;
|
|
235
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error("Shell action maxBufferBytes must be a non-negative safe integer");
|
|
236
|
+
}
|
|
237
|
+
function createShellOutputCapture(maxBufferBytes, inactive, overflow) {
|
|
238
|
+
const output = {
|
|
239
|
+
stdout: "",
|
|
240
|
+
stderr: ""
|
|
241
|
+
};
|
|
242
|
+
const bytes = {
|
|
243
|
+
stdout: 0,
|
|
244
|
+
stderr: 0
|
|
245
|
+
};
|
|
246
|
+
return {
|
|
247
|
+
output,
|
|
248
|
+
append(stream, chunk) {
|
|
249
|
+
if (inactive()) return;
|
|
250
|
+
if (maxBufferBytes !== void 0) {
|
|
251
|
+
bytes[stream] += Buffer.byteLength(chunk, "utf8");
|
|
252
|
+
if (bytes[stream] > maxBufferBytes) {
|
|
253
|
+
overflow(/* @__PURE__ */ new Error(`Shell action exceeded maxBuffer (${maxBufferBytes} bytes) on ${stream}`));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
output[stream] += chunk;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region src/flows/executors/shell-process.ts
|
|
263
|
+
const KILL_GRACE_MS = 1e3;
|
|
264
|
+
async function readPosixProcesses() {
|
|
265
|
+
return (await runTimedExecFile("ps", ["-eo", "pid=,ppid=,pgid=,stat="])).trim().split("\n").map((line) => {
|
|
266
|
+
const [pid, parent, group, state = ""] = line.trim().split(/\s+/u);
|
|
267
|
+
return {
|
|
268
|
+
pid: Number(pid),
|
|
269
|
+
parent: Number(parent),
|
|
270
|
+
group: Number(group),
|
|
271
|
+
state
|
|
272
|
+
};
|
|
273
|
+
}).filter((entry) => Number.isInteger(entry.pid) && entry.pid > 0);
|
|
274
|
+
}
|
|
275
|
+
function isLive(entry) {
|
|
276
|
+
return entry.state.length > 0 && !entry.state.startsWith("Z");
|
|
277
|
+
}
|
|
278
|
+
function rememberOwnedProcesses(rows, root, owned) {
|
|
279
|
+
for (const entry of rows) if (entry.group === root) owned.add(entry.pid);
|
|
280
|
+
for (const parent of owned) for (const entry of rows) if (entry.parent === parent) owned.add(entry.pid);
|
|
281
|
+
}
|
|
282
|
+
function signalPid(pid, signal) {
|
|
283
|
+
try {
|
|
284
|
+
process.kill(pid, signal);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (error.code !== "ESRCH") throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async function signalGroup(root, signal) {
|
|
290
|
+
try {
|
|
291
|
+
process.kill(-root, signal);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
const code = error.code;
|
|
294
|
+
if (code === "ESRCH") return;
|
|
295
|
+
if (code === "EPERM" && !(await readPosixProcesses()).some((entry) => entry.group === root && isLive(entry))) return;
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function signalOwned(root, owned, signal) {
|
|
300
|
+
const rows = await readPosixProcesses();
|
|
301
|
+
rememberOwnedProcesses(rows, root, owned);
|
|
302
|
+
await signalGroup(root, signal);
|
|
303
|
+
for (const entry of rows) if (owned.has(entry.pid) && entry.group !== root && isLive(entry)) signalPid(entry.pid, signal);
|
|
304
|
+
}
|
|
305
|
+
async function waitForOwned(root, owned) {
|
|
306
|
+
const deadline = Date.now() + KILL_GRACE_MS;
|
|
307
|
+
do {
|
|
308
|
+
const rows = await readPosixProcesses();
|
|
309
|
+
rememberOwnedProcesses(rows, root, owned);
|
|
310
|
+
if (!rows.some((entry) => owned.has(entry.pid) && isLive(entry))) return true;
|
|
311
|
+
await setTimeout$1(25);
|
|
312
|
+
} while (Date.now() < deadline);
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
async function forceKnownProcesses(root, owned) {
|
|
316
|
+
const results = await Promise.allSettled([signalGroup(root, "SIGKILL"), ...[...owned].map(async (pid) => signalPid(pid, "SIGKILL"))]);
|
|
317
|
+
const errors = [];
|
|
318
|
+
for (const result of results) if (result.status === "rejected") errors.push(result.reason);
|
|
319
|
+
if (errors.length > 0) throw new AggregateError(errors, "Known shell processes could not be stopped", { cause: errors[0] });
|
|
320
|
+
}
|
|
321
|
+
async function stopPosixTree(pid, signal) {
|
|
322
|
+
const owned = /* @__PURE__ */ new Set([pid]);
|
|
323
|
+
try {
|
|
324
|
+
await signalOwned(pid, owned, signal);
|
|
325
|
+
if (await waitForOwned(pid, owned)) return;
|
|
326
|
+
await signalOwned(pid, owned, "SIGKILL");
|
|
327
|
+
if (!await waitForOwned(pid, owned)) throw new Error("Shell process tree did not terminate after SIGKILL");
|
|
328
|
+
} catch (error) {
|
|
329
|
+
try {
|
|
330
|
+
await forceKnownProcesses(pid, owned);
|
|
331
|
+
} catch (cleanupError) {
|
|
332
|
+
throw new AggregateError([error, cleanupError], "Shell process cleanup failed", { cause: cleanupError });
|
|
333
|
+
}
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function stopWindowsTree(child) {
|
|
338
|
+
if (child.pid == null || child.exitCode != null || child.signalCode != null) return;
|
|
339
|
+
try {
|
|
340
|
+
await runTimedExecFile("taskkill", [
|
|
341
|
+
"/pid",
|
|
342
|
+
String(child.pid),
|
|
343
|
+
"/t",
|
|
344
|
+
"/f"
|
|
345
|
+
], { windowsHide: true });
|
|
346
|
+
} catch (error) {
|
|
347
|
+
child.kill("SIGKILL");
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
async function waitForShellClose(closed) {
|
|
352
|
+
try {
|
|
353
|
+
await withTimeout(closed, KILL_GRACE_MS);
|
|
354
|
+
} catch (cause) {
|
|
355
|
+
throw new Error("Shell process streams did not close after termination", { cause });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async function stopShellProcess(child, closed, signal) {
|
|
359
|
+
try {
|
|
360
|
+
if (child.pid != null) {
|
|
361
|
+
if (process.platform === "win32") await stopWindowsTree(child);
|
|
362
|
+
else await stopPosixTree(child.pid, signal);
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
try {
|
|
366
|
+
await waitForShellClose(closed);
|
|
367
|
+
} catch (closeError) {
|
|
368
|
+
throw new AggregateError([error, closeError], "Shell process cleanup failed", { cause: closeError });
|
|
369
|
+
}
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
await waitForShellClose(closed);
|
|
373
|
+
}
|
|
374
|
+
function hasShellProcesses(child) {
|
|
375
|
+
if (child.pid == null) return false;
|
|
376
|
+
if (process.platform === "win32") return child.exitCode == null && child.signalCode == null;
|
|
377
|
+
try {
|
|
378
|
+
process.kill(-child.pid, 0);
|
|
379
|
+
return true;
|
|
380
|
+
} catch (error) {
|
|
381
|
+
return error.code !== "ESRCH";
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
231
385
|
//#region src/flows/executors/shell.ts
|
|
386
|
+
function writeShellStdin(child, stdin) {
|
|
387
|
+
const stream = child.stdin;
|
|
388
|
+
if (!stream) return;
|
|
389
|
+
stream.on("error", () => {});
|
|
390
|
+
if (stdin != null && stream.writable && !stream.writableEnded) stream.write(stdin);
|
|
391
|
+
if (stream.writable && !stream.writableEnded) stream.end();
|
|
392
|
+
}
|
|
232
393
|
function formatShellActionSummary(spec) {
|
|
233
394
|
return `shell: ${renderShellCommand(spec.command, spec.args ?? [])}`;
|
|
234
395
|
}
|
|
@@ -241,43 +402,59 @@ function createShellFailureError(spec, args, exitCode, signal, stderr) {
|
|
|
241
402
|
const details = stderr.length > 0 ? `\n${stderr.trim()}` : "";
|
|
242
403
|
return /* @__PURE__ */ new Error(`Shell action failed (${renderShellCommand(spec.command, args)}): ${status}${details}`);
|
|
243
404
|
}
|
|
244
|
-
|
|
245
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Resolve a shell-action timeout.
|
|
407
|
+
* Non-positive values match withTimeout: no deadline (undefined).
|
|
408
|
+
* Positive values arm SIGTERM/SIGKILL after that many ms.
|
|
409
|
+
*/
|
|
410
|
+
function resolveShellActionTimeoutMs(timeoutMs) {
|
|
411
|
+
if (timeoutMs == null || !(timeoutMs > 0)) return;
|
|
412
|
+
return timeoutMs;
|
|
413
|
+
}
|
|
414
|
+
async function withShellAbort(run, signal) {
|
|
415
|
+
signal.throwIfAborted();
|
|
416
|
+
let rejectAbort = () => {};
|
|
417
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
418
|
+
rejectAbort = reject;
|
|
419
|
+
});
|
|
420
|
+
const onAbort = () => rejectAbort(signal.reason);
|
|
421
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
422
|
+
try {
|
|
423
|
+
return await Promise.race([run(), aborted]);
|
|
424
|
+
} finally {
|
|
425
|
+
signal.removeEventListener("abort", onAbort);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function rejectIfShellFailed(spec, args, result, timedOut, timeoutMs) {
|
|
429
|
+
if (timedOut) return new TimeoutError(timeoutMs ?? spec.timeoutMs ?? 0);
|
|
246
430
|
if (((result.exitCode ?? 0) !== 0 || result.signal != null) && spec.allowNonZeroExit !== true) return createShellFailureError(spec, args, result.exitCode, result.signal, result.stderr);
|
|
247
431
|
}
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
"
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
let stdout = "";
|
|
267
|
-
let stderr = "";
|
|
268
|
-
let timedOut = false;
|
|
269
|
-
let timeout;
|
|
270
|
-
const finish = new Promise((resolve, reject) => {
|
|
271
|
-
child.stdout.setEncoding("utf8");
|
|
272
|
-
child.stderr.setEncoding("utf8");
|
|
273
|
-
child.stdout.on("data", (chunk) => {
|
|
274
|
-
stdout += chunk;
|
|
432
|
+
function waitForShellExit(child, spec, args, cwd, startMs, timeoutMs, termination) {
|
|
433
|
+
const stdoutStream = child.stdout;
|
|
434
|
+
const stderrStream = child.stderr;
|
|
435
|
+
if (!stdoutStream || !stderrStream) throw new Error("Shell action child is missing stdio pipes");
|
|
436
|
+
return new Promise((resolve, reject) => {
|
|
437
|
+
let settled = false;
|
|
438
|
+
const fail = (error) => {
|
|
439
|
+
settled = true;
|
|
440
|
+
reject(error);
|
|
441
|
+
};
|
|
442
|
+
const capture = createShellOutputCapture(spec.maxBufferBytes, () => settled || termination.timedOut(), (error) => {
|
|
443
|
+
fail(error);
|
|
444
|
+
termination.cancel("SIGTERM").catch(fail);
|
|
445
|
+
});
|
|
446
|
+
stdoutStream.setEncoding("utf8");
|
|
447
|
+
stderrStream.setEncoding("utf8");
|
|
448
|
+
stdoutStream.on("data", (chunk) => {
|
|
449
|
+
capture.append("stdout", chunk);
|
|
275
450
|
});
|
|
276
|
-
|
|
277
|
-
stderr
|
|
451
|
+
stderrStream.on("data", (chunk) => {
|
|
452
|
+
capture.append("stderr", chunk);
|
|
278
453
|
});
|
|
279
|
-
child.once("error",
|
|
454
|
+
child.once("error", fail);
|
|
280
455
|
child.once("exit", (exitCode, signal) => {
|
|
456
|
+
settled = true;
|
|
457
|
+
const { stdout, stderr } = capture.output;
|
|
281
458
|
const result = {
|
|
282
459
|
command: spec.command,
|
|
283
460
|
args,
|
|
@@ -289,7 +466,7 @@ async function runShellAction(spec) {
|
|
|
289
466
|
signal,
|
|
290
467
|
durationMs: Date.now() - startMs
|
|
291
468
|
};
|
|
292
|
-
const error = rejectIfShellFailed(spec, args, result, timedOut);
|
|
469
|
+
const error = rejectIfShellFailed(spec, args, result, termination.timedOut(), timeoutMs);
|
|
293
470
|
if (error) {
|
|
294
471
|
reject(error);
|
|
295
472
|
return;
|
|
@@ -297,19 +474,101 @@ async function runShellAction(spec) {
|
|
|
297
474
|
resolve(result);
|
|
298
475
|
});
|
|
299
476
|
});
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
477
|
+
}
|
|
478
|
+
function createShellTermination(child, timeoutMs, options) {
|
|
479
|
+
const closed = new Promise((resolve) => child.once("close", () => resolve()));
|
|
480
|
+
let rejectCleanup = () => {};
|
|
481
|
+
const cleanupFailure = new Promise((_resolve, reject) => {
|
|
482
|
+
rejectCleanup = reject;
|
|
483
|
+
});
|
|
484
|
+
let termination;
|
|
485
|
+
let timedOut = false;
|
|
486
|
+
let released = false;
|
|
487
|
+
let deadline;
|
|
488
|
+
let monitor;
|
|
489
|
+
let unregister;
|
|
490
|
+
const clearDeadline = () => {
|
|
491
|
+
if (deadline) clearTimeout(deadline);
|
|
492
|
+
};
|
|
493
|
+
const release = () => {
|
|
494
|
+
if (released) return;
|
|
495
|
+
released = true;
|
|
496
|
+
clearDeadline();
|
|
497
|
+
if (monitor) clearInterval(monitor);
|
|
498
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
499
|
+
unregister?.();
|
|
500
|
+
};
|
|
501
|
+
const cancel = (signal) => {
|
|
502
|
+
if (termination) return termination;
|
|
503
|
+
if (released) return Promise.resolve();
|
|
303
504
|
timedOut = true;
|
|
304
|
-
child.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
505
|
+
termination = stopShellProcess(child, closed, signal).finally(release);
|
|
506
|
+
termination.catch(rejectCleanup);
|
|
507
|
+
return termination;
|
|
508
|
+
};
|
|
509
|
+
const onAbort = () => {
|
|
510
|
+
cancel(options.terminationSignal ?? "SIGTERM");
|
|
511
|
+
};
|
|
512
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
513
|
+
if (timeoutMs != null) deadline = setTimeout(() => {
|
|
514
|
+
cancel("SIGTERM");
|
|
515
|
+
}, timeoutMs);
|
|
516
|
+
unregister = options.registerOwner?.({
|
|
517
|
+
cancel,
|
|
518
|
+
release
|
|
519
|
+
});
|
|
520
|
+
if (unregister) child.once("close", () => {
|
|
521
|
+
if (released || termination) return;
|
|
522
|
+
const prune = () => {
|
|
523
|
+
if (!termination && !hasShellProcesses(child)) release();
|
|
524
|
+
};
|
|
525
|
+
monitor = setInterval(prune, 100);
|
|
526
|
+
monitor.unref();
|
|
527
|
+
prune();
|
|
528
|
+
});
|
|
529
|
+
return {
|
|
530
|
+
cancel,
|
|
531
|
+
timedOut: () => timedOut,
|
|
532
|
+
cleanupFailure,
|
|
533
|
+
async dispose() {
|
|
534
|
+
clearDeadline();
|
|
535
|
+
try {
|
|
536
|
+
await termination;
|
|
537
|
+
} finally {
|
|
538
|
+
if (!unregister) release();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
async function runShellAction(spec, options = {}) {
|
|
544
|
+
if (options?.signal?.aborted) throw options.signal.reason;
|
|
545
|
+
const cwd = spec.cwd ?? process.cwd();
|
|
546
|
+
const args = spec.args ?? [];
|
|
547
|
+
const startMs = Date.now();
|
|
548
|
+
const timeoutMs = resolveShellActionTimeoutMs(spec.timeoutMs);
|
|
549
|
+
validateShellActionMaxBufferBytes(spec.maxBufferBytes);
|
|
550
|
+
const child = spawn(spec.command, args, {
|
|
551
|
+
cwd,
|
|
552
|
+
env: {
|
|
553
|
+
...process.env,
|
|
554
|
+
...spec.env
|
|
555
|
+
},
|
|
556
|
+
shell: spec.shell,
|
|
557
|
+
stdio: [
|
|
558
|
+
"pipe",
|
|
559
|
+
"pipe",
|
|
560
|
+
"pipe"
|
|
561
|
+
],
|
|
562
|
+
windowsHide: true,
|
|
563
|
+
detached: process.platform !== "win32"
|
|
564
|
+
});
|
|
565
|
+
const termination = createShellTermination(child, timeoutMs, options);
|
|
566
|
+
const finish = waitForShellExit(child, spec, args, cwd, startMs, timeoutMs, termination);
|
|
567
|
+
writeShellStdin(child, spec.stdin);
|
|
309
568
|
try {
|
|
310
|
-
return await finish;
|
|
569
|
+
return await Promise.race([finish, termination.cleanupFailure]);
|
|
311
570
|
} finally {
|
|
312
|
-
|
|
571
|
+
await termination.dispose();
|
|
313
572
|
}
|
|
314
573
|
}
|
|
315
574
|
//#endregion
|
|
@@ -1020,6 +1279,9 @@ var FlowRunner = class {
|
|
|
1020
1279
|
services;
|
|
1021
1280
|
store;
|
|
1022
1281
|
pendingPersistentSessionClients = /* @__PURE__ */ new Map();
|
|
1282
|
+
shellInterrupts = /* @__PURE__ */ new Map();
|
|
1283
|
+
runInterruptions = /* @__PURE__ */ new Map();
|
|
1284
|
+
shellOwners = /* @__PURE__ */ new Map();
|
|
1023
1285
|
constructor(options) {
|
|
1024
1286
|
this.resolveAgent = options.resolveAgent;
|
|
1025
1287
|
this.defaultCwd = options.resolveAgent(void 0).cwd;
|
|
@@ -1068,19 +1330,84 @@ var FlowRunner = class {
|
|
|
1068
1330
|
inputArtifact
|
|
1069
1331
|
});
|
|
1070
1332
|
try {
|
|
1071
|
-
return await
|
|
1072
|
-
await persistRunFailure(this.store, runDir, state, new InterruptedError());
|
|
1073
|
-
});
|
|
1333
|
+
return await this.runWithShellOwnership(flow, input, runDir, state);
|
|
1074
1334
|
} finally {
|
|
1075
1335
|
await this.closePendingPersistentSessionClients();
|
|
1076
1336
|
}
|
|
1077
1337
|
}
|
|
1338
|
+
async runWithShellOwnership(flow, input, runDir, state) {
|
|
1339
|
+
let execution;
|
|
1340
|
+
let cancellation;
|
|
1341
|
+
let interruption;
|
|
1342
|
+
const result = await withInterrupt(() => {
|
|
1343
|
+
execution = this.executeFlowRun(flow, input, runDir, state);
|
|
1344
|
+
return execution;
|
|
1345
|
+
}, async (signal) => {
|
|
1346
|
+
const reason = interruption ?? new InterruptedError();
|
|
1347
|
+
interruption = reason;
|
|
1348
|
+
this.runInterruptions.set(runDir, reason);
|
|
1349
|
+
cancellation ??= this.cancelShellOwners(runDir, signal);
|
|
1350
|
+
cancellation.catch(() => {});
|
|
1351
|
+
const interruptShell = this.shellInterrupts.get(runDir);
|
|
1352
|
+
if (interruptShell) {
|
|
1353
|
+
interruptShell(reason, signal);
|
|
1354
|
+
await execution?.catch(() => {});
|
|
1355
|
+
}
|
|
1356
|
+
}).then((value) => ({
|
|
1357
|
+
ok: true,
|
|
1358
|
+
value
|
|
1359
|
+
}), (error) => ({
|
|
1360
|
+
ok: false,
|
|
1361
|
+
error
|
|
1362
|
+
}));
|
|
1363
|
+
try {
|
|
1364
|
+
try {
|
|
1365
|
+
await cancellation;
|
|
1366
|
+
} catch (cleanupError) {
|
|
1367
|
+
const failure = result.ok || result.error === cleanupError ? cleanupError : new AggregateError([result.error, cleanupError], "Shell cleanup failed during interruption", { cause: cleanupError });
|
|
1368
|
+
await persistRunFailure(this.store, runDir, state, failure);
|
|
1369
|
+
throw failure;
|
|
1370
|
+
}
|
|
1371
|
+
if (result.ok && !interruption) return result.value;
|
|
1372
|
+
const failure = result.ok ? interruption : result.error;
|
|
1373
|
+
if (interruption) await persistRunFailure(this.store, runDir, state, failure);
|
|
1374
|
+
throw failure;
|
|
1375
|
+
} finally {
|
|
1376
|
+
this.releaseShellOwners(runDir);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
registerShellOwner(runDir, owner) {
|
|
1380
|
+
let owners = this.shellOwners.get(runDir);
|
|
1381
|
+
if (!owners) {
|
|
1382
|
+
owners = /* @__PURE__ */ new Set();
|
|
1383
|
+
this.shellOwners.set(runDir, owners);
|
|
1384
|
+
}
|
|
1385
|
+
owners.add(owner);
|
|
1386
|
+
const registered = owners;
|
|
1387
|
+
return () => {
|
|
1388
|
+
registered.delete(owner);
|
|
1389
|
+
if (registered.size === 0) this.shellOwners.delete(runDir);
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
async cancelShellOwners(runDir, signal) {
|
|
1393
|
+
const owners = [...this.shellOwners.get(runDir) ?? []];
|
|
1394
|
+
const results = await Promise.allSettled(owners.map((owner) => owner.cancel(signal)));
|
|
1395
|
+
const errors = [];
|
|
1396
|
+
for (const result of results) if (result.status === "rejected") errors.push(result.reason);
|
|
1397
|
+
if (errors.length > 0) throw new AggregateError(errors, "Shell process cleanup failed", { cause: errors[0] });
|
|
1398
|
+
}
|
|
1399
|
+
releaseShellOwners(runDir) {
|
|
1400
|
+
for (const owner of this.shellOwners.get(runDir) ?? []) owner.release();
|
|
1401
|
+
this.shellOwners.delete(runDir);
|
|
1402
|
+
}
|
|
1078
1403
|
async executeFlowRun(flow, input, runDir, state) {
|
|
1079
1404
|
let current = flow.startAt;
|
|
1080
1405
|
const attemptCounts = /* @__PURE__ */ new Map();
|
|
1081
1406
|
try {
|
|
1082
1407
|
while (current) {
|
|
1408
|
+
this.throwIfRunInterrupted(runDir);
|
|
1083
1409
|
const step = await this.executeFlowStep(flow, input, runDir, state, current, attemptCounts);
|
|
1410
|
+
this.throwIfRunInterrupted(runDir, step.executionError);
|
|
1084
1411
|
const waiting = await this.maybeCompleteCheckpointStep(runDir, state, step);
|
|
1085
1412
|
if (waiting) return waiting;
|
|
1086
1413
|
await this.recordFlowStepOutcome(runDir, state, step);
|
|
@@ -1088,10 +1415,16 @@ var FlowRunner = class {
|
|
|
1088
1415
|
}
|
|
1089
1416
|
return await this.completeFlowRun(runDir, state);
|
|
1090
1417
|
} catch (error) {
|
|
1091
|
-
await persistRunFailure(this.store, runDir, state, error);
|
|
1418
|
+
if (!this.runInterruptions.has(runDir)) await persistRunFailure(this.store, runDir, state, error);
|
|
1092
1419
|
throw error;
|
|
1420
|
+
} finally {
|
|
1421
|
+
this.runInterruptions.delete(runDir);
|
|
1093
1422
|
}
|
|
1094
1423
|
}
|
|
1424
|
+
throwIfRunInterrupted(runDir, error) {
|
|
1425
|
+
const interrupted = this.runInterruptions.get(runDir);
|
|
1426
|
+
if (interrupted) throw error ?? interrupted;
|
|
1427
|
+
}
|
|
1095
1428
|
async executeFlowStep(flow, input, runDir, state, nodeId, attemptCounts) {
|
|
1096
1429
|
const node = flow.nodes[nodeId];
|
|
1097
1430
|
if (!node) throw new Error(`Unknown flow node: ${nodeId}`);
|
|
@@ -1126,7 +1459,9 @@ var FlowRunner = class {
|
|
|
1126
1459
|
async executeStartedFlowStep(params) {
|
|
1127
1460
|
const context = makeFlowNodeContext(params.state, params.input, this.services);
|
|
1128
1461
|
try {
|
|
1462
|
+
this.throwIfRunInterrupted(params.runDir);
|
|
1129
1463
|
const executed = await this.executeNode(params.runDir, params.state, params.flow, params.nodeId, params.node, context);
|
|
1464
|
+
this.throwIfRunInterrupted(params.runDir);
|
|
1130
1465
|
return await this.createSuccessfulFlowStep(params, executed);
|
|
1131
1466
|
} catch (error) {
|
|
1132
1467
|
return await this.createFailedFlowStep(params, error);
|
|
@@ -1279,12 +1614,27 @@ var FlowRunner = class {
|
|
|
1279
1614
|
agentInfo: null,
|
|
1280
1615
|
trace: { action: { actionType: "function" } }
|
|
1281
1616
|
};
|
|
1282
|
-
const
|
|
1617
|
+
const shellAbort = new AbortController();
|
|
1618
|
+
let runningOwner;
|
|
1619
|
+
const shellControl = {
|
|
1620
|
+
signal: shellAbort.signal,
|
|
1621
|
+
registerOwner: (owner) => {
|
|
1622
|
+
runningOwner = owner;
|
|
1623
|
+
return this.registerShellOwner(runDir, owner);
|
|
1624
|
+
}
|
|
1625
|
+
};
|
|
1626
|
+
this.shellInterrupts.set(runDir, (reason, signal) => {
|
|
1627
|
+
shellControl.terminationSignal = signal;
|
|
1628
|
+
shellAbort.abort(reason);
|
|
1629
|
+
});
|
|
1630
|
+
let runningShell;
|
|
1631
|
+
const { output, rawText, trace } = await this.runWithHeartbeat(runDir, state, state.currentNode ?? "", node, nodeTimeoutMs, () => withShellAbort(async () => {
|
|
1283
1632
|
const execution = await Promise.resolve(node.exec(context));
|
|
1633
|
+
shellAbort.signal.throwIfAborted();
|
|
1284
1634
|
const effectiveExecution = {
|
|
1285
1635
|
...execution,
|
|
1286
1636
|
cwd: resolveShellActionCwd(this.defaultCwd, execution.cwd),
|
|
1287
|
-
timeoutMs: execution.timeoutMs ?? nodeTimeoutMs
|
|
1637
|
+
timeoutMs: resolveShellActionTimeoutMs(execution.timeoutMs ?? nodeTimeoutMs)
|
|
1288
1638
|
};
|
|
1289
1639
|
updateStatusDetail(state, formatShellActionSummary(effectiveExecution));
|
|
1290
1640
|
await this.store.writeLive(runDir, state, {
|
|
@@ -1294,6 +1644,7 @@ var FlowRunner = class {
|
|
|
1294
1644
|
attemptId: state.currentAttemptId,
|
|
1295
1645
|
payload: { statusDetail: state.statusDetail }
|
|
1296
1646
|
});
|
|
1647
|
+
shellAbort.signal.throwIfAborted();
|
|
1297
1648
|
await this.store.appendTrace(runDir, state, {
|
|
1298
1649
|
scope: "action",
|
|
1299
1650
|
type: "action_prepared",
|
|
@@ -1306,7 +1657,9 @@ var FlowRunner = class {
|
|
|
1306
1657
|
cwd: effectiveExecution.cwd
|
|
1307
1658
|
} }
|
|
1308
1659
|
});
|
|
1309
|
-
|
|
1660
|
+
runningShell = runShellAction(effectiveExecution, shellControl);
|
|
1661
|
+
const result = await runningShell;
|
|
1662
|
+
shellAbort.signal.throwIfAborted();
|
|
1310
1663
|
const stdoutArtifact = await this.store.writeArtifact(runDir, state, result.stdout, {
|
|
1311
1664
|
mediaType: "text/plain",
|
|
1312
1665
|
extension: "txt",
|
|
@@ -1319,6 +1672,7 @@ var FlowRunner = class {
|
|
|
1319
1672
|
nodeId: state.currentNode,
|
|
1320
1673
|
attemptId: state.currentAttemptId
|
|
1321
1674
|
});
|
|
1675
|
+
shellAbort.signal.throwIfAborted();
|
|
1322
1676
|
await this.store.appendTrace(runDir, state, {
|
|
1323
1677
|
scope: "action",
|
|
1324
1678
|
type: "action_completed",
|
|
@@ -1351,9 +1705,11 @@ var FlowRunner = class {
|
|
|
1351
1705
|
stdoutArtifact,
|
|
1352
1706
|
stderrArtifact
|
|
1353
1707
|
};
|
|
1708
|
+
shellAbort.signal.throwIfAborted();
|
|
1354
1709
|
let parsedOutput;
|
|
1355
1710
|
try {
|
|
1356
1711
|
parsedOutput = node.parse ? await node.parse(result, context) : result;
|
|
1712
|
+
shellAbort.signal.throwIfAborted();
|
|
1357
1713
|
} catch (error) {
|
|
1358
1714
|
throw attachStepTrace(error, trace);
|
|
1359
1715
|
}
|
|
@@ -1362,6 +1718,17 @@ var FlowRunner = class {
|
|
|
1362
1718
|
rawText: result.combinedOutput,
|
|
1363
1719
|
trace
|
|
1364
1720
|
};
|
|
1721
|
+
}, shellAbort.signal)).catch(async (error) => {
|
|
1722
|
+
shellAbort.abort(error);
|
|
1723
|
+
try {
|
|
1724
|
+
await runningOwner?.cancel(shellControl.terminationSignal ?? "SIGTERM");
|
|
1725
|
+
await runningShell;
|
|
1726
|
+
} catch (cleanupError) {
|
|
1727
|
+
if (!(cleanupError instanceof TimeoutError) && cleanupError !== error) throw new AggregateError([error, cleanupError], "Shell action cleanup failed", { cause: cleanupError });
|
|
1728
|
+
}
|
|
1729
|
+
throw error;
|
|
1730
|
+
}).finally(() => {
|
|
1731
|
+
this.shellInterrupts.delete(runDir);
|
|
1365
1732
|
});
|
|
1366
1733
|
return {
|
|
1367
1734
|
output,
|
|
@@ -1520,17 +1887,23 @@ var FlowRunner = class {
|
|
|
1520
1887
|
const heartbeatMs = Math.max(0, Math.round(node.heartbeatMs ?? DEFAULT_FLOW_HEARTBEAT_MS));
|
|
1521
1888
|
let timer;
|
|
1522
1889
|
let active = true;
|
|
1890
|
+
let heartbeatPending = false;
|
|
1523
1891
|
const heartbeat = async () => {
|
|
1524
|
-
if (!active) return;
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1892
|
+
if (!active || heartbeatPending) return;
|
|
1893
|
+
heartbeatPending = true;
|
|
1894
|
+
try {
|
|
1895
|
+
state.lastHeartbeatAt = isoNow$1();
|
|
1896
|
+
state.updatedAt = state.lastHeartbeatAt;
|
|
1897
|
+
await this.store.writeLive(runDir, state, {
|
|
1898
|
+
scope: "node",
|
|
1899
|
+
type: "node_heartbeat",
|
|
1900
|
+
nodeId,
|
|
1901
|
+
attemptId: state.currentAttemptId,
|
|
1902
|
+
payload: { statusDetail: state.statusDetail }
|
|
1903
|
+
});
|
|
1904
|
+
} finally {
|
|
1905
|
+
heartbeatPending = false;
|
|
1906
|
+
}
|
|
1534
1907
|
};
|
|
1535
1908
|
if (heartbeatMs > 0) timer = setInterval(() => {
|
|
1536
1909
|
heartbeat().catch(() => {});
|
|
@@ -1927,4 +2300,4 @@ function formatDecisionPrompt(question, choices, field) {
|
|
|
1927
2300
|
//#endregion
|
|
1928
2301
|
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 };
|
|
1929
2302
|
|
|
1930
|
-
//# sourceMappingURL=flows-
|
|
2303
|
+
//# sourceMappingURL=flows-D-F3Y2o9.js.map
|