@sema-agent/core 7.9.2 → 7.11.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 +102 -0
- package/dist/agents/child-model-seat.d.ts +71 -14
- package/dist/agents/child-model-seat.js +23 -7
- package/dist/agents/subagent.d.ts +2 -2
- package/dist/agents/subagent.js +8 -5
- package/dist/agents/teacher.js +2 -2
- package/dist/core/ask-unresolvable-notice.d.ts +52 -0
- package/dist/core/ask-unresolvable-notice.js +25 -0
- package/dist/core/auto-mode-defaults.d.ts +15 -3
- package/dist/core/auto-mode-defaults.js +1 -0
- package/dist/core/auto-mode.d.ts +86 -23
- package/dist/core/auto-mode.js +43 -12
- package/dist/core/checkpoint-store.d.ts +14 -0
- package/dist/core/checkpoint-store.js +2 -1
- package/dist/core/engine-notice.d.ts +28 -7
- package/dist/core/gate-fold.js +1 -0
- package/dist/core/gate-lanes.d.ts +6 -1
- package/dist/core/gate-lanes.js +60 -18
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +4 -0
- package/dist/core/hooks.d.ts +37 -1
- package/dist/core/hooks.js +2 -0
- package/dist/core/permission-rule-model.d.ts +53 -16
- package/dist/core/permission-rule-model.js +59 -21
- package/dist/core/permission-rules.d.ts +6 -4
- package/dist/core/permission-rules.js +14 -14
- package/dist/core/persisted-rule-arms.js +4 -3
- package/dist/core/read-only-shell-table.d.ts +87 -0
- package/dist/core/read-only-shell-table.js +485 -0
- package/dist/core/read-only-shell.d.ts +42 -0
- package/dist/core/read-only-shell.js +316 -0
- package/dist/core/roles.d.ts +3 -2
- package/dist/core/runner/contracts.d.ts +26 -2
- package/dist/core/runner/denial-limit-arms.d.ts +14 -3
- package/dist/core/runner/denial-limit-arms.js +15 -5
- package/dist/core/runner/gate-exit.d.ts +5 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +7 -1
- package/dist/core/runner/permission-rule-lanes.js +9 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.js +35 -14
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -2
- package/dist/core/runner/prepare-gate-stations.js +12 -0
- package/dist/core/runner/prepare-policy-chain.js +7 -6
- package/dist/core/runner/prepare-task.js +1 -1
- package/dist/core/runner/prepare-turn-wiring.js +1 -1
- package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -1
- package/dist/core/runner/prepare-wiring-manifest.js +8 -1
- package/dist/core/runner/runtask.d.ts +33 -32
- package/dist/core/runner/runtask.js +63 -33
- package/dist/core/runner-deps.d.ts +9 -2
- package/dist/core/shell-lexer.d.ts +18 -0
- package/dist/core/shell-lexer.js +17 -10
- package/dist/core/shell-wrapper-table.js +8 -5
- package/dist/core/swappable-deps.d.ts +90 -0
- package/dist/core/swappable-deps.js +55 -0
- package/dist/core/tool-policy.d.ts +30 -1
- package/dist/core/tool-policy.js +6 -2
- package/dist/core/tools.d.ts +28 -7
- package/dist/core/tools.js +44 -4
- package/dist/core/trace.d.ts +15 -0
- package/dist/core/wiring-manifest.d.ts +15 -1
- package/dist/core/wiring-manifest.js +10 -2
- package/dist/core/workflow-journal-store.d.ts +21 -2
- package/dist/core/workflow-journal-store.js +1 -1
- package/dist/engine/execution-env/node-execution-env.d.ts +2 -0
- package/dist/engine/execution-env/node-execution-env.js +2 -1
- package/dist/engine/harness/agent-harness.d.ts +3 -1
- package/dist/engine/harness/agent-harness.js +1 -1
- package/dist/engine/harness/types.d.ts +15 -2
- package/dist/index.d.ts +8 -4
- package/dist/index.js +7 -3
- package/dist/orchestration/run-workflow-tool.d.ts +22 -2
- package/dist/orchestration/run-workflow-tool.js +14 -1
- package/dist/orchestration/workflow-governance.d.ts +3 -2
- package/dist/orchestration/workflow-observe.d.ts +1 -1
- package/dist/orchestration/workflow-observe.js +2 -0
- package/dist/orchestration/workflow-primitives.d.ts +4 -1
- package/dist/orchestration/workflow-primitives.js +1 -6
- package/dist/orchestration/workflow-types.d.ts +37 -2
- package/dist/orchestration/workflow-types.js +16 -0
- package/dist/orchestration/workflow.d.ts +53 -6
- package/dist/orchestration/workflow.js +319 -51
- package/dist/prompt-assembly/turn-snapshot.d.ts +4 -2
- package/dist/stores/file/workflow-journal-store.js +10 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +85 -5
|
@@ -7,18 +7,19 @@ import { TERMINAL_CAUSE_IS_REPLAYABLE, isTerminalCauseKind } from "../core/termi
|
|
|
7
7
|
import { terminalProjection } from "../core/runner/terminal-projection.js";
|
|
8
8
|
import { builtinAgentDefinitions } from "../agents/builtin-agents.js";
|
|
9
9
|
import { GENERAL_PURPOSE_SUBAGENT_TYPE, markerFragment } from "../agents/subagent.js";
|
|
10
|
+
import { deploymentSubagentSystemPrompt, runnerRoleMap } from "../agents/child-model-seat.js";
|
|
10
11
|
import { combinePolicies, createAllowDenyPolicy } from "../core/tool-policy.js";
|
|
11
12
|
import { createSafeNotifier } from "../core/safe-notify.js";
|
|
12
13
|
import { callKeyOrdinal, oversizeJournalResult, journalOversizeTombstone, JOURNAL_OVERSIZE_ERROR_CODE, MAX_JOURNAL_RESULT_BYTES } from "../core/workflow-journal-store.js";
|
|
13
14
|
import { isWorkflowRunActive, closeWorkflowChannel, markWorkflowActive, publishWorkflowEvent } from "./workflow-observe.js";
|
|
14
|
-
import { isDurablePause
|
|
15
|
+
import { isDurablePause } from "../agents/suspend-guard.js";
|
|
15
16
|
import { RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
|
|
16
17
|
import { boundInputHashOf } from "../core/canonical-json.js";
|
|
17
18
|
import { boundedRedactedSummary } from "../core/untrusted-egress.js";
|
|
18
19
|
import { delimitUntrusted } from "../core/untrusted-text.js";
|
|
19
20
|
import { OUTPUT_TOOL_NAME } from "../core/runner/synthetic-tools.js";
|
|
20
21
|
import { compileOutputSchema } from "../core/runner/strict-output-schema.js";
|
|
21
|
-
import { WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentBlockedError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowMaxAgentsError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } from "./workflow-types.js";
|
|
22
|
+
import { WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentBlockedError, WorkflowAgentParkedError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowMaxAgentsError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } from "./workflow-types.js";
|
|
22
23
|
import { WorkflowScriptError } from "./workflow-meta.js";
|
|
23
24
|
export * from "./workflow-types.js";
|
|
24
25
|
const MAX_TRANSCRIPT_CHARS = 4000;
|
|
@@ -132,6 +133,16 @@ function withWorkflowChildPersona(spec, schema) {
|
|
|
132
133
|
const note = schema ? WORKFLOW_SUBAGENT_APPEND_SCHEMA : WORKFLOW_SUBAGENT_APPEND;
|
|
133
134
|
return { ...spec, appendSystemPrompt: spec.appendSystemPrompt ? `${spec.appendSystemPrompt}\n\n${note}` : note };
|
|
134
135
|
}
|
|
136
|
+
function withWorkflowChildSeats(typed, inheritedModelSnap, inheritedThinkingSnap, roles) {
|
|
137
|
+
const statedPersona = deploymentSubagentSystemPrompt(roles);
|
|
138
|
+
return {
|
|
139
|
+
...typed,
|
|
140
|
+
modelRole: typed.modelRole ?? "subagent",
|
|
141
|
+
...(typed.model === undefined && inheritedModelSnap !== undefined ? { model: inheritedModelSnap } : {}),
|
|
142
|
+
...(typed.thinking === undefined && inheritedThinkingSnap !== undefined ? { thinking: inheritedThinkingSnap } : {}),
|
|
143
|
+
...(typed.systemPrompt === undefined && statedPersona !== undefined ? { systemPrompt: statedPersona } : {}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
135
146
|
export function workflowAgentCallKey(ordinal, spec, opts) {
|
|
136
147
|
const identity = {
|
|
137
148
|
...spec,
|
|
@@ -206,10 +217,9 @@ export class WorkflowJournalIncompatibleError extends Error {
|
|
|
206
217
|
fromRunId;
|
|
207
218
|
ordinal;
|
|
208
219
|
code = "workflow.journal_incompatible";
|
|
209
|
-
constructor(fromRunId, ordinal) {
|
|
210
|
-
super(`startWorkflow: resume from "${fromRunId}" was REFUSED — journal entry #${ordinal} carries no terminal cause ` +
|
|
211
|
-
"
|
|
212
|
-
"start a fresh run instead of resuming this journal.");
|
|
220
|
+
constructor(fromRunId, ordinal, reason) {
|
|
221
|
+
super(`startWorkflow: resume from "${fromRunId}" was REFUSED — ${reason ?? `journal entry #${ordinal} carries no terminal cause (a journal written before TaskResult.terminal, or a malformed entry)`}. ` +
|
|
222
|
+
"Completed work must not run again; start a fresh run instead of resuming this journal.");
|
|
213
223
|
this.fromRunId = fromRunId;
|
|
214
224
|
this.ordinal = ordinal;
|
|
215
225
|
this.name = "WorkflowJournalIncompatibleError";
|
|
@@ -448,6 +458,12 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
448
458
|
if (opts.resumeFromRunId !== undefined && opts.journalStore === undefined) {
|
|
449
459
|
throw new Error("runWorkflow: resumeFromRunId requires a journalStore to load the prior run's journal");
|
|
450
460
|
}
|
|
461
|
+
if (opts.defaultDurableApproval !== undefined && opts.store === undefined) {
|
|
462
|
+
throw new Error("runWorkflow: defaultDurableApproval requires a WorkflowRunStore — a workflow child's park is recorded on its wa* row, and without a store there is no durable row for a host to route the parked approval by");
|
|
463
|
+
}
|
|
464
|
+
if (opts.parkedResume !== undefined && opts.resumeFromRunId === undefined) {
|
|
465
|
+
throw new Error("runWorkflow: parkedResume drives a PARKED ordinal of a prior run and requires resumeFromRunId");
|
|
466
|
+
}
|
|
451
467
|
const now = opts.now ?? (() => Date.now());
|
|
452
468
|
const scope = opts.scope ?? "default";
|
|
453
469
|
const runId = opts.runId ?? randomUUID();
|
|
@@ -769,6 +785,60 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
769
785
|
let resumeClaim;
|
|
770
786
|
const replayByOrdinal = [];
|
|
771
787
|
let diverged = false;
|
|
788
|
+
let parkedLeg;
|
|
789
|
+
const parkedError = () => {
|
|
790
|
+
const p = parkedLeg;
|
|
791
|
+
return new WorkflowAgentParkedError(p.label, p.paused.terminal.gate.kind, p.paused.terminal.checkpointId);
|
|
792
|
+
};
|
|
793
|
+
const parkedResumeFor = (token) => opts.parkedResume?.find((d) => d.token === token);
|
|
794
|
+
const parkedCallChangedError = (ordinal) => Object.assign(new Error(`workflow resume: ordinal ${ordinal} is PARKED on an approval checkpoint, but the call at that ordinal changed (its call key differs from the journaled one) — ` +
|
|
795
|
+
"the script or its args were edited at a parked call, and a fresh spawn there would run beside the pinned child. Restore the call and resume again, or decide the checkpoint " +
|
|
796
|
+
"through the deployment's approval channel and start a fresh run."), { code: "workflow.parked_call_changed" });
|
|
797
|
+
let inFlightLegs = 0;
|
|
798
|
+
let legsIdleResolve;
|
|
799
|
+
const legStart = () => {
|
|
800
|
+
inFlightLegs += 1;
|
|
801
|
+
};
|
|
802
|
+
const legEnd = () => {
|
|
803
|
+
inFlightLegs -= 1;
|
|
804
|
+
if (inFlightLegs <= 0) {
|
|
805
|
+
inFlightLegs = 0;
|
|
806
|
+
const r = legsIdleResolve;
|
|
807
|
+
legsIdleResolve = undefined;
|
|
808
|
+
r?.();
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
const awaitLegsIdle = async (signal) => {
|
|
812
|
+
while (inFlightLegs > 0 && signal?.aborted !== true) {
|
|
813
|
+
await new Promise((resolve) => {
|
|
814
|
+
legsIdleResolve = resolve;
|
|
815
|
+
signal?.addEventListener("abort", () => resolve(), { once: true });
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
const writtenOrdinals = new Set();
|
|
820
|
+
const carryUnresolvedParks = async () => {
|
|
821
|
+
if (opts.resumeFromRunId === undefined || !journalStore)
|
|
822
|
+
return;
|
|
823
|
+
for (const e of replayByOrdinal) {
|
|
824
|
+
if (e?.parked === undefined)
|
|
825
|
+
continue;
|
|
826
|
+
const ord = callKeyOrdinal(e.callKey);
|
|
827
|
+
if (writtenOrdinals.has(ord))
|
|
828
|
+
continue;
|
|
829
|
+
if (workflowSignal.aborted) {
|
|
830
|
+
emitLogLine(`resume-journal: the prior run's PARKED leg at ordinal ${ord} was NOT carried into this run's journal (the run was cancelled or timed out first) — resume from the prior run, not from this one.`);
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
const outcome = await finalizeWithin(timers, journalAppendParked(e.callKey, e.parked).then(() => "ok", (err) => ({ err })), "timeout");
|
|
834
|
+
if (outcome !== "ok") {
|
|
835
|
+
const why = outcome === "timeout" ? `the append did not settle within ${WORKFLOW_RESUME_CLAIM_FINALIZE_TIMEOUT_MS}ms` : outcome.err instanceof Error ? outcome.err.message : String(outcome.err);
|
|
836
|
+
emitLogLine(`resume-journal: the prior run's PARKED leg at ordinal ${ord} could not be carried into this run's journal (${why}) — resume from the prior run, not from this one.`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
const driveNeverStarted = (r) => r.terminal.kind === "failed" && (r.terminal.code === "resume.reopened_unstarted" || r.terminal.code === "resume.reopen_failed_unstarted");
|
|
841
|
+
const isPausedResult = (r) => isDurablePause(r.terminal);
|
|
772
842
|
let isolationAdvised = false;
|
|
773
843
|
const adviseIsolationIfConcurrent = (opts) => {
|
|
774
844
|
if (isolationAdvised || opts.isolation || sem.active <= 1)
|
|
@@ -783,6 +853,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
783
853
|
};
|
|
784
854
|
let journalTail = Promise.resolve();
|
|
785
855
|
const JOURNAL_DRAIN_MAX_MS = 5_000;
|
|
856
|
+
const durableApprovalInherit = (s) => s.durableApproval === undefined && opts.defaultDurableApproval !== undefined ? { durableApproval: { ...opts.defaultDurableApproval } } : {};
|
|
786
857
|
const journalDurableResult = (result) => {
|
|
787
858
|
const t = result.terminal;
|
|
788
859
|
if (t === undefined || t.kind !== "failed" || typeof t.message !== "string" || t.message === "")
|
|
@@ -821,8 +892,10 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
821
892
|
if (dbg)
|
|
822
893
|
console.error(`[sema:wf-journal] runId=${runId} callKey=${callKey} TOMBSTONE-ERROR ${err instanceof Error ? err.message : String(err)}`);
|
|
823
894
|
}
|
|
895
|
+
writtenOrdinals.add(callKeyOrdinal(callKey));
|
|
824
896
|
return;
|
|
825
897
|
}
|
|
898
|
+
writtenOrdinals.add(callKeyOrdinal(callKey));
|
|
826
899
|
const p = journalStore.append(runId, scope, { callKey, result: stored });
|
|
827
900
|
journalTail = journalTail.then(() => p).catch(() => undefined);
|
|
828
901
|
try {
|
|
@@ -836,6 +909,56 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
836
909
|
throw err;
|
|
837
910
|
}
|
|
838
911
|
};
|
|
912
|
+
const journalAppendParked = async (callKey, paused) => {
|
|
913
|
+
if (!journalStore)
|
|
914
|
+
return;
|
|
915
|
+
writtenOrdinals.add(callKeyOrdinal(callKey));
|
|
916
|
+
let lastErr;
|
|
917
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
918
|
+
const p = journalStore.append(runId, scope, { callKey, parked: paused });
|
|
919
|
+
journalTail = journalTail.then(() => p).catch(() => undefined);
|
|
920
|
+
try {
|
|
921
|
+
await p;
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
catch (err) {
|
|
925
|
+
lastErr = err;
|
|
926
|
+
if (attempt < 2)
|
|
927
|
+
await new Promise((r) => timers.setTimeout(r, 25 * (attempt + 1)));
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
throw lastErr;
|
|
931
|
+
};
|
|
932
|
+
const parkAgentLeg = async (rec, paused, activityTail, chargeLive) => {
|
|
933
|
+
const s = paused.stats;
|
|
934
|
+
accumulateStats(paused, chargeLive);
|
|
935
|
+
rec.status = "parked";
|
|
936
|
+
rec.parkedCheckpointToken = paused.terminal.token;
|
|
937
|
+
rec.taskStatus = terminalProjection(paused.terminal).status;
|
|
938
|
+
if (paused.sessionId)
|
|
939
|
+
rec.sessionId = paused.sessionId;
|
|
940
|
+
setLegRunId(rec, paused);
|
|
941
|
+
rec.endedAt = now();
|
|
942
|
+
rec.stats = { tokens: s.tokens ?? 0, turns: s.turns ?? 0, costMicroUsd: s.costMicroUsd };
|
|
943
|
+
if (s.toolCalls !== undefined)
|
|
944
|
+
rec.toolCalls = s.toolCalls;
|
|
945
|
+
if (activityTail.length > 0)
|
|
946
|
+
rec.activity = activityTail.slice();
|
|
947
|
+
foldLegApprovalReport(rec, paused);
|
|
948
|
+
emit({ type: "agent_end", runId, label: rec.label, phase: rec.phase, ...(rec.groupId !== undefined ? { groupId: rec.groupId } : {}), status: "parked", ...(paused.model !== undefined ? { modelResolved: paused.model } : {}), ts: rec.endedAt });
|
|
949
|
+
void persist("update");
|
|
950
|
+
bceLive.delete(waIdOf(rec.callKey));
|
|
951
|
+
if (parkedLeg === undefined)
|
|
952
|
+
parkedLeg = { label: rec.label, callKey: rec.callKey, paused };
|
|
953
|
+
try {
|
|
954
|
+
await journalAppendParked(rec.callKey, paused);
|
|
955
|
+
}
|
|
956
|
+
catch (err) {
|
|
957
|
+
emitRunLog(`resume-journal: the PARKED leg at ordinal ${callKeyOrdinal(rec.callKey)} ("${rec.label.slice(0, 80)}") could not be recorded (${err instanceof Error ? err.message : String(err)}) — ` +
|
|
958
|
+
"a resume of this run will NOT find the park at its ordinal; decide the checkpoint through the deployment's approval channel and start a fresh run instead of resuming this one.");
|
|
959
|
+
}
|
|
960
|
+
throw parkedError();
|
|
961
|
+
};
|
|
839
962
|
const createActivityCapture = (callKey, label, groupId, rec) => {
|
|
840
963
|
const tail = [];
|
|
841
964
|
let beatCount = 0;
|
|
@@ -1027,7 +1150,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1027
1150
|
const phase = agentOpts.phase ?? currentPhase?.title;
|
|
1028
1151
|
const phaseInstance = resolveAgentPhase(agentOpts.phase);
|
|
1029
1152
|
const groupId = currentGroup;
|
|
1030
|
-
const
|
|
1153
|
+
const typeDef = agentOpts.agentType !== undefined ? agentRegistry.find((d) => d.name === agentOpts.agentType) : undefined;
|
|
1154
|
+
const typeDefModel = typeDef?.model;
|
|
1031
1155
|
let inheritedModelSnap;
|
|
1032
1156
|
let inheritedModelError;
|
|
1033
1157
|
if (spec.model === undefined && typeDefModel === undefined) {
|
|
@@ -1038,12 +1162,17 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1038
1162
|
inheritedModelError = err instanceof Error ? err : new Error(String(err));
|
|
1039
1163
|
}
|
|
1040
1164
|
}
|
|
1041
|
-
const
|
|
1165
|
+
const inheritedThinkingSnap = spec.thinking === undefined ? opts.defaultThinking?.() : undefined;
|
|
1166
|
+
const specForIdentity = {
|
|
1167
|
+
...spec,
|
|
1168
|
+
...(inheritedModelSnap !== undefined ? { model: inheritedModelSnap } : {}),
|
|
1169
|
+
...(inheritedThinkingSnap !== undefined ? { thinking: inheritedThinkingSnap } : {}),
|
|
1170
|
+
};
|
|
1042
1171
|
const callKey = workflowAgentCallKey(run.agents.length, specForIdentity, agentOpts);
|
|
1043
1172
|
const prompt = boundedRedactedSummary(spec.systemPrompt ? `${spec.systemPrompt}\n\n${spec.objective}` : spec.objective, MAX_TRANSCRIPT_CHARS);
|
|
1044
1173
|
const specForLabel = spec.model === undefined && typeDefModel !== undefined ? { ...spec, model: typeDefModel } : specForIdentity;
|
|
1045
1174
|
const model = workflowModelLabel(specForLabel, modelCatalog);
|
|
1046
|
-
return { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, callKey, prompt, model };
|
|
1175
|
+
return { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, inheritedThinkingSnap, callKey, prompt, model };
|
|
1047
1176
|
};
|
|
1048
1177
|
const reviewSpawnBeforeLaunch = async (lane, label, callKey, runSpec, effectiveSignal) => {
|
|
1049
1178
|
const review = opts.autoModeReview;
|
|
@@ -1190,10 +1319,35 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1190
1319
|
},
|
|
1191
1320
|
async agent(spec, agentOpts = {}) {
|
|
1192
1321
|
const effectiveSignal = assertAgentSpawnAllowed("ctx.agent", spec, agentOpts);
|
|
1193
|
-
const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
|
|
1322
|
+
const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, inheritedThinkingSnap, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
|
|
1323
|
+
let drive;
|
|
1324
|
+
const cached = opts.resumeFromRunId !== undefined ? replayByOrdinal[run.agents.length] : undefined;
|
|
1325
|
+
if (cached?.parked !== undefined) {
|
|
1326
|
+
if (cached.callKey !== callKey)
|
|
1327
|
+
throw parkedCallChangedError(run.agents.length);
|
|
1328
|
+
{
|
|
1329
|
+
const decision = parkedResumeFor(cached.parked.terminal.token);
|
|
1330
|
+
if (decision === undefined) {
|
|
1331
|
+
if (finalized)
|
|
1332
|
+
return cached.parked;
|
|
1333
|
+
const at = now();
|
|
1334
|
+
const reparkRec = { label, callKey, ...(groupId !== undefined ? { groupId } : {}), phase, prompt, ...(cached.parked.model !== undefined ? { model: cached.parked.model } : {}), replayed: true, status: "running", queuedAt: at, startedAt: at };
|
|
1335
|
+
if (run.resume)
|
|
1336
|
+
run.resume.replayed += 1;
|
|
1337
|
+
run.agents.push(reparkRec);
|
|
1338
|
+
if (phaseInstance)
|
|
1339
|
+
agentPhaseOf.set(reparkRec, phaseInstance);
|
|
1340
|
+
emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(cached.parked.model !== undefined ? { model: cached.parked.model } : {}), replayed: true, ts: at });
|
|
1341
|
+
bceSpawn(callKey, label, agentOpts.agentType, true, cached.parked.sessionId || undefined, cached.parked.model);
|
|
1342
|
+
return await parkAgentLeg(reparkRec, cached.parked, [], false);
|
|
1343
|
+
}
|
|
1344
|
+
drive = { token: decision.token, outcome: decision.outcome, ...(decision.inheritedGate !== undefined ? { inheritedGate: decision.inheritedGate } : {}), sessionId: cached.parked.sessionId || undefined, paused: cached.parked };
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
if (parkedLeg !== undefined && drive === undefined)
|
|
1348
|
+
throw parkedError();
|
|
1194
1349
|
if (opts.resumeFromRunId !== undefined && !diverged) {
|
|
1195
|
-
|
|
1196
|
-
if (cached && cached.callKey === callKey && TERMINAL_CAUSE_IS_REPLAYABLE[cached.result.terminal.kind]) {
|
|
1350
|
+
if (drive === undefined && cached && cached.callKey === callKey && cached.result !== undefined && TERMINAL_CAUSE_IS_REPLAYABLE[cached.result.terminal.kind]) {
|
|
1197
1351
|
if (finalized)
|
|
1198
1352
|
return cached.result;
|
|
1199
1353
|
const r = cached.result;
|
|
@@ -1237,18 +1391,20 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1237
1391
|
}
|
|
1238
1392
|
return r;
|
|
1239
1393
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
?
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1394
|
+
if (drive === undefined) {
|
|
1395
|
+
noteDivergence(run.agents.length, cached === undefined
|
|
1396
|
+
? run.resume?.journalEntries === 0
|
|
1397
|
+
? "the journal loaded no entries"
|
|
1398
|
+
: run.agents.length >= replayByOrdinal.length
|
|
1399
|
+
? "a new call past the journaled prefix — the prior run ended before this point (the intended extend-the-script resume shape)"
|
|
1400
|
+
: "no journal entry at this ordinal (never recorded, e.g. an oversize result under a pre-tombstone engine, or dropped)"
|
|
1401
|
+
: cached.callKey !== callKey
|
|
1402
|
+
? "the call key changed — the script or its args differ here"
|
|
1403
|
+
: cached.result?.terminal?.kind === "failed" && cached.result.terminal.code === JOURNAL_OVERSIZE_ERROR_CODE
|
|
1404
|
+
? "the prior result exceeded the journal size cap and was never cached"
|
|
1405
|
+
: `the journaled result was ${cached.result?.terminal?.kind ?? "(no terminal record)"}, not completed`);
|
|
1406
|
+
diverged = true;
|
|
1407
|
+
}
|
|
1252
1408
|
}
|
|
1253
1409
|
if (budgetTotal !== null && spent() >= budgetTotal) {
|
|
1254
1410
|
throw new WorkflowBudgetExceededError(spent(), budgetTotal);
|
|
@@ -1260,6 +1416,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1260
1416
|
agentPhaseOf.set(rec, phaseInstance);
|
|
1261
1417
|
emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(model !== undefined ? { model } : {}), queuedAt: rec.queuedAt, ts: rec.queuedAt });
|
|
1262
1418
|
void persist("update");
|
|
1419
|
+
legStart();
|
|
1263
1420
|
const release = await sem.acquire();
|
|
1264
1421
|
adviseIsolationIfConcurrent(agentOpts);
|
|
1265
1422
|
let result;
|
|
@@ -1268,21 +1425,24 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1268
1425
|
throw new Error("workflow aborted");
|
|
1269
1426
|
if (finalized)
|
|
1270
1427
|
throw new Error("workflow run already finalized — ctx.agent cannot spawn after the run ended");
|
|
1428
|
+
if (parkedLeg !== undefined && drive === undefined)
|
|
1429
|
+
throw parkedError();
|
|
1271
1430
|
if (budgetTotal !== null && spent() >= budgetTotal) {
|
|
1272
1431
|
throw new WorkflowBudgetExceededError(spent(), budgetTotal);
|
|
1273
1432
|
}
|
|
1274
1433
|
rec.startedAt = now();
|
|
1275
|
-
const bornChildSessionId = resolveChildSessionIdAtSpawn(spec);
|
|
1434
|
+
const bornChildSessionId = drive !== undefined ? drive.sessionId : resolveChildSessionIdAtSpawn(spec);
|
|
1276
1435
|
bceSpawn(callKey, label, agentOpts.agentType, false, bornChildSessionId, model);
|
|
1277
1436
|
if (inheritedModelError !== undefined)
|
|
1278
1437
|
throw inheritedModelError;
|
|
1279
1438
|
const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
|
|
1280
|
-
const typedSpec = typedSpec0
|
|
1439
|
+
const typedSpec = withWorkflowChildSeats(typedSpec0, inheritedModelSnap, inheritedThinkingSnap, runnerRoleMap(runner));
|
|
1281
1440
|
const framedSpec = withWorkflowChildPersona(typedSpec, agentOpts.schema ?? typedSpec.outputSchema);
|
|
1282
1441
|
const authInherit = framedSpec.getApiKeyAndHeaders === undefined && opts.defaultGetApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: opts.defaultGetApiKeyAndHeaders } : {};
|
|
1442
|
+
const parkInherit = durableApprovalInherit(framedSpec);
|
|
1283
1443
|
const preReviewSpec = agentOpts.schema
|
|
1284
|
-
? { ...framedSpec, ...authInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
|
|
1285
|
-
: { ...framedSpec, ...authInherit, signal: effectiveSignal };
|
|
1444
|
+
? { ...framedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
|
|
1445
|
+
: { ...framedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal };
|
|
1286
1446
|
const runSpec = await reviewSpawnBeforeLaunch("ctx.agent", label, callKey, preReviewSpec, effectiveSignal);
|
|
1287
1447
|
const enrichedForward = opts.onForwardEvent !== undefined
|
|
1288
1448
|
? (e) => {
|
|
@@ -1319,7 +1479,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1319
1479
|
const onCallerAbort = () => attemptCtl.abort(new Error("workflow aborted"));
|
|
1320
1480
|
effectiveSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
1321
1481
|
armWatchdog();
|
|
1322
|
-
const attemptSessionId = attempts === 1 ? bornChildSessionId : resolveChildSessionIdAtSpawn(runSpec);
|
|
1482
|
+
const attemptSessionId = drive !== undefined ? drive.sessionId : attempts === 1 ? bornChildSessionId : resolveChildSessionIdAtSpawn(runSpec);
|
|
1323
1483
|
const attemptSpec = attemptSessionId !== undefined ? { ...runSpec, sessionId: attemptSessionId, signal: attemptCtl.signal } : { ...runSpec, signal: attemptCtl.signal };
|
|
1324
1484
|
if (attemptSessionId !== undefined && !finalized) {
|
|
1325
1485
|
rec.sessionId = attemptSessionId;
|
|
@@ -1373,10 +1533,15 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1373
1533
|
activeUsageBeats.add(activeBeat);
|
|
1374
1534
|
try {
|
|
1375
1535
|
attemptResult = await workflowDepthStore.run({ depth: depth + 1 }, async () => {
|
|
1376
|
-
if (typeof runner.runTaskStream !== "function") {
|
|
1536
|
+
if (drive === undefined && typeof runner.runTaskStream !== "function") {
|
|
1377
1537
|
return runner.runTask(attemptSpec, attemptInternals);
|
|
1378
1538
|
}
|
|
1379
|
-
const stream =
|
|
1539
|
+
const stream = drive === undefined
|
|
1540
|
+
? runner.runTaskStream(attemptSpec, undefined, attemptInternals)
|
|
1541
|
+
: await (() => {
|
|
1542
|
+
const { objective: _o, sessionId: _s, ...resumeConfig } = attemptSpec;
|
|
1543
|
+
return runner.resumeStream(drive.token, drive.outcome, resumeConfig, { ...attemptInternals, ...(drive.inheritedGate !== undefined ? { inheritedGate: drive.inheritedGate } : {}) });
|
|
1544
|
+
})();
|
|
1380
1545
|
for await (const ev of stream) {
|
|
1381
1546
|
if (ev.type === "text_delta" || ev.type === "reasoning_delta")
|
|
1382
1547
|
onStreamProgress();
|
|
@@ -1409,7 +1574,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1409
1574
|
}
|
|
1410
1575
|
throw new Error("workflow aborted");
|
|
1411
1576
|
}
|
|
1412
|
-
if (stallFired && attemptResult?.terminal.kind !== "completed") {
|
|
1577
|
+
if (drive === undefined && stallFired && attemptResult?.terminal.kind !== "completed") {
|
|
1413
1578
|
if (attemptResult !== undefined && !finalized)
|
|
1414
1579
|
accumulateStats(attemptResult, true);
|
|
1415
1580
|
if (attemptResult !== undefined && !finalized)
|
|
@@ -1438,7 +1603,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1438
1603
|
(result.stats.outputTokens ?? Number.POSITIVE_INFINITY) < 50 &&
|
|
1439
1604
|
lastStopReason === undefined &&
|
|
1440
1605
|
durationMs > stallMs * 0.5;
|
|
1441
|
-
if (degraded && attempts === 1 && !throttleRetried) {
|
|
1606
|
+
if (degraded && attempts === 1 && !throttleRetried && drive === undefined) {
|
|
1442
1607
|
throttleRetried = true;
|
|
1443
1608
|
lastAttemptReason = "throttled";
|
|
1444
1609
|
emitRunLog(`[${label}] throttled response (${(result.result ?? "").trim().length} chars in ${Math.round(durationMs / 1000)}s) — sleeping ${Math.round(throttleBackoffMs / 1000)}s before retry`);
|
|
@@ -1474,6 +1639,10 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1474
1639
|
}
|
|
1475
1640
|
}
|
|
1476
1641
|
catch (err) {
|
|
1642
|
+
if (drive !== undefined && !finalized && !(err instanceof WorkflowAgentParkedError)) {
|
|
1643
|
+
emitRunLog(`[park] the parked leg "${label}" could not be driven — the resume was refused before the checkpoint was consumed (${err instanceof Error ? err.message : String(err)}); the park stands.`);
|
|
1644
|
+
return await parkAgentLeg(rec, drive.paused, activityTail, false);
|
|
1645
|
+
}
|
|
1477
1646
|
if (!finalized) {
|
|
1478
1647
|
rec.status = "failed";
|
|
1479
1648
|
rec.endedAt = now();
|
|
@@ -1520,7 +1689,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1520
1689
|
},
|
|
1521
1690
|
stats: { turns: 0, tokens: 0, costMicroUsd: 0 },
|
|
1522
1691
|
}, label).catch(() => undefined);
|
|
1523
|
-
if (!(err instanceof WorkflowBudgetExceededError && rec.startedAt === undefined))
|
|
1692
|
+
if (!((err instanceof WorkflowBudgetExceededError || err instanceof WorkflowAgentParkedError) && rec.startedAt === undefined))
|
|
1524
1693
|
await journaled;
|
|
1525
1694
|
bceTerminal(callKey, "failed", rec.output ?? (err instanceof Error ? err.message : String(err)), rec.sessionId, rec.stats);
|
|
1526
1695
|
}
|
|
@@ -1528,11 +1697,16 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1528
1697
|
}
|
|
1529
1698
|
finally {
|
|
1530
1699
|
release();
|
|
1700
|
+
legEnd();
|
|
1531
1701
|
}
|
|
1532
1702
|
if (finalized)
|
|
1533
1703
|
return result;
|
|
1534
|
-
if (
|
|
1535
|
-
|
|
1704
|
+
if (isPausedResult(result)) {
|
|
1705
|
+
return await parkAgentLeg(rec, result, activityTail, true);
|
|
1706
|
+
}
|
|
1707
|
+
if (drive !== undefined && driveNeverStarted(result)) {
|
|
1708
|
+
emitRunLog(`[park] the parked leg "${label}" was driven but the approved action never started (${result.terminal.kind === "failed" ? result.terminal.code : ""}); the park stands.`);
|
|
1709
|
+
return await parkAgentLeg(rec, drive.paused, activityTail, false);
|
|
1536
1710
|
}
|
|
1537
1711
|
await settleAgentResult(rec, result, activityTail, agentOpts, "awaited");
|
|
1538
1712
|
return result;
|
|
@@ -1542,9 +1716,31 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1542
1716
|
if (budgetTotal !== null && spent() >= budgetTotal) {
|
|
1543
1717
|
throw new WorkflowBudgetExceededError(spent(), budgetTotal);
|
|
1544
1718
|
}
|
|
1545
|
-
const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
|
|
1546
|
-
|
|
1547
|
-
|
|
1719
|
+
const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, inheritedThinkingSnap, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
|
|
1720
|
+
let drive;
|
|
1721
|
+
const parkedHere = opts.resumeFromRunId !== undefined ? replayByOrdinal[run.agents.length] : undefined;
|
|
1722
|
+
if (parkedHere?.parked !== undefined) {
|
|
1723
|
+
if (parkedHere.callKey !== callKey)
|
|
1724
|
+
throw parkedCallChangedError(run.agents.length);
|
|
1725
|
+
const decision = parkedResumeFor(parkedHere.parked.terminal.token);
|
|
1726
|
+
if (decision === undefined) {
|
|
1727
|
+
const at = now();
|
|
1728
|
+
const reparkRec = { label, callKey, ...(groupId !== undefined ? { groupId } : {}), phase, prompt, ...(parkedHere.parked.model !== undefined ? { model: parkedHere.parked.model } : {}), replayed: true, status: "running", queuedAt: at, startedAt: at };
|
|
1729
|
+
if (run.resume)
|
|
1730
|
+
run.resume.replayed += 1;
|
|
1731
|
+
run.agents.push(reparkRec);
|
|
1732
|
+
if (phaseInstance)
|
|
1733
|
+
agentPhaseOf.set(reparkRec, phaseInstance);
|
|
1734
|
+
emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(parkedHere.parked.model !== undefined ? { model: parkedHere.parked.model } : {}), replayed: true, ts: at });
|
|
1735
|
+
bceSpawn(callKey, label, agentOpts.agentType, true, parkedHere.parked.sessionId || undefined, parkedHere.parked.model);
|
|
1736
|
+
return await parkAgentLeg(reparkRec, parkedHere.parked, [], false);
|
|
1737
|
+
}
|
|
1738
|
+
drive = { token: decision.token, outcome: decision.outcome, ...(decision.inheritedGate !== undefined ? { inheritedGate: decision.inheritedGate } : {}), sessionId: parkedHere.parked.sessionId || undefined, paused: parkedHere.parked };
|
|
1739
|
+
}
|
|
1740
|
+
if (drive === undefined) {
|
|
1741
|
+
noteDivergence(run.agents.length, "ctx.agentStream results are never replayed");
|
|
1742
|
+
diverged = true;
|
|
1743
|
+
}
|
|
1548
1744
|
const rec = { label, callKey, ...(groupId !== undefined ? { groupId } : {}), phase, prompt, ...(model !== undefined ? { model } : {}), status: "running", queuedAt: now() };
|
|
1549
1745
|
const { tail: activityTail, onActivity } = createActivityCapture(callKey, label, groupId, rec);
|
|
1550
1746
|
run.agents.push(rec);
|
|
@@ -1552,6 +1748,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1552
1748
|
agentPhaseOf.set(rec, phaseInstance);
|
|
1553
1749
|
emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(model !== undefined ? { model } : {}), queuedAt: rec.queuedAt, ts: rec.queuedAt });
|
|
1554
1750
|
void persist("update");
|
|
1751
|
+
legStart();
|
|
1555
1752
|
const release = await sem.acquire();
|
|
1556
1753
|
adviseIsolationIfConcurrent(agentOpts);
|
|
1557
1754
|
let released = false;
|
|
@@ -1569,17 +1766,34 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1569
1766
|
void persist("update");
|
|
1570
1767
|
}
|
|
1571
1768
|
};
|
|
1769
|
+
const releaseLeg = () => {
|
|
1770
|
+
if (!released)
|
|
1771
|
+
legEnd();
|
|
1772
|
+
releaseOnce();
|
|
1773
|
+
};
|
|
1572
1774
|
if (effectiveSignal?.aborted || finalized) {
|
|
1573
1775
|
recordFailed();
|
|
1574
|
-
|
|
1776
|
+
releaseLeg();
|
|
1575
1777
|
throw new Error(finalized ? "workflow run already finalized — ctx.agentStream cannot spawn after the run ended" : "workflow aborted");
|
|
1576
1778
|
}
|
|
1779
|
+
if (parkedLeg !== undefined && drive === undefined) {
|
|
1780
|
+
const parked = parkedError();
|
|
1781
|
+
rec.errorMessage = boundedRedactedSummary(parked.message, MAX_TRANSCRIPT_CHARS);
|
|
1782
|
+
recordFailed();
|
|
1783
|
+
releaseLeg();
|
|
1784
|
+
throw parked;
|
|
1785
|
+
}
|
|
1577
1786
|
if (budgetTotal !== null && spent() >= budgetTotal) {
|
|
1787
|
+
if (drive !== undefined) {
|
|
1788
|
+
emitRunLog(`[park] the parked leg "${label}" could not be driven — the run's token ceiling was reached before its turn; the park stands.`);
|
|
1789
|
+
releaseLeg();
|
|
1790
|
+
return await parkAgentLeg(rec, drive.paused, [], false);
|
|
1791
|
+
}
|
|
1578
1792
|
const refusal = new WorkflowBudgetExceededError(spent(), budgetTotal);
|
|
1579
1793
|
rec.errorCode = refusal.code;
|
|
1580
1794
|
rec.errorMessage = boundedRedactedSummary(refusal.message, MAX_TRANSCRIPT_CHARS);
|
|
1581
1795
|
recordFailed();
|
|
1582
|
-
|
|
1796
|
+
releaseLeg();
|
|
1583
1797
|
void journalAppend(callKey, {
|
|
1584
1798
|
taskId: callKey,
|
|
1585
1799
|
sessionId: "",
|
|
@@ -1590,19 +1804,20 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1590
1804
|
throw refusal;
|
|
1591
1805
|
}
|
|
1592
1806
|
rec.startedAt = now();
|
|
1593
|
-
const childSessionId = resolveChildSessionIdAtSpawn(spec);
|
|
1807
|
+
const childSessionId = drive !== undefined ? drive.sessionId : resolveChildSessionIdAtSpawn(spec);
|
|
1594
1808
|
bceSpawn(callKey, label, agentOpts.agentType, false, childSessionId, model);
|
|
1595
1809
|
let stream;
|
|
1596
1810
|
try {
|
|
1597
1811
|
if (inheritedModelError !== undefined)
|
|
1598
1812
|
throw inheritedModelError;
|
|
1599
1813
|
const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
|
|
1600
|
-
const typedSpec = typedSpec0
|
|
1814
|
+
const typedSpec = withWorkflowChildSeats(typedSpec0, inheritedModelSnap, inheritedThinkingSnap, runnerRoleMap(runner));
|
|
1601
1815
|
const framedSpec = withWorkflowChildPersona(typedSpec, agentOpts.schema ?? typedSpec.outputSchema);
|
|
1602
1816
|
const authInherit = framedSpec.getApiKeyAndHeaders === undefined && opts.defaultGetApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: opts.defaultGetApiKeyAndHeaders } : {};
|
|
1817
|
+
const parkInherit = durableApprovalInherit(framedSpec);
|
|
1603
1818
|
const baseRunSpec = agentOpts.schema
|
|
1604
|
-
? { ...framedSpec, ...authInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
|
|
1605
|
-
: { ...framedSpec, ...authInherit, signal: effectiveSignal };
|
|
1819
|
+
? { ...framedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
|
|
1820
|
+
: { ...framedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal };
|
|
1606
1821
|
const preReviewSpec = childSessionId !== undefined ? { ...baseRunSpec, sessionId: childSessionId } : baseRunSpec;
|
|
1607
1822
|
const runSpec = await reviewSpawnBeforeLaunch("ctx.agentStream", label, callKey, preReviewSpec, effectiveSignal);
|
|
1608
1823
|
const enrichedForwardS = opts.onForwardEvent !== undefined
|
|
@@ -1634,9 +1849,20 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1634
1849
|
}
|
|
1635
1850
|
: {}),
|
|
1636
1851
|
};
|
|
1637
|
-
stream =
|
|
1852
|
+
stream = drive === undefined
|
|
1853
|
+
? workflowDepthStore.run({ depth: depth + 1 }, () => runner.runTaskStream(runSpec, undefined, runInternals))
|
|
1854
|
+
: await workflowDepthStore.run({ depth: depth + 1 }, () => {
|
|
1855
|
+
const d = drive;
|
|
1856
|
+
const { objective: _o, sessionId: _s, ...resumeConfig } = runSpec;
|
|
1857
|
+
return runner.resumeStream(d.token, d.outcome, resumeConfig, { ...runInternals, ...(d.inheritedGate !== undefined ? { inheritedGate: d.inheritedGate } : {}) });
|
|
1858
|
+
});
|
|
1638
1859
|
}
|
|
1639
1860
|
catch (err) {
|
|
1861
|
+
if (drive !== undefined && !finalized) {
|
|
1862
|
+
emitRunLog(`[park] the parked leg "${label}" could not be driven — the resume was refused before the checkpoint was consumed (${err instanceof Error ? err.message : String(err)}); the park stands.`);
|
|
1863
|
+
releaseLeg();
|
|
1864
|
+
return await parkAgentLeg(rec, drive.paused, [], false);
|
|
1865
|
+
}
|
|
1640
1866
|
if (!finalized && rec.errorMessage === undefined && err instanceof Error && err.message !== "")
|
|
1641
1867
|
rec.errorMessage = boundedRedactedSummary(err.message, MAX_TRANSCRIPT_CHARS);
|
|
1642
1868
|
if (!finalized && err instanceof WorkflowAgentBlockedError) {
|
|
@@ -1651,7 +1877,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1651
1877
|
}).catch(() => undefined);
|
|
1652
1878
|
}
|
|
1653
1879
|
recordFailed();
|
|
1654
|
-
|
|
1880
|
+
releaseLeg();
|
|
1655
1881
|
bceTerminal(callKey, "failed", err instanceof Error ? err.message : String(err), undefined, undefined);
|
|
1656
1882
|
throw err;
|
|
1657
1883
|
}
|
|
@@ -1691,7 +1917,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1691
1917
|
accumulateStats({ stats: partialSpend }, true);
|
|
1692
1918
|
}
|
|
1693
1919
|
recordFailed();
|
|
1694
|
-
|
|
1920
|
+
releaseLeg();
|
|
1695
1921
|
void journalAppend(callKey, {
|
|
1696
1922
|
taskId: callKey,
|
|
1697
1923
|
sessionId: "",
|
|
@@ -1704,11 +1930,15 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1704
1930
|
}
|
|
1705
1931
|
rollbackUsageBeat();
|
|
1706
1932
|
activeUsageBeats.delete(activeBeat);
|
|
1707
|
-
|
|
1933
|
+
releaseLeg();
|
|
1708
1934
|
if (finalized)
|
|
1709
1935
|
return result;
|
|
1710
|
-
if (
|
|
1711
|
-
|
|
1936
|
+
if (isPausedResult(result)) {
|
|
1937
|
+
return await parkAgentLeg(rec, result, activityTail, true);
|
|
1938
|
+
}
|
|
1939
|
+
if (drive !== undefined && driveNeverStarted(result)) {
|
|
1940
|
+
emitRunLog(`[park] the parked leg "${label}" was driven but the approved action never started (${result.terminal.kind === "failed" ? result.terminal.code : ""}); the park stands.`);
|
|
1941
|
+
return await parkAgentLeg(rec, drive.paused, activityTail, false);
|
|
1712
1942
|
}
|
|
1713
1943
|
await settleAgentResult(rec, result, activityTail, agentOpts, "best-effort");
|
|
1714
1944
|
return result;
|
|
@@ -1866,12 +2096,40 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1866
2096
|
}
|
|
1867
2097
|
const entries = await journalStore.load(opts.resumeFromRunId, scope);
|
|
1868
2098
|
for (const e of entries) {
|
|
2099
|
+
const parked = e.parked;
|
|
2100
|
+
if (parked !== undefined) {
|
|
2101
|
+
const pt = parked?.terminal;
|
|
2102
|
+
if (pt === null || typeof pt !== "object" || pt.kind !== "paused" || typeof pt.token !== "string" || pt.token.length === 0) {
|
|
2103
|
+
throw new WorkflowJournalIncompatibleError(clampRunIdText(opts.resumeFromRunId), callKeyOrdinal(e.callKey));
|
|
2104
|
+
}
|
|
2105
|
+
replayByOrdinal[callKeyOrdinal(e.callKey)] = e;
|
|
2106
|
+
continue;
|
|
2107
|
+
}
|
|
1869
2108
|
const t = e.result?.terminal;
|
|
1870
2109
|
if (t === null || typeof t !== "object" || !isTerminalCauseKind(t.kind)) {
|
|
1871
2110
|
throw new WorkflowJournalIncompatibleError(clampRunIdText(opts.resumeFromRunId), callKeyOrdinal(e.callKey));
|
|
1872
2111
|
}
|
|
1873
2112
|
replayByOrdinal[callKeyOrdinal(e.callKey)] = e;
|
|
1874
2113
|
}
|
|
2114
|
+
if (opts.store !== undefined) {
|
|
2115
|
+
let prior;
|
|
2116
|
+
try {
|
|
2117
|
+
prior = await opts.store.get(opts.resumeFromRunId);
|
|
2118
|
+
}
|
|
2119
|
+
catch (err) {
|
|
2120
|
+
throw new WorkflowJournalIncompatibleError(clampRunIdText(opts.resumeFromRunId), -1, `the prior run's record could not be read from the run store (${err instanceof Error ? err.message : String(err)}), so a lost park cannot be ruled out`);
|
|
2121
|
+
}
|
|
2122
|
+
if (prior !== null && prior.scope === scope) {
|
|
2123
|
+
for (const row of prior.agents) {
|
|
2124
|
+
if (row.status !== "parked")
|
|
2125
|
+
continue;
|
|
2126
|
+
const ord = callKeyOrdinal(row.callKey);
|
|
2127
|
+
if (replayByOrdinal[ord]?.parked === undefined) {
|
|
2128
|
+
throw new WorkflowJournalIncompatibleError(clampRunIdText(opts.resumeFromRunId), ord, `the prior run's agent #${ord} ("${row.label.slice(0, 80)}") is PARKED on an approval checkpoint but its journal entry is missing (the park was not recorded) — a resume would run that call live beside the pinned child`);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
1875
2133
|
run.resume = {
|
|
1876
2134
|
fromRunId: clampRunIdText(opts.resumeFromRunId),
|
|
1877
2135
|
journalEntries: entries.length,
|
|
@@ -1912,6 +2170,11 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1912
2170
|
else {
|
|
1913
2171
|
result = await bodyPromise;
|
|
1914
2172
|
}
|
|
2173
|
+
if (parkedLeg !== undefined) {
|
|
2174
|
+
await awaitLegsIdle(workflowSignal);
|
|
2175
|
+
throw parkedError();
|
|
2176
|
+
}
|
|
2177
|
+
await carryUnresolvedParks();
|
|
1915
2178
|
if (maxResultChars !== undefined) {
|
|
1916
2179
|
let size = 0;
|
|
1917
2180
|
try {
|
|
@@ -1944,6 +2207,9 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1944
2207
|
return { result, runId, run };
|
|
1945
2208
|
}
|
|
1946
2209
|
catch (err) {
|
|
2210
|
+
if (parkedLeg !== undefined)
|
|
2211
|
+
await awaitLegsIdle(workflowSignal);
|
|
2212
|
+
await carryUnresolvedParks();
|
|
1947
2213
|
finalized = true;
|
|
1948
2214
|
const unsettledOnFailure = settleActiveUsageBeats();
|
|
1949
2215
|
stampBudgetOvershoot(unsettledOnFailure);
|
|
@@ -1952,6 +2218,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1952
2218
|
run.status = "failed";
|
|
1953
2219
|
run.completionId ??= uuidv7();
|
|
1954
2220
|
run.endedAt = now();
|
|
2221
|
+
if (parkedLeg !== undefined && !(err instanceof WorkflowAgentParkedError))
|
|
2222
|
+
err = parkedError();
|
|
1955
2223
|
run.error = err instanceof Error ? err.message : String(err);
|
|
1956
2224
|
closeAbandonedAgents(run.endedAt);
|
|
1957
2225
|
restampPhaseFailures();
|