@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.
- package/LICENSE +201 -0
- package/README.md +38 -0
- package/dist/adapters/in-memory-fs-adapter.d.ts +3 -0
- package/dist/adapters/in-memory-fs-adapter.js +245 -0
- package/dist/adapters/in-memory-shell-adapter.d.ts +9 -0
- package/dist/adapters/in-memory-shell-adapter.js +20 -0
- package/dist/adapters/in-memory-subprocess-adapter.d.ts +5 -0
- package/dist/adapters/in-memory-subprocess-adapter.js +126 -0
- package/dist/adapters/local-fs-adapter.d.ts +3 -0
- package/dist/adapters/local-fs-adapter.js +43 -0
- package/dist/adapters/local-shell-adapter.d.ts +51 -0
- package/dist/adapters/local-shell-adapter.js +167 -0
- package/dist/adapters/local-subprocess-adapter.d.ts +16 -0
- package/dist/adapters/local-subprocess-adapter.js +187 -0
- package/dist/adapters/openrouter.d.ts +57 -0
- package/dist/adapters/openrouter.js +248 -0
- package/dist/builders/channel-builder.d.ts +36 -0
- package/dist/builders/channel-builder.js +15 -0
- package/dist/builders/control-flow-builders.d.ts +79 -0
- package/dist/builders/control-flow-builders.js +53 -0
- package/dist/builders/every.d.ts +45 -0
- package/dist/builders/every.js +60 -0
- package/dist/builders/layer-provides-builders.d.ts +32 -0
- package/dist/builders/layer-provides-builders.js +33 -0
- package/dist/builders/loop-builder.d.ts +23 -0
- package/dist/builders/loop-builder.js +46 -0
- package/dist/builders/memory-builder.d.ts +11 -0
- package/dist/builders/memory-builder.js +15 -0
- package/dist/builders/provide-builder.d.ts +20 -0
- package/dist/builders/provide-builder.js +34 -0
- package/dist/builders/spawn-builder.d.ts +20 -0
- package/dist/builders/spawn-builder.js +33 -0
- package/dist/builders/step-builders.d.ts +62 -0
- package/dist/builders/step-builders.js +99 -0
- package/dist/builders/tool-builder.d.ts +52 -0
- package/dist/builders/tool-builder.js +59 -0
- package/dist/conditions/conditions.d.ts +114 -0
- package/dist/conditions/conditions.js +271 -0
- package/dist/conditions/cosine-similarity.d.ts +11 -0
- package/dist/conditions/cosine-similarity.js +28 -0
- package/dist/conditions/index.d.ts +3 -0
- package/dist/conditions/index.js +2 -0
- package/dist/errors/noetic-config-error.d.ts +41 -0
- package/dist/errors/noetic-config-error.js +44 -0
- package/dist/errors/noetic-error.d.ts +18 -0
- package/dist/errors/noetic-error.js +52 -0
- package/dist/index.d.ts +152 -0
- package/dist/index.js +112 -0
- package/dist/internal-test.d.ts +11 -0
- package/dist/internal-test.js +11 -0
- package/dist/interpreter/clone-guard.d.ts +4 -0
- package/dist/interpreter/clone-guard.js +18 -0
- package/dist/interpreter/collect-tools.d.ts +12 -0
- package/dist/interpreter/collect-tools.js +70 -0
- package/dist/interpreter/execute-branch.d.ts +3 -0
- package/dist/interpreter/execute-branch.js +10 -0
- package/dist/interpreter/execute-every.d.ts +15 -0
- package/dist/interpreter/execute-every.js +143 -0
- package/dist/interpreter/execute-fork.d.ts +3 -0
- package/dist/interpreter/execute-fork.js +215 -0
- package/dist/interpreter/execute-llm.d.ts +4 -0
- package/dist/interpreter/execute-llm.js +217 -0
- package/dist/interpreter/execute-loop.d.ts +3 -0
- package/dist/interpreter/execute-loop.js +171 -0
- package/dist/interpreter/execute-provide.d.ts +10 -0
- package/dist/interpreter/execute-provide.js +64 -0
- package/dist/interpreter/execute-run.d.ts +3 -0
- package/dist/interpreter/execute-run.js +39 -0
- package/dist/interpreter/execute-spawn.d.ts +11 -0
- package/dist/interpreter/execute-spawn.js +113 -0
- package/dist/interpreter/execute-tool.d.ts +5 -0
- package/dist/interpreter/execute-tool.js +81 -0
- package/dist/interpreter/execute.d.ts +14 -0
- package/dist/interpreter/execute.js +119 -0
- package/dist/interpreter/framework-cast.d.ts +10 -0
- package/dist/interpreter/framework-cast.js +16 -0
- package/dist/interpreter/message-helpers.d.ts +9 -0
- package/dist/interpreter/message-helpers.js +42 -0
- package/dist/interpreter/typeguards.d.ts +25 -0
- package/dist/interpreter/typeguards.js +50 -0
- package/dist/memory/budget.d.ts +23 -0
- package/dist/memory/budget.js +117 -0
- package/dist/memory/function-call-utils.d.ts +6 -0
- package/dist/memory/function-call-utils.js +25 -0
- package/dist/memory/layer-api.d.ts +14 -0
- package/dist/memory/layer-api.js +91 -0
- package/dist/memory/layer-lifecycle.d.ts +160 -0
- package/dist/memory/layer-lifecycle.js +552 -0
- package/dist/memory/layer-usage.d.ts +15 -0
- package/dist/memory/layer-usage.js +48 -0
- package/dist/memory/layers/durable-task-state.d.ts +79 -0
- package/dist/memory/layers/durable-task-state.js +118 -0
- package/dist/memory/layers/file-reference.d.ts +55 -0
- package/dist/memory/layers/file-reference.js +538 -0
- package/dist/memory/layers/history-window.d.ts +29 -0
- package/dist/memory/layers/history-window.js +114 -0
- package/dist/memory/layers/observational-memory.d.ts +53 -0
- package/dist/memory/layers/observational-memory.js +110 -0
- package/dist/memory/layers/plan.d.ts +55 -0
- package/dist/memory/layers/plan.js +369 -0
- package/dist/memory/layers/static-content.d.ts +27 -0
- package/dist/memory/layers/static-content.js +35 -0
- package/dist/memory/layers/steering.d.ts +51 -0
- package/dist/memory/layers/steering.js +258 -0
- package/dist/memory/layers/tool-memory-layer.d.ts +16 -0
- package/dist/memory/layers/tool-memory-layer.js +43 -0
- package/dist/memory/layers/working-memory.d.ts +44 -0
- package/dist/memory/layers/working-memory.js +92 -0
- package/dist/memory/projector.d.ts +10 -0
- package/dist/memory/projector.js +19 -0
- package/dist/memory/scope.d.ts +3 -0
- package/dist/memory/scope.js +31 -0
- package/dist/memory/strip-unresolved.d.ts +13 -0
- package/dist/memory/strip-unresolved.js +35 -0
- package/dist/observability/genai-attributes.d.ts +11 -0
- package/dist/observability/genai-attributes.js +12 -0
- package/dist/observability/span-impl.d.ts +20 -0
- package/dist/observability/span-impl.js +33 -0
- package/dist/observability/trace-exporter.d.ts +15 -0
- package/dist/observability/trace-exporter.js +26 -0
- package/dist/patterns/flow.d.ts +52 -0
- package/dist/patterns/flow.js +103 -0
- package/dist/patterns/interview.d.ts +53 -0
- package/dist/patterns/interview.js +113 -0
- package/dist/patterns/plans.d.ts +45 -0
- package/dist/patterns/plans.js +125 -0
- package/dist/patterns/ralph-wiggum.d.ts +20 -0
- package/dist/patterns/ralph-wiggum.js +37 -0
- package/dist/patterns/react.d.ts +18 -0
- package/dist/patterns/react.js +39 -0
- package/dist/portable.d.ts +26 -0
- package/dist/portable.js +34 -0
- package/dist/runtime/agent-harness.d.ts +206 -0
- package/dist/runtime/agent-harness.js +1170 -0
- package/dist/runtime/broadcaster-utils.d.ts +30 -0
- package/dist/runtime/broadcaster-utils.js +53 -0
- package/dist/runtime/channel-store.d.ts +18 -0
- package/dist/runtime/channel-store.js +187 -0
- package/dist/runtime/context-impl.d.ts +80 -0
- package/dist/runtime/context-impl.js +140 -0
- package/dist/runtime/cwd-helpers.d.ts +42 -0
- package/dist/runtime/cwd-helpers.js +68 -0
- package/dist/runtime/detached-handle.d.ts +14 -0
- package/dist/runtime/detached-handle.js +56 -0
- package/dist/runtime/event-broadcaster.d.ts +61 -0
- package/dist/runtime/event-broadcaster.js +187 -0
- package/dist/runtime/exec-context-factory.d.ts +25 -0
- package/dist/runtime/exec-context-factory.js +30 -0
- package/dist/runtime/in-memory-storage.d.ts +9 -0
- package/dist/runtime/in-memory-storage.js +33 -0
- package/dist/runtime/item-log-impl.d.ts +11 -0
- package/dist/runtime/item-log-impl.js +25 -0
- package/dist/runtime/message-queue.d.ts +30 -0
- package/dist/runtime/message-queue.js +46 -0
- package/dist/runtime/session-runner.d.ts +77 -0
- package/dist/runtime/session-runner.js +253 -0
- package/dist/runtime/session-streams.d.ts +11 -0
- package/dist/runtime/session-streams.js +204 -0
- package/dist/runtime/tool-memory.d.ts +6 -0
- package/dist/runtime/tool-memory.js +22 -0
- package/dist/schemas/item.d.ts +35 -0
- package/dist/schemas/item.js +155 -0
- package/dist/types/channel.d.ts +29 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/common.d.ts +111 -0
- package/dist/types/common.js +1 -0
- package/dist/types/context.d.ts +93 -0
- package/dist/types/context.js +1 -0
- package/dist/types/detached.d.ts +17 -0
- package/dist/types/detached.js +7 -0
- package/dist/types/embed.d.ts +5 -0
- package/dist/types/embed.js +1 -0
- package/dist/types/error.d.ts +51 -0
- package/dist/types/error.js +1 -0
- package/dist/types/fs-adapter.d.ts +51 -0
- package/dist/types/fs-adapter.js +3 -0
- package/dist/types/harness-result.d.ts +43 -0
- package/dist/types/harness-result.js +2 -0
- package/dist/types/items.d.ts +115 -0
- package/dist/types/items.js +2 -0
- package/dist/types/memory.d.ts +336 -0
- package/dist/types/memory.js +13 -0
- package/dist/types/mutable-context.d.ts +13 -0
- package/dist/types/mutable-context.js +1 -0
- package/dist/types/observability.d.ts +30 -0
- package/dist/types/observability.js +1 -0
- package/dist/types/runtime.d.ts +255 -0
- package/dist/types/runtime.js +1 -0
- package/dist/types/shell-adapter.d.ts +34 -0
- package/dist/types/shell-adapter.js +10 -0
- package/dist/types/steering.d.ts +89 -0
- package/dist/types/steering.js +14 -0
- package/dist/types/step.d.ts +206 -0
- package/dist/types/step.js +1 -0
- package/dist/types/subprocess-adapter.d.ts +41 -0
- package/dist/types/subprocess-adapter.js +1 -0
- package/dist/types/tool-context.d.ts +34 -0
- package/dist/types/tool-context.js +1 -0
- package/dist/unstable.d.ts +28 -0
- package/dist/unstable.js +24 -0
- package/dist/until/combinators.d.ts +17 -0
- package/dist/until/combinators.js +49 -0
- package/dist/until/never.d.ts +9 -0
- package/dist/until/never.js +12 -0
- package/dist/until/predicates.d.ts +43 -0
- package/dist/until/predicates.js +126 -0
- 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
|