@noetic-tools/core 0.0.1 → 0.1.1

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 (254) hide show
  1. package/README.md +31 -11
  2. package/dist/adapters/in-memory-fs-adapter.d.ts +1 -1
  3. package/dist/adapters/in-memory-fs-adapter.js +1 -1
  4. package/dist/adapters/in-memory-shell-adapter.d.ts +1 -1
  5. package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +8 -0
  6. package/dist/adapters/in-memory-subprocess/manifest-persistence.js +56 -0
  7. package/dist/adapters/in-memory-subprocess/metadata.d.ts +12 -0
  8. package/dist/adapters/in-memory-subprocess/metadata.js +58 -0
  9. package/dist/adapters/in-memory-subprocess/step-completion.d.ts +3 -0
  10. package/dist/adapters/in-memory-subprocess/step-completion.js +86 -0
  11. package/dist/adapters/in-memory-subprocess/types.d.ts +88 -0
  12. package/dist/adapters/in-memory-subprocess/types.js +2 -0
  13. package/dist/adapters/in-memory-subprocess-adapter.d.ts +12 -4
  14. package/dist/adapters/in-memory-subprocess-adapter.js +164 -55
  15. package/dist/adapters/openrouter.d.ts +16 -7
  16. package/dist/adapters/openrouter.js +68 -7
  17. package/dist/builders/channel-builder.d.ts +1 -1
  18. package/dist/builders/control-flow-builders.d.ts +3 -3
  19. package/dist/builders/control-flow-builders.js +9 -4
  20. package/dist/builders/every.d.ts +3 -3
  21. package/dist/builders/every.js +5 -2
  22. package/dist/builders/layer-provides-builders.d.ts +1 -32
  23. package/dist/builders/layer-provides-builders.js +1 -33
  24. package/dist/builders/loop-builder.d.ts +2 -2
  25. package/dist/builders/loop-builder.js +5 -2
  26. package/dist/builders/memory-builder.d.ts +1 -1
  27. package/dist/builders/memory-builder.js +1 -1
  28. package/dist/builders/provide-builder.d.ts +2 -2
  29. package/dist/builders/provide-builder.js +5 -2
  30. package/dist/builders/spawn-builder.d.ts +22 -9
  31. package/dist/builders/spawn-builder.js +17 -3
  32. package/dist/builders/step-builders.d.ts +51 -30
  33. package/dist/builders/step-builders.js +31 -12
  34. package/dist/builders/tool-builder.d.ts +7 -3
  35. package/dist/builders/tool-builder.js +3 -1
  36. package/dist/builders/workflow-hydrator.d.ts +38 -0
  37. package/dist/builders/workflow-hydrator.js +336 -0
  38. package/dist/conditions/conditions.d.ts +4 -4
  39. package/dist/conditions/conditions.js +2 -2
  40. package/dist/errors/noetic-error.d.ts +1 -1
  41. package/dist/errors/noetic-error.js +2 -0
  42. package/dist/{runtime → harness}/agent-harness.d.ts +65 -45
  43. package/dist/harness/agent-harness.js +576 -0
  44. package/dist/harness/deps/adapters.d.ts +4 -0
  45. package/dist/harness/deps/adapters.js +4 -0
  46. package/dist/harness/deps/interpreter.d.ts +5 -0
  47. package/dist/harness/deps/interpreter.js +4 -0
  48. package/dist/harness/deps/memory.d.ts +5 -0
  49. package/dist/harness/deps/memory.js +4 -0
  50. package/dist/harness/deps/runtime.d.ts +10 -0
  51. package/dist/harness/deps/runtime.js +7 -0
  52. package/dist/harness/deps/types.d.ts +17 -0
  53. package/dist/harness/deps/types.js +1 -0
  54. package/dist/harness/model-call.d.ts +50 -0
  55. package/dist/harness/model-call.js +694 -0
  56. package/dist/harness/model-schema.d.ts +19 -0
  57. package/dist/harness/model-schema.js +46 -0
  58. package/dist/harness/step-bootstrap.d.ts +25 -0
  59. package/dist/harness/step-bootstrap.js +170 -0
  60. package/dist/index.d.ts +114 -76
  61. package/dist/index.js +74 -50
  62. package/dist/internal-test.d.ts +4 -4
  63. package/dist/internal-test.js +4 -4
  64. package/dist/interpreter/action-deps.d.ts +12 -0
  65. package/dist/interpreter/action-deps.js +10 -0
  66. package/dist/interpreter/action-types.d.ts +8 -0
  67. package/dist/interpreter/action-types.js +1 -0
  68. package/dist/interpreter/collect-tools.d.ts +2 -2
  69. package/dist/interpreter/collect-tools.js +6 -2
  70. package/dist/interpreter/execute-action.d.ts +22 -0
  71. package/dist/interpreter/execute-action.js +527 -0
  72. package/dist/interpreter/{execute-every.d.ts → execute-control.d.ts} +8 -2
  73. package/dist/interpreter/execute-control.js +530 -0
  74. package/dist/interpreter/execute.d.ts +16 -3
  75. package/dist/interpreter/execute.js +154 -51
  76. package/dist/interpreter/message-helpers.d.ts +1 -9
  77. package/dist/interpreter/message-helpers.js +1 -42
  78. package/dist/interpreter/step-dispatcher.d.ts +61 -0
  79. package/dist/interpreter/step-dispatcher.js +67 -0
  80. package/dist/interpreter/typeguards.d.ts +8 -15
  81. package/dist/interpreter/typeguards.js +4 -21
  82. package/dist/memory/budget.d.ts +2 -2
  83. package/dist/memory/budget.js +1 -1
  84. package/dist/{runtime → memory}/exec-context-factory.d.ts +2 -2
  85. package/dist/{runtime → memory}/exec-context-factory.js +1 -1
  86. package/dist/memory/flow-schema.d.ts +52 -0
  87. package/dist/memory/flow-schema.js +102 -0
  88. package/dist/memory/function-call-utils.d.ts +1 -1
  89. package/dist/memory/function-call-utils.js +4 -2
  90. package/dist/memory/layer-api.d.ts +4 -4
  91. package/dist/memory/layer-api.js +2 -2
  92. package/dist/memory/layer-lifecycle.d.ts +6 -6
  93. package/dist/memory/layer-lifecycle.js +6 -6
  94. package/dist/memory/layer-provides.d.ts +32 -0
  95. package/dist/memory/layer-provides.js +33 -0
  96. package/dist/memory/layer-usage.d.ts +4 -3
  97. package/dist/memory/layer-usage.js +9 -3
  98. package/dist/memory/layers/durable-task-state.d.ts +8 -8
  99. package/dist/memory/layers/durable-task-state.js +2 -2
  100. package/dist/memory/layers/file-reference.d.ts +1 -1
  101. package/dist/memory/layers/file-reference.js +303 -247
  102. package/dist/memory/layers/history-window.d.ts +1 -1
  103. package/dist/memory/layers/history-window.js +4 -4
  104. package/dist/memory/layers/observational-memory.d.ts +5 -5
  105. package/dist/memory/layers/observational-memory.js +2 -3
  106. package/dist/memory/layers/plan.d.ts +2 -2
  107. package/dist/memory/layers/plan.js +15 -10
  108. package/dist/memory/layers/static-content.d.ts +2 -2
  109. package/dist/memory/layers/static-content.js +1 -1
  110. package/dist/memory/layers/steering.d.ts +7 -7
  111. package/dist/memory/layers/steering.js +5 -5
  112. package/dist/memory/layers/tool-memory-layer.d.ts +2 -2
  113. package/dist/memory/layers/tool-memory-layer.js +1 -1
  114. package/dist/memory/layers/working-memory.d.ts +7 -7
  115. package/dist/memory/layers/working-memory.js +4 -4
  116. package/dist/memory/projector.d.ts +2 -2
  117. package/dist/memory/scope.d.ts +1 -1
  118. package/dist/memory/strip-unresolved.d.ts +1 -1
  119. package/dist/observability/span-impl.d.ts +1 -1
  120. package/dist/observability/trace-exporter.d.ts +2 -2
  121. package/dist/observability/trace-exporter.js +1 -1
  122. package/dist/patterns/dynamic-workflow.d.ts +55 -0
  123. package/dist/patterns/dynamic-workflow.js +194 -0
  124. package/dist/patterns/flow.d.ts +2 -52
  125. package/dist/patterns/flow.js +1 -103
  126. package/dist/patterns/interview.d.ts +2 -2
  127. package/dist/patterns/interview.js +5 -5
  128. package/dist/patterns/plans.d.ts +6 -6
  129. package/dist/patterns/plans.js +4 -4
  130. package/dist/patterns/ralph-wiggum.d.ts +4 -4
  131. package/dist/patterns/ralph-wiggum.js +5 -5
  132. package/dist/patterns/react.d.ts +3 -3
  133. package/dist/patterns/react.js +5 -5
  134. package/dist/portable/adapters.d.ts +6 -0
  135. package/dist/portable/adapters.js +6 -0
  136. package/dist/portable/ask-user.d.ts +4 -0
  137. package/dist/portable/ask-user.js +2 -0
  138. package/dist/portable/builders.d.ts +12 -0
  139. package/dist/portable/builders.js +12 -0
  140. package/dist/portable/memory-layers.d.ts +12 -0
  141. package/dist/portable/memory-layers.js +12 -0
  142. package/dist/portable/runtime.d.ts +4 -0
  143. package/dist/portable/runtime.js +4 -0
  144. package/dist/portable/until.d.ts +4 -0
  145. package/dist/portable/until.js +4 -0
  146. package/dist/portable.d.ts +8 -26
  147. package/dist/portable.js +8 -34
  148. package/dist/runtime/broadcaster-utils.d.ts +7 -3
  149. package/dist/runtime/broadcaster-utils.js +11 -6
  150. package/dist/runtime/channel-store.d.ts +1 -1
  151. package/dist/runtime/channel-store.js +2 -2
  152. package/dist/runtime/context-deps.d.ts +4 -0
  153. package/dist/runtime/context-deps.js +2 -0
  154. package/dist/runtime/context-impl.d.ts +42 -12
  155. package/dist/runtime/context-impl.js +52 -5
  156. package/dist/runtime/context-type-deps.d.ts +14 -0
  157. package/dist/runtime/context-type-deps.js +1 -0
  158. package/dist/runtime/cwd-helpers.d.ts +1 -1
  159. package/dist/runtime/detached-handle.d.ts +52 -4
  160. package/dist/runtime/detached-handle.js +218 -14
  161. package/dist/runtime/durable/checkpoint-store.d.ts +38 -0
  162. package/dist/runtime/durable/checkpoint-store.js +101 -0
  163. package/dist/runtime/durable/detached-signal.d.ts +37 -0
  164. package/dist/runtime/durable/detached-signal.js +52 -0
  165. package/dist/runtime/durable/harness-checkpoints.d.ts +51 -0
  166. package/dist/runtime/durable/harness-checkpoints.js +104 -0
  167. package/dist/runtime/durable/index.d.ts +10 -0
  168. package/dist/runtime/durable/index.js +5 -0
  169. package/dist/runtime/durable/runnable-loop.d.ts +100 -0
  170. package/dist/runtime/durable/runnable-loop.js +90 -0
  171. package/dist/runtime/durable/session-seed.d.ts +28 -0
  172. package/dist/runtime/durable/session-seed.js +27 -0
  173. package/dist/runtime/durable/stall-nudge.d.ts +82 -0
  174. package/dist/runtime/durable/stall-nudge.js +87 -0
  175. package/dist/runtime/event-broadcaster.d.ts +1 -1
  176. package/dist/runtime/in-memory-storage.d.ts +1 -1
  177. package/dist/runtime/in-memory-storage.js +1 -1
  178. package/dist/runtime/item-log-impl.d.ts +3 -3
  179. package/dist/runtime/item-log-impl.js +1 -1
  180. package/dist/runtime/message-queue.d.ts +2 -2
  181. package/dist/runtime/session-runner.d.ts +7 -7
  182. package/dist/runtime/session-runner.js +3 -3
  183. package/dist/runtime/session-streams.d.ts +3 -3
  184. package/dist/runtime/session-streams.js +1 -1
  185. package/dist/runtime/step-registry.d.ts +42 -0
  186. package/dist/runtime/step-registry.js +77 -0
  187. package/dist/runtime/tool-memory.d.ts +3 -3
  188. package/dist/schemas/item.d.ts +1 -1
  189. package/dist/schemas/workflow.d.ts +139 -0
  190. package/dist/schemas/workflow.js +232 -0
  191. package/dist/types/ask-user-types.d.ts +57 -0
  192. package/dist/types/ask-user-types.js +74 -0
  193. package/dist/types/checkpoint.d.ts +98 -0
  194. package/dist/types/checkpoint.js +81 -0
  195. package/dist/types/common.d.ts +1 -50
  196. package/dist/types/context-parts/item-log.d.ts +6 -0
  197. package/dist/types/context-parts/item-log.js +1 -0
  198. package/dist/types/context-parts/layer-usage.d.ts +18 -0
  199. package/dist/types/context-parts/layer-usage.js +1 -0
  200. package/dist/types/context.d.ts +140 -31
  201. package/dist/types/error.d.ts +15 -0
  202. package/dist/types/harness-result.d.ts +2 -2
  203. package/dist/types/items.d.ts +18 -2
  204. package/dist/types/memory-context.d.ts +39 -0
  205. package/dist/types/memory-context.js +1 -0
  206. package/dist/types/memory.d.ts +38 -68
  207. package/dist/types/mutable-context.d.ts +3 -3
  208. package/dist/types/runtime.d.ts +14 -13
  209. package/dist/types/steering.d.ts +2 -2
  210. package/dist/types/step-registrar.d.ts +46 -0
  211. package/dist/types/step-registrar.js +56 -0
  212. package/dist/types/step.d.ts +34 -9
  213. package/dist/types/subprocess-adapter.d.ts +119 -3
  214. package/dist/types/subprocess-adapter.js +2 -0
  215. package/dist/types/tool-context.d.ts +6 -6
  216. package/dist/types/tool.d.ts +64 -0
  217. package/dist/types/tool.js +1 -0
  218. package/dist/unstable.d.ts +9 -9
  219. package/dist/unstable.js +7 -7
  220. package/dist/until/combinators.d.ts +1 -1
  221. package/dist/until/never.d.ts +1 -1
  222. package/dist/until/predicates.d.ts +13 -4
  223. package/dist/until/predicates.js +16 -2
  224. package/dist/util/message-helpers.d.ts +17 -0
  225. package/dist/util/message-helpers.js +64 -0
  226. package/package.json +6 -4
  227. package/dist/adapters/local-fs-adapter.d.ts +0 -3
  228. package/dist/adapters/local-fs-adapter.js +0 -43
  229. package/dist/adapters/local-shell-adapter.d.ts +0 -51
  230. package/dist/adapters/local-shell-adapter.js +0 -167
  231. package/dist/adapters/local-subprocess-adapter.d.ts +0 -16
  232. package/dist/adapters/local-subprocess-adapter.js +0 -187
  233. package/dist/conditions/index.d.ts +0 -3
  234. package/dist/conditions/index.js +0 -2
  235. package/dist/interpreter/execute-branch.d.ts +0 -3
  236. package/dist/interpreter/execute-branch.js +0 -10
  237. package/dist/interpreter/execute-every.js +0 -143
  238. package/dist/interpreter/execute-fork.d.ts +0 -3
  239. package/dist/interpreter/execute-fork.js +0 -215
  240. package/dist/interpreter/execute-llm.d.ts +0 -4
  241. package/dist/interpreter/execute-llm.js +0 -217
  242. package/dist/interpreter/execute-loop.d.ts +0 -3
  243. package/dist/interpreter/execute-loop.js +0 -171
  244. package/dist/interpreter/execute-provide.d.ts +0 -10
  245. package/dist/interpreter/execute-provide.js +0 -64
  246. package/dist/interpreter/execute-run.d.ts +0 -3
  247. package/dist/interpreter/execute-run.js +0 -39
  248. package/dist/interpreter/execute-spawn.d.ts +0 -11
  249. package/dist/interpreter/execute-spawn.js +0 -113
  250. package/dist/interpreter/execute-tool.d.ts +0 -5
  251. package/dist/interpreter/execute-tool.js +0 -81
  252. package/dist/runtime/agent-harness.js +0 -1170
  253. /package/dist/{interpreter → util}/framework-cast.d.ts +0 -0
  254. /package/dist/{interpreter → util}/framework-cast.js +0 -0
