@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.
Files changed (152) hide show
  1. package/README.md +9 -7
  2. package/dist/builtins/autodoc.workflow.d.ts +5 -5
  3. package/dist/builtins/autoimplement.workflow.d.ts +69 -61
  4. package/dist/builtins/autoimplement.workflow.js +47 -8
  5. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  6. package/dist/builtins/autoplan.workflow.d.ts +11 -5
  7. package/dist/builtins/autoplan.workflow.js +68 -32
  8. package/dist/builtins/autoplan.workflow.js.map +1 -1
  9. package/dist/builtins/catalog.js +4 -4
  10. package/dist/builtins/change-verification.workflow.d.ts +1 -1
  11. package/dist/builtins/plain-summary.workflow.d.ts +1 -1
  12. package/dist/builtins/plain-summary.workflow.js +35 -24
  13. package/dist/builtins/plain-summary.workflow.js.map +1 -1
  14. package/dist/builtins/plan-approval.workflow.d.ts +1 -1
  15. package/dist/builtins/plan-change.workflow.d.ts +25 -23
  16. package/dist/builtins/sanity-check.workflow.d.ts +3 -3
  17. package/dist/builtins/sanity-check.workflow.js +19 -22
  18. package/dist/builtins/sanity-check.workflow.js.map +1 -1
  19. package/dist/builtins/workspace-preparation.workflow.d.ts +1 -1
  20. package/dist/controllers/index.d.ts +2 -2
  21. package/dist/controllers/index.js.map +1 -1
  22. package/dist/controllers/sqlite.js +88 -61
  23. package/dist/controllers/sqlite.js.map +1 -1
  24. package/dist/controllers/types.d.ts +34 -0
  25. package/dist/controllers/workflow-engine-scheduler.d.ts +5 -1
  26. package/dist/controllers/workflow-engine-scheduler.js +83 -1
  27. package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
  28. package/dist/controllers/workflows.d.ts +8 -1
  29. package/dist/controllers/workflows.js +36 -0
  30. package/dist/controllers/workflows.js.map +1 -1
  31. package/dist/extension/controller-host.d.ts +2 -1
  32. package/dist/extension/controller-host.js +1 -1
  33. package/dist/extension/controller-host.js.map +1 -1
  34. package/dist/extension/decision-channels.js +45 -7
  35. package/dist/extension/decision-channels.js.map +1 -1
  36. package/dist/extension/follow-up-coordinator.d.ts +27 -0
  37. package/dist/extension/follow-up-coordinator.js +131 -0
  38. package/dist/extension/follow-up-coordinator.js.map +1 -0
  39. package/dist/extension/index.d.ts +9 -0
  40. package/dist/extension/index.js +462 -76
  41. package/dist/extension/index.js.map +1 -1
  42. package/dist/extension/recorder.d.ts +1 -1
  43. package/dist/extension/recorder.js +8 -8
  44. package/dist/extension/recorder.js.map +1 -1
  45. package/dist/extension/session-events.d.ts +2 -3
  46. package/dist/extension/session-events.js +11 -10
  47. package/dist/extension/session-events.js.map +1 -1
  48. package/dist/extension/widget.js +9 -0
  49. package/dist/extension/widget.js.map +1 -1
  50. package/dist/state/database.d.ts +2 -1
  51. package/dist/state/database.js +11 -13
  52. package/dist/state/database.js.map +1 -1
  53. package/dist/state/json.js +6 -1
  54. package/dist/state/json.js.map +1 -1
  55. package/dist/state/mutation.d.ts +1 -1
  56. package/dist/state/mutation.js.map +1 -1
  57. package/dist/state/prune.d.ts +18 -0
  58. package/dist/state/prune.js +373 -0
  59. package/dist/state/prune.js.map +1 -0
  60. package/dist/state/schema.d.ts +1 -1
  61. package/dist/state/schema.js +130 -13
  62. package/dist/state/schema.js.map +1 -1
  63. package/dist/viewer/cli.d.ts +3 -1
  64. package/dist/viewer/cli.js +55 -4
  65. package/dist/viewer/cli.js.map +1 -1
  66. package/dist/viewer/render.js +19 -1
  67. package/dist/viewer/render.js.map +1 -1
  68. package/dist/viewer/session-reducer.d.ts +0 -1
  69. package/dist/viewer/session-reducer.js +2 -24
  70. package/dist/viewer/session-reducer.js.map +1 -1
  71. package/dist/workflows/composition.d.ts +2 -0
  72. package/dist/workflows/composition.js +15 -0
  73. package/dist/workflows/composition.js.map +1 -1
  74. package/dist/workflows/definition.d.ts +6 -3
  75. package/dist/workflows/definition.js +3 -0
  76. package/dist/workflows/definition.js.map +1 -1
  77. package/dist/workflows/engine.d.ts +6 -1
  78. package/dist/workflows/engine.js +303 -36
  79. package/dist/workflows/engine.js.map +1 -1
  80. package/dist/workflows/human-decision.d.ts +1 -0
  81. package/dist/workflows/human-decision.js +25 -9
  82. package/dist/workflows/human-decision.js.map +1 -1
  83. package/dist/workflows/index.d.ts +3 -1
  84. package/dist/workflows/index.js +2 -0
  85. package/dist/workflows/index.js.map +1 -1
  86. package/dist/workflows/json-patch.d.ts +47 -0
  87. package/dist/workflows/json-patch.js +298 -0
  88. package/dist/workflows/json-patch.js.map +1 -0
  89. package/dist/workflows/schema.js +29 -1
  90. package/dist/workflows/schema.js.map +1 -1
  91. package/dist/workflows/settings.d.ts +128 -0
  92. package/dist/workflows/settings.js +199 -0
  93. package/dist/workflows/settings.js.map +1 -0
  94. package/dist/workflows/store.d.ts +81 -3
  95. package/dist/workflows/store.js +1563 -50
  96. package/dist/workflows/store.js.map +1 -1
  97. package/dist/workflows/tool-input.d.ts +22 -0
  98. package/dist/workflows/tool-input.js +43 -0
  99. package/dist/workflows/tool-input.js.map +1 -1
  100. package/dist/workflows/types.d.ts +41 -6
  101. package/docs/2026-08-25-workflow-follow-ups.md +132 -0
  102. package/docs/2026-08-25-workflow-settings.md +169 -0
  103. package/docs/DEFERRED_TURNS.md +6 -0
  104. package/docs/DESIGN_PHILOSOPHY.md +2 -0
  105. package/docs/SQLITE_STATE.md +36 -17
  106. package/docs/WORKFLOW_COMPOSITION.md +15 -0
  107. package/docs/WORKFLOW_STEP_MESSAGES.md +4 -2
  108. package/docs/plans/2026-08-25-autoplan-user-intent-capture-plan.md +107 -0
  109. package/docs/plans/2026-08-25-live-workflow-settings-plan.md +532 -0
  110. package/docs/plans/2026-08-25-workflow-run-storage-plan.md +67 -0
  111. package/docs/session-event-journal.md +2 -3
  112. package/docs/tui-viewer.md +7 -6
  113. package/docs/workflows.md +58 -10
  114. package/examples/workflows/live-settings.workflow.ts +93 -0
  115. package/herdr-plugin.toml +1 -1
  116. package/package.json +1 -1
  117. package/src/builtins/autoimplement.workflow.ts +56 -8
  118. package/src/builtins/autoplan.workflow.ts +80 -32
  119. package/src/builtins/catalog.ts +4 -4
  120. package/src/builtins/plain-summary.workflow.ts +38 -40
  121. package/src/builtins/sanity-check.workflow.ts +19 -22
  122. package/src/controllers/index.ts +6 -0
  123. package/src/controllers/sqlite.ts +132 -72
  124. package/src/controllers/types.ts +40 -0
  125. package/src/controllers/workflow-engine-scheduler.ts +103 -1
  126. package/src/controllers/workflows.ts +68 -0
  127. package/src/extension/controller-host.ts +6 -1
  128. package/src/extension/decision-channels.ts +47 -7
  129. package/src/extension/follow-up-coordinator.ts +151 -0
  130. package/src/extension/index.ts +540 -89
  131. package/src/extension/recorder.ts +10 -8
  132. package/src/extension/session-events.ts +15 -13
  133. package/src/extension/widget.ts +8 -0
  134. package/src/state/database.ts +12 -12
  135. package/src/state/json.ts +6 -1
  136. package/src/state/mutation.ts +4 -1
  137. package/src/state/prune.ts +502 -0
  138. package/src/state/schema.ts +130 -13
  139. package/src/viewer/cli.ts +52 -5
  140. package/src/viewer/render.ts +43 -1
  141. package/src/viewer/session-reducer.ts +2 -29
  142. package/src/workflows/composition.ts +19 -0
  143. package/src/workflows/definition.ts +19 -5
  144. package/src/workflows/engine.ts +365 -36
  145. package/src/workflows/human-decision.ts +41 -11
  146. package/src/workflows/index.ts +43 -0
  147. package/src/workflows/json-patch.ts +375 -0
  148. package/src/workflows/schema.ts +31 -1
  149. package/src/workflows/settings.ts +430 -0
  150. package/src/workflows/store.ts +2268 -121
  151. package/src/workflows/tool-input.ts +58 -0
  152. package/src/workflows/types.ts +43 -5
