@osolmaz/pi-workflows 0.13.1 → 0.13.3
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 +9 -7
- package/dist/builtins/autodoc.workflow.d.ts +5 -5
- package/dist/builtins/autoimplement.workflow.d.ts +69 -61
- package/dist/builtins/autoimplement.workflow.js +47 -8
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/autoplan.workflow.d.ts +11 -5
- package/dist/builtins/autoplan.workflow.js +68 -32
- package/dist/builtins/autoplan.workflow.js.map +1 -1
- package/dist/builtins/catalog.js +4 -4
- package/dist/builtins/change-verification.workflow.d.ts +1 -1
- package/dist/builtins/plain-summary.workflow.d.ts +1 -1
- package/dist/builtins/plain-summary.workflow.js +35 -24
- package/dist/builtins/plain-summary.workflow.js.map +1 -1
- package/dist/builtins/plan-approval.workflow.d.ts +1 -1
- package/dist/builtins/plan-change.workflow.d.ts +25 -23
- package/dist/builtins/sanity-check.workflow.d.ts +3 -3
- package/dist/builtins/sanity-check.workflow.js +19 -22
- package/dist/builtins/sanity-check.workflow.js.map +1 -1
- package/dist/builtins/workspace-preparation.workflow.d.ts +1 -1
- package/dist/controllers/index.d.ts +2 -2
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.js +88 -61
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/controllers/types.d.ts +34 -0
- package/dist/controllers/workflow-engine-scheduler.d.ts +5 -1
- package/dist/controllers/workflow-engine-scheduler.js +83 -1
- package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
- package/dist/controllers/workflows.d.ts +8 -1
- package/dist/controllers/workflows.js +36 -0
- package/dist/controllers/workflows.js.map +1 -1
- package/dist/extension/controller-host.d.ts +2 -1
- package/dist/extension/controller-host.js +1 -1
- package/dist/extension/controller-host.js.map +1 -1
- package/dist/extension/decision-channels.js +45 -7
- package/dist/extension/decision-channels.js.map +1 -1
- package/dist/extension/follow-up-coordinator.d.ts +27 -0
- package/dist/extension/follow-up-coordinator.js +131 -0
- package/dist/extension/follow-up-coordinator.js.map +1 -0
- package/dist/extension/index.d.ts +9 -0
- package/dist/extension/index.js +462 -76
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +1 -1
- package/dist/extension/recorder.js +8 -8
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/session-events.d.ts +2 -3
- package/dist/extension/session-events.js +11 -10
- package/dist/extension/session-events.js.map +1 -1
- package/dist/extension/widget.js +9 -0
- package/dist/extension/widget.js.map +1 -1
- package/dist/state/database.d.ts +2 -1
- package/dist/state/database.js +11 -13
- package/dist/state/database.js.map +1 -1
- package/dist/state/json.js +6 -1
- package/dist/state/json.js.map +1 -1
- package/dist/state/mutation.d.ts +1 -1
- package/dist/state/mutation.js.map +1 -1
- package/dist/state/prune.d.ts +18 -0
- package/dist/state/prune.js +373 -0
- package/dist/state/prune.js.map +1 -0
- package/dist/state/schema.d.ts +1 -1
- package/dist/state/schema.js +130 -13
- package/dist/state/schema.js.map +1 -1
- package/dist/viewer/cli.d.ts +3 -1
- package/dist/viewer/cli.js +55 -4
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/render.js +19 -1
- package/dist/viewer/render.js.map +1 -1
- package/dist/viewer/session-reducer.d.ts +0 -1
- package/dist/viewer/session-reducer.js +2 -24
- package/dist/viewer/session-reducer.js.map +1 -1
- package/dist/workflows/composition.d.ts +2 -0
- package/dist/workflows/composition.js +15 -0
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.d.ts +6 -3
- package/dist/workflows/definition.js +3 -0
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +6 -1
- package/dist/workflows/engine.js +303 -36
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +1 -0
- package/dist/workflows/human-decision.js +25 -9
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -1
- package/dist/workflows/index.js +2 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/json-patch.d.ts +47 -0
- package/dist/workflows/json-patch.js +298 -0
- package/dist/workflows/json-patch.js.map +1 -0
- package/dist/workflows/schema.js +29 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/settings.d.ts +128 -0
- package/dist/workflows/settings.js +199 -0
- package/dist/workflows/settings.js.map +1 -0
- package/dist/workflows/store.d.ts +81 -3
- package/dist/workflows/store.js +1563 -50
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +22 -0
- package/dist/workflows/tool-input.js +43 -0
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +41 -6
- package/docs/2026-08-25-workflow-follow-ups.md +132 -0
- package/docs/2026-08-25-workflow-settings.md +169 -0
- package/docs/DEFERRED_TURNS.md +6 -0
- package/docs/DESIGN_PHILOSOPHY.md +2 -0
- package/docs/SQLITE_STATE.md +36 -17
- package/docs/WORKFLOW_COMPOSITION.md +15 -0
- package/docs/WORKFLOW_STEP_MESSAGES.md +4 -2
- package/docs/plans/2026-08-25-autoplan-user-intent-capture-plan.md +107 -0
- package/docs/plans/2026-08-25-live-workflow-settings-plan.md +532 -0
- package/docs/plans/2026-08-25-workflow-run-storage-plan.md +67 -0
- package/docs/session-event-journal.md +2 -3
- package/docs/tui-viewer.md +7 -6
- package/docs/workflows.md +58 -10
- package/examples/workflows/live-settings.workflow.ts +93 -0
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/src/builtins/autoimplement.workflow.ts +56 -8
- package/src/builtins/autoplan.workflow.ts +80 -32
- package/src/builtins/catalog.ts +4 -4
- package/src/builtins/plain-summary.workflow.ts +38 -40
- package/src/builtins/sanity-check.workflow.ts +19 -22
- package/src/controllers/index.ts +6 -0
- package/src/controllers/sqlite.ts +132 -72
- package/src/controllers/types.ts +40 -0
- package/src/controllers/workflow-engine-scheduler.ts +103 -1
- package/src/controllers/workflows.ts +68 -0
- package/src/extension/controller-host.ts +6 -1
- package/src/extension/decision-channels.ts +47 -7
- package/src/extension/follow-up-coordinator.ts +151 -0
- package/src/extension/index.ts +540 -89
- package/src/extension/recorder.ts +10 -8
- package/src/extension/session-events.ts +15 -13
- package/src/extension/widget.ts +8 -0
- package/src/state/database.ts +12 -12
- package/src/state/json.ts +6 -1
- package/src/state/mutation.ts +4 -1
- package/src/state/prune.ts +502 -0
- package/src/state/schema.ts +130 -13
- package/src/viewer/cli.ts +52 -5
- package/src/viewer/render.ts +43 -1
- package/src/viewer/session-reducer.ts +2 -29
- package/src/workflows/composition.ts +19 -0
- package/src/workflows/definition.ts +19 -5
- package/src/workflows/engine.ts +365 -36
- package/src/workflows/human-decision.ts +41 -11
- package/src/workflows/index.ts +43 -0
- package/src/workflows/json-patch.ts +375 -0
- package/src/workflows/schema.ts +31 -1
- package/src/workflows/settings.ts +430 -0
- package/src/workflows/store.ts +2268 -121
- package/src/workflows/tool-input.ts +58 -0
- package/src/workflows/types.ts +43 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.js","sourceRoot":"","sources":["../../src/workflows/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,EACzB,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,EAC1B,kCAAkC,GACnC,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"definition.js","sourceRoot":"","sources":["../../src/workflows/definition.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ResolvedHumanDecision, AgentExpectedOutput, WorkflowDefinition, WorkflowEngineOptions, WorkflowRunResult, WorkflowSource, WorkflowUpdateInput, WorkflowUpdateReceipt } from "./types.js";
|
|
1
|
+
import type { ResolvedHumanDecision, AgentExpectedOutput, WorkflowDefinition, WorkflowEngineOptions, WorkflowRunResult, WorkflowRunState, WorkflowSource, WorkflowUpdateInput, WorkflowUpdateReceipt } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Executes a workflow graph step by step. Agent steps are delegated to the
|
|
4
4
|
* configured executor; compute/action/checkpoint nodes run inline. Every
|
|
@@ -21,6 +21,7 @@ export declare class WorkflowEngine {
|
|
|
21
21
|
private cancelled;
|
|
22
22
|
private parked;
|
|
23
23
|
private paused;
|
|
24
|
+
private presentationRequired;
|
|
24
25
|
private wakePause;
|
|
25
26
|
constructor(options: WorkflowEngineOptions);
|
|
26
27
|
get databasePath(): string;
|
|
@@ -43,6 +44,7 @@ export declare class WorkflowEngine {
|
|
|
43
44
|
resume(): void;
|
|
44
45
|
/** True when a pause has been requested or the run is already held. */
|
|
45
46
|
get pauseRequested(): boolean;
|
|
47
|
+
get activeSettingsScopeId(): string | undefined;
|
|
46
48
|
run(workflow: WorkflowDefinition, input: unknown, options?: {
|
|
47
49
|
workflowSource?: WorkflowSource;
|
|
48
50
|
runId?: string;
|
|
@@ -95,6 +97,8 @@ export declare class WorkflowEngine {
|
|
|
95
97
|
private runNodeWithTimeout;
|
|
96
98
|
private resolveNodeTimeout;
|
|
97
99
|
private dispatchNode;
|
|
100
|
+
private captureSettingsBinding;
|
|
101
|
+
private persistedSettingsBinding;
|
|
98
102
|
private createNodeContext;
|
|
99
103
|
private runAgentNode;
|
|
100
104
|
private acceptSubmission;
|
|
@@ -102,6 +106,7 @@ export declare class WorkflowEngine {
|
|
|
102
106
|
private persist;
|
|
103
107
|
private finishRun;
|
|
104
108
|
}
|
|
109
|
+
export declare function workflowIdentityMismatch(state: WorkflowRunState, workflow: WorkflowDefinition, source: WorkflowSource | undefined): boolean;
|
|
105
110
|
/**
|
|
106
111
|
* The step contract appended to every agent-node prompt. This is the
|
|
107
112
|
* documented standard for how the model completes a workflow step.
|
package/dist/workflows/engine.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
+
import { canonicalJson } from "../state/json.js";
|
|
4
|
+
import { StaleResourceError } from "../state/mutation.js";
|
|
3
5
|
import { compileWorkflowDefinition, compositionMetadata, isCompiledWorkflow, } from "./composition.js";
|
|
4
6
|
import { CancelledError, errorMessage, isAbortLikeError, isClaimLostError, isRunParkedError, RunParkedError, TimeoutError, WorkflowSourceChangedError, } from "./errors.js";
|
|
5
7
|
import { resolveNext, resolveNextForOutcome, validateWorkflowDefinition } from "./graph.js";
|
|
6
8
|
import { HumanDecisionStore, createHumanDecisionRequest, validateHumanDecisionResponse, } from "./human-decision.js";
|
|
7
9
|
import { extractJsonValue } from "./json.js";
|
|
10
|
+
import { parseWorkflowSettingsValue, resolveInitialWorkflowSettings, } from "./settings.js";
|
|
8
11
|
import { runShellAction, shellResultFromError } from "./shell.js";
|
|
9
12
|
import { RUN_STATE_SCHEMA, WorkflowRunStore, createDefinitionSnapshot, createRunId, } from "./store.js";
|
|
10
13
|
import { UpdateRateLimiter, updateReceipt, validateWorkflowUpdate } from "./updates.js";
|
|
@@ -14,6 +17,7 @@ const TITLE_TIMEOUT_MS = 30_000;
|
|
|
14
17
|
const TIMEOUT_RESOLUTION_TIMEOUT_MS = 30_000;
|
|
15
18
|
// Covers the shell SIGTERM → SIGKILL escalation (1s) plus stdio close.
|
|
16
19
|
const ABORT_CLEANUP_GRACE_MS = 2_000;
|
|
20
|
+
const MAX_SETTINGS_ROUTE_RETRIES = 16;
|
|
17
21
|
/**
|
|
18
22
|
* Executes a workflow graph step by step. Agent steps are delegated to the
|
|
19
23
|
* configured executor; compute/action/checkpoint nodes run inline. Every
|
|
@@ -36,6 +40,7 @@ export class WorkflowEngine {
|
|
|
36
40
|
cancelled = false;
|
|
37
41
|
parked = false;
|
|
38
42
|
paused = false;
|
|
43
|
+
presentationRequired = false;
|
|
39
44
|
wakePause = null;
|
|
40
45
|
constructor(options) {
|
|
41
46
|
this.executor = options.executor;
|
|
@@ -122,9 +127,13 @@ export class WorkflowEngine {
|
|
|
122
127
|
get pauseRequested() {
|
|
123
128
|
return this.paused;
|
|
124
129
|
}
|
|
130
|
+
get activeSettingsScopeId() {
|
|
131
|
+
return this.activeAttempt?.settingsScopeId;
|
|
132
|
+
}
|
|
125
133
|
async run(workflow, input, options = {}) {
|
|
126
134
|
workflow = isCompiledWorkflow(workflow) ? workflow : compileWorkflowDefinition(workflow);
|
|
127
135
|
validateWorkflowDefinition(workflow);
|
|
136
|
+
this.presentationRequired = workflow.presentationPrompt !== undefined;
|
|
128
137
|
// Fail before any run row exists so bad input cannot leave partial state.
|
|
129
138
|
const suppliedInput = input === undefined ? null : input;
|
|
130
139
|
const normalizedInput = workflow.input ? await workflow.input(suppliedInput) : suppliedInput;
|
|
@@ -136,7 +145,17 @@ export class WorkflowEngine {
|
|
|
136
145
|
this.paused = false;
|
|
137
146
|
this.parked = false;
|
|
138
147
|
const state = await this.createRunState(workflow, normalizedInput, options.workflowSource, options.runId);
|
|
139
|
-
const
|
|
148
|
+
const initialSettings = workflow.settings === undefined
|
|
149
|
+
? []
|
|
150
|
+
: [
|
|
151
|
+
{
|
|
152
|
+
mountPath: "",
|
|
153
|
+
invocation: 1,
|
|
154
|
+
settings: (await resolveInitialWorkflowSettings(workflow.settings, normalizedInput))
|
|
155
|
+
.json,
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
const runId = await this.store.initializeRun(workflow, state, { initialSettings });
|
|
140
159
|
await this.persist(runId, state, {
|
|
141
160
|
scope: "run",
|
|
142
161
|
type: "run_started",
|
|
@@ -170,6 +189,7 @@ export class WorkflowEngine {
|
|
|
170
189
|
async resumeRun(workflow, runId, options = {}) {
|
|
171
190
|
workflow = isCompiledWorkflow(workflow) ? workflow : compileWorkflowDefinition(workflow);
|
|
172
191
|
validateWorkflowDefinition(workflow);
|
|
192
|
+
this.presentationRequired = workflow.presentationPrompt !== undefined;
|
|
173
193
|
// Reset before any await: a park or cancel landing during preparation
|
|
174
194
|
// must survive, or a host drain would hang while the run executes.
|
|
175
195
|
this.cancelled = false;
|
|
@@ -183,17 +203,20 @@ export class WorkflowEngine {
|
|
|
183
203
|
}
|
|
184
204
|
const point = this.resumePointFor(workflow, state, "wait");
|
|
185
205
|
const interruptedNode = state.currentNode !== undefined ? workflow.nodes[state.currentNode] : undefined;
|
|
186
|
-
|
|
206
|
+
let resumedAttempt;
|
|
207
|
+
if (state.currentNode !== undefined &&
|
|
187
208
|
state.currentAttemptId !== undefined &&
|
|
188
209
|
state.currentNodeStartedAt !== undefined &&
|
|
189
210
|
interruptedNode?.nodeType === "agent" &&
|
|
190
|
-
assistantMessageConfig(interruptedNode) !== undefined
|
|
191
|
-
|
|
211
|
+
assistantMessageConfig(interruptedNode) !== undefined) {
|
|
212
|
+
const resumedSettings = this.persistedSettingsBinding(state);
|
|
213
|
+
resumedAttempt = {
|
|
192
214
|
nodeId: state.currentNode,
|
|
193
215
|
attemptId: state.currentAttemptId,
|
|
194
216
|
startedAt: state.currentNodeStartedAt,
|
|
195
|
-
|
|
196
|
-
|
|
217
|
+
...(resumedSettings !== undefined ? { settings: resumedSettings } : {}),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
197
220
|
// A resumed run starts unpaused. Submitted and non-agent nodes discard
|
|
198
221
|
// stale in-flight markers and start a new attempt. Assistant-message
|
|
199
222
|
// nodes keep their attempt id so the origin session can adopt an already
|
|
@@ -203,6 +226,9 @@ export class WorkflowEngine {
|
|
|
203
226
|
delete state.currentNode;
|
|
204
227
|
delete state.currentAttemptId;
|
|
205
228
|
delete state.currentNodeStartedAt;
|
|
229
|
+
delete state.currentSettingsScopeId;
|
|
230
|
+
delete state.currentSettingsChangeNumber;
|
|
231
|
+
delete state.currentSettingsHash;
|
|
206
232
|
delete state.statusDetail;
|
|
207
233
|
}
|
|
208
234
|
await this.persist(runId, state, {
|
|
@@ -257,6 +283,7 @@ export class WorkflowEngine {
|
|
|
257
283
|
async continueRun(workflow, parentRunId, input, options = {}) {
|
|
258
284
|
workflow = isCompiledWorkflow(workflow) ? workflow : compileWorkflowDefinition(workflow);
|
|
259
285
|
validateWorkflowDefinition(workflow);
|
|
286
|
+
this.presentationRequired = workflow.presentationPrompt !== undefined;
|
|
260
287
|
this.cancelled = false;
|
|
261
288
|
this.paused = false;
|
|
262
289
|
this.parked = false;
|
|
@@ -480,6 +507,7 @@ export class WorkflowEngine {
|
|
|
480
507
|
let currentNodeId = startNodeId;
|
|
481
508
|
let executedSteps = executedStepsBase;
|
|
482
509
|
let lastOutput = initialLastOutput;
|
|
510
|
+
const settingsRouteRetries = new Map();
|
|
483
511
|
while (currentNodeId !== null) {
|
|
484
512
|
await this.holdWhilePaused(state, runId);
|
|
485
513
|
const isTransition = composition?.entries[currentNodeId] !== undefined ||
|
|
@@ -503,28 +531,81 @@ export class WorkflowEngine {
|
|
|
503
531
|
// as in-flight, and resume reruns it with a fresh attempt.
|
|
504
532
|
throw new RunParkedError();
|
|
505
533
|
}
|
|
534
|
+
const beforeRecord = structuredClone(state);
|
|
506
535
|
this.recordAttempt(workflow, state, attempt);
|
|
507
|
-
// The
|
|
508
|
-
//
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
536
|
+
// The durable step row owns the output. The trace keeps the terminal fact,
|
|
537
|
+
// settings binding, and compact execution metadata.
|
|
538
|
+
try {
|
|
539
|
+
await this.persist(runId, state, {
|
|
540
|
+
scope: "node",
|
|
541
|
+
type: attempt.result.outcome === "ok" ? "node_finished" : "node_failed",
|
|
542
|
+
nodeId: attempt.result.nodeId,
|
|
543
|
+
attemptId: attempt.result.attemptId,
|
|
544
|
+
payload: {
|
|
545
|
+
outcome: attempt.result.outcome,
|
|
546
|
+
durationMs: attempt.result.durationMs,
|
|
547
|
+
...(attempt.result.outcome === "ok" ? { output: attempt.result.output ?? null } : {}),
|
|
548
|
+
...(attempt.result.error !== undefined ? { error: attempt.result.error } : {}),
|
|
549
|
+
...(attempt.execution?.action !== undefined
|
|
550
|
+
? { action: attempt.execution.action }
|
|
551
|
+
: {}),
|
|
552
|
+
...(attempt.execution?.assistantMessage !== undefined
|
|
553
|
+
? { assistantMessage: attempt.execution.assistantMessage }
|
|
554
|
+
: {}),
|
|
555
|
+
...(attempt.execution?.conversation !== undefined
|
|
556
|
+
? { conversation: attempt.execution.conversation }
|
|
557
|
+
: {}),
|
|
558
|
+
...(attempt.settings !== undefined
|
|
559
|
+
? {
|
|
560
|
+
settingsScopeId: attempt.settings.scopeId,
|
|
561
|
+
settingsChangeNumber: attempt.settings.changeNumber,
|
|
562
|
+
settingsHash: attempt.settings.settingsHash,
|
|
563
|
+
}
|
|
564
|
+
: {}),
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
if (!(error instanceof StaleResourceError) ||
|
|
570
|
+
node.nodeType !== "compute" ||
|
|
571
|
+
node.settingsRoute !== true ||
|
|
572
|
+
attempt.settings === undefined) {
|
|
573
|
+
throw error;
|
|
574
|
+
}
|
|
575
|
+
restoreRunState(state, beforeRecord);
|
|
576
|
+
const retryCount = (settingsRouteRetries.get(currentNodeId) ?? 0) + 1;
|
|
577
|
+
if (retryCount > MAX_SETTINGS_ROUTE_RETRIES) {
|
|
578
|
+
throw new Error(`Settings route ${currentNodeId} changed more than ${MAX_SETTINGS_ROUTE_RETRIES} times before settlement`);
|
|
579
|
+
}
|
|
580
|
+
settingsRouteRetries.set(currentNodeId, retryCount);
|
|
581
|
+
const latest = await this.captureSettingsBinding(workflow, state, currentNodeId);
|
|
582
|
+
if (latest === undefined) {
|
|
583
|
+
throw new Error(`Settings route ${currentNodeId} lost its settings scope`);
|
|
584
|
+
}
|
|
585
|
+
state.currentSettingsScopeId = latest.scopeId;
|
|
586
|
+
state.currentSettingsChangeNumber = latest.changeNumber;
|
|
587
|
+
state.currentSettingsHash = latest.settingsHash;
|
|
588
|
+
await this.persist(runId, state, {
|
|
589
|
+
scope: "run",
|
|
590
|
+
type: "settings_route_retried",
|
|
591
|
+
nodeId: currentNodeId,
|
|
592
|
+
attemptId: attempt.result.attemptId,
|
|
593
|
+
payload: {
|
|
594
|
+
scopeId: latest.scopeId,
|
|
595
|
+
previousChangeNumber: attempt.settings.changeNumber,
|
|
596
|
+
changeNumber: latest.changeNumber,
|
|
597
|
+
},
|
|
598
|
+
});
|
|
599
|
+
resumedAttempt = {
|
|
600
|
+
nodeId: currentNodeId,
|
|
601
|
+
attemptId: attempt.result.attemptId,
|
|
602
|
+
startedAt: attempt.result.startedAt,
|
|
603
|
+
settings: latest,
|
|
604
|
+
};
|
|
605
|
+
executedSteps -= 1;
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
settingsRouteRetries.delete(currentNodeId);
|
|
528
609
|
if (attempt.result.outcome !== "ok") {
|
|
529
610
|
currentNodeId = this.routeAfterFailure(workflow, state, attempt);
|
|
530
611
|
continue;
|
|
@@ -648,19 +729,40 @@ export class WorkflowEngine {
|
|
|
648
729
|
...(attempt.execution?.conversation !== undefined
|
|
649
730
|
? { conversation: attempt.execution.conversation }
|
|
650
731
|
: {}),
|
|
732
|
+
...(attempt.settings !== undefined
|
|
733
|
+
? {
|
|
734
|
+
settingsScopeId: attempt.settings.scopeId,
|
|
735
|
+
settingsChangeNumber: attempt.settings.changeNumber,
|
|
736
|
+
settingsHash: attempt.settings.settingsHash,
|
|
737
|
+
}
|
|
738
|
+
: {}),
|
|
651
739
|
};
|
|
652
740
|
state.steps.push(step);
|
|
653
741
|
delete state.currentNode;
|
|
654
742
|
delete state.currentAttemptId;
|
|
655
743
|
delete state.currentNodeStartedAt;
|
|
744
|
+
delete state.currentSettingsScopeId;
|
|
745
|
+
delete state.currentSettingsChangeNumber;
|
|
746
|
+
delete state.currentSettingsHash;
|
|
656
747
|
delete state.statusDetail;
|
|
657
748
|
}
|
|
658
749
|
async executeNode(workflow, state, runId, nodeId, node, resumedAttempt) {
|
|
659
750
|
const attemptId = resumedAttempt?.attemptId ?? randomUUID();
|
|
660
751
|
const startedAt = resumedAttempt?.startedAt ?? new Date().toISOString();
|
|
752
|
+
const settings = resumedAttempt?.settings ?? (await this.captureSettingsBinding(workflow, state, nodeId));
|
|
661
753
|
state.currentNode = nodeId;
|
|
662
754
|
state.currentAttemptId = attemptId;
|
|
663
755
|
state.currentNodeStartedAt = startedAt;
|
|
756
|
+
if (settings !== undefined) {
|
|
757
|
+
state.currentSettingsScopeId = settings.scopeId;
|
|
758
|
+
state.currentSettingsChangeNumber = settings.changeNumber;
|
|
759
|
+
state.currentSettingsHash = settings.settingsHash;
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
delete state.currentSettingsScopeId;
|
|
763
|
+
delete state.currentSettingsChangeNumber;
|
|
764
|
+
delete state.currentSettingsHash;
|
|
765
|
+
}
|
|
664
766
|
if (node.statusDetail !== undefined) {
|
|
665
767
|
state.statusDetail = node.statusDetail;
|
|
666
768
|
}
|
|
@@ -670,15 +772,25 @@ export class WorkflowEngine {
|
|
|
670
772
|
type: "node_started",
|
|
671
773
|
nodeId,
|
|
672
774
|
attemptId,
|
|
673
|
-
payload: {
|
|
775
|
+
payload: {
|
|
776
|
+
nodeType: node.nodeType,
|
|
777
|
+
...(settings !== undefined
|
|
778
|
+
? {
|
|
779
|
+
settingsScopeId: settings.scopeId,
|
|
780
|
+
settingsChangeNumber: settings.changeNumber,
|
|
781
|
+
settingsHash: settings.settingsHash,
|
|
782
|
+
}
|
|
783
|
+
: {}),
|
|
784
|
+
},
|
|
674
785
|
});
|
|
675
786
|
}
|
|
676
787
|
const meta = { promptText: null };
|
|
677
788
|
try {
|
|
678
|
-
const execution = await this.runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta);
|
|
789
|
+
const execution = await this.runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta, settings);
|
|
679
790
|
return {
|
|
680
791
|
result: this.createNodeResult(nodeId, node, attemptId, startedAt, "ok", execution.output),
|
|
681
792
|
execution,
|
|
793
|
+
...(settings !== undefined ? { settings } : {}),
|
|
682
794
|
};
|
|
683
795
|
}
|
|
684
796
|
catch (error) {
|
|
@@ -695,6 +807,7 @@ export class WorkflowEngine {
|
|
|
695
807
|
promptText: meta.promptText,
|
|
696
808
|
...(meta.action !== undefined ? { action: meta.action } : {}),
|
|
697
809
|
},
|
|
810
|
+
...(settings !== undefined ? { settings } : {}),
|
|
698
811
|
error,
|
|
699
812
|
};
|
|
700
813
|
}
|
|
@@ -721,9 +834,9 @@ export class WorkflowEngine {
|
|
|
721
834
|
...(output !== undefined ? { output } : {}),
|
|
722
835
|
};
|
|
723
836
|
}
|
|
724
|
-
async runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta) {
|
|
837
|
+
async runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta, settings) {
|
|
725
838
|
const abort = new AbortController();
|
|
726
|
-
const context = this.createNodeContext(state, abort.signal);
|
|
839
|
+
const context = this.createNodeContext(state, abort.signal, settings);
|
|
727
840
|
let timer;
|
|
728
841
|
let dispatchSettled;
|
|
729
842
|
this.activeAbort = abort;
|
|
@@ -745,7 +858,14 @@ export class WorkflowEngine {
|
|
|
745
858
|
abort.abort(new TimeoutError(timeoutMs));
|
|
746
859
|
}, timeoutMs);
|
|
747
860
|
}
|
|
748
|
-
this.activeAttempt = {
|
|
861
|
+
this.activeAttempt = {
|
|
862
|
+
runId,
|
|
863
|
+
state,
|
|
864
|
+
nodeId,
|
|
865
|
+
attemptId,
|
|
866
|
+
...(settings !== undefined ? { settingsScopeId: settings.scopeId } : {}),
|
|
867
|
+
signal: abort.signal,
|
|
868
|
+
};
|
|
749
869
|
const dispatched = this.dispatchNode(workflow, state, runId, nodeId, attemptId, node, context, abort.signal, meta);
|
|
750
870
|
dispatchSettled = dispatched.then(() => undefined, () => undefined);
|
|
751
871
|
// Race the dispatch against the abort signal so timeouts and cancel
|
|
@@ -847,12 +967,71 @@ export class WorkflowEngine {
|
|
|
847
967
|
});
|
|
848
968
|
}
|
|
849
969
|
}
|
|
850
|
-
|
|
970
|
+
async captureSettingsBinding(workflow, state, nodeId) {
|
|
971
|
+
const metadata = compositionMetadata(workflow);
|
|
972
|
+
let mountPath = "";
|
|
973
|
+
let definition = workflow.settings;
|
|
974
|
+
if (metadata !== undefined) {
|
|
975
|
+
const entry = metadata.entries[nodeId];
|
|
976
|
+
const exit = metadata.exits[nodeId];
|
|
977
|
+
const scope = entry !== undefined
|
|
978
|
+
? metadata.scopes[entry.parentPath]
|
|
979
|
+
: exit !== undefined
|
|
980
|
+
? metadata.scopes[exit.mountPath]
|
|
981
|
+
: Object.values(metadata.scopes).find((candidate) => Object.values(candidate.authoredNodes).includes(nodeId));
|
|
982
|
+
if (scope === undefined) {
|
|
983
|
+
throw new Error(`Workflow settings scope is missing for node ${nodeId}`);
|
|
984
|
+
}
|
|
985
|
+
mountPath = scope.path;
|
|
986
|
+
definition = scope.settings;
|
|
987
|
+
}
|
|
988
|
+
if (definition === undefined)
|
|
989
|
+
return undefined;
|
|
990
|
+
const invocation = mountPath === "" ? 1 : currentMountInvocation(state, mountPath);
|
|
991
|
+
const existing = this.store.findSettingsScope(state.runId, mountPath, invocation);
|
|
992
|
+
if (existing !== undefined)
|
|
993
|
+
return existing;
|
|
994
|
+
const input = mountPath === "" ? state.input : currentMountInput(state, mountPath);
|
|
995
|
+
const mappedSettings = mountPath === "" ? { present: false } : currentMountSettings(state, mountPath);
|
|
996
|
+
const initial = mappedSettings.present
|
|
997
|
+
? await parseWorkflowSettingsValue(definition, mappedSettings.value)
|
|
998
|
+
: await resolveInitialWorkflowSettings(definition, input);
|
|
999
|
+
return this.store.ensureSettingsScope({
|
|
1000
|
+
runId: state.runId,
|
|
1001
|
+
mountPath,
|
|
1002
|
+
invocation,
|
|
1003
|
+
settings: initial.json,
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
persistedSettingsBinding(state) {
|
|
1007
|
+
if (state.currentSettingsScopeId === undefined)
|
|
1008
|
+
return undefined;
|
|
1009
|
+
if (state.currentSettingsChangeNumber === undefined ||
|
|
1010
|
+
state.currentSettingsHash === undefined) {
|
|
1011
|
+
throw new Error("Saved workflow settings binding is incomplete");
|
|
1012
|
+
}
|
|
1013
|
+
const scope = this.store.getSettingsScopeAtChange(state.currentSettingsScopeId, state.currentSettingsChangeNumber);
|
|
1014
|
+
if (scope === undefined) {
|
|
1015
|
+
throw new Error(`Saved workflow settings scope is missing: ${state.currentSettingsScopeId}`);
|
|
1016
|
+
}
|
|
1017
|
+
if (scope.settingsHash !== state.currentSettingsHash) {
|
|
1018
|
+
throw new Error("Saved workflow settings binding does not match its saved change");
|
|
1019
|
+
}
|
|
1020
|
+
return scope;
|
|
1021
|
+
}
|
|
1022
|
+
createNodeContext(state, signal, settings) {
|
|
851
1023
|
return {
|
|
852
1024
|
input: state.input,
|
|
853
1025
|
outputs: state.outputs,
|
|
854
1026
|
results: state.results,
|
|
855
1027
|
state,
|
|
1028
|
+
...(settings !== undefined
|
|
1029
|
+
? {
|
|
1030
|
+
settings: deepFreezeJson(structuredClone(settings.settings)),
|
|
1031
|
+
settingsScopeId: settings.scopeId,
|
|
1032
|
+
settingsChangeNumber: settings.changeNumber,
|
|
1033
|
+
}
|
|
1034
|
+
: {}),
|
|
856
1035
|
signal,
|
|
857
1036
|
};
|
|
858
1037
|
}
|
|
@@ -875,7 +1054,8 @@ export class WorkflowEngine {
|
|
|
875
1054
|
throw new Error("Assistant completion requires an origin Pi session");
|
|
876
1055
|
}
|
|
877
1056
|
}
|
|
878
|
-
const
|
|
1057
|
+
const authoredPrompt = await node.prompt(context);
|
|
1058
|
+
const basePrompt = appendLiveControlInstructions(authoredPrompt, workflow, nodeId, context);
|
|
879
1059
|
if (signal.aborted) {
|
|
880
1060
|
// The node timed out or the run was cancelled while the async prompt
|
|
881
1061
|
// builder ran; a late continuation must not write into a run that
|
|
@@ -992,6 +1172,9 @@ export class WorkflowEngine {
|
|
|
992
1172
|
delete state.currentNode;
|
|
993
1173
|
delete state.currentAttemptId;
|
|
994
1174
|
delete state.currentNodeStartedAt;
|
|
1175
|
+
delete state.currentSettingsScopeId;
|
|
1176
|
+
delete state.currentSettingsChangeNumber;
|
|
1177
|
+
delete state.currentSettingsHash;
|
|
995
1178
|
await this.persist(runId, state, {
|
|
996
1179
|
scope: "run",
|
|
997
1180
|
type: `run_${status}`,
|
|
@@ -1000,6 +1183,7 @@ export class WorkflowEngine {
|
|
|
1000
1183
|
...(fields.error !== undefined ? { error: fields.error } : {}),
|
|
1001
1184
|
...(fields.waitingOn !== undefined ? { waitingOn: fields.waitingOn } : {}),
|
|
1002
1185
|
...(fields.finalOutput !== undefined ? { finalOutput: fields.finalOutput } : {}),
|
|
1186
|
+
presentationRequired: status === "completed" && this.presentationRequired,
|
|
1003
1187
|
},
|
|
1004
1188
|
});
|
|
1005
1189
|
}
|
|
@@ -1093,6 +1277,54 @@ function abortRejection(signal) {
|
|
|
1093
1277
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
1094
1278
|
});
|
|
1095
1279
|
}
|
|
1280
|
+
function restoreRunState(target, saved) {
|
|
1281
|
+
for (const key of Object.keys(target)) {
|
|
1282
|
+
delete target[key];
|
|
1283
|
+
}
|
|
1284
|
+
Object.assign(target, saved);
|
|
1285
|
+
}
|
|
1286
|
+
function currentMountInvocation(state, mountPath) {
|
|
1287
|
+
for (let index = state.steps.length - 1; index >= 0; index -= 1) {
|
|
1288
|
+
const step = state.steps[index];
|
|
1289
|
+
if (step?.nodeId !== mountPath || step.outcome !== "ok")
|
|
1290
|
+
continue;
|
|
1291
|
+
const output = step.output;
|
|
1292
|
+
if (typeof output.invocation === "number" && Number.isInteger(output.invocation)) {
|
|
1293
|
+
return output.invocation;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
throw new Error(`Workflow include entry is missing for settings scope ${mountPath}`);
|
|
1297
|
+
}
|
|
1298
|
+
function currentMountInput(state, mountPath) {
|
|
1299
|
+
for (let index = state.steps.length - 1; index >= 0; index -= 1) {
|
|
1300
|
+
const step = state.steps[index];
|
|
1301
|
+
if (step?.nodeId !== mountPath || step.outcome !== "ok")
|
|
1302
|
+
continue;
|
|
1303
|
+
const output = step.output;
|
|
1304
|
+
return output.input ?? null;
|
|
1305
|
+
}
|
|
1306
|
+
throw new Error(`Workflow include input is missing for settings scope ${mountPath}`);
|
|
1307
|
+
}
|
|
1308
|
+
function currentMountSettings(state, mountPath) {
|
|
1309
|
+
for (let index = state.steps.length - 1; index >= 0; index -= 1) {
|
|
1310
|
+
const step = state.steps[index];
|
|
1311
|
+
if (step?.nodeId !== mountPath || step.outcome !== "ok")
|
|
1312
|
+
continue;
|
|
1313
|
+
const output = step.output;
|
|
1314
|
+
return Object.hasOwn(output, "settings")
|
|
1315
|
+
? { present: true, value: output.settings }
|
|
1316
|
+
: { present: false };
|
|
1317
|
+
}
|
|
1318
|
+
throw new Error(`Workflow include entry is missing for settings scope ${mountPath}`);
|
|
1319
|
+
}
|
|
1320
|
+
function deepFreezeJson(value) {
|
|
1321
|
+
if (value !== null && typeof value === "object") {
|
|
1322
|
+
Object.freeze(value);
|
|
1323
|
+
for (const item of Object.values(value))
|
|
1324
|
+
deepFreezeJson(item);
|
|
1325
|
+
}
|
|
1326
|
+
return value;
|
|
1327
|
+
}
|
|
1096
1328
|
function countExecutableSteps(workflow, steps) {
|
|
1097
1329
|
const metadata = compositionMetadata(workflow);
|
|
1098
1330
|
if (metadata === undefined)
|
|
@@ -1143,7 +1375,7 @@ function workflowSourceMismatch(state, source) {
|
|
|
1143
1375
|
return (state.workflowHash !== undefined &&
|
|
1144
1376
|
(source.kind !== "file" || state.workflowHash !== source.hash));
|
|
1145
1377
|
}
|
|
1146
|
-
function workflowIdentityMismatch(state, workflow, source) {
|
|
1378
|
+
export function workflowIdentityMismatch(state, workflow, source) {
|
|
1147
1379
|
if (workflowSourceMismatch(state, source))
|
|
1148
1380
|
return true;
|
|
1149
1381
|
const metadata = compositionMetadata(workflow);
|
|
@@ -1155,7 +1387,7 @@ function workflowIdentityMismatch(state, workflow, source) {
|
|
|
1155
1387
|
}
|
|
1156
1388
|
function definitionDigest(workflow) {
|
|
1157
1389
|
return `sha256:${createHash("sha256")
|
|
1158
|
-
.update(
|
|
1390
|
+
.update(canonicalJson(createDefinitionSnapshot(workflow)))
|
|
1159
1391
|
.digest("hex")}`;
|
|
1160
1392
|
}
|
|
1161
1393
|
function assertJsonSerializable(value, what) {
|
|
@@ -1194,6 +1426,41 @@ function assistantMessageOutput(expectedOutput) {
|
|
|
1194
1426
|
function assistantMessageConfig(node) {
|
|
1195
1427
|
return assistantMessageOutput(node.expectedOutput);
|
|
1196
1428
|
}
|
|
1429
|
+
function appendLiveControlInstructions(prompt, workflow, nodeId, context) {
|
|
1430
|
+
const lines = [prompt.trimEnd(), "", "Workflow changes during this run:"];
|
|
1431
|
+
if (context.settingsScopeId !== undefined &&
|
|
1432
|
+
context.settingsChangeNumber !== undefined &&
|
|
1433
|
+
context.settings !== undefined) {
|
|
1434
|
+
const definition = settingsDefinitionForNode(workflow, nodeId);
|
|
1435
|
+
const value = canonicalJson(context.settings);
|
|
1436
|
+
const summary = value.length <= 8_192 ? value : `${value.slice(0, 8_192)}… [settings truncated]`;
|
|
1437
|
+
const allowed = (definition?.paths ?? [])
|
|
1438
|
+
.flatMap((rule) => Object.entries(rule.permissions)
|
|
1439
|
+
.filter(([, actors]) => actors?.includes("session") === true)
|
|
1440
|
+
.map(([permission]) => `${permission} ${rule.path || "/"}`))
|
|
1441
|
+
.join(", ");
|
|
1442
|
+
lines.push(`- Active settings scope: ${context.settingsScopeId}`, `- Current settings change number: ${context.settingsChangeNumber}`, ...(definition?.description !== undefined
|
|
1443
|
+
? [`- Settings purpose: ${definition.description}`]
|
|
1444
|
+
: []), `- Current settings: ${summary}`, `- Changes allowed from this model step: ${allowed || "none"}`, "- Treat the current settings as authoritative for future behavior in this workflow scope.", "- To change future workflow settings, use the workflow tool with action change-settings, this scopeId, an RFC 6902 patch, and optionally expectedChangeNumber.");
|
|
1445
|
+
}
|
|
1446
|
+
else {
|
|
1447
|
+
lines.push("- This workflow scope has no editable settings.");
|
|
1448
|
+
}
|
|
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
|
+
return lines.join("\n");
|
|
1451
|
+
}
|
|
1452
|
+
function settingsDefinitionForNode(workflow, nodeId) {
|
|
1453
|
+
const metadata = compositionMetadata(workflow);
|
|
1454
|
+
if (metadata === undefined)
|
|
1455
|
+
return workflow.settings;
|
|
1456
|
+
const entry = metadata.entries[nodeId];
|
|
1457
|
+
if (entry !== undefined)
|
|
1458
|
+
return metadata.scopes[entry.parentPath]?.settings;
|
|
1459
|
+
const exit = metadata.exits[nodeId];
|
|
1460
|
+
if (exit !== undefined)
|
|
1461
|
+
return metadata.scopes[exit.mountPath]?.settings;
|
|
1462
|
+
return Object.values(metadata.scopes).find((scope) => Object.values(scope.authoredNodes).includes(nodeId))?.settings;
|
|
1463
|
+
}
|
|
1197
1464
|
/**
|
|
1198
1465
|
* The step contract appended to every agent-node prompt. This is the
|
|
1199
1466
|
* documented standard for how the model completes a workflow step.
|