@signalridge/pi-workflows 1.7.4 → 1.8.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/CHANGELOG.md +26 -0
- package/README.md +145 -10
- package/package.json +7 -6
- package/skills/workflow-authoring/SKILL.md +1 -1
- package/skills/workflow-authoring/references/capabilities.md +1 -1
- package/skills/workflow-authoring/references/capability-details.md +1 -1
- package/skills/workflow-patterns/SKILL.md +1 -1
- package/skills/workflow-review/SKILL.md +1 -1
- package/src/arming.ts +264 -3
- package/src/engine.ts +156 -14
- package/src/errors.ts +26 -0
- package/src/index.ts +381 -156
- package/src/journal.ts +5 -0
- package/src/runtime.ts +40 -20
- package/src/safe-text.ts +4 -35
- package/src/ui.ts +38 -14
- package/src/vm-bootstrap.ts +218 -0
- package/src/vm-helper.ts +502 -0
package/src/engine.ts
CHANGED
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
import { createHash, randomUUID } from "node:crypto";
|
|
21
21
|
import type { ManagedSpawnResponse } from "@signalridge/pi-subagents-protocol";
|
|
22
22
|
import { isManagedAgentTier } from "@signalridge/pi-subagents-protocol";
|
|
23
|
-
import { WorkflowError, WorkflowErrorCode, wrapError } from "./errors.js";
|
|
23
|
+
import { describeWorkflowError, WorkflowError, WorkflowErrorCode, wrapError } from "./errors.js";
|
|
24
24
|
import {
|
|
25
|
+
applyRecoveryEvent,
|
|
25
26
|
buildResumeJournal,
|
|
26
27
|
type CallResult,
|
|
27
28
|
type CallTierIdentity,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
type JournalEvent,
|
|
32
33
|
type JournalWriter,
|
|
33
34
|
type RecoveryTerminalResult,
|
|
35
|
+
type RunRecoveryEvent,
|
|
34
36
|
replayJournal,
|
|
35
37
|
type ScriptRun,
|
|
36
38
|
type SessionEntryLike,
|
|
@@ -311,6 +313,8 @@ export class WorkflowEngine {
|
|
|
311
313
|
private readonly runs = new Map<string, ScriptRunState>();
|
|
312
314
|
/** Fired when a run settles terminal; wired by the UI surface for widget refresh. */
|
|
313
315
|
onRunSettled: (() => void) | undefined;
|
|
316
|
+
/** Every accepted background execution, including automatic and UI resumes. */
|
|
317
|
+
onBackgroundExecution: ((runId: string) => void) | undefined;
|
|
314
318
|
/** Optional in-process observer for phase/task/quality runtime events. */
|
|
315
319
|
onRuntimeEvent: ((runId: string, event: WorkflowRuntimeEvent) => void) | undefined;
|
|
316
320
|
/**
|
|
@@ -339,10 +343,14 @@ export class WorkflowEngine {
|
|
|
339
343
|
private admissionsClosed = false;
|
|
340
344
|
private pendingAdmissions = 0;
|
|
341
345
|
private recoveryBranchGeneration = 0;
|
|
342
|
-
private readonly journalRetries = new Map<
|
|
346
|
+
private readonly journalRetries = new Map<
|
|
347
|
+
string,
|
|
348
|
+
{ timer?: ReturnType<typeof setTimeout>; attempt: number; done: Promise<void>; resolve: () => void }
|
|
349
|
+
>();
|
|
343
350
|
private readonly journalBlockedRuns = new Set<string>();
|
|
344
351
|
private readonly providerResumeTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
345
352
|
private journalMutationKey: string | undefined;
|
|
353
|
+
private journalMutationSequence = 0;
|
|
346
354
|
private readonly quarantinedRunIds = new Set<string>();
|
|
347
355
|
|
|
348
356
|
constructor(
|
|
@@ -351,6 +359,7 @@ export class WorkflowEngine {
|
|
|
351
359
|
private readonly journal: JournalWriter,
|
|
352
360
|
private readonly readEntries: () => readonly SessionEntryLike[] = () => [],
|
|
353
361
|
protocolGate?: () => Promise<ManagedProtocolCheck>,
|
|
362
|
+
private readonly cwd: string = process.cwd(),
|
|
354
363
|
) {
|
|
355
364
|
this.protocolGate =
|
|
356
365
|
protocolGate ??
|
|
@@ -866,8 +875,52 @@ export class WorkflowEngine {
|
|
|
866
875
|
clearTimeout(providerTimer);
|
|
867
876
|
this.providerResumeTimers.delete(runId);
|
|
868
877
|
}
|
|
869
|
-
if (
|
|
878
|
+
if (
|
|
879
|
+
run.nonResumable ||
|
|
880
|
+
run.terminalIntent === "stop" ||
|
|
881
|
+
(isTerminalWorkflow(run.status) && run.status !== "failed" && run.status !== "stopped")
|
|
882
|
+
)
|
|
870
883
|
return undefined;
|
|
884
|
+
if (isTerminalWorkflow(run.status)) {
|
|
885
|
+
await this.drainExecution(state, state.execution);
|
|
886
|
+
for (const [nodeId, agentId] of Object.entries(run.agentIds)) {
|
|
887
|
+
if (run.callStatus[nodeId] !== "running") continue; // Completed/local calls own no live child.
|
|
888
|
+
const attemptId = run.attemptIds[nodeId];
|
|
889
|
+
if (!attemptId) continue;
|
|
890
|
+
await this.stopAndQuiesce(
|
|
891
|
+
state,
|
|
892
|
+
agentId,
|
|
893
|
+
{
|
|
894
|
+
extension: "pi-workflows",
|
|
895
|
+
runId,
|
|
896
|
+
nodeId: `call-${nodeId}`,
|
|
897
|
+
attemptId: `attempt-${generationFromAttemptId(attemptId)}`,
|
|
898
|
+
},
|
|
899
|
+
state.executionGeneration,
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
if (run.nonResumable || this.runs.get(runId) !== state || epoch !== this.branchEpoch || this.lifecyclePaused)
|
|
903
|
+
return undefined;
|
|
904
|
+
if (
|
|
905
|
+
this.journalBlockedRuns.has(runId) ||
|
|
906
|
+
[...this.journalRetries.keys()].some((key) => key.startsWith(`${runId}\u0000`))
|
|
907
|
+
)
|
|
908
|
+
throw new Error("Workflow journal has not settled; retry resume after persistence recovers");
|
|
909
|
+
const timestamp = Date.now();
|
|
910
|
+
const recovery: RunRecoveryEvent = {
|
|
911
|
+
kind: "run_recovery",
|
|
912
|
+
schemaVersion: JOURNAL_SCHEMA_VERSION,
|
|
913
|
+
runId,
|
|
914
|
+
status: "interrupted",
|
|
915
|
+
branchGeneration: this.recoveryBranchGeneration,
|
|
916
|
+
rotations: [],
|
|
917
|
+
recoveryId: deriveRecoveryId({ runId, rotations: [], timestamp }),
|
|
918
|
+
timestamp,
|
|
919
|
+
};
|
|
920
|
+
this.appendRequired(recovery);
|
|
921
|
+
applyRecoveryEvent(run, recovery);
|
|
922
|
+
state.result = undefined;
|
|
923
|
+
}
|
|
871
924
|
if (replacementScript !== undefined) {
|
|
872
925
|
const parsed = parseWorkflowScript(replacementScript);
|
|
873
926
|
const revision = (run.revision ?? 0) + 1;
|
|
@@ -930,7 +983,10 @@ export class WorkflowEngine {
|
|
|
930
983
|
);
|
|
931
984
|
const execution = state.execution;
|
|
932
985
|
void execution.catch(() => {});
|
|
933
|
-
if (options.background)
|
|
986
|
+
if (options.background) {
|
|
987
|
+
this.onBackgroundExecution?.(runId);
|
|
988
|
+
return { runId, status: run.status, background: true };
|
|
989
|
+
}
|
|
934
990
|
try {
|
|
935
991
|
const settled = await this.waitFor(runId, options.signal);
|
|
936
992
|
return { runId, status: settled.status, background: false };
|
|
@@ -1110,6 +1166,7 @@ export class WorkflowEngine {
|
|
|
1110
1166
|
|
|
1111
1167
|
try {
|
|
1112
1168
|
const result = await runWorkflow(script, {
|
|
1169
|
+
cwd: this.cwd,
|
|
1113
1170
|
args: options.args,
|
|
1114
1171
|
agent: runner,
|
|
1115
1172
|
mainModel: options.mainModel,
|
|
@@ -1168,6 +1225,9 @@ export class WorkflowEngine {
|
|
|
1168
1225
|
recoverable: true,
|
|
1169
1226
|
});
|
|
1170
1227
|
}
|
|
1228
|
+
// A terminal frame cannot overtake accepted journal facts. In particular,
|
|
1229
|
+
// a delayed call_attempt must precede its result, not reset it on replay.
|
|
1230
|
+
await this.drainJournal(runId);
|
|
1171
1231
|
const live = this.runs.get(runId);
|
|
1172
1232
|
const run = live?.run;
|
|
1173
1233
|
if (run && isSameBranch() && !isTerminalWorkflow(run.status)) {
|
|
@@ -1179,7 +1239,7 @@ export class WorkflowEngine {
|
|
|
1179
1239
|
await runner.waitForCleanup?.();
|
|
1180
1240
|
const run = this.runs.get(runId)?.run;
|
|
1181
1241
|
if (run && isSameBranch() && !isTerminalWorkflow(run.status)) {
|
|
1182
|
-
const message =
|
|
1242
|
+
const message = describeWorkflowError(error);
|
|
1183
1243
|
const workflowError = error instanceof WorkflowError ? error : wrapError(error);
|
|
1184
1244
|
if (workflowError.code === WorkflowErrorCode.PROVIDER_USAGE_LIMIT) {
|
|
1185
1245
|
this.setWorkflowStatus(run, "paused", message);
|
|
@@ -1415,7 +1475,36 @@ export class WorkflowEngine {
|
|
|
1415
1475
|
state.agentWaiters.delete(pendingId);
|
|
1416
1476
|
state.bufferedTerminals.delete(spawnKey);
|
|
1417
1477
|
// Stop acknowledges cancellation, not provider/tool finally completion.
|
|
1418
|
-
void this.stopAndQuiesce(state, pendingId, owner, executionGeneration).then(() => {
|
|
1478
|
+
void this.stopAndQuiesce(state, pendingId, owner, executionGeneration).then(async () => {
|
|
1479
|
+
if (
|
|
1480
|
+
!nested &&
|
|
1481
|
+
this.runs.get(runId) === state &&
|
|
1482
|
+
state.executionGeneration === executionGeneration &&
|
|
1483
|
+
!run.nonResumable
|
|
1484
|
+
) {
|
|
1485
|
+
const result = resultFromLifecycle(
|
|
1486
|
+
"stopped",
|
|
1487
|
+
pendingId,
|
|
1488
|
+
undefined,
|
|
1489
|
+
"workflow call cancelled",
|
|
1490
|
+
run.compactions[nodeId] ?? 0,
|
|
1491
|
+
undefined,
|
|
1492
|
+
resolvedTierIdentity?.tier ?? tier,
|
|
1493
|
+
);
|
|
1494
|
+
result.attemptId = attemptId;
|
|
1495
|
+
try {
|
|
1496
|
+
await this.drainJournal(runId);
|
|
1497
|
+
this.commitCallTerminal(runId, nodeId, result, true);
|
|
1498
|
+
} catch (error) {
|
|
1499
|
+
this.fenceExecution(
|
|
1500
|
+
state,
|
|
1501
|
+
executionGeneration,
|
|
1502
|
+
"Could not persist cancelled call terminal fact; run is non-resumable",
|
|
1503
|
+
);
|
|
1504
|
+
waiter.reject(error);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1419
1508
|
waiter.reject(
|
|
1420
1509
|
new WorkflowError("workflow aborted", WorkflowErrorCode.WORKFLOW_ABORTED, { recoverable: true }),
|
|
1421
1510
|
);
|
|
@@ -1519,6 +1608,27 @@ export class WorkflowEngine {
|
|
|
1519
1608
|
compactionCount: 0,
|
|
1520
1609
|
updatedAt: Date.now(),
|
|
1521
1610
|
};
|
|
1611
|
+
// Checkpoints are local calls, not managed children. Give their completed
|
|
1612
|
+
// result a journal identity without fabricating a manager record.
|
|
1613
|
+
if (!run.attemptIds[nodeId]) {
|
|
1614
|
+
const generation = (state.generations.get(nodeId) ?? 0) + 1;
|
|
1615
|
+
const attemptId = `${runId}/${nodeId}/attempt-${generation}`;
|
|
1616
|
+
this.appendRequired({
|
|
1617
|
+
kind: "call_attempt",
|
|
1618
|
+
schemaVersion: JOURNAL_SCHEMA_VERSION,
|
|
1619
|
+
runId,
|
|
1620
|
+
nodeId,
|
|
1621
|
+
attemptId,
|
|
1622
|
+
generation,
|
|
1623
|
+
owner: { extension: "pi-workflows", runId, nodeId, attemptId },
|
|
1624
|
+
timestamp: Date.now(),
|
|
1625
|
+
});
|
|
1626
|
+
state.generations.set(nodeId, generation);
|
|
1627
|
+
run.attemptTracking = true;
|
|
1628
|
+
run.attempts[nodeId] = generation;
|
|
1629
|
+
run.attemptIds[nodeId] = attemptId;
|
|
1630
|
+
run.callStatus[nodeId] = "running";
|
|
1631
|
+
}
|
|
1522
1632
|
const attemptId = run.attemptIds[nodeId];
|
|
1523
1633
|
if (attemptId) callResult.attemptId = attemptId;
|
|
1524
1634
|
const event: JournalEvent = {
|
|
@@ -1578,7 +1688,7 @@ export class WorkflowEngine {
|
|
|
1578
1688
|
state.run.updatedAt = Date.now();
|
|
1579
1689
|
}
|
|
1580
1690
|
|
|
1581
|
-
private commitCallTerminal(runId: string, nodeId: string, result: CallResult): void {
|
|
1691
|
+
private commitCallTerminal(runId: string, nodeId: string, result: CallResult, required = false): void {
|
|
1582
1692
|
const state = this.runs.get(runId);
|
|
1583
1693
|
if (!state) return;
|
|
1584
1694
|
const run = state.run;
|
|
@@ -1593,7 +1703,8 @@ export class WorkflowEngine {
|
|
|
1593
1703
|
...(attemptId ? { owner: { extension: "pi-workflows", runId, nodeId, attemptId } } : {}),
|
|
1594
1704
|
timestamp: Date.now(),
|
|
1595
1705
|
};
|
|
1596
|
-
|
|
1706
|
+
if (required) this.appendRequired(event);
|
|
1707
|
+
else this.persist(runId, `call-terminal:${nodeId}`, () => this.write(event));
|
|
1597
1708
|
run.callResults[nodeId] = result;
|
|
1598
1709
|
run.callTiers[nodeId] = result.tier === undefined ? {} : { tier: result.tier };
|
|
1599
1710
|
run.callStatus[nodeId] = result.status;
|
|
@@ -1809,15 +1920,24 @@ export class WorkflowEngine {
|
|
|
1809
1920
|
* whose appends keep failing is marked blocked (reported once, not spammed).
|
|
1810
1921
|
*/
|
|
1811
1922
|
private scheduleJournalRetry(runId: string, key: string, mutation: () => void): void {
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
const
|
|
1923
|
+
// Every accepted fact has its own slot, including later retry generations
|
|
1924
|
+
// for the same call. Coalescing by node key would silently drop that fact.
|
|
1925
|
+
const retryKey = `${runId}\u0000${key}\u0000${++this.journalMutationSequence}`;
|
|
1926
|
+
const predecessors = [...this.journalRetries.entries()]
|
|
1927
|
+
.filter(([id]) => id.startsWith(`${runId}\u0000`))
|
|
1928
|
+
.map(([, pending]) => pending.done);
|
|
1929
|
+
let resolve!: () => void;
|
|
1930
|
+
const done = new Promise<void>((settled) => {
|
|
1931
|
+
resolve = settled;
|
|
1932
|
+
});
|
|
1933
|
+
const retry = { attempt: 0, timer: undefined as ReturnType<typeof setTimeout> | undefined, done, resolve };
|
|
1815
1934
|
const attempt = (): void => {
|
|
1816
1935
|
const current = this.journalRetries.get(retryKey);
|
|
1817
1936
|
if (!current) return;
|
|
1818
1937
|
current.timer = undefined;
|
|
1819
1938
|
if (this.lifecyclePaused || !this.runs.has(runId)) {
|
|
1820
1939
|
this.journalRetries.delete(retryKey);
|
|
1940
|
+
current.resolve();
|
|
1821
1941
|
return;
|
|
1822
1942
|
}
|
|
1823
1943
|
const previousKey = this.journalMutationKey;
|
|
@@ -1825,9 +1945,12 @@ export class WorkflowEngine {
|
|
|
1825
1945
|
try {
|
|
1826
1946
|
mutation();
|
|
1827
1947
|
this.journalRetries.delete(retryKey);
|
|
1948
|
+
current.resolve();
|
|
1828
1949
|
} catch (error: unknown) {
|
|
1829
1950
|
if (!(error instanceof JournalAppendError)) {
|
|
1830
1951
|
this.journalRetries.delete(retryKey);
|
|
1952
|
+
current.resolve();
|
|
1953
|
+
this.journalBlockedRuns.add(runId);
|
|
1831
1954
|
console.warn(
|
|
1832
1955
|
`[pi-workflows] journal retry abandoned: ${error instanceof Error ? error.message : String(error)}`,
|
|
1833
1956
|
);
|
|
@@ -1836,6 +1959,7 @@ export class WorkflowEngine {
|
|
|
1836
1959
|
current.attempt += 1;
|
|
1837
1960
|
if (current.attempt >= JOURNAL_RETRY_MAX_ATTEMPTS) {
|
|
1838
1961
|
this.journalRetries.delete(retryKey);
|
|
1962
|
+
current.resolve();
|
|
1839
1963
|
this.journalBlockedRuns.add(runId);
|
|
1840
1964
|
console.warn(`[pi-workflows] journal retry exhausted for ${runId}: ${error.message}`);
|
|
1841
1965
|
return;
|
|
@@ -1850,7 +1974,19 @@ export class WorkflowEngine {
|
|
|
1850
1974
|
}
|
|
1851
1975
|
};
|
|
1852
1976
|
this.journalRetries.set(retryKey, retry);
|
|
1853
|
-
|
|
1977
|
+
void Promise.all(predecessors).then(attempt);
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
private async drainJournal(runId: string): Promise<void> {
|
|
1981
|
+
let pending = [...this.journalRetries.entries()].filter(([id]) => id.startsWith(`${runId}\u0000`));
|
|
1982
|
+
while (pending.length > 0) {
|
|
1983
|
+
await Promise.all(pending.map(([, retry]) => retry.done));
|
|
1984
|
+
pending = [...this.journalRetries.entries()].filter(([id]) => id.startsWith(`${runId}\u0000`));
|
|
1985
|
+
}
|
|
1986
|
+
if (this.journalBlockedRuns.has(runId))
|
|
1987
|
+
throw new WorkflowError("Workflow journal has not settled", WorkflowErrorCode.PERSISTENCE_ERROR, {
|
|
1988
|
+
recoverable: true,
|
|
1989
|
+
});
|
|
1854
1990
|
}
|
|
1855
1991
|
|
|
1856
1992
|
private appendRequired(event: JournalEvent): void {
|
|
@@ -1886,6 +2022,10 @@ export class WorkflowEngine {
|
|
|
1886
2022
|
* a transient failure, schedules a retry of the exact same append.
|
|
1887
2023
|
*/
|
|
1888
2024
|
private persist(runId: string, key: string, mutation: () => void): void {
|
|
2025
|
+
if ([...this.journalRetries.keys()].some((id) => id.startsWith(`${runId}\u0000`))) {
|
|
2026
|
+
this.scheduleJournalRetry(runId, key, mutation);
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
1889
2029
|
const previousKey = this.journalMutationKey;
|
|
1890
2030
|
this.journalMutationKey = `${runId}:${key}`;
|
|
1891
2031
|
try {
|
|
@@ -1940,8 +2080,9 @@ export class WorkflowEngine {
|
|
|
1940
2080
|
this.providerResumeTimers.set(runId, timer);
|
|
1941
2081
|
}
|
|
1942
2082
|
private clearJournalRetries(): void {
|
|
1943
|
-
for (const { timer } of this.journalRetries.values()) {
|
|
2083
|
+
for (const { timer, resolve } of this.journalRetries.values()) {
|
|
1944
2084
|
if (timer) clearTimeout(timer);
|
|
2085
|
+
resolve();
|
|
1945
2086
|
}
|
|
1946
2087
|
this.journalRetries.clear();
|
|
1947
2088
|
this.journalBlockedRuns.clear();
|
|
@@ -1975,6 +2116,7 @@ export class WorkflowEngine {
|
|
|
1975
2116
|
}),
|
|
1976
2117
|
);
|
|
1977
2118
|
run.status = status;
|
|
2119
|
+
if (status === "running") run.error = undefined;
|
|
1978
2120
|
if (error !== undefined) run.error = error;
|
|
1979
2121
|
if (runFinalResult !== undefined) run.finalResult = runFinalResult;
|
|
1980
2122
|
if (terminalIntent !== undefined) run.terminalIntent = terminalIntent;
|
|
@@ -2079,7 +2221,7 @@ export class WorkflowEngine {
|
|
|
2079
2221
|
return {
|
|
2080
2222
|
index: Number(nodeId),
|
|
2081
2223
|
status,
|
|
2082
|
-
...(result?.result !== undefined ? { resultPreview:
|
|
2224
|
+
...(result?.result !== undefined ? { resultPreview: renderWorkflowValue(result.result).slice(0, 300) } : {}),
|
|
2083
2225
|
...(result?.error ? { error: result.error.slice(0, 300) } : {}),
|
|
2084
2226
|
...(result?.tokenCount !== undefined ? { tokenCount: result.tokenCount } : {}),
|
|
2085
2227
|
...(run.agentIds[nodeId] ? { agentId: run.agentIds[nodeId] } : {}),
|
package/src/errors.ts
CHANGED
|
@@ -67,6 +67,25 @@ export class WorkflowError extends Error {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// Transported VM throws must not acquire provider/abort classification merely
|
|
71
|
+
// because IPC reconstructed them in the host Error realm. Keep provenance and
|
|
72
|
+
// helper-computed text outside user data (including non-Error object throws).
|
|
73
|
+
const scriptThrows = new WeakMap<object, string>();
|
|
74
|
+
|
|
75
|
+
export function markScriptThrow(value: unknown, text: string): void {
|
|
76
|
+
if ((typeof value === "object" && value !== null) || typeof value === "function") scriptThrows.set(value, text);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function scriptThrowText(value: unknown): string | undefined {
|
|
80
|
+
return (typeof value === "object" && value !== null) || typeof value === "function"
|
|
81
|
+
? scriptThrows.get(value)
|
|
82
|
+
: undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function describeWorkflowError(value: unknown): string {
|
|
86
|
+
return scriptThrowText(value) ?? (value instanceof Error ? value.message : String(value));
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
/** Narrow an unknown failure to WorkflowError. */
|
|
71
90
|
export function isWorkflowError(error: unknown): error is WorkflowError {
|
|
72
91
|
return error instanceof WorkflowError;
|
|
@@ -102,6 +121,13 @@ function providerResetHint(message: string): string | undefined {
|
|
|
102
121
|
*/
|
|
103
122
|
export function wrapError(error: unknown, context?: { agentLabel?: string }): WorkflowError {
|
|
104
123
|
if (isWorkflowError(error)) return error;
|
|
124
|
+
const scriptText = scriptThrowText(error);
|
|
125
|
+
if (scriptText !== undefined)
|
|
126
|
+
return new WorkflowError(scriptText, WorkflowErrorCode.AGENT_EXECUTION_ERROR, {
|
|
127
|
+
recoverable: true,
|
|
128
|
+
agentLabel: context?.agentLabel,
|
|
129
|
+
details: error,
|
|
130
|
+
});
|
|
105
131
|
|
|
106
132
|
if (isAbortError(error)) {
|
|
107
133
|
return new WorkflowError(
|