@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,530 @@
1
+ /**
2
+ * Control-flow step handlers: branch, fork, loop, every.
3
+ */
4
+ import { isNoeticError, NoeticErrorImpl } from '../errors/noetic-error.js';
5
+ import { ContextImpl } from '../runtime/context-impl.js';
6
+ import { snapshotCwdState } from '../runtime/cwd-helpers.js';
7
+ import { frameworkCast } from '../util/framework-cast.js';
8
+ import { createMessage } from '../util/message-helpers.js';
9
+ import { cloneWithGuard } from './clone-guard.js';
10
+ import { getContextChannelStore, isContextImpl, isMutableContext } from './typeguards.js';
11
+ //#region branch
12
+ export async function executeBranch(step, input, ctx, executeStep) {
13
+ const selected = await step.route(input, ctx);
14
+ if (selected === null) {
15
+ // Requires I assignable to O for null route — when no branch is selected,
16
+ // the input passes through. Callers must ensure I is compatible with O.
17
+ return frameworkCast(input);
18
+ }
19
+ return executeStep(selected, input, ctx);
20
+ }
21
+ //#endregion
22
+ //#region fork
23
+ function createChildContexts(ctx, count, stepId) {
24
+ const threadId = isContextImpl(ctx) ? ctx.threadId : crypto.randomUUID();
25
+ const resourceId = isContextImpl(ctx) ? ctx.resourceId : undefined;
26
+ const channelStore = getContextChannelStore(ctx);
27
+ return Array.from({
28
+ length: count,
29
+ }, () => new ContextImpl({
30
+ harness: ctx.harness,
31
+ parent: ctx,
32
+ items: [
33
+ ...ctx.itemLog.items,
34
+ ],
35
+ state: cloneWithGuard(ctx.state, `Fork '${stepId}'`),
36
+ threadId,
37
+ resourceId,
38
+ channelStore,
39
+ cwdState: snapshotCwdState(ctx),
40
+ }));
41
+ }
42
+ export async function executeFork(step, input, ctx, executeStep) {
43
+ const baseCtx = frameworkCast(ctx);
44
+ const paths = step.paths(input, ctx);
45
+ if (paths.length === 0) {
46
+ if (step.mode === 'all') {
47
+ return step.merge([], ctx);
48
+ }
49
+ if (step.mode === 'settle') {
50
+ return step.merge([], ctx);
51
+ }
52
+ throw new NoeticErrorImpl({
53
+ kind: 'fork_partial',
54
+ stepId: step.id,
55
+ succeeded: [],
56
+ failed: [],
57
+ });
58
+ }
59
+ const childContexts = createChildContexts(baseCtx, paths.length, step.id);
60
+ const concurrency = step.concurrency ?? paths.length;
61
+ switch (step.mode) {
62
+ case 'all':
63
+ return executeAll(step, paths, input, ctx, childContexts, executeStep, concurrency);
64
+ case 'race':
65
+ return executeRace(step, paths, input, ctx, childContexts, executeStep, concurrency);
66
+ case 'settle':
67
+ return executeSettle(step, paths, input, ctx, childContexts, executeStep, concurrency);
68
+ default: {
69
+ const _exhaustive = step;
70
+ throw new NoeticErrorImpl({
71
+ kind: 'step_failed',
72
+ stepId: 'unknown',
73
+ cause: new Error('Unknown fork mode'),
74
+ retriesExhausted: false,
75
+ });
76
+ }
77
+ }
78
+ }
79
+ async function runWithConcurrency(tasks, concurrency) {
80
+ const results = new Array(tasks.length);
81
+ let nextIndex = 0;
82
+ async function runNext() {
83
+ while (nextIndex < tasks.length) {
84
+ const index = nextIndex++;
85
+ try {
86
+ const value = await tasks[index]();
87
+ results[index] = {
88
+ status: 'fulfilled',
89
+ value,
90
+ };
91
+ }
92
+ catch (reason) {
93
+ results[index] = {
94
+ status: 'rejected',
95
+ reason,
96
+ };
97
+ }
98
+ }
99
+ }
100
+ const workers = Array.from({
101
+ length: Math.min(concurrency, tasks.length),
102
+ }, () => runNext());
103
+ await Promise.all(workers);
104
+ return results;
105
+ }
106
+ function toNoeticError(err, stepId) {
107
+ if (isNoeticError(err)) {
108
+ return err.noeticError;
109
+ }
110
+ return {
111
+ kind: 'step_failed',
112
+ stepId,
113
+ cause: err instanceof Error ? err : new Error(String(err)),
114
+ retriesExhausted: false,
115
+ };
116
+ }
117
+ function classifyResults(settled, paths) {
118
+ const succeeded = [];
119
+ const failed = [];
120
+ settled.forEach((result, i) => {
121
+ if (result.status === 'fulfilled') {
122
+ succeeded.push({
123
+ stepId: paths[i].id,
124
+ value: result.value,
125
+ });
126
+ }
127
+ else {
128
+ failed.push({
129
+ stepId: paths[i].id,
130
+ error: toNoeticError(result.reason, paths[i].id),
131
+ });
132
+ }
133
+ });
134
+ return {
135
+ succeeded,
136
+ failed,
137
+ };
138
+ }
139
+ async function executeAll(step, paths, _input, ctx, childContexts, executeStep, concurrency) {
140
+ const tasks = paths.map((path, i) => () => executeStep(path, _input, frameworkCast(childContexts[i])));
141
+ const settled = await runWithConcurrency(tasks, concurrency);
142
+ const { succeeded, failed } = classifyResults(settled, paths);
143
+ if (failed.length > 0) {
144
+ throw new NoeticErrorImpl({
145
+ kind: 'fork_partial',
146
+ stepId: step.id,
147
+ succeeded,
148
+ failed,
149
+ });
150
+ }
151
+ const results = succeeded.map((s) => s.value);
152
+ return step.merge(results, ctx);
153
+ }
154
+ async function executeRace(step, paths, input, ctx, childContexts, executeStep, concurrency) {
155
+ const raceBaseCtx = frameworkCast(ctx);
156
+ return new Promise((resolve, reject) => {
157
+ let settled = false;
158
+ let failedCount = 0;
159
+ let nextIndex = 0;
160
+ const errors = [];
161
+ const totalPaths = paths.length;
162
+ function startNext() {
163
+ if (settled || nextIndex >= totalPaths) {
164
+ return;
165
+ }
166
+ const i = nextIndex++;
167
+ executeStep(paths[i], input, frameworkCast(childContexts[i]))
168
+ .then((result) => {
169
+ if (settled) {
170
+ return;
171
+ }
172
+ settled = true;
173
+ // Winner's state replaces parent's
174
+ if (isContextImpl(raceBaseCtx)) {
175
+ raceBaseCtx.state = childContexts[i].state;
176
+ }
177
+ // Resolve first, then abort losers (non-critical path)
178
+ resolve(result);
179
+ for (let j = 0; j < childContexts.length; j++) {
180
+ if (j !== i) {
181
+ childContexts[j].abort('race lost');
182
+ }
183
+ }
184
+ })
185
+ .catch((err) => {
186
+ failedCount++;
187
+ errors.push({
188
+ stepId: paths[i].id,
189
+ error: toNoeticError(err, paths[i].id),
190
+ });
191
+ // Start next task if available
192
+ if (!settled) {
193
+ startNext();
194
+ }
195
+ if (failedCount === totalPaths && !settled) {
196
+ settled = true;
197
+ reject(new NoeticErrorImpl({
198
+ kind: 'fork_partial',
199
+ stepId: step.id,
200
+ succeeded: [],
201
+ failed: errors,
202
+ }));
203
+ }
204
+ });
205
+ }
206
+ // Launch initial batch respecting concurrency
207
+ const initialBatch = Math.min(concurrency, totalPaths);
208
+ for (let i = 0; i < initialBatch; i++) {
209
+ startNext();
210
+ }
211
+ });
212
+ }
213
+ async function executeSettle(step, paths, input, _ctx, childContexts, executeStep, concurrency) {
214
+ const tasks = paths.map((path, i) => () => executeStep(path, input, frameworkCast(childContexts[i])));
215
+ const settled = await runWithConcurrency(tasks, concurrency);
216
+ const results = settled.map((result, i) => {
217
+ if (result.status === 'fulfilled') {
218
+ return {
219
+ stepId: paths[i].id,
220
+ status: 'fulfilled',
221
+ value: result.value,
222
+ };
223
+ }
224
+ return {
225
+ stepId: paths[i].id,
226
+ status: 'rejected',
227
+ error: toNoeticError(result.reason, paths[i].id),
228
+ };
229
+ });
230
+ return step.merge(results, _ctx);
231
+ }
232
+ function hasTextField(value) {
233
+ return typeof value === 'object' && value !== null && 'text' in value;
234
+ }
235
+ async function recvInboxWithTimeout(ctx, step) {
236
+ if (!step.inbox) {
237
+ return null;
238
+ }
239
+ if ((step.parkTimeout ?? 0) <= 0) {
240
+ return ctx.tryRecv(step.inbox);
241
+ }
242
+ try {
243
+ return await ctx.recv(step.inbox, {
244
+ timeout: step.parkTimeout,
245
+ });
246
+ }
247
+ catch {
248
+ // Expected: channel_timeout error when parkTimeout expires with no message.
249
+ return null;
250
+ }
251
+ }
252
+ function prepareNextInput(step, lastOutput, verdict, ctx) {
253
+ if (step.prepareNext) {
254
+ return step.prepareNext(lastOutput, verdict, ctx);
255
+ }
256
+ // Requires I === O when prepareNext is omitted — the loop feeds output
257
+ // back as input. Callers must ensure I and O are compatible types.
258
+ return frameworkCast(lastOutput);
259
+ }
260
+ export async function executeLoop(step, input, ctx, executeStep) {
261
+ const baseCtx = frameworkCast(ctx);
262
+ let currentInput = input;
263
+ let lastOutput;
264
+ let lastText = '';
265
+ const history = [];
266
+ const startTime = Date.now();
267
+ let stepCount = 0;
268
+ const maxIterations = step.maxIterations ?? 1e3;
269
+ const maxHistory = step.maxHistorySize ?? 100;
270
+ let totalIterations = 0;
271
+ // Validate maxIterations
272
+ if (!Number.isFinite(maxIterations) || maxIterations < 1) {
273
+ throw new NoeticErrorImpl({
274
+ kind: 'step_failed',
275
+ stepId: step.id,
276
+ cause: new Error(`Invalid maxIterations: ${step.maxIterations}`),
277
+ retriesExhausted: false,
278
+ });
279
+ }
280
+ while (true) {
281
+ // Abort check at top of each iteration
282
+ if (ctx.aborted) {
283
+ throw new NoeticErrorImpl({
284
+ kind: 'cancelled',
285
+ reason: ctx.abortReason ?? 'context aborted',
286
+ });
287
+ }
288
+ // Enforce hard iteration ceiling (includes retries)
289
+ totalIterations++;
290
+ if (totalIterations > maxIterations) {
291
+ throw new NoeticErrorImpl({
292
+ kind: 'step_failed',
293
+ stepId: step.id,
294
+ cause: new Error(`Loop exceeded maximum iterations (${maxIterations})`),
295
+ retriesExhausted: false,
296
+ });
297
+ }
298
+ // Execute body steps sequentially
299
+ let output;
300
+ try {
301
+ let stepOutput = currentInput;
302
+ for (const bodyStep of step.steps) {
303
+ stepOutput = await executeStep(bodyStep, frameworkCast(stepOutput), ctx);
304
+ }
305
+ output = frameworkCast(stepOutput);
306
+ stepCount++;
307
+ }
308
+ catch (e) {
309
+ if (!step.onError || !isNoeticError(e)) {
310
+ throw e;
311
+ }
312
+ const action = step.onError(e.noeticError, ctx);
313
+ if (action === 'retry') {
314
+ continue;
315
+ }
316
+ if (action !== 'skip') {
317
+ throw e;
318
+ }
319
+ // Skip does not increment stepCount — it is not a successful execution.
320
+ if (lastOutput === undefined) {
321
+ continue;
322
+ }
323
+ output = lastOutput;
324
+ }
325
+ lastOutput = output;
326
+ history.push(output);
327
+ // Trim history if it exceeds maxHistorySize
328
+ if (history.length > maxHistory) {
329
+ history.splice(0, history.length - maxHistory);
330
+ }
331
+ // Extract text from output for snapshot
332
+ if (typeof output === 'string') {
333
+ lastText = output;
334
+ }
335
+ else if (hasTextField(output)) {
336
+ lastText = String(output.text);
337
+ }
338
+ else {
339
+ lastText = output === undefined ? '' : JSON.stringify(output);
340
+ }
341
+ // Build snapshot
342
+ const snapshot = {
343
+ stepCount,
344
+ tokens: {
345
+ ...ctx.tokens,
346
+ },
347
+ elapsed: Date.now() - startTime,
348
+ cost: ctx.cost,
349
+ lastOutput: output,
350
+ lastText,
351
+ history: [
352
+ ...history,
353
+ ],
354
+ depth: ctx.depth,
355
+ lastStepMeta: isMutableContext(baseCtx) ? baseCtx.lastStepMeta : null,
356
+ };
357
+ // Evaluate until predicate
358
+ let verdict;
359
+ try {
360
+ verdict = await step.until(snapshot);
361
+ }
362
+ catch (predicateError) {
363
+ // Per spec: if until predicate throws, treat as stop
364
+ verdict = {
365
+ stop: true,
366
+ reason: `Predicate error: ${predicateError instanceof Error ? predicateError.message : String(predicateError)}`,
367
+ };
368
+ }
369
+ if (verdict.stop) {
370
+ if (lastOutput === undefined) {
371
+ throw new NoeticErrorImpl({
372
+ kind: 'step_failed',
373
+ stepId: step.id,
374
+ cause: new Error('Loop completed with no successful output'),
375
+ retriesExhausted: false,
376
+ });
377
+ }
378
+ // Check inbox before truly stopping
379
+ if (step.inbox) {
380
+ const inboxMessage = await recvInboxWithTimeout(baseCtx, step);
381
+ if (inboxMessage !== null) {
382
+ baseCtx.itemLog.append(createMessage(inboxMessage, 'developer'));
383
+ // Continue the loop — don't stop
384
+ currentInput = prepareNextInput(step, lastOutput, verdict, ctx);
385
+ continue;
386
+ }
387
+ }
388
+ return lastOutput;
389
+ }
390
+ // Prepare input for next iteration
391
+ currentInput = prepareNextInput(step, output, verdict, ctx);
392
+ }
393
+ }
394
+ /** Returns the next park duration in ms, applying random jitter clamped to `[ms - jitter, ms + jitter]`. */
395
+ function nextParkMs(ms, jitter) {
396
+ if (jitter <= 0) {
397
+ return ms;
398
+ }
399
+ // Math.random() returns [0, 1); shift to [-1, 1) so jitter is symmetric.
400
+ const offset = (Math.random() * 2 - 1) * jitter;
401
+ const value = ms + offset;
402
+ if (value < 0) {
403
+ return 0;
404
+ }
405
+ return value;
406
+ }
407
+ /**
408
+ * Parks for `ms` ms, returning early if `wakeOn` receives a message or the
409
+ * context is aborted. Always resolves; never throws (cancellation surfaces on
410
+ * the next iteration's abort check).
411
+ */
412
+ function park(parkCtx) {
413
+ const duration = nextParkMs(parkCtx.ms, parkCtx.jitter);
414
+ return new Promise((resolve) => {
415
+ let settled = false;
416
+ let timer = null;
417
+ let abortPoll = null;
418
+ let wakeUnsub = null;
419
+ const settle = () => {
420
+ if (settled) {
421
+ return;
422
+ }
423
+ settled = true;
424
+ if (timer !== null) {
425
+ clearTimeout(timer);
426
+ }
427
+ if (abortPoll !== null) {
428
+ clearInterval(abortPoll);
429
+ }
430
+ if (wakeUnsub !== null) {
431
+ wakeUnsub();
432
+ }
433
+ resolve();
434
+ };
435
+ timer = setTimeout(settle, duration);
436
+ // Poll the abort flag at a coarse interval to wake parking when the
437
+ // context is aborted. The Context interface exposes only `aborted` as a
438
+ // boolean, so this is the simplest interruption strategy that respects
439
+ // the public contract.
440
+ abortPoll = setInterval(() => {
441
+ if (parkCtx.ctx.aborted) {
442
+ settle();
443
+ }
444
+ }, 5);
445
+ if (!parkCtx.wakeOn) {
446
+ return;
447
+ }
448
+ const { channelStore } = parkCtx;
449
+ if (channelStore) {
450
+ // Non-consuming subscription so the body still observes the message that
451
+ // woke us — `recv()` would dequeue queue-mode messages and leave the
452
+ // body's drain loop empty.
453
+ wakeUnsub = channelStore.subscribeWake(parkCtx.wakeOn, settle);
454
+ return;
455
+ }
456
+ // Contexts without a ContextImpl/channel store have no body draining the
457
+ // channel anyway; the destructive `recv` is acceptable in that edge case.
458
+ parkCtx.ctx
459
+ .recv(parkCtx.wakeOn, {
460
+ timeout: Math.max(duration, 1) + 1e2,
461
+ })
462
+ .then(settle)
463
+ .catch(() => {
464
+ // Channel timeout / store error — another path settles us.
465
+ });
466
+ });
467
+ }
468
+ function recordIterationError(ctx, error) {
469
+ const message = error instanceof Error ? error.message : String(error);
470
+ const stack = error instanceof Error && error.stack ? error.stack : '';
471
+ ctx.span.addEvent('every.iteration.error', {
472
+ message,
473
+ stack,
474
+ });
475
+ }
476
+ function throwCancelled(reason) {
477
+ throw new NoeticErrorImpl({
478
+ kind: 'cancelled',
479
+ reason: reason ?? 'context aborted',
480
+ });
481
+ }
482
+ /**
483
+ * Executes an `every` step: runs the body step forever, paced by `ms ± jitter`,
484
+ * woken early by `wakeOn`. Throws `cancelled` when the context is aborted.
485
+ *
486
+ * On body throw, behavior depends on `onError`:
487
+ * - `'continue'` (default): emit `every.iteration.error` span event and proceed
488
+ * to the park step as if no error occurred.
489
+ * - `'fail'`: re-throw, terminating the operator.
490
+ *
491
+ * The body's output is discarded — `every` runs forever and does not accumulate
492
+ * iteration outputs. Only ever returns by throwing on cancellation or `fail`.
493
+ */
494
+ export async function executeEvery(step, input, ctx, executeStep) {
495
+ const onError = step.onError ?? 'continue';
496
+ const jitter = step.jitter ?? 0;
497
+ // Resolve once — the channel store reference is stable for the lifetime
498
+ // of the every loop, no need to re-derive it per park.
499
+ const channelStore = getContextChannelStore(ctx);
500
+ while (true) {
501
+ if (ctx.aborted) {
502
+ throwCancelled(ctx.abortReason);
503
+ }
504
+ try {
505
+ await executeStep(step.step, input, ctx);
506
+ }
507
+ catch (e) {
508
+ if (onError === 'fail') {
509
+ throw e;
510
+ }
511
+ // 'continue' policy — but a cancellation should still terminate the
512
+ // operator promptly rather than be swallowed and re-parked.
513
+ if (isNoeticError(e) && e.noeticError.kind === 'cancelled') {
514
+ throw e;
515
+ }
516
+ recordIterationError(ctx, e);
517
+ }
518
+ if (ctx.aborted) {
519
+ throwCancelled(ctx.abortReason);
520
+ }
521
+ await park({
522
+ ms: step.ms,
523
+ jitter,
524
+ wakeOn: step.wakeOn,
525
+ ctx,
526
+ channelStore,
527
+ });
528
+ }
529
+ }
530
+ //#endregion
@@ -1,6 +1,19 @@
1
- import type { Context } from '../types/context';
2
- import type { ContextMemory } from '../types/memory';
3
- import type { Step } from '../types/step';
1
+ import type { Context } from '../types/context.js';
2
+ import type { ContextMemory } from '../types/memory.js';
3
+ import type { Step } from '../types/step.js';
4
+ /**
5
+ * @internal
6
+ * Dispatch a step at the per-kind handler level without adapter routing.
7
+ *
8
+ * Used as the top-level `_localExecutor` for `StepRun` / `StepSpawn`
9
+ * dispatches: the outer adapter call already recorded the request, so
10
+ * running the handler directly avoids a re-entrant adapter round-trip
11
+ * inside the same logical step. Nested step dispatches (e.g. `executeSpawn`
12
+ * descending into `step.child`) still go through `execute()` and route
13
+ * through the adapter as normal, so per-step overrides on descendants
14
+ * continue to work.
15
+ */
16
+ export declare function executeNoAdapter<TMemory, I, O>(step: Step<TMemory, I, O>, input: I, ctx: Context<TMemory>): Promise<O>;
4
17
  /**
5
18
  * Executes a step within the interpreter, dispatching to the appropriate handler by step kind.
6
19
  *