@osolmaz/pi-workflows 0.14.0 → 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 +78 -110
- package/dist/builtins/autoimplement.workflow.js +3 -1
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/change-verification.workflow.js +7 -2
- package/dist/builtins/change-verification.workflow.js.map +1 -1
- package/dist/builtins/metadata.d.ts +22 -0
- package/dist/builtins/metadata.js +10 -0
- package/dist/builtins/metadata.js.map +1 -0
- package/dist/builtins/monitor.workflow.js +4 -1
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/sanity-check.workflow.js +4 -1
- package/dist/builtins/sanity-check.workflow.js.map +1 -1
- package/dist/builtins/workspace-preparation.workflow.js +3 -1
- package/dist/builtins/workspace-preparation.workflow.js.map +1 -1
- package/dist/controllers/index.d.ts +0 -1
- package/dist/controllers/index.js +0 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +51 -0
- package/dist/controllers/sqlite.js +433 -57
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/controllers/store.d.ts +1 -0
- package/dist/extension/controller-command.d.ts +22 -0
- package/dist/extension/controller-command.js +32 -0
- package/dist/extension/controller-command.js.map +1 -0
- package/dist/extension/index.d.ts +2 -9
- package/dist/extension/index.js +876 -3447
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/message-card.d.ts +13 -0
- package/dist/extension/message-card.js +46 -0
- package/dist/extension/message-card.js.map +1 -0
- package/dist/extension/step-message.d.ts +5 -6
- package/dist/extension/step-message.js +41 -52
- package/dist/extension/step-message.js.map +1 -1
- package/dist/host/child-worker-supervisor.d.ts +50 -0
- package/dist/host/child-worker-supervisor.js +159 -0
- package/dist/host/child-worker-supervisor.js.map +1 -0
- package/dist/host/client.d.ts +48 -0
- package/dist/host/client.js +216 -0
- package/dist/host/client.js.map +1 -0
- package/dist/host/controller-worker-entry.d.ts +2 -0
- package/dist/host/controller-worker-entry.js +244 -0
- package/dist/host/controller-worker-entry.js.map +1 -0
- package/dist/host/controller-worker-protocol.d.ts +36 -0
- package/dist/host/controller-worker-protocol.js +49 -0
- package/dist/host/controller-worker-protocol.js.map +1 -0
- package/dist/host/controller-worker-supervisor.d.ts +21 -0
- package/dist/host/controller-worker-supervisor.js +54 -0
- package/dist/host/controller-worker-supervisor.js.map +1 -0
- package/dist/host/host-entry.d.ts +2 -0
- package/dist/host/host-entry.js +23 -0
- package/dist/host/host-entry.js.map +1 -0
- package/dist/host/processes.d.ts +17 -12
- package/dist/host/processes.js +154 -50
- package/dist/host/processes.js.map +1 -1
- package/dist/host/protocol.d.ts +38 -0
- package/dist/host/protocol.js +156 -0
- package/dist/host/protocol.js.map +1 -0
- package/dist/host/resolver-entry.d.ts +49 -0
- package/dist/host/resolver-entry.js +149 -0
- package/dist/host/resolver-entry.js.map +1 -0
- package/dist/host/rpc-executor.d.ts +14 -3
- package/dist/host/rpc-executor.js +63 -30
- package/dist/host/rpc-executor.js.map +1 -1
- package/dist/host/runner.d.ts +95 -29
- package/dist/host/runner.js +2311 -348
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +174 -0
- package/dist/host/state.js +689 -0
- package/dist/host/state.js.map +1 -0
- package/dist/host/worker-entry.d.ts +10 -0
- package/dist/host/worker-entry.js +446 -0
- package/dist/host/worker-entry.js.map +1 -0
- package/dist/host/worker-protocol.d.ts +31 -0
- package/dist/host/worker-protocol.js +122 -0
- package/dist/host/worker-protocol.js.map +1 -0
- package/dist/host/worker-store.d.ts +80 -0
- package/dist/host/worker-store.js +91 -0
- package/dist/host/worker-store.js.map +1 -0
- package/dist/host/worker-supervisor.d.ts +22 -0
- package/dist/host/worker-supervisor.js +54 -0
- package/dist/host/worker-supervisor.js.map +1 -0
- package/dist/render/canvas.d.ts +2 -1
- package/dist/render/canvas.js +40 -15
- package/dist/render/canvas.js.map +1 -1
- package/dist/render/graph-render.d.ts +7 -3
- package/dist/render/graph-render.js +125 -74
- package/dist/render/graph-render.js.map +1 -1
- package/dist/state/database.js +3 -1
- package/dist/state/database.js.map +1 -1
- package/dist/state/index.d.ts +1 -0
- package/dist/state/index.js +1 -0
- package/dist/state/index.js.map +1 -1
- package/dist/state/schema.d.ts +1 -1
- package/dist/state/schema.js +171 -3
- package/dist/state/schema.js.map +1 -1
- package/dist/state/viewer.d.ts +46 -0
- package/dist/state/viewer.js +249 -0
- package/dist/state/viewer.js.map +1 -0
- package/dist/viewer/cli.d.ts +1 -1
- package/dist/viewer/cli.js +47 -18
- package/dist/viewer/cli.js.map +1 -1
- package/dist/workflows/composition.js +25 -2
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.d.ts +3 -1
- package/dist/workflows/definition.js +25 -0
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +1 -0
- package/dist/workflows/engine.js +157 -42
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +3 -1
- package/dist/workflows/errors.js +4 -7
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +3 -0
- package/dist/workflows/human-decision.js +31 -0
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +1 -1
- package/dist/workflows/index.js +1 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/schema.js +17 -6
- package/dist/workflows/schema.js.map +1 -1
- package/dist/{viewer → workflows}/session-reducer.d.ts +3 -1
- package/dist/{viewer → workflows}/session-reducer.js +4 -0
- package/dist/workflows/session-reducer.js.map +1 -0
- package/dist/workflows/store.d.ts +87 -1
- package/dist/workflows/store.js +892 -93
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +0 -26
- package/dist/workflows/tool-input.js +2 -50
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +36 -4
- package/docs/2026-08-30-out-of-process-workflow-host-plan.md +353 -0
- package/docs/CONTROLLERS.md +11 -11
- package/docs/DEFERRED_TURNS.md +23 -2
- package/docs/SQLITE_STATE.md +41 -31
- package/docs/WORKFLOW_HOST.md +452 -0
- package/docs/development.md +46 -30
- package/docs/live-replay-protocol.md +129 -100
- package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +42 -0
- package/docs/plans/2026-08-28-piw-incremental-viewer-plan.md +293 -0
- package/docs/plans/piw-viewer-experience-implementation-plan.md +11 -1
- package/docs/plans/tui-viewer-implementation-plan.md +5 -0
- package/docs/tui-viewer.md +18 -3
- package/docs/workflows.md +162 -210
- package/examples/workflows/command-batch.workflow.ts +2 -0
- package/examples/workflows/shell.workflow.ts +2 -1
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/src/builtins/autoimplement.workflow.ts +3 -0
- package/src/builtins/change-verification.workflow.ts +7 -2
- package/src/builtins/metadata.ts +9 -0
- package/src/builtins/monitor.workflow.ts +4 -0
- package/src/builtins/sanity-check.workflow.ts +4 -0
- package/src/builtins/workspace-preparation.workflow.ts +3 -1
- package/src/controllers/index.ts +0 -5
- package/src/controllers/sqlite.ts +709 -76
- package/src/controllers/store.ts +1 -0
- package/src/extension/controller-command.ts +45 -0
- package/src/extension/index.ts +944 -4163
- package/src/extension/message-card.ts +61 -0
- package/src/extension/step-message.ts +58 -63
- package/src/host/child-worker-supervisor.ts +183 -0
- package/src/host/client.ts +293 -0
- package/src/host/controller-worker-entry.ts +311 -0
- package/src/host/controller-worker-protocol.ts +104 -0
- package/src/host/controller-worker-supervisor.ts +79 -0
- package/src/host/host-entry.ts +23 -0
- package/src/host/processes.ts +171 -54
- package/src/host/protocol.ts +196 -0
- package/src/host/resolver-entry.ts +241 -0
- package/src/host/rpc-executor.ts +76 -34
- package/src/host/runner.ts +2813 -422
- package/src/host/state.ts +1160 -0
- package/src/host/worker-entry.ts +533 -0
- package/src/host/worker-protocol.ts +165 -0
- package/src/host/worker-store.ts +229 -0
- package/src/host/worker-supervisor.ts +74 -0
- package/src/render/canvas.ts +44 -10
- package/src/render/graph-render.ts +145 -90
- package/src/state/database.ts +2 -1
- package/src/state/index.ts +14 -0
- package/src/state/schema.ts +171 -3
- package/src/state/viewer.ts +356 -0
- package/src/viewer/cli.ts +49 -17
- package/src/workflows/composition.ts +36 -2
- package/src/workflows/definition.ts +32 -0
- package/src/workflows/engine.ts +157 -54
- package/src/workflows/errors.ts +11 -2
- package/src/workflows/human-decision.ts +49 -0
- package/src/workflows/index.ts +2 -0
- package/src/workflows/schema.ts +19 -6
- package/src/{viewer → workflows}/session-reducer.ts +11 -1
- package/src/workflows/store.ts +1316 -108
- package/src/workflows/tool-input.ts +3 -69
- package/src/workflows/types.ts +32 -4
- package/dist/controllers/workflow-engine-scheduler.d.ts +0 -29
- package/dist/controllers/workflow-engine-scheduler.js +0 -175
- package/dist/controllers/workflow-engine-scheduler.js.map +0 -1
- package/dist/extension/controller-host.d.ts +0 -48
- package/dist/extension/controller-host.js +0 -110
- package/dist/extension/controller-host.js.map +0 -1
- package/dist/extension/deferred-turn-coordinator.d.ts +0 -32
- package/dist/extension/deferred-turn-coordinator.js +0 -143
- package/dist/extension/deferred-turn-coordinator.js.map +0 -1
- package/dist/extension/deferred-turn.d.ts +0 -44
- package/dist/extension/deferred-turn.js +0 -110
- package/dist/extension/deferred-turn.js.map +0 -1
- package/dist/extension/executor.d.ts +0 -86
- package/dist/extension/executor.js +0 -311
- package/dist/extension/executor.js.map +0 -1
- package/dist/extension/follow-up-coordinator.d.ts +0 -27
- package/dist/extension/follow-up-coordinator.js +0 -131
- package/dist/extension/follow-up-coordinator.js.map +0 -1
- package/dist/extension/recorder.d.ts +0 -84
- package/dist/extension/recorder.js +0 -528
- package/dist/extension/recorder.js.map +0 -1
- package/dist/extension/restart-policy.d.ts +0 -38
- package/dist/extension/restart-policy.js +0 -116
- package/dist/extension/restart-policy.js.map +0 -1
- package/dist/extension/session-events.d.ts +0 -133
- package/dist/extension/session-events.js +0 -61
- package/dist/extension/session-events.js.map +0 -1
- package/dist/extension/terminal-decision.d.ts +0 -51
- package/dist/extension/terminal-decision.js +0 -110
- package/dist/extension/terminal-decision.js.map +0 -1
- package/dist/viewer/session-reducer.js.map +0 -1
- package/src/controllers/workflow-engine-scheduler.ts +0 -246
- package/src/extension/controller-host.ts +0 -167
- package/src/extension/deferred-turn-coordinator.ts +0 -171
- package/src/extension/deferred-turn.ts +0 -166
- package/src/extension/executor.ts +0 -411
- package/src/extension/follow-up-coordinator.ts +0 -151
- package/src/extension/recorder.ts +0 -655
- package/src/extension/restart-policy.ts +0 -163
- package/src/extension/session-events.ts +0 -121
- package/src/extension/terminal-decision.ts +0 -172
package/dist/workflows/engine.js
CHANGED
|
@@ -5,7 +5,7 @@ import { StaleResourceError } from "../state/mutation.js";
|
|
|
5
5
|
import { compileWorkflowDefinition, compositionMetadata, isCompiledWorkflow, } from "./composition.js";
|
|
6
6
|
import { CancelledError, errorMessage, isAbortLikeError, isClaimLostError, isRunParkedError, RunParkedError, TimeoutError, WorkflowSourceChangedError, } from "./errors.js";
|
|
7
7
|
import { resolveNext, resolveNextForOutcome, validateWorkflowDefinition } from "./graph.js";
|
|
8
|
-
import {
|
|
8
|
+
import { createHumanDecisionRequest, validateHumanDecisionResponse } from "./human-decision.js";
|
|
9
9
|
import { extractJsonValue } from "./json.js";
|
|
10
10
|
import { parseWorkflowSettingsValue, resolveInitialWorkflowSettings, } from "./settings.js";
|
|
11
11
|
import { runShellAction, shellResultFromError } from "./shell.js";
|
|
@@ -173,6 +173,8 @@ export class WorkflowEngine {
|
|
|
173
173
|
await this.executeGraph(workflow, state, runId);
|
|
174
174
|
}
|
|
175
175
|
catch (error) {
|
|
176
|
+
if (isClaimLostError(error))
|
|
177
|
+
throw error;
|
|
176
178
|
if (isRunParkedError(error) || this.parked) {
|
|
177
179
|
return { runId, state };
|
|
178
180
|
}
|
|
@@ -208,24 +210,29 @@ export class WorkflowEngine {
|
|
|
208
210
|
state.currentAttemptId !== undefined &&
|
|
209
211
|
state.currentNodeStartedAt !== undefined &&
|
|
210
212
|
interruptedNode?.nodeType === "agent" &&
|
|
211
|
-
assistantMessageConfig(interruptedNode) !== undefined
|
|
212
|
-
|
|
213
|
+
(assistantMessageConfig(interruptedNode) !== undefined ||
|
|
214
|
+
options.resumeInteractionAttemptId === state.currentAttemptId)) {
|
|
215
|
+
const resumedSettings = await this.persistedSettingsBinding(state);
|
|
213
216
|
resumedAttempt = {
|
|
214
217
|
nodeId: state.currentNode,
|
|
215
218
|
attemptId: state.currentAttemptId,
|
|
216
219
|
startedAt: state.currentNodeStartedAt,
|
|
220
|
+
...(state.currentNodeDeadlineAt === undefined
|
|
221
|
+
? {}
|
|
222
|
+
: { deadlineAt: state.currentNodeDeadlineAt }),
|
|
217
223
|
...(resumedSettings !== undefined ? { settings: resumedSettings } : {}),
|
|
218
224
|
};
|
|
219
225
|
}
|
|
220
|
-
// A resumed run starts unpaused. Submitted and non-agent nodes
|
|
221
|
-
// stale in-flight markers and start a new attempt.
|
|
222
|
-
//
|
|
223
|
-
//
|
|
226
|
+
// A resumed run starts unpaused. Submitted and non-agent nodes normally
|
|
227
|
+
// discard stale in-flight markers and start a new attempt. A submitted
|
|
228
|
+
// interaction and an assistant-message node keep their exact attempt id
|
|
229
|
+
// so durable origin-session work is never detached from its contract.
|
|
224
230
|
delete state.paused;
|
|
225
231
|
if (resumedAttempt === undefined) {
|
|
226
232
|
delete state.currentNode;
|
|
227
233
|
delete state.currentAttemptId;
|
|
228
234
|
delete state.currentNodeStartedAt;
|
|
235
|
+
delete state.currentNodeDeadlineAt;
|
|
229
236
|
delete state.currentSettingsScopeId;
|
|
230
237
|
delete state.currentSettingsChangeNumber;
|
|
231
238
|
delete state.currentSettingsHash;
|
|
@@ -267,6 +274,8 @@ export class WorkflowEngine {
|
|
|
267
274
|
await this.executeGraph(workflow, state, runId, point.nodeId, countExecutableSteps(workflow, state.steps), point.lastOutput, resumedAttempt);
|
|
268
275
|
}
|
|
269
276
|
catch (error) {
|
|
277
|
+
if (isClaimLostError(error))
|
|
278
|
+
throw error;
|
|
270
279
|
if (isRunParkedError(error) || this.parked) {
|
|
271
280
|
return { runId, state };
|
|
272
281
|
}
|
|
@@ -287,7 +296,7 @@ export class WorkflowEngine {
|
|
|
287
296
|
this.cancelled = false;
|
|
288
297
|
this.paused = false;
|
|
289
298
|
this.parked = false;
|
|
290
|
-
const parent = this.store.readRun(parentRunId);
|
|
299
|
+
const parent = await this.store.readRun(parentRunId);
|
|
291
300
|
if (parent === null) {
|
|
292
301
|
throw new Error(`Cannot continue from unreadable workflow run: ${parentRunId}`);
|
|
293
302
|
}
|
|
@@ -314,9 +323,7 @@ export class WorkflowEngine {
|
|
|
314
323
|
request.requestDigest !== options.humanDecision.requestDigest) {
|
|
315
324
|
throw new Error("Accepted human decision does not match the waiting request");
|
|
316
325
|
}
|
|
317
|
-
const durableDecision = await
|
|
318
|
-
state: this.store.state,
|
|
319
|
-
}).readResolved(request.decisionId);
|
|
326
|
+
const durableDecision = await this.store.readResolvedHumanDecision(request.decisionId);
|
|
320
327
|
if (durableDecision === null || !isDeepStrictEqual(durableDecision, options.humanDecision)) {
|
|
321
328
|
throw new Error("Accepted human decision does not match the durable decision record");
|
|
322
329
|
}
|
|
@@ -393,6 +400,8 @@ export class WorkflowEngine {
|
|
|
393
400
|
await this.executeGraph(workflow, state, runId, point.nodeId, countExecutableSteps(workflow, state.steps), point.lastOutput);
|
|
394
401
|
}
|
|
395
402
|
catch (error) {
|
|
403
|
+
if (isClaimLostError(error))
|
|
404
|
+
throw error;
|
|
396
405
|
if (isRunParkedError(error) || this.parked) {
|
|
397
406
|
return { runId, state };
|
|
398
407
|
}
|
|
@@ -741,6 +750,7 @@ export class WorkflowEngine {
|
|
|
741
750
|
delete state.currentNode;
|
|
742
751
|
delete state.currentAttemptId;
|
|
743
752
|
delete state.currentNodeStartedAt;
|
|
753
|
+
delete state.currentNodeDeadlineAt;
|
|
744
754
|
delete state.currentSettingsScopeId;
|
|
745
755
|
delete state.currentSettingsChangeNumber;
|
|
746
756
|
delete state.currentSettingsHash;
|
|
@@ -786,7 +796,7 @@ export class WorkflowEngine {
|
|
|
786
796
|
}
|
|
787
797
|
const meta = { promptText: null };
|
|
788
798
|
try {
|
|
789
|
-
const execution = await this.runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta, settings);
|
|
799
|
+
const execution = await this.runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta, settings, resumedAttempt?.deadlineAt);
|
|
790
800
|
return {
|
|
791
801
|
result: this.createNodeResult(nodeId, node, attemptId, startedAt, "ok", execution.output),
|
|
792
802
|
execution,
|
|
@@ -794,6 +804,8 @@ export class WorkflowEngine {
|
|
|
794
804
|
};
|
|
795
805
|
}
|
|
796
806
|
catch (error) {
|
|
807
|
+
if (isRunParkedError(error) || isClaimLostError(error))
|
|
808
|
+
throw error;
|
|
797
809
|
const outcome = this.outcomeForError(error);
|
|
798
810
|
return {
|
|
799
811
|
result: {
|
|
@@ -834,7 +846,7 @@ export class WorkflowEngine {
|
|
|
834
846
|
...(output !== undefined ? { output } : {}),
|
|
835
847
|
};
|
|
836
848
|
}
|
|
837
|
-
async runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta, settings) {
|
|
849
|
+
async runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta, settings, persistedDeadlineAt) {
|
|
838
850
|
const abort = new AbortController();
|
|
839
851
|
const context = this.createNodeContext(state, abort.signal, settings);
|
|
840
852
|
let timer;
|
|
@@ -849,13 +861,54 @@ export class WorkflowEngine {
|
|
|
849
861
|
if (this.cancelled) {
|
|
850
862
|
throw new CancelledError();
|
|
851
863
|
}
|
|
852
|
-
|
|
864
|
+
let timeoutMs;
|
|
865
|
+
let timeoutErrorMs;
|
|
866
|
+
if (persistedDeadlineAt === undefined) {
|
|
867
|
+
timeoutMs = await this.resolveNodeTimeout(node, context, abort);
|
|
868
|
+
timeoutErrorMs = timeoutMs;
|
|
869
|
+
if (node.nodeType === "agent" && this.executor.preservesDeadlineWhileParked === true) {
|
|
870
|
+
state.currentNodeDeadlineAt =
|
|
871
|
+
timeoutMs === null ? null : new Date(Date.now() + timeoutMs).toISOString();
|
|
872
|
+
await this.persist(runId, state, {
|
|
873
|
+
scope: "node",
|
|
874
|
+
type: "node_deadline_set",
|
|
875
|
+
nodeId,
|
|
876
|
+
attemptId,
|
|
877
|
+
payload: { deadlineAt: state.currentNodeDeadlineAt },
|
|
878
|
+
});
|
|
879
|
+
if (state.currentNodeDeadlineAt !== null && timeoutErrorMs !== null) {
|
|
880
|
+
timeoutMs = Date.parse(state.currentNodeDeadlineAt) - Date.now();
|
|
881
|
+
if (timeoutMs <= 0)
|
|
882
|
+
throw new TimeoutError(timeoutErrorMs);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
delete state.currentNodeDeadlineAt;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
else if (persistedDeadlineAt === null) {
|
|
890
|
+
state.currentNodeDeadlineAt = null;
|
|
891
|
+
timeoutMs = null;
|
|
892
|
+
timeoutErrorMs = null;
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
const deadline = Date.parse(persistedDeadlineAt);
|
|
896
|
+
const started = Date.parse(state.currentNodeStartedAt ?? persistedDeadlineAt);
|
|
897
|
+
if (!Number.isFinite(deadline) || !Number.isFinite(started)) {
|
|
898
|
+
throw new Error("Persisted node deadline is invalid");
|
|
899
|
+
}
|
|
900
|
+
state.currentNodeDeadlineAt = persistedDeadlineAt;
|
|
901
|
+
timeoutMs = deadline - Date.now();
|
|
902
|
+
timeoutErrorMs = Math.max(1, deadline - started);
|
|
903
|
+
if (timeoutMs <= 0)
|
|
904
|
+
throw new TimeoutError(timeoutErrorMs);
|
|
905
|
+
}
|
|
853
906
|
if (abort.signal.aborted) {
|
|
854
907
|
throw abortError(abort.signal);
|
|
855
908
|
}
|
|
856
|
-
if (timeoutMs !== null) {
|
|
909
|
+
if (timeoutMs !== null && timeoutErrorMs !== null) {
|
|
857
910
|
timer = setTimeout(() => {
|
|
858
|
-
abort.abort(new TimeoutError(
|
|
911
|
+
abort.abort(new TimeoutError(timeoutErrorMs));
|
|
859
912
|
}, timeoutMs);
|
|
860
913
|
}
|
|
861
914
|
this.activeAttempt = {
|
|
@@ -988,7 +1041,7 @@ export class WorkflowEngine {
|
|
|
988
1041
|
if (definition === undefined)
|
|
989
1042
|
return undefined;
|
|
990
1043
|
const invocation = mountPath === "" ? 1 : currentMountInvocation(state, mountPath);
|
|
991
|
-
const existing = this.store.findSettingsScope(state.runId, mountPath, invocation);
|
|
1044
|
+
const existing = await this.store.findSettingsScope(state.runId, mountPath, invocation);
|
|
992
1045
|
if (existing !== undefined)
|
|
993
1046
|
return existing;
|
|
994
1047
|
const input = mountPath === "" ? state.input : currentMountInput(state, mountPath);
|
|
@@ -996,21 +1049,21 @@ export class WorkflowEngine {
|
|
|
996
1049
|
const initial = mappedSettings.present
|
|
997
1050
|
? await parseWorkflowSettingsValue(definition, mappedSettings.value)
|
|
998
1051
|
: await resolveInitialWorkflowSettings(definition, input);
|
|
999
|
-
return this.store.ensureSettingsScope({
|
|
1052
|
+
return await this.store.ensureSettingsScope({
|
|
1000
1053
|
runId: state.runId,
|
|
1001
1054
|
mountPath,
|
|
1002
1055
|
invocation,
|
|
1003
1056
|
settings: initial.json,
|
|
1004
1057
|
});
|
|
1005
1058
|
}
|
|
1006
|
-
persistedSettingsBinding(state) {
|
|
1059
|
+
async persistedSettingsBinding(state) {
|
|
1007
1060
|
if (state.currentSettingsScopeId === undefined)
|
|
1008
1061
|
return undefined;
|
|
1009
1062
|
if (state.currentSettingsChangeNumber === undefined ||
|
|
1010
1063
|
state.currentSettingsHash === undefined) {
|
|
1011
1064
|
throw new Error("Saved workflow settings binding is incomplete");
|
|
1012
1065
|
}
|
|
1013
|
-
const scope = this.store.getSettingsScopeAtChange(state.currentSettingsScopeId, state.currentSettingsChangeNumber);
|
|
1066
|
+
const scope = await this.store.getSettingsScopeAtChange(state.currentSettingsScopeId, state.currentSettingsChangeNumber);
|
|
1014
1067
|
if (scope === undefined) {
|
|
1015
1068
|
throw new Error(`Saved workflow settings scope is missing: ${state.currentSettingsScopeId}`);
|
|
1016
1069
|
}
|
|
@@ -1125,16 +1178,79 @@ export class WorkflowEngine {
|
|
|
1125
1178
|
}
|
|
1126
1179
|
}
|
|
1127
1180
|
async runActionNode(node, context, nodeId, attemptId, signal, meta) {
|
|
1181
|
+
/* istanbul ignore if -- graph validation rejects action nodes without managed effects */
|
|
1182
|
+
if (node.effect === undefined) {
|
|
1183
|
+
throw new Error(`Action node ${nodeId} must declare a managed effect with an explicit recovery policy`);
|
|
1184
|
+
}
|
|
1185
|
+
const effectType = node.effect.type.trim();
|
|
1186
|
+
const idempotencyKey = typeof node.effect.idempotencyKey === "function"
|
|
1187
|
+
? await node.effect.idempotencyKey(context)
|
|
1188
|
+
: node.effect.idempotencyKey;
|
|
1189
|
+
const request = typeof node.effect.request === "function"
|
|
1190
|
+
? await node.effect.request(context)
|
|
1191
|
+
: node.effect.request;
|
|
1192
|
+
/* istanbul ignore if -- definition validation and effect helpers reject empty fields */
|
|
1193
|
+
if (effectType.length === 0 || idempotencyKey.trim().length === 0) {
|
|
1194
|
+
throw new Error("Managed effect type and idempotency key must be nonempty text");
|
|
1195
|
+
}
|
|
1196
|
+
canonicalJson(request);
|
|
1197
|
+
const reservation = await this.store.reserveEffect({
|
|
1198
|
+
runId: context.state.runId,
|
|
1199
|
+
attemptId,
|
|
1200
|
+
effectType,
|
|
1201
|
+
idempotencyKey,
|
|
1202
|
+
request: request,
|
|
1203
|
+
recovery: node.effect.recovery,
|
|
1204
|
+
});
|
|
1205
|
+
if (reservation.disposition === "ambiguous")
|
|
1206
|
+
throw new RunParkedError();
|
|
1207
|
+
if (reservation.disposition === "adopted") {
|
|
1208
|
+
const adopted = reservation.result;
|
|
1209
|
+
if (adopted === null || typeof adopted !== "object" || Array.isArray(adopted)) {
|
|
1210
|
+
throw new Error("Managed effect receipt is invalid");
|
|
1211
|
+
}
|
|
1212
|
+
const execution = adopted;
|
|
1213
|
+
if (execution.action !== undefined)
|
|
1214
|
+
meta.action = execution.action;
|
|
1215
|
+
return execution;
|
|
1216
|
+
}
|
|
1128
1217
|
const actionContext = {
|
|
1129
1218
|
...context,
|
|
1219
|
+
effect: { type: effectType, idempotencyKey, recovery: node.effect.recovery },
|
|
1130
1220
|
publishUpdate: async (update) => await this.publishUpdate(nodeId, attemptId, update),
|
|
1131
1221
|
};
|
|
1132
|
-
|
|
1133
|
-
|
|
1222
|
+
try {
|
|
1223
|
+
let execution;
|
|
1224
|
+
if ("exec" in node) {
|
|
1225
|
+
execution = await runShellActionNode(node, actionContext, signal, meta);
|
|
1226
|
+
}
|
|
1227
|
+
else {
|
|
1228
|
+
meta.action = { actionType: "function" };
|
|
1229
|
+
const output = await node.run(actionContext);
|
|
1230
|
+
execution = { output, promptText: null, action: { actionType: "function" } };
|
|
1231
|
+
}
|
|
1232
|
+
canonicalJson(execution);
|
|
1233
|
+
await this.store.settleEffect({
|
|
1234
|
+
runId: context.state.runId,
|
|
1235
|
+
effectId: reservation.effectId,
|
|
1236
|
+
attemptNumber: reservation.attemptNumber,
|
|
1237
|
+
outcome: "applied",
|
|
1238
|
+
result: execution,
|
|
1239
|
+
});
|
|
1240
|
+
return execution;
|
|
1241
|
+
}
|
|
1242
|
+
catch (error) {
|
|
1243
|
+
if (isRunParkedError(error) || isClaimLostError(error))
|
|
1244
|
+
throw error;
|
|
1245
|
+
await this.store.settleEffect({
|
|
1246
|
+
runId: context.state.runId,
|
|
1247
|
+
effectId: reservation.effectId,
|
|
1248
|
+
attemptNumber: reservation.attemptNumber,
|
|
1249
|
+
outcome: "rejected",
|
|
1250
|
+
error: errorMessage(error),
|
|
1251
|
+
});
|
|
1252
|
+
throw error;
|
|
1134
1253
|
}
|
|
1135
|
-
meta.action = { actionType: "function" };
|
|
1136
|
-
const output = await node.run(actionContext);
|
|
1137
|
-
return { output, promptText: null, action: { actionType: "function" } };
|
|
1138
1254
|
}
|
|
1139
1255
|
async persist(runId, state, event) {
|
|
1140
1256
|
const traceEvent = await this.store.writeSnapshot(runId, state, event);
|
|
@@ -1145,19 +1261,12 @@ export class WorkflowEngine {
|
|
|
1145
1261
|
// Observers (UI updates, loggers) must never determine workflow
|
|
1146
1262
|
// correctness; a throwing observer would otherwise fail the run.
|
|
1147
1263
|
}
|
|
1264
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1148
1265
|
}
|
|
1149
1266
|
async finishRun(runId, state, status, fields) {
|
|
1150
1267
|
if (status === "failed" && state.status === "timed_out") {
|
|
1151
1268
|
status = "timed_out";
|
|
1152
1269
|
}
|
|
1153
|
-
// Let observers (e.g. the session recorder) stop and drain before the
|
|
1154
|
-
// terminal event exists, so the terminal fact is immutable from that point on.
|
|
1155
|
-
try {
|
|
1156
|
-
await this.onRunFinishing?.(runId, state);
|
|
1157
|
-
}
|
|
1158
|
-
catch {
|
|
1159
|
-
// Finishing the run wins over observer failures.
|
|
1160
|
-
}
|
|
1161
1270
|
state.status = status;
|
|
1162
1271
|
state.finishedAt = new Date().toISOString();
|
|
1163
1272
|
if (fields.error !== undefined) {
|
|
@@ -1172,9 +1281,18 @@ export class WorkflowEngine {
|
|
|
1172
1281
|
delete state.currentNode;
|
|
1173
1282
|
delete state.currentAttemptId;
|
|
1174
1283
|
delete state.currentNodeStartedAt;
|
|
1284
|
+
delete state.currentNodeDeadlineAt;
|
|
1175
1285
|
delete state.currentSettingsScopeId;
|
|
1176
1286
|
delete state.currentSettingsChangeNumber;
|
|
1177
1287
|
delete state.currentSettingsHash;
|
|
1288
|
+
// Let observers stop, drain, and prepare terminal delivery from the final
|
|
1289
|
+
// in-memory state before the immutable terminal event is committed.
|
|
1290
|
+
try {
|
|
1291
|
+
await this.onRunFinishing?.(runId, state);
|
|
1292
|
+
}
|
|
1293
|
+
catch {
|
|
1294
|
+
// Finishing the run wins over observer failures.
|
|
1295
|
+
}
|
|
1178
1296
|
await this.persist(runId, state, {
|
|
1179
1297
|
scope: "run",
|
|
1180
1298
|
type: `run_${status}`,
|
|
@@ -1206,9 +1324,7 @@ async function runCheckpointNode(node, context, execution) {
|
|
|
1206
1324
|
prompt,
|
|
1207
1325
|
...(timeout !== undefined ? { timeout } : {}),
|
|
1208
1326
|
});
|
|
1209
|
-
await
|
|
1210
|
-
state: execution.store.state,
|
|
1211
|
-
}).createRequest(request);
|
|
1327
|
+
await execution.store.createHumanDecisionRequest(request);
|
|
1212
1328
|
return { output: request, promptText: null };
|
|
1213
1329
|
}
|
|
1214
1330
|
const output = node.run ? await node.run(context) : { summary: node.summary ?? "checkpoint" };
|
|
@@ -1225,8 +1341,8 @@ function shellReceipt(result) {
|
|
|
1225
1341
|
durationMs: result.durationMs,
|
|
1226
1342
|
};
|
|
1227
1343
|
}
|
|
1228
|
-
async function runShellActionNode(node,
|
|
1229
|
-
const spec = await node.exec(
|
|
1344
|
+
async function runShellActionNode(node, actionContext, signal, meta) {
|
|
1345
|
+
const spec = await node.exec(actionContext);
|
|
1230
1346
|
const streams = new Set(node.updates?.streams ?? ["stdout"]);
|
|
1231
1347
|
let result;
|
|
1232
1348
|
try {
|
|
@@ -1251,7 +1367,7 @@ async function runShellActionNode(node, context, actionContext, signal, meta) {
|
|
|
1251
1367
|
throw error;
|
|
1252
1368
|
}
|
|
1253
1369
|
meta.action = shellReceipt(result);
|
|
1254
|
-
const output = node.parse ? await node.parse(result,
|
|
1370
|
+
const output = node.parse ? await node.parse(result, actionContext) : result;
|
|
1255
1371
|
return { output, promptText: null, action: shellReceipt(result) };
|
|
1256
1372
|
}
|
|
1257
1373
|
function assertValidTimeout(value) {
|
|
@@ -1427,7 +1543,7 @@ function assistantMessageConfig(node) {
|
|
|
1427
1543
|
return assistantMessageOutput(node.expectedOutput);
|
|
1428
1544
|
}
|
|
1429
1545
|
function appendLiveControlInstructions(prompt, workflow, nodeId, context) {
|
|
1430
|
-
const lines = [prompt.trimEnd(), "", "Workflow
|
|
1546
|
+
const lines = [prompt.trimEnd(), "", "Workflow context for this run:"];
|
|
1431
1547
|
if (context.settingsScopeId !== undefined &&
|
|
1432
1548
|
context.settingsChangeNumber !== undefined &&
|
|
1433
1549
|
context.settings !== undefined) {
|
|
@@ -1441,12 +1557,11 @@ function appendLiveControlInstructions(prompt, workflow, nodeId, context) {
|
|
|
1441
1557
|
.join(", ");
|
|
1442
1558
|
lines.push(`- Active settings scope: ${context.settingsScopeId}`, `- Current settings change number: ${context.settingsChangeNumber}`, ...(definition?.description !== undefined
|
|
1443
1559
|
? [`- Settings purpose: ${definition.description}`]
|
|
1444
|
-
: []), `- Current settings: ${summary}`, `-
|
|
1560
|
+
: []), `- Current settings: ${summary}`, `- Settings permissions: ${allowed || "none"}`, "- Treat the current settings as authoritative for this workflow scope.");
|
|
1445
1561
|
}
|
|
1446
1562
|
else {
|
|
1447
1563
|
lines.push("- This workflow scope has no editable settings.");
|
|
1448
1564
|
}
|
|
1449
|
-
lines.push("- To save normal user work for after successful completion, use workflow action queue-follow-up once for each prompt.", "- To remove an unsent prompt that this model queued, use workflow action remove-follow-up with its followUpId.", "- Setting changes affect only future node attempts and future settings routes. They do not change this running attempt or completed work.");
|
|
1450
1565
|
return lines.join("\n");
|
|
1451
1566
|
}
|
|
1452
1567
|
function settingsDefinitionForNode(workflow, nodeId) {
|