@noetic-tools/core 0.0.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 (207) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +38 -0
  3. package/dist/adapters/in-memory-fs-adapter.d.ts +3 -0
  4. package/dist/adapters/in-memory-fs-adapter.js +245 -0
  5. package/dist/adapters/in-memory-shell-adapter.d.ts +9 -0
  6. package/dist/adapters/in-memory-shell-adapter.js +20 -0
  7. package/dist/adapters/in-memory-subprocess-adapter.d.ts +5 -0
  8. package/dist/adapters/in-memory-subprocess-adapter.js +126 -0
  9. package/dist/adapters/local-fs-adapter.d.ts +3 -0
  10. package/dist/adapters/local-fs-adapter.js +43 -0
  11. package/dist/adapters/local-shell-adapter.d.ts +51 -0
  12. package/dist/adapters/local-shell-adapter.js +167 -0
  13. package/dist/adapters/local-subprocess-adapter.d.ts +16 -0
  14. package/dist/adapters/local-subprocess-adapter.js +187 -0
  15. package/dist/adapters/openrouter.d.ts +57 -0
  16. package/dist/adapters/openrouter.js +248 -0
  17. package/dist/builders/channel-builder.d.ts +36 -0
  18. package/dist/builders/channel-builder.js +15 -0
  19. package/dist/builders/control-flow-builders.d.ts +79 -0
  20. package/dist/builders/control-flow-builders.js +53 -0
  21. package/dist/builders/every.d.ts +45 -0
  22. package/dist/builders/every.js +60 -0
  23. package/dist/builders/layer-provides-builders.d.ts +32 -0
  24. package/dist/builders/layer-provides-builders.js +33 -0
  25. package/dist/builders/loop-builder.d.ts +23 -0
  26. package/dist/builders/loop-builder.js +46 -0
  27. package/dist/builders/memory-builder.d.ts +11 -0
  28. package/dist/builders/memory-builder.js +15 -0
  29. package/dist/builders/provide-builder.d.ts +20 -0
  30. package/dist/builders/provide-builder.js +34 -0
  31. package/dist/builders/spawn-builder.d.ts +20 -0
  32. package/dist/builders/spawn-builder.js +33 -0
  33. package/dist/builders/step-builders.d.ts +62 -0
  34. package/dist/builders/step-builders.js +99 -0
  35. package/dist/builders/tool-builder.d.ts +52 -0
  36. package/dist/builders/tool-builder.js +59 -0
  37. package/dist/conditions/conditions.d.ts +114 -0
  38. package/dist/conditions/conditions.js +271 -0
  39. package/dist/conditions/cosine-similarity.d.ts +11 -0
  40. package/dist/conditions/cosine-similarity.js +28 -0
  41. package/dist/conditions/index.d.ts +3 -0
  42. package/dist/conditions/index.js +2 -0
  43. package/dist/errors/noetic-config-error.d.ts +41 -0
  44. package/dist/errors/noetic-config-error.js +44 -0
  45. package/dist/errors/noetic-error.d.ts +18 -0
  46. package/dist/errors/noetic-error.js +52 -0
  47. package/dist/index.d.ts +152 -0
  48. package/dist/index.js +112 -0
  49. package/dist/internal-test.d.ts +11 -0
  50. package/dist/internal-test.js +11 -0
  51. package/dist/interpreter/clone-guard.d.ts +4 -0
  52. package/dist/interpreter/clone-guard.js +18 -0
  53. package/dist/interpreter/collect-tools.d.ts +12 -0
  54. package/dist/interpreter/collect-tools.js +70 -0
  55. package/dist/interpreter/execute-branch.d.ts +3 -0
  56. package/dist/interpreter/execute-branch.js +10 -0
  57. package/dist/interpreter/execute-every.d.ts +15 -0
  58. package/dist/interpreter/execute-every.js +143 -0
  59. package/dist/interpreter/execute-fork.d.ts +3 -0
  60. package/dist/interpreter/execute-fork.js +215 -0
  61. package/dist/interpreter/execute-llm.d.ts +4 -0
  62. package/dist/interpreter/execute-llm.js +217 -0
  63. package/dist/interpreter/execute-loop.d.ts +3 -0
  64. package/dist/interpreter/execute-loop.js +171 -0
  65. package/dist/interpreter/execute-provide.d.ts +10 -0
  66. package/dist/interpreter/execute-provide.js +64 -0
  67. package/dist/interpreter/execute-run.d.ts +3 -0
  68. package/dist/interpreter/execute-run.js +39 -0
  69. package/dist/interpreter/execute-spawn.d.ts +11 -0
  70. package/dist/interpreter/execute-spawn.js +113 -0
  71. package/dist/interpreter/execute-tool.d.ts +5 -0
  72. package/dist/interpreter/execute-tool.js +81 -0
  73. package/dist/interpreter/execute.d.ts +14 -0
  74. package/dist/interpreter/execute.js +119 -0
  75. package/dist/interpreter/framework-cast.d.ts +10 -0
  76. package/dist/interpreter/framework-cast.js +16 -0
  77. package/dist/interpreter/message-helpers.d.ts +9 -0
  78. package/dist/interpreter/message-helpers.js +42 -0
  79. package/dist/interpreter/typeguards.d.ts +25 -0
  80. package/dist/interpreter/typeguards.js +50 -0
  81. package/dist/memory/budget.d.ts +23 -0
  82. package/dist/memory/budget.js +117 -0
  83. package/dist/memory/function-call-utils.d.ts +6 -0
  84. package/dist/memory/function-call-utils.js +25 -0
  85. package/dist/memory/layer-api.d.ts +14 -0
  86. package/dist/memory/layer-api.js +91 -0
  87. package/dist/memory/layer-lifecycle.d.ts +160 -0
  88. package/dist/memory/layer-lifecycle.js +552 -0
  89. package/dist/memory/layer-usage.d.ts +15 -0
  90. package/dist/memory/layer-usage.js +48 -0
  91. package/dist/memory/layers/durable-task-state.d.ts +79 -0
  92. package/dist/memory/layers/durable-task-state.js +118 -0
  93. package/dist/memory/layers/file-reference.d.ts +55 -0
  94. package/dist/memory/layers/file-reference.js +538 -0
  95. package/dist/memory/layers/history-window.d.ts +29 -0
  96. package/dist/memory/layers/history-window.js +114 -0
  97. package/dist/memory/layers/observational-memory.d.ts +53 -0
  98. package/dist/memory/layers/observational-memory.js +110 -0
  99. package/dist/memory/layers/plan.d.ts +55 -0
  100. package/dist/memory/layers/plan.js +369 -0
  101. package/dist/memory/layers/static-content.d.ts +27 -0
  102. package/dist/memory/layers/static-content.js +35 -0
  103. package/dist/memory/layers/steering.d.ts +51 -0
  104. package/dist/memory/layers/steering.js +258 -0
  105. package/dist/memory/layers/tool-memory-layer.d.ts +16 -0
  106. package/dist/memory/layers/tool-memory-layer.js +43 -0
  107. package/dist/memory/layers/working-memory.d.ts +44 -0
  108. package/dist/memory/layers/working-memory.js +92 -0
  109. package/dist/memory/projector.d.ts +10 -0
  110. package/dist/memory/projector.js +19 -0
  111. package/dist/memory/scope.d.ts +3 -0
  112. package/dist/memory/scope.js +31 -0
  113. package/dist/memory/strip-unresolved.d.ts +13 -0
  114. package/dist/memory/strip-unresolved.js +35 -0
  115. package/dist/observability/genai-attributes.d.ts +11 -0
  116. package/dist/observability/genai-attributes.js +12 -0
  117. package/dist/observability/span-impl.d.ts +20 -0
  118. package/dist/observability/span-impl.js +33 -0
  119. package/dist/observability/trace-exporter.d.ts +15 -0
  120. package/dist/observability/trace-exporter.js +26 -0
  121. package/dist/patterns/flow.d.ts +52 -0
  122. package/dist/patterns/flow.js +103 -0
  123. package/dist/patterns/interview.d.ts +53 -0
  124. package/dist/patterns/interview.js +113 -0
  125. package/dist/patterns/plans.d.ts +45 -0
  126. package/dist/patterns/plans.js +125 -0
  127. package/dist/patterns/ralph-wiggum.d.ts +20 -0
  128. package/dist/patterns/ralph-wiggum.js +37 -0
  129. package/dist/patterns/react.d.ts +18 -0
  130. package/dist/patterns/react.js +39 -0
  131. package/dist/portable.d.ts +26 -0
  132. package/dist/portable.js +34 -0
  133. package/dist/runtime/agent-harness.d.ts +206 -0
  134. package/dist/runtime/agent-harness.js +1170 -0
  135. package/dist/runtime/broadcaster-utils.d.ts +30 -0
  136. package/dist/runtime/broadcaster-utils.js +53 -0
  137. package/dist/runtime/channel-store.d.ts +18 -0
  138. package/dist/runtime/channel-store.js +187 -0
  139. package/dist/runtime/context-impl.d.ts +80 -0
  140. package/dist/runtime/context-impl.js +140 -0
  141. package/dist/runtime/cwd-helpers.d.ts +42 -0
  142. package/dist/runtime/cwd-helpers.js +68 -0
  143. package/dist/runtime/detached-handle.d.ts +14 -0
  144. package/dist/runtime/detached-handle.js +56 -0
  145. package/dist/runtime/event-broadcaster.d.ts +61 -0
  146. package/dist/runtime/event-broadcaster.js +187 -0
  147. package/dist/runtime/exec-context-factory.d.ts +25 -0
  148. package/dist/runtime/exec-context-factory.js +30 -0
  149. package/dist/runtime/in-memory-storage.d.ts +9 -0
  150. package/dist/runtime/in-memory-storage.js +33 -0
  151. package/dist/runtime/item-log-impl.d.ts +11 -0
  152. package/dist/runtime/item-log-impl.js +25 -0
  153. package/dist/runtime/message-queue.d.ts +30 -0
  154. package/dist/runtime/message-queue.js +46 -0
  155. package/dist/runtime/session-runner.d.ts +77 -0
  156. package/dist/runtime/session-runner.js +253 -0
  157. package/dist/runtime/session-streams.d.ts +11 -0
  158. package/dist/runtime/session-streams.js +204 -0
  159. package/dist/runtime/tool-memory.d.ts +6 -0
  160. package/dist/runtime/tool-memory.js +22 -0
  161. package/dist/schemas/item.d.ts +35 -0
  162. package/dist/schemas/item.js +155 -0
  163. package/dist/types/channel.d.ts +29 -0
  164. package/dist/types/channel.js +1 -0
  165. package/dist/types/common.d.ts +111 -0
  166. package/dist/types/common.js +1 -0
  167. package/dist/types/context.d.ts +93 -0
  168. package/dist/types/context.js +1 -0
  169. package/dist/types/detached.d.ts +17 -0
  170. package/dist/types/detached.js +7 -0
  171. package/dist/types/embed.d.ts +5 -0
  172. package/dist/types/embed.js +1 -0
  173. package/dist/types/error.d.ts +51 -0
  174. package/dist/types/error.js +1 -0
  175. package/dist/types/fs-adapter.d.ts +51 -0
  176. package/dist/types/fs-adapter.js +3 -0
  177. package/dist/types/harness-result.d.ts +43 -0
  178. package/dist/types/harness-result.js +2 -0
  179. package/dist/types/items.d.ts +115 -0
  180. package/dist/types/items.js +2 -0
  181. package/dist/types/memory.d.ts +336 -0
  182. package/dist/types/memory.js +13 -0
  183. package/dist/types/mutable-context.d.ts +13 -0
  184. package/dist/types/mutable-context.js +1 -0
  185. package/dist/types/observability.d.ts +30 -0
  186. package/dist/types/observability.js +1 -0
  187. package/dist/types/runtime.d.ts +255 -0
  188. package/dist/types/runtime.js +1 -0
  189. package/dist/types/shell-adapter.d.ts +34 -0
  190. package/dist/types/shell-adapter.js +10 -0
  191. package/dist/types/steering.d.ts +89 -0
  192. package/dist/types/steering.js +14 -0
  193. package/dist/types/step.d.ts +206 -0
  194. package/dist/types/step.js +1 -0
  195. package/dist/types/subprocess-adapter.d.ts +41 -0
  196. package/dist/types/subprocess-adapter.js +1 -0
  197. package/dist/types/tool-context.d.ts +34 -0
  198. package/dist/types/tool-context.js +1 -0
  199. package/dist/unstable.d.ts +28 -0
  200. package/dist/unstable.js +24 -0
  201. package/dist/until/combinators.d.ts +17 -0
  202. package/dist/until/combinators.js +49 -0
  203. package/dist/until/never.d.ts +9 -0
  204. package/dist/until/never.js +12 -0
  205. package/dist/until/predicates.d.ts +43 -0
  206. package/dist/until/predicates.js +126 -0
  207. package/package.json +81 -0