@@ -0,0 +1,527 @@
1
+ /**
2
+ * Action step handlers: run, llm, spawn, provide, tool.
3
+ */
4
+ import { ZodError } from 'zod';
5
+ import { NoeticConfigError } from '../errors/noetic-config-error.js';
6
+ import { NoeticErrorImpl } from '../errors/noetic-error.js';
7
+ import { frameworkCast } from '../util/framework-cast.js';
8
+ import { createMessage, extractAssistantText } from '../util/message-helpers.js';
9
+ import { assembleView, buildToolExecutionContext, ContextImpl, commitLayerUsage, computeLayerUsage, contextToExecCtx, defaultItemSchemaRegistry, emitFrameworkEvent, getBroadcaster, resolveLayerTools, returnLayers, snapshotCwdState, spawnLayers, } from './action-deps.js';
10
+ import { SteeringAction } from './action-types.js';
11
+ import { cloneWithGuard } from './clone-guard.js';
12
+ import { collectAllTools, deduplicateTools } from './collect-tools.js';
13
+ import { trackUsage } from './message-helpers.js';
14
+ import { getContextChannelStore, isFunctionCall, isMutableContext } from './typeguards.js';
15
+ //#region run
16
+ export async function executeRun(step, input, ctx) {
17
+ const retry = step.retry;
18
+ const maxAttempts = retry?.maxAttempts ?? 1;
19
+ let lastError = new Error('No attempts executed');
20
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
21
+ try {
22
+ return await step.execute(input, ctx);
23
+ }
24
+ catch (e) {
25
+ lastError = e instanceof Error ? e : new Error(String(e));
26
+ if (attempt < maxAttempts - 1 && retry) {
27
+ const delay = computeDelay(retry, attempt);
28
+ await new Promise((r) => setTimeout(r, delay));
29
+ }
30
+ }
31
+ }
32
+ throw new NoeticErrorImpl({
33
+ kind: 'step_failed',
34
+ stepId: step.id,
35
+ cause: lastError,
36
+ retriesExhausted: maxAttempts > 1,
37
+ });
38
+ }
39
+ function computeDelay(retry, attempt) {
40
+ let delay;
41
+ switch (retry.backoff) {
42
+ case 'fixed':
43
+ delay = retry.initialDelay;
44
+ break;
45
+ case 'linear':
46
+ delay = retry.initialDelay * (attempt + 1);
47
+ break;
48
+ case 'exponential':
49
+ delay = retry.initialDelay * 2 ** attempt;
50
+ break;
51
+ }
52
+ return Math.min(delay, retry.maxDelay ?? 30_000);
53
+ }
54
+ //#endregion
55
+ //#region llm
56
+ const MAX_STEERING_RETRIES = 3;
57
+ const emptyRecall = [];
58
+ /**
59
+ * Resolves a `Lazy<T>` step field. If the value is a function, it is invoked
60
+ * with the current context; otherwise the value is returned as-is. Supports
61
+ * both sync and async getters.
62
+ *
63
+ * Lazy fields let a step inspect `ctx.harness.config.params`, `ctx.unifiedTools`,
64
+ * or memory layer state to produce per-execution values (model, instructions,
65
+ * tools) without baking them in at build time.
66
+ */
67
+ async function resolveLazy(value, ctx) {
68
+ if (typeof value !== 'function') {
69
+ return value;
70
+ }
71
+ // The `function` narrowing collapses to `T & Function` when `T` itself can
72
+ // be a function, so we defer to `frameworkCast` — the single sanctioned
73
+ // unsafe coercion helper. Runtime check above guarantees safety.
74
+ const getter = frameworkCast(value);
75
+ return await getter(ctx);
76
+ }
77
+ /**
78
+ * Resolves which tools to send and what restrictions to apply.
79
+ *
80
+ * When a unified tool set exists on the context (collected before execution),
81
+ * every LLM call sends the full set and step.tools narrows via allowedToolNames.
82
+ *
83
+ * Semantics:
84
+ * step.tools = undefined → unrestricted (all tools, no allowedToolNames)
85
+ * step.tools = [] → no tools at all
86
+ * step.tools = [a, b] → full set sent, restrict to a and b
87
+ *
88
+ * Fallback: when no unified set exists (e.g. harness.run() called directly),
89
+ * merge step tools with layer tools as before.
90
+ */
91
+ function resolveToolsAndRestrictions(stepTools, layers, ctx) {
92
+ // stepTools = [] → explicit opt-out
93
+ if (stepTools && stepTools.length === 0) {
94
+ return {
95
+ tools: undefined,
96
+ allowedToolNames: undefined,
97
+ };
98
+ }
99
+ const unified = ctx.unifiedTools;
100
+ if (unified && unified.length > 0) {
101
+ const allowedToolNames = stepTools ? stepTools.map((t) => t.name) : undefined;
102
+ return {
103
+ tools: [
104
+ ...unified,
105
+ ],
106
+ allowedToolNames,
107
+ };
108
+ }
109
+ // Fallback: no unified set (direct harness.run() path)
110
+ const layerTools = layers && layers.length > 0 ? resolveLayerTools(layers, ctx.harness, ctx) : [];
111
+ if (layerTools.length === 0 && !stepTools) {
112
+ return {
113
+ tools: undefined,
114
+ allowedToolNames: undefined,
115
+ };
116
+ }
117
+ const merged = [
118
+ ...(stepTools ?? []),
119
+ ...layerTools,
120
+ ];
121
+ return {
122
+ tools: merged.length > 0 ? merged : undefined,
123
+ allowedToolNames: undefined,
124
+ };
125
+ }
126
+ async function runInputPipeline({ ctx, layers, input }) {
127
+ const userItem = createMessage(input, 'user');
128
+ const { items: finalItems } = await ctx.harness.runAppendPipeline(layers, [
129
+ userItem,
130
+ ], ctx);
131
+ for (const item of finalItems) {
132
+ ctx.itemLog.append(item);
133
+ }
134
+ }
135
+ export async function executeLLM(step, input, ctx, layers) {
136
+ const baseCtx = frameworkCast(ctx);
137
+ const hasLayers = layers !== undefined && layers.length > 0;
138
+ // Resolve lazy step fields up-front. Function-form getters run once per
139
+ // step execution with the live context, so `ctx.harness.config.params`,
140
+ // `ctx.unifiedTools`, and memory layer state are all visible at resolution.
141
+ const resolvedModel = await resolveLazy(step.model, ctx);
142
+ if (!resolvedModel || resolvedModel.trim() === '') {
143
+ throw new NoeticConfigError({
144
+ code: 'MISSING_MODEL',
145
+ message: `step.llm(${JSON.stringify(step.id)}) resolved model to an empty string.`,
146
+ hint: "Ensure `model` (or your `(ctx) => string` getter) returns a non-empty identifier, e.g. 'anthropic/claude-sonnet-4-20250514'.",
147
+ });
148
+ }
149
+ const resolvedInstructions = await resolveLazy(step.instructions, ctx);
150
+ const resolvedStepTools = await resolveLazy(step.tools, ctx);
151
+ // Append user input — through layer pipeline if layers exist, otherwise direct.
152
+ if (typeof input === 'string' && input.length > 0) {
153
+ if (hasLayers) {
154
+ await runInputPipeline({
155
+ ctx: baseCtx,
156
+ layers,
157
+ input,
158
+ });
159
+ }
160
+ else {
161
+ baseCtx.itemLog.append(createMessage(input, 'user'));
162
+ }
163
+ }
164
+ const { tools: resolvedTools, allowedToolNames } = resolveToolsAndRestrictions(resolvedStepTools, layers, baseCtx);
165
+ // Recall once per LLM step: every layer with a recall hook contributes its
166
+ // current view. Results drive both the assembled context window and the
167
+ // per-layer usage breakdown (ctx.lastLayerUsage). Recall fires before the
168
+ // steering retry loop because retries replay the same context.
169
+ const recallQuery = typeof input === 'string' ? input : '';
170
+ const recallResults = hasLayers
171
+ ? await baseCtx.harness.recallLayers(layers, recallQuery, baseCtx)
172
+ : emptyRecall;
173
+ const layerOutputItems = recallResults.flatMap((r) => r.items);
174
+ let retries = 0;
175
+ while (retries <= MAX_STEERING_RETRIES) {
176
+ const rawHistoryItems = baseCtx.itemLog.items;
177
+ const projectedHistoryItems = hasLayers
178
+ ? await baseCtx.harness.projectHistory(layers, rawHistoryItems, baseCtx)
179
+ : rawHistoryItems;
180
+ const assembledItems = layerOutputItems.length > 0
181
+ ? assembleView({
182
+ systemPromptItems: [],
183
+ layerOutputItems,
184
+ historyItems: [
185
+ ...projectedHistoryItems,
186
+ ],
187
+ })
188
+ : projectedHistoryItems === rawHistoryItems
189
+ ? rawHistoryItems
190
+ : [
191
+ ...projectedHistoryItems,
192
+ ];
193
+ const request = resolvedTools
194
+ ? {
195
+ model: resolvedModel,
196
+ items: assembledItems,
197
+ instructions: resolvedInstructions,
198
+ tools: resolvedTools,
199
+ params: step.params,
200
+ outputSchema: step.output,
201
+ emit: step.emit,
202
+ ctx: baseCtx,
203
+ layers,
204
+ allowedToolNames,
205
+ }
206
+ : {
207
+ model: resolvedModel,
208
+ items: assembledItems,
209
+ instructions: resolvedInstructions,
210
+ params: step.params,
211
+ outputSchema: step.output,
212
+ emit: step.emit,
213
+ };
214
+ const response = await baseCtx.harness.callModel(request);
215
+ if (hasLayers) {
216
+ const decision = await baseCtx.harness.afterModelCall(layers, response, baseCtx);
217
+ if (decision.action === SteeringAction.Deny) {
218
+ throw new NoeticErrorImpl({
219
+ kind: 'steering_denied',
220
+ guidance: decision.guidance,
221
+ });
222
+ }
223
+ if (decision.action === SteeringAction.Guide && retries < MAX_STEERING_RETRIES) {
224
+ baseCtx.itemLog.append(createMessage(decision.guidance ?? 'Please adjust your response.', 'developer'));
225
+ retries++;
226
+ continue;
227
+ }
228
+ }
229
+ const toolCalls = [];
230
+ for (const item of response.items) {
231
+ baseCtx.itemLog.append(item);
232
+ if (isFunctionCall(item)) {
233
+ toolCalls.push(item);
234
+ }
235
+ }
236
+ const meta = {
237
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
238
+ usage: response.usage,
239
+ cost: response.cost,
240
+ responseItems: response.items,
241
+ };
242
+ if (isMutableContext(baseCtx)) {
243
+ baseCtx.lastStepMeta = meta;
244
+ }
245
+ trackUsage(baseCtx, response);
246
+ commitLayerUsage(baseCtx, computeLayerUsage({
247
+ ctx: baseCtx,
248
+ modelId: resolvedModel,
249
+ instructions: resolvedInstructions,
250
+ tools: resolvedTools,
251
+ recallResults,
252
+ }));
253
+ if (hasLayers) {
254
+ await baseCtx.harness.storeLayers(layers, response, baseCtx);
255
+ }
256
+ const lastText = extractAssistantText(response.items);
257
+ if (step.output) {
258
+ try {
259
+ const parsed = JSON.parse(lastText);
260
+ return step.output.parse(parsed);
261
+ }
262
+ catch (e) {
263
+ if (e instanceof SyntaxError || e instanceof ZodError) {
264
+ throw new NoeticErrorImpl({
265
+ kind: 'llm_parse_error',
266
+ stepId: step.id,
267
+ raw: lastText,
268
+ schema: step.output,
269
+ zodError: e instanceof ZodError
270
+ ? e
271
+ : new ZodError([
272
+ {
273
+ code: 'custom',
274
+ message: `Invalid JSON: ${e.message}`,
275
+ path: [],
276
+ },
277
+ ]),
278
+ });
279
+ }
280
+ throw e;
281
+ }
282
+ }
283
+ return frameworkCast(lastText);
284
+ }
285
+ // Safety net: the loop above always returns or throws within the body.
286
+ // This throw is unreachable but protects against future refactors that
287
+ // might break the loop invariant.
288
+ throw new NoeticErrorImpl({
289
+ kind: 'step_failed',
290
+ stepId: step.id,
291
+ cause: new Error('Steering retries exhausted'),
292
+ retriesExhausted: true,
293
+ });
294
+ }
295
+ function isMemoryConfig(value) {
296
+ return typeof value === 'object' && value !== null && 'layers' in value;
297
+ }
298
+ function resolveLayersForSpawn(step, parentLayers) {
299
+ if (!step.memory) {
300
+ return parentLayers ?? [];
301
+ }
302
+ if (isMemoryConfig(step.memory)) {
303
+ return [
304
+ ...step.memory.layers,
305
+ ];
306
+ }
307
+ return step.memory;
308
+ }
309
+ async function collectSpawnItems({ layers, parentExecutionCtx, childExecutionCtx, layerStore, itemSchemas = defaultItemSchemaRegistry, }) {
310
+ const spawnResults = await spawnLayers({
311
+ layers,
312
+ parentCtx: parentExecutionCtx,
313
+ childCtx: childExecutionCtx,
314
+ store: layerStore,
315
+ itemSchemas,
316
+ });
317
+ return spawnResults.flatMap((r) => r.items);
318
+ }
319
+ export async function executeSpawn(step, input, ctx, executeStep, opts) {
320
+ const baseCtx = frameworkCast(ctx);
321
+ const layers = resolveLayersForSpawn(step, opts?.parentLayers);
322
+ const childId = crypto.randomUUID();
323
+ const childExecutionCtx = contextToExecCtx(baseCtx, {
324
+ executionId: childId,
325
+ depth: baseCtx.depth + 1,
326
+ stepNumber: 0,
327
+ tokenUsage: {
328
+ input: 0,
329
+ output: 0,
330
+ },
331
+ cost: 0,
332
+ readLayerStateId: childId,
333
+ });
334
+ const layerStore = opts?.layerStore;
335
+ const hasLayers = layers.length > 0 && layerStore !== undefined;
336
+ // Collect items from memory layers via onSpawn hooks
337
+ let childItems = [];
338
+ let parentExecutionCtx;
339
+ if (hasLayers) {
340
+ parentExecutionCtx = contextToExecCtx(baseCtx);
341
+ childItems = await collectSpawnItems({
342
+ layers,
343
+ parentExecutionCtx,
344
+ childExecutionCtx,
345
+ layerStore,
346
+ itemSchemas: opts?.itemSchemas,
347
+ });
348
+ }
349
+ // Build unified tool set for child from its step tree + layers
350
+ const childStepTools = collectAllTools(step.child);
351
+ const childLayerTools = layers.length > 0 ? resolveLayerTools(layers, baseCtx.harness, baseCtx) : [];
352
+ const childUnifiedTools = deduplicateTools([
353
+ ...childStepTools,
354
+ ...childLayerTools,
355
+ ]);
356
+ // Create child context — empty by default, layers provide items via onSpawn.
357
+ // `id: childId` keeps the ContextImpl's id in sync with the executionId
358
+ // used by spawnLayers/returnLayers in the layer-state store, so writes via
359
+ // `ctx.memory[layerId]` and spawn-boundary hooks read from the same key.
360
+ const childCtx = new ContextImpl({
361
+ harness: baseCtx.harness,
362
+ parent: baseCtx,
363
+ items: childItems,
364
+ state: cloneWithGuard(baseCtx.state, `Spawn '${step.id}'`),
365
+ threadId: baseCtx.threadId,
366
+ resourceId: baseCtx.resourceId,
367
+ channelStore: getContextChannelStore(baseCtx),
368
+ layers: layers.length > 0 ? layers : undefined,
369
+ unifiedTools: childUnifiedTools.length > 0 ? childUnifiedTools : undefined,
370
+ cwdState: snapshotCwdState(baseCtx),
371
+ id: childId,
372
+ });
373
+ try {
374
+ // Execute the child step
375
+ const childOutput = await executeStep(step.child, input, frameworkCast(childCtx));
376
+ // Pipeline result through layer onReturn hooks
377
+ if (!hasLayers || !parentExecutionCtx) {
378
+ return childOutput;
379
+ }
380
+ const pipelinedResult = await returnLayers({
381
+ layers,
382
+ parentCtx: parentExecutionCtx,
383
+ childCtx: childExecutionCtx,
384
+ childLog: childCtx.itemLog,
385
+ result: childOutput,
386
+ store: layerStore,
387
+ });
388
+ return pipelinedResult;
389
+ }
390
+ finally {
391
+ // Clean up child execution state to prevent memory leaks — runs on success and error
392
+ if (hasLayers) {
393
+ layerStore.cleanup(childExecutionCtx.executionId);
394
+ }
395
+ }
396
+ }
397
+ //#endregion
398
+ //#region provide
399
+ function resolveLayers(step) {
400
+ if (isMemoryConfig(step.memory)) {
401
+ return [
402
+ ...step.memory.layers,
403
+ ];
404
+ }
405
+ return step.memory;
406
+ }
407
+ function mergeLayers(existing, provided) {
408
+ if (!existing || existing.length === 0) {
409
+ return provided;
410
+ }
411
+ // Provided layers override existing layers with the same id (like nested React context)
412
+ const providedIds = new Set(provided.map((l) => l.id));
413
+ const kept = existing.filter((l) => !providedIds.has(l.id));
414
+ return [
415
+ ...kept,
416
+ ...provided,
417
+ ];
418
+ }
419
+ /**
420
+ * Executes a provide step by attaching memory layers to the current context
421
+ * without creating an isolated child context.
422
+ *
423
+ * Unlike spawn, provide does not create a new itemLog or clone state.
424
+ * Events flow through to the parent in real-time.
425
+ */
426
+ export async function executeProvide(step, input, ctx, executeStep) {
427
+ const baseCtx = frameworkCast(ctx);
428
+ const previousLayers = baseCtx.layers;
429
+ const previousUnifiedTools = baseCtx.unifiedTools;
430
+ const newLayers = resolveLayers(step);
431
+ const mergedLayers = mergeLayers(previousLayers, newLayers);
432
+ // Attach layers to the current context (no isolation)
433
+ baseCtx.layers = mergedLayers;
434
+ // Merge new layer-provided tools into the unified tool set
435
+ if (baseCtx.unifiedTools && newLayers.length > 0) {
436
+ const layerTools = resolveLayerTools(newLayers, baseCtx.harness, baseCtx);
437
+ if (layerTools.length > 0) {
438
+ baseCtx.unifiedTools = deduplicateTools([
439
+ ...baseCtx.unifiedTools,
440
+ ...layerTools,
441
+ ]);
442
+ }
443
+ }
444
+ try {
445
+ return await executeStep(step.child, input, ctx);
446
+ }
447
+ finally {
448
+ // Restore previous layers and unified tools so siblings are not affected
449
+ baseCtx.layers = previousLayers;
450
+ baseCtx.unifiedTools = previousUnifiedTools;
451
+ }
452
+ }
453
+ //#endregion
454
+ //#region tool
455
+ function isAsyncGenerator(value) {
456
+ if (typeof value !== 'object' || value === null) {
457
+ return false;
458
+ }
459
+ return Symbol.asyncIterator in value;
460
+ }
461
+ async function consumeToolGenerator(params) {
462
+ const broadcaster = getBroadcaster(params.ctx);
463
+ const agentName = params.ctx.harness.config.name;
464
+ while (true) {
465
+ const next = await params.generator.next();
466
+ if (next.done) {
467
+ return next.value;
468
+ }
469
+ emitFrameworkEvent({
470
+ broadcaster,
471
+ agentName,
472
+ eventType: 'tool_progress',
473
+ data: {
474
+ stepId: params.stepId,
475
+ toolName: params.toolName,
476
+ event: next.value,
477
+ },
478
+ });
479
+ }
480
+ }
481
+ export async function executeTool(step, input, ctx, harness, layers) {
482
+ const baseCtx = frameworkCast(ctx);
483
+ const args = step.args ? Object.assign({}, input, step.args) : input;
484
+ const parseResult = step.tool.input.safeParse(args);
485
+ if (!parseResult.success) {
486
+ throw new NoeticErrorImpl({
487
+ kind: 'step_failed',
488
+ stepId: step.id,
489
+ cause: new Error(`Tool input validation failed: ${parseResult.error.message}`),
490
+ retriesExhausted: false,
491
+ });
492
+ }
493
+ if (layers && layers.length > 0) {
494
+ const decision = await harness.beforeToolCall(layers, step.tool.name, parseResult.data, baseCtx);
495
+ if (decision.action !== SteeringAction.Allow) {
496
+ throw new NoeticErrorImpl({
497
+ kind: 'steering_denied',
498
+ guidance: decision.guidance,
499
+ });
500
+ }
501
+ }
502
+ try {
503
+ const toolCtx = buildToolExecutionContext(baseCtx, harness);
504
+ const result = step.tool.execute(parseResult.data, toolCtx);
505
+ if (isAsyncGenerator(result)) {
506
+ return frameworkCast(await consumeToolGenerator({
507
+ generator: result,
508
+ stepId: step.id,
509
+ toolName: step.tool.name,
510
+ ctx: baseCtx,
511
+ }));
512
+ }
513
+ return frameworkCast(await result);
514
+ }
515
+ catch (e) {
516
+ if (e instanceof NoeticErrorImpl) {
517
+ throw e;
518
+ }
519
+ throw new NoeticErrorImpl({
520
+ kind: 'step_failed',
521
+ stepId: step.id,
522
+ cause: e instanceof Error ? e : new Error(String(e)),
523
+ retriesExhausted: false,
524
+ });
525
+ }
526
+ }
527
+ //#endregion
@@ -1,5 +1,11 @@
1
- import type { Context } from '../types/context';
2
- import type { ExecuteStepFn, StepEvery } from '../types/step';
1
+ /**
2
+ * Control-flow step handlers: branch, fork, loop, every.
3
+ */
4
+ import type { Context } from '../types/context.js';
5
+ import type { ExecuteStepFn, StepBranch, StepEvery, StepFork, StepLoop } from '../types/step.js';
6
+ export declare function executeBranch<TMemory, I, O>(step: StepBranch<TMemory, I, O>, input: I, ctx: Context<TMemory>, executeStep: ExecuteStepFn): Promise<O>;
7
+ export declare function executeFork<TMemory, I, O>(step: StepFork<TMemory, I, O>, input: I, ctx: Context<TMemory>, executeStep: ExecuteStepFn): Promise<O>;
8
+ export declare function executeLoop<TMemory, I, O>(step: StepLoop<TMemory, I, O>, input: I, ctx: Context<TMemory>, executeStep: ExecuteStepFn): Promise<O>;
3
9
  /**
4
10
  * Executes an `every` step: runs the body step forever, paced by `ms ± jitter`,
5
11
  * woken early by `wakeOn`. Throws `cancelled` when the context is aborted.