@@ -1 +1 @@
1
- {"version":3,"file":"definition.js","sourceRoot":"","sources":["../../src/workflows/definition.ts"],"names":[],"mappings":"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"}
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;AA0BrB,MAAM,yBAAyB,GAAG,MAAM,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;AAoBxE,MAAM,UAAU,cAAc,CAU5B,UAAiF;IAMjF,kCAAkC,CAAC,UAAgC,CAAC,CAAC;IACrE,MAAM,KAAK,GAAG,UAIb,CAAC;IACF,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,yBAAyB,EAAE;QACtD,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,KAAK;QACjB,YAAY,EAAE,KAAK;QACnB,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAgBD,MAAM,UAAU,eAAe,CAC7B,oBAAsE,EACtE,OAAO,GAOH,EAAE;IAEN,MAAM,UAAU,GAAG,oBAAoB,CAAC,oBAAoB,CAAC;QAC3D,CAAC,CAAC,EAAE,QAAQ,EAAE,oBAAoB,EAAE,GAAG,OAAO,EAAE;QAChD,CAAC,CAAC,oBAAoB,CAAC;IACzB,IAAI,OAAO,UAAU,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1F,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;IAC1F,CAAC;IACD,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QAC7E,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,UAAU,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,UAAU,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QACnF,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,UAAU,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,QAAmB,EACnB,KAAc;IAEd,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,YAAY,QAAQ,CAAC,IAAI,2BAA2B,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,IAAI,GAAI,KAA4B,CAAC,IAAI,CAAC;IAChD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,KAAK,CAAC,YAAY,QAAQ,CAAC,IAAI,4BAA4B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/F,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,YAAY,QAAQ,CAAC,IAAI,yBAAyB,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,KAA0C,CAAC;AACpD,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,sBAAsB,CAEpC,QAAmB;IACnB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,qCAAqC,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,kDAAkD,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACrF,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,OAAO,CACL,KAAK,IAAI,IAAI;QACb,OAAO,KAAK,KAAK,QAAQ;QACxB,KAAsC,CAAC,yBAAyB,CAAC,KAAK,IAAI,CAC5E,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAO,GAA0B,EAAE;IAClE,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC;IAC7F,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,UAAU,CAAC,CAAC;IACzE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,oEAAoE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CACjG,CAAC;IACJ,CAAC;IACD,IACE,OAAO,CAAC,QAAQ,KAAK,SAAS;QAC9B,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,EAC9D,CAAC;QACD,MAAM,IAAI,KAAK,CACb,mFAAmF,CACpF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,mBAA4B;QAClC,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1E,CAAC,CAAC;AACL,CAAC;AAQD,MAAM,UAAU,KAAK,CAAC,UAAiD;IACrE,MAAM,IAAI,GAAG;QACX,QAAQ,EAAE,OAAgB;QAC1B,GAAG,UAAU;KACS,CAAC;IACzB,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,UAAmD;IAEnD,MAAM,IAAI,GAA0B;QAClC,QAAQ,EAAE,SAAS;QACnB,GAAG,UAAU;KACd,CAAC;IACF,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,UAAkD;IACvE,MAAM,IAAI,GAAyB;QACjC,QAAQ,EAAE,QAAQ;QAClB,GAAG,UAAU;KACd,CAAC;IACF,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,IAAI,CAAC;AACd,CAAC;AAQD,MAAM,UAAU,MAAM,CACpB,UAE+C;IAE/C,MAAM,IAAI,GAAyB;QACjC,QAAQ,EAAE,QAAQ;QAClB,GAAG,UAAU;KACd,CAAC;IACF,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,KAAK,CACnB,UAAuD;IAEvD,MAAM,IAAI,GAA8B;QACtC,QAAQ,EAAE,QAAQ;QAClB,GAAG,UAAU;KACd,CAAC;IACF,0BAA0B,CAAC,IAAI,CAAC,CAAC;IACjC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,UAAU,GAA+C,EAAE;IAE3D,MAAM,IAAI,GAA6B;QACrC,QAAQ,EAAE,YAAY;QACtB,GAAG,UAAU;KACd,CAAC;IACF,yBAAyB,CAAC,IAAI,CAAC,CAAC;IAChC,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -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.
@@ -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 runId = await this.store.initializeRun(workflow, state);
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
- const resumedAttempt = state.currentNode !== undefined &&
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
- : undefined;
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 terminal node event carries the output, receipt, and conversation
508
- // linkage so the trace alone is sufficient to reconstruct the run.
509
- await this.persist(runId, state, {
510
- scope: "node",
511
- type: attempt.result.outcome === "ok" ? "node_finished" : "node_failed",
512
- nodeId: attempt.result.nodeId,
513
- attemptId: attempt.result.attemptId,
514
- payload: {
515
- outcome: attempt.result.outcome,
516
- durationMs: attempt.result.durationMs,
517
- ...(attempt.result.outcome === "ok" ? { output: attempt.result.output ?? null } : {}),
518
- ...(attempt.result.error !== undefined ? { error: attempt.result.error } : {}),
519
- ...(attempt.execution?.action !== undefined ? { action: attempt.execution.action } : {}),
520
- ...(attempt.execution?.assistantMessage !== undefined
521
- ? { assistantMessage: attempt.execution.assistantMessage }
522
- : {}),
523
- ...(attempt.execution?.conversation !== undefined
524
- ? { conversation: attempt.execution.conversation }
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: { nodeType: node.nodeType },
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 = { runId, state, nodeId, attemptId, signal: abort.signal };
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
- createNodeContext(state, signal) {
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 basePrompt = await node.prompt(context);
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(JSON.stringify(createDefinitionSnapshot(workflow)))
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.