@@ -0,0 +1,30 @@
1
+ import type { Context } from '../types/context';
2
+ import type { EventBroadcaster } from './event-broadcaster';
3
+ /** @internal Option controlling framework event emission on a step or callModel request. */
4
+ export type EmitOption = boolean | ((eventType: string, data: Record<string, unknown>) => boolean);
5
+ /**
6
+ * Check whether a framework event should be emitted given an emit option.
7
+ *
8
+ * When `emit` is `undefined` (the default for non-LLM steps where the
9
+ * field does not exist on the Step type), events are always emitted.
10
+ * Only StepLLM exposes `emit` for opt-out or filtering.
11
+ *
12
+ * @internal
13
+ */
14
+ export declare function shouldEmit(emit: EmitOption | undefined, eventType: string, data: Record<string, unknown>): boolean;
15
+ /**
16
+ * Get the EventBroadcaster from a context if available.
17
+ * Uses property check rather than instanceof to support mock contexts in tests.
18
+ * @internal
19
+ */
20
+ export declare function getBroadcaster(ctx?: Context): EventBroadcaster | undefined;
21
+ /**
22
+ * Emit a framework event to the broadcaster if available.
23
+ * @internal
24
+ */
25
+ export declare function emitFrameworkEvent(opts: {
26
+ broadcaster: EventBroadcaster | undefined;
27
+ agentName: string;
28
+ eventType: string;
29
+ data: Record<string, unknown>;
30
+ }): void;
@@ -0,0 +1,53 @@
1
+ //#endregion
2
+ //#region Public API
3
+ /**
4
+ * Check whether a framework event should be emitted given an emit option.
5
+ *
6
+ * When `emit` is `undefined` (the default for non-LLM steps where the
7
+ * field does not exist on the Step type), events are always emitted.
8
+ * Only StepLLM exposes `emit` for opt-out or filtering.
9
+ *
10
+ * @internal
11
+ */
12
+ export function shouldEmit(emit, eventType, data) {
13
+ if (emit === undefined || emit === true) {
14
+ return true;
15
+ }
16
+ if (emit === false) {
17
+ return false;
18
+ }
19
+ return emit(eventType, data);
20
+ }
21
+ /** Typeguard: context carries an internal _broadcaster property. */
22
+ function hasBroadcaster(ctx) {
23
+ if (!('_broadcaster' in ctx)) {
24
+ return false;
25
+ }
26
+ return typeof ctx._broadcaster === 'object' && ctx._broadcaster !== null;
27
+ }
28
+ /**
29
+ * Get the EventBroadcaster from a context if available.
30
+ * Uses property check rather than instanceof to support mock contexts in tests.
31
+ * @internal
32
+ */
33
+ export function getBroadcaster(ctx) {
34
+ if (!ctx) {
35
+ return undefined;
36
+ }
37
+ if (hasBroadcaster(ctx)) {
38
+ return ctx._broadcaster;
39
+ }
40
+ return undefined;
41
+ }
42
+ /**
43
+ * Emit a framework event to the broadcaster if available.
44
+ * @internal
45
+ */
46
+ export function emitFrameworkEvent(opts) {
47
+ opts.broadcaster?.emit({
48
+ source: 'framework',
49
+ type: `${opts.agentName}:${opts.eventType}`,
50
+ data: opts.data,
51
+ });
52
+ }
53
+ //#endregion
@@ -0,0 +1,18 @@
1
+ import type { Channel, ChannelHandle, ExternalChannel } from '../types/channel';
2
+ export declare class ChannelStore {
3
+ private channels;
4
+ private closedExecutions;
5
+ private getOrCreate;
6
+ /**
7
+ * Subscribe to wake notifications on a channel without consuming any value.
8
+ * Fired once on the next `send()` for any mode, then auto-removed. Returns
9
+ * an `unsubscribe` so the caller can detach if it cancels first.
10
+ */
11
+ subscribeWake<T>(channel: Channel<T>, callback: () => void): () => void;
12
+ send<T>(channel: Channel<T>, value: T): void;
13
+ recv<T>(channel: Channel<T>, timeout?: number): Promise<T>;
14
+ tryRecv<T>(channel: Channel<T>): T | null;
15
+ private waitWithTimeout;
16
+ getHandle<T>(channel: ExternalChannel<T>, executionId: string): ChannelHandle<T>;
17
+ closeExecution(executionId: string): void;
18
+ }
@@ -0,0 +1,187 @@
1
+ import { NoeticErrorImpl } from '../errors/noetic-error';
2
+ import { frameworkCast } from '../interpreter/framework-cast';
3
+ function isExternalChannel(ch) {
4
+ return 'external' in ch && ch.external === true;
5
+ }
6
+ const MAX_TOPIC_TIMEOUT = 300_000; // 5 minutes
7
+ export class ChannelStore {
8
+ channels = new Map();
9
+ closedExecutions = new Set();
10
+ getOrCreate(channel) {
11
+ let state = frameworkCast(this.channels.get(channel.name));
12
+ if (!state) {
13
+ state = {
14
+ mode: channel.mode,
15
+ hasValue: false,
16
+ valueWaiters: [],
17
+ queue: [],
18
+ capacity: channel.capacity ?? 1_000,
19
+ queueWaiters: [],
20
+ topicSubscribers: new Set(),
21
+ wakeSubscribers: new Set(),
22
+ };
23
+ this.channels.set(channel.name, frameworkCast(state));
24
+ }
25
+ return state;
26
+ }
27
+ /**
28
+ * Subscribe to wake notifications on a channel without consuming any value.
29
+ * Fired once on the next `send()` for any mode, then auto-removed. Returns
30
+ * an `unsubscribe` so the caller can detach if it cancels first.
31
+ */
32
+ subscribeWake(channel, callback) {
33
+ const state = this.getOrCreate(channel);
34
+ // Capture the set the callback is added to. `send()` swaps in a fresh set
35
+ // before firing, so once a wake fires the captured `subscribers` is the
36
+ // *old* set — this unsubscribe is then a no-op, which is correct.
37
+ const subscribers = state.wakeSubscribers;
38
+ subscribers.add(callback);
39
+ return () => {
40
+ subscribers.delete(callback);
41
+ };
42
+ }
43
+ send(channel, value) {
44
+ const state = this.getOrCreate(channel);
45
+ switch (state.mode) {
46
+ case 'value':
47
+ state.currentValue = value;
48
+ state.hasValue = true;
49
+ if (state.valueWaiters.length > 0) {
50
+ state.valueWaiters.shift().resolve(value);
51
+ }
52
+ break;
53
+ case 'queue':
54
+ if (state.queueWaiters.length > 0) {
55
+ state.queueWaiters.shift().resolve(value);
56
+ }
57
+ else if (state.queue.length < state.capacity) {
58
+ state.queue.push(value);
59
+ }
60
+ else {
61
+ // At capacity
62
+ console.warn(`[noetic] Channel '${channel.name}': queue at capacity (${state.capacity}), dropping message.`);
63
+ if (isExternalChannel(channel)) {
64
+ state.queue.shift();
65
+ state.queue.push(value);
66
+ }
67
+ // For internal, drop the new value
68
+ }
69
+ break;
70
+ case 'topic':
71
+ for (const sub of state.topicSubscribers) {
72
+ sub(value);
73
+ }
74
+ break;
75
+ }
76
+ if (state.wakeSubscribers.size > 0) {
77
+ // Swap in a fresh set before firing so a callback that re-subscribes
78
+ // (typical for `every` re-arming on its next iteration) lands on the
79
+ // new set and survives until the next send.
80
+ const wakers = state.wakeSubscribers;
81
+ state.wakeSubscribers = new Set();
82
+ for (const wake of wakers) {
83
+ wake();
84
+ }
85
+ }
86
+ }
87
+ async recv(channel, timeout = 30_000) {
88
+ const state = this.getOrCreate(channel);
89
+ switch (state.mode) {
90
+ case 'value':
91
+ if (state.hasValue) {
92
+ return state.currentValue;
93
+ }
94
+ return this.waitWithTimeout(state.valueWaiters, channel.name, timeout);
95
+ case 'queue':
96
+ if (state.queue.length > 0) {
97
+ return state.queue.shift();
98
+ }
99
+ return this.waitWithTimeout(state.queueWaiters, channel.name, timeout);
100
+ case 'topic': {
101
+ // Clamp timeout to prevent indefinite subscriber leaks
102
+ let effectiveTimeout = timeout;
103
+ if (effectiveTimeout <= 0) {
104
+ console.warn(`[noetic] Channel '${channel.name}': topic recv with non-positive timeout, clamping to ${MAX_TOPIC_TIMEOUT}ms`);
105
+ effectiveTimeout = MAX_TOPIC_TIMEOUT;
106
+ }
107
+ return new Promise((resolve, reject) => {
108
+ const timer = setTimeout(() => {
109
+ state.topicSubscribers.delete(handler);
110
+ reject(new NoeticErrorImpl({
111
+ kind: 'channel_timeout',
112
+ channelName: channel.name,
113
+ timeout: effectiveTimeout,
114
+ }));
115
+ }, effectiveTimeout);
116
+ const handler = (value) => {
117
+ clearTimeout(timer);
118
+ state.topicSubscribers.delete(handler);
119
+ resolve(value);
120
+ };
121
+ state.topicSubscribers.add(handler);
122
+ });
123
+ }
124
+ }
125
+ }
126
+ tryRecv(channel) {
127
+ const state = this.getOrCreate(channel);
128
+ switch (state.mode) {
129
+ case 'value':
130
+ return state.hasValue ? state.currentValue : null;
131
+ case 'queue':
132
+ return state.queue.length > 0 ? state.queue.shift() : null;
133
+ case 'topic':
134
+ return null;
135
+ }
136
+ }
137
+ waitWithTimeout(waiters, channelName, timeout) {
138
+ return new Promise((resolve, reject) => {
139
+ let timer = null;
140
+ const wrappedResolve = (v) => {
141
+ if (timer) {
142
+ clearTimeout(timer);
143
+ }
144
+ resolve(v);
145
+ };
146
+ const entry = {
147
+ resolve: wrappedResolve,
148
+ reject,
149
+ };
150
+ waiters.push(entry);
151
+ if (timeout > 0) {
152
+ timer = setTimeout(() => {
153
+ const idx = waiters.indexOf(entry);
154
+ if (idx >= 0) {
155
+ waiters.splice(idx, 1);
156
+ reject(new NoeticErrorImpl({
157
+ kind: 'channel_timeout',
158
+ channelName,
159
+ timeout,
160
+ }));
161
+ }
162
+ }, timeout);
163
+ }
164
+ });
165
+ }
166
+ getHandle(channel, executionId) {
167
+ const store = this;
168
+ return {
169
+ get closed() {
170
+ return store.closedExecutions.has(executionId);
171
+ },
172
+ channel,
173
+ send(value) {
174
+ if (store.closedExecutions.has(executionId)) {
175
+ throw new NoeticErrorImpl({
176
+ kind: 'channel_closed',
177
+ channelName: channel.name,
178
+ });
179
+ }
180
+ store.send(channel, value);
181
+ },
182
+ };
183
+ }
184
+ closeExecution(executionId) {
185
+ this.closedExecutions.add(executionId);
186
+ }
187
+ }
@@ -0,0 +1,80 @@
1
+ import type { ItemSchemaRegistry } from '../schemas/item';
2
+ import type { Channel } from '../types/channel';
3
+ import type { StepMeta, TokenUsage, Tool } from '../types/common';
4
+ import type { Context, CwdState, ItemLog, LastLayerUsage } from '../types/context';
5
+ import type { FsAdapter } from '../types/fs-adapter';
6
+ import type { Item } from '../types/items';
7
+ import type { ContextMemory, MemoryLayer } from '../types/memory';
8
+ import type { Span } from '../types/observability';
9
+ import type { AgentHarnessContract } from '../types/runtime';
10
+ import type { ChannelStore } from './channel-store';
11
+ import type { EventBroadcaster } from './event-broadcaster';
12
+ export declare class ContextImpl implements Context<ContextMemory> {
13
+ readonly id: string;
14
+ stepCount: number;
15
+ tokens: TokenUsage;
16
+ cost: number;
17
+ state: unknown;
18
+ readonly parent: Context<ContextMemory> | null;
19
+ readonly depth: number;
20
+ readonly span: Span;
21
+ readonly threadId: string;
22
+ readonly resourceId?: string;
23
+ readonly itemLog: ItemLog;
24
+ lastStepMeta: StepMeta | null;
25
+ lastLayerUsage: LastLayerUsage | undefined;
26
+ readonly harness: AgentHarnessContract;
27
+ readonly layers?: MemoryLayer[];
28
+ unifiedTools?: ReadonlyArray<Tool>;
29
+ readonly itemSchemas?: ItemSchemaRegistry;
30
+ readonly cwdState: CwdState;
31
+ /** @internal Event broadcaster for streaming — not part of public Context interface. */
32
+ readonly _broadcaster?: EventBroadcaster;
33
+ private readonly _createdAt;
34
+ /**
35
+ * Shared channel store for cross-context communication. Inherited by
36
+ * `fork` / `spawn` children so channels published by a sibling are visible
37
+ * to peers — see `interpreter/execute-fork.ts` and `interpreter/execute-spawn.ts`.
38
+ * @internal
39
+ */
40
+ readonly channelStore?: ChannelStore;
41
+ private _checkpointFn?;
42
+ private _completionValue?;
43
+ private _completed;
44
+ private _aborted;
45
+ private _abortReason?;
46
+ private _memory?;
47
+ constructor(opts: {
48
+ harness: AgentHarnessContract;
49
+ parent?: Context;
50
+ items?: Item[];
51
+ state?: unknown;
52
+ threadId?: string;
53
+ resourceId?: string;
54
+ span?: Span;
55
+ channelStore?: ChannelStore;
56
+ checkpointFn?: () => Promise<void>;
57
+ layers?: MemoryLayer[];
58
+ unifiedTools?: ReadonlyArray<Tool>;
59
+ itemSchemas?: ItemSchemaRegistry;
60
+ cwdState?: CwdState;
61
+ _broadcaster?: EventBroadcaster;
62
+ });
63
+ get elapsed(): number;
64
+ get aborted(): boolean;
65
+ get fs(): FsAdapter;
66
+ get shell(): AgentHarnessContract['shell'];
67
+ get subprocess(): AgentHarnessContract['subprocess'];
68
+ get memory(): ContextMemory;
69
+ recv<T>(ch: Channel<T>, opts?: {
70
+ timeout?: number;
71
+ }): Promise<T>;
72
+ send<T>(ch: Channel<T>, value: T): void;
73
+ tryRecv<T>(ch: Channel<T>): T | null;
74
+ checkpoint(): Promise<void>;
75
+ complete<T>(value: T): void;
76
+ get completed(): boolean;
77
+ get completionValue(): unknown;
78
+ get abortReason(): string | undefined;
79
+ abort(reason?: string): void;
80
+ }
@@ -0,0 +1,140 @@
1
+ import { buildContextMemory } from '../memory/layer-api';
2
+ import { defaultItemSchemaRegistry } from '../schemas/item';
3
+ import { ItemLogImpl } from './item-log-impl';
4
+ const EMPTY_MEMORY = Object.freeze({});
5
+ class NoopSpan {
6
+ traceId = crypto.randomUUID();
7
+ spanId = crypto.randomUUID();
8
+ parentSpanId = null;
9
+ setAttribute(_key, _value) { }
10
+ addEvent(_name, _attributes) { }
11
+ end() { }
12
+ }
13
+ export class ContextImpl {
14
+ id;
15
+ stepCount = 0;
16
+ tokens = {
17
+ input: 0,
18
+ output: 0,
19
+ total: 0,
20
+ };
21
+ cost = 0;
22
+ state;
23
+ parent;
24
+ depth;
25
+ span;
26
+ threadId;
27
+ resourceId;
28
+ itemLog;
29
+ lastStepMeta = null;
30
+ lastLayerUsage = undefined;
31
+ harness;
32
+ layers;
33
+ unifiedTools;
34
+ itemSchemas;
35
+ cwdState;
36
+ /** @internal Event broadcaster for streaming — not part of public Context interface. */
37
+ _broadcaster;
38
+ _createdAt;
39
+ /**
40
+ * Shared channel store for cross-context communication. Inherited by
41
+ * `fork` / `spawn` children so channels published by a sibling are visible
42
+ * to peers — see `interpreter/execute-fork.ts` and `interpreter/execute-spawn.ts`.
43
+ * @internal
44
+ */
45
+ channelStore;
46
+ _checkpointFn;
47
+ _completionValue;
48
+ _completed = false;
49
+ _aborted = false;
50
+ _abortReason;
51
+ _memory;
52
+ constructor(opts) {
53
+ this.id = crypto.randomUUID();
54
+ this._createdAt = Date.now();
55
+ this.harness = opts.harness;
56
+ this.state = opts.state ?? {};
57
+ this.parent = opts.parent ?? null;
58
+ this.depth = this.parent ? this.parent.depth + 1 : 0;
59
+ this.span = opts.span ?? new NoopSpan();
60
+ this.threadId = opts.threadId ?? crypto.randomUUID();
61
+ this.resourceId = opts.resourceId;
62
+ this.channelStore = opts.channelStore;
63
+ this._checkpointFn = opts.checkpointFn;
64
+ this.layers = opts.layers;
65
+ this.unifiedTools = opts.unifiedTools;
66
+ this.itemSchemas = opts.itemSchemas ?? defaultItemSchemaRegistry;
67
+ this.cwdState = opts.cwdState ?? {
68
+ cwd: process.cwd(),
69
+ };
70
+ this._broadcaster = opts._broadcaster;
71
+ const log = new ItemLogImpl(this.itemSchemas);
72
+ if (opts.items) {
73
+ for (const item of opts.items) {
74
+ log.append(item);
75
+ }
76
+ }
77
+ this.itemLog = log;
78
+ }
79
+ get elapsed() {
80
+ return Date.now() - this._createdAt;
81
+ }
82
+ get aborted() {
83
+ return this._aborted;
84
+ }
85
+ get fs() {
86
+ return this.harness.fs;
87
+ }
88
+ get shell() {
89
+ return this.harness.shell;
90
+ }
91
+ get subprocess() {
92
+ return this.harness.subprocess;
93
+ }
94
+ get memory() {
95
+ if (!this._memory) {
96
+ this._memory = this.layers ? buildContextMemory(this.layers, this) : EMPTY_MEMORY;
97
+ }
98
+ return this._memory;
99
+ }
100
+ recv(ch, opts) {
101
+ if (!this.channelStore) {
102
+ return Promise.reject(new Error('No channel store configured'));
103
+ }
104
+ return this.channelStore.recv(ch, opts?.timeout);
105
+ }
106
+ send(ch, value) {
107
+ if (!this.channelStore) {
108
+ throw new Error('No channel store configured');
109
+ }
110
+ this.channelStore.send(ch, value);
111
+ }
112
+ tryRecv(ch) {
113
+ if (!this.channelStore) {
114
+ throw new Error('No channel store configured');
115
+ }
116
+ return this.channelStore.tryRecv(ch);
117
+ }
118
+ async checkpoint() {
119
+ if (this._checkpointFn) {
120
+ await this._checkpointFn();
121
+ }
122
+ }
123
+ complete(value) {
124
+ this._completed = true;
125
+ this._completionValue = value;
126
+ }
127
+ get completed() {
128
+ return this._completed;
129
+ }
130
+ get completionValue() {
131
+ return this._completionValue;
132
+ }
133
+ get abortReason() {
134
+ return this._abortReason;
135
+ }
136
+ abort(reason) {
137
+ this._aborted = true;
138
+ this._abortReason = reason;
139
+ }
140
+ }
@@ -0,0 +1,42 @@
1
+ import type { Context, CwdState } from '../types/context';
2
+ /**
3
+ * @public Resolve the live cwd for a tool execution. Falls back to the
4
+ * tool's factory-time cwd (then `process.cwd()`) so partial test contexts
5
+ * without a `cwdState` keep working.
6
+ */
7
+ export declare function getToolCwd(ctx: Context | undefined, fallback?: string): string;
8
+ /**
9
+ * @public Update the shared cwd state for a Context. Callers MUST pass an
10
+ * absolute, normalized path — validation (`statSync`, `~` expansion, etc.)
11
+ * is the caller's responsibility.
12
+ *
13
+ * No-op when `nextCwd` equals the current cwd: leaves `previousCwd` alone
14
+ * so a `cd .` (or any redundant cd to the same path) does not stomp the
15
+ * preserved OLDPWD that powers `cd -`.
16
+ */
17
+ export declare function setToolCwd(ctx: Context, nextCwd: string): {
18
+ previousCwd: string;
19
+ newCwd: string;
20
+ };
21
+ /**
22
+ * @public Snapshot a parent's cwd state for a child Context. Spawned/forked
23
+ * children must not share the parent's `CwdState` reference — POSIX-fork
24
+ * semantics require child mutations stay local to the child.
25
+ */
26
+ export declare function snapshotCwdState(parent: Context): CwdState;
27
+ /**
28
+ * @internal Briefly retarget `cwdState.cwd` so an immediately-following
29
+ * `executeSpawn` snapshots `nextCwd` onto the spawned child, then restore
30
+ * the parent's prior cwd via the returned callback.
31
+ *
32
+ * Unlike {@link setToolCwd}, this does NOT update `previousCwd` — the
33
+ * retarget is invisible to the parent's logical cd history (it only exists
34
+ * to seed the child's snapshot). The bypass is the entire reason this
35
+ * helper is separate from `setToolCwd`.
36
+ *
37
+ * Safe ONLY when the caller holds exclusive use of the context for the
38
+ * duration of the retarget — typically a sync spawn that awaits before
39
+ * restoring. `harness.run()` returns a Promise (not an iterator) so no
40
+ * yield occurs between retarget and restore.
41
+ */
42
+ export declare function retargetCwdForSpawn(ctx: Context, nextCwd: string): () => void;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @public Resolve the live cwd for a tool execution. Falls back to the
3
+ * tool's factory-time cwd (then `process.cwd()`) so partial test contexts
4
+ * without a `cwdState` keep working.
5
+ */
6
+ export function getToolCwd(ctx, fallback) {
7
+ return ctx?.cwdState?.cwd ?? fallback ?? process.cwd();
8
+ }
9
+ /**
10
+ * @public Update the shared cwd state for a Context. Callers MUST pass an
11
+ * absolute, normalized path — validation (`statSync`, `~` expansion, etc.)
12
+ * is the caller's responsibility.
13
+ *
14
+ * No-op when `nextCwd` equals the current cwd: leaves `previousCwd` alone
15
+ * so a `cd .` (or any redundant cd to the same path) does not stomp the
16
+ * preserved OLDPWD that powers `cd -`.
17
+ */
18
+ export function setToolCwd(ctx, nextCwd) {
19
+ const currentCwd = ctx.cwdState.cwd;
20
+ if (nextCwd === currentCwd) {
21
+ return {
22
+ previousCwd: ctx.cwdState.previousCwd ?? currentCwd,
23
+ newCwd: currentCwd,
24
+ };
25
+ }
26
+ const previousCwd = ctx.cwdState.cwd;
27
+ ctx.cwdState.previousCwd = previousCwd;
28
+ ctx.cwdState.cwd = nextCwd;
29
+ return {
30
+ previousCwd,
31
+ newCwd: nextCwd,
32
+ };
33
+ }
34
+ /**
35
+ * @public Snapshot a parent's cwd state for a child Context. Spawned/forked
36
+ * children must not share the parent's `CwdState` reference — POSIX-fork
37
+ * semantics require child mutations stay local to the child.
38
+ */
39
+ export function snapshotCwdState(parent) {
40
+ return {
41
+ cwd: parent.cwdState.cwd,
42
+ previousCwd: parent.cwdState.previousCwd,
43
+ };
44
+ }
45
+ /**
46
+ * @internal Briefly retarget `cwdState.cwd` so an immediately-following
47
+ * `executeSpawn` snapshots `nextCwd` onto the spawned child, then restore
48
+ * the parent's prior cwd via the returned callback.
49
+ *
50
+ * Unlike {@link setToolCwd}, this does NOT update `previousCwd` — the
51
+ * retarget is invisible to the parent's logical cd history (it only exists
52
+ * to seed the child's snapshot). The bypass is the entire reason this
53
+ * helper is separate from `setToolCwd`.
54
+ *
55
+ * Safe ONLY when the caller holds exclusive use of the context for the
56
+ * duration of the retarget — typically a sync spawn that awaits before
57
+ * restoring. `harness.run()` returns a Promise (not an iterator) so no
58
+ * yield occurs between retarget and restore.
59
+ */
60
+ export function retargetCwdForSpawn(ctx, nextCwd) {
61
+ const savedCwd = ctx.cwdState.cwd;
62
+ const savedPrev = ctx.cwdState.previousCwd;
63
+ ctx.cwdState.cwd = nextCwd;
64
+ return () => {
65
+ ctx.cwdState.cwd = savedCwd;
66
+ ctx.cwdState.previousCwd = savedPrev;
67
+ };
68
+ }
@@ -0,0 +1,14 @@
1
+ import type { DetachedHandle } from '../types/detached';
2
+ import { DetachedStatus } from '../types/detached';
3
+ export declare class DetachedHandleImpl<O> implements DetachedHandle<O> {
4
+ readonly id: string;
5
+ private _status;
6
+ private _result;
7
+ private _error;
8
+ private readonly promise;
9
+ constructor(id: string, promise: Promise<O>);
10
+ get status(): DetachedStatus;
11
+ get result(): O | undefined;
12
+ get error(): string | undefined;
13
+ await(timeout?: number): Promise<O>;
14
+ }
@@ -0,0 +1,56 @@
1
+ import { NoeticErrorImpl } from '../errors/noetic-error';
2
+ import { DetachedStatus } from '../types/detached';
3
+ //#region DetachedHandleImpl
4
+ export class DetachedHandleImpl {
5
+ id;
6
+ _status = DetachedStatus.Running;
7
+ _result;
8
+ _error;
9
+ promise;
10
+ constructor(id, promise) {
11
+ this.id = id;
12
+ this.promise = promise;
13
+ promise.then((value) => {
14
+ this._status = DetachedStatus.Completed;
15
+ this._result = value;
16
+ }, (err) => {
17
+ this._status = DetachedStatus.Failed;
18
+ this._error = err instanceof Error ? err.message : String(err);
19
+ });
20
+ }
21
+ get status() {
22
+ return this._status;
23
+ }
24
+ get result() {
25
+ return this._result;
26
+ }
27
+ get error() {
28
+ return this._error;
29
+ }
30
+ async await(timeout) {
31
+ if (timeout === undefined || timeout <= 0) {
32
+ return this.promise;
33
+ }
34
+ let timerId;
35
+ const timeoutPromise = new Promise((_resolve, reject) => {
36
+ timerId = setTimeout(() => {
37
+ reject(new NoeticErrorImpl({
38
+ kind: 'step_failed',
39
+ stepId: this.id,
40
+ cause: new Error(`Detached spawn timed out after ${timeout}ms`),
41
+ retriesExhausted: false,
42
+ }));
43
+ }, timeout);
44
+ });
45
+ try {
46
+ return await Promise.race([
47
+ this.promise,
48
+ timeoutPromise,
49
+ ]);
50
+ }
51
+ finally {
52
+ clearTimeout(timerId);
53
+ }
54
+ }
55
+ }
56
+ //#endregion