@noetic-tools/core 0.2.1 → 1.0.0
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/dist/adapters/in-memory-fs-adapter.d.ts +1 -1
- package/dist/adapters/in-memory-fs-adapter.js +1 -1
- package/dist/adapters/in-memory-shell-adapter.d.ts +1 -1
- package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +2 -2
- package/dist/adapters/in-memory-subprocess/metadata.d.ts +1 -1
- package/dist/adapters/in-memory-subprocess/types.d.ts +2 -2
- package/dist/adapters/in-memory-subprocess-adapter.d.ts +1 -1
- package/dist/adapters/openrouter.d.ts +2 -6
- package/dist/adapters/openrouter.js +1 -4
- package/dist/builders/channel-builder.d.ts +1 -1
- package/dist/builders/control-flow-builders.d.ts +2 -3
- package/dist/builders/control-flow-builders.js +1 -2
- package/dist/builders/every.d.ts +2 -3
- package/dist/builders/every.js +1 -1
- package/dist/builders/layer-provides-builders.d.ts +1 -1
- package/dist/builders/layer-provides-builders.js +1 -1
- package/dist/builders/loop-builder.d.ts +2 -2
- package/dist/builders/loop-builder.js +1 -1
- package/dist/builders/memory-builder.d.ts +1 -1
- package/dist/builders/memory-builder.js +1 -1
- package/dist/builders/provide-builder.d.ts +2 -2
- package/dist/builders/provide-builder.js +1 -1
- package/dist/builders/spawn-builder.d.ts +2 -3
- package/dist/builders/spawn-builder.js +1 -1
- package/dist/builders/step-builders.d.ts +2 -6
- package/dist/builders/step-builders.js +1 -1
- package/dist/builders/tool-builder.d.ts +1 -3
- package/dist/builders/tool-builder.js +1 -1
- package/dist/builders/workflow-hydrator.d.ts +2 -3
- package/dist/builders/workflow-hydrator.js +1 -2
- package/dist/conditions/conditions.d.ts +2 -3
- package/dist/conditions/conditions.js +1 -1
- package/dist/harness/agent-harness.d.ts +15 -5
- package/dist/harness/agent-harness.js +65 -10
- package/dist/harness/deps/memory.d.ts +2 -5
- package/dist/harness/deps/memory.js +1 -4
- package/dist/harness/deps/types.d.ts +3 -16
- package/dist/harness/deps/types.js +1 -1
- package/dist/harness/model-call.d.ts +1 -3
- package/dist/harness/model-call.js +10 -10
- package/dist/harness/model-schema.d.ts +10 -4
- package/dist/harness/model-schema.js +9 -1
- package/dist/index.d.ts +28 -49
- package/dist/index.js +9 -27
- package/dist/interpreter/action-deps.d.ts +4 -8
- package/dist/interpreter/action-deps.js +2 -6
- package/dist/interpreter/action-types.d.ts +3 -8
- package/dist/interpreter/action-types.js +1 -1
- package/dist/interpreter/collect-tools.d.ts +1 -2
- package/dist/interpreter/collect-tools.js +1 -1
- package/dist/interpreter/execute-action.js +122 -27
- package/dist/interpreter/execute-control.d.ts +1 -2
- package/dist/interpreter/execute-control.js +60 -7
- package/dist/interpreter/execute.d.ts +2 -3
- package/dist/interpreter/execute.js +1 -2
- package/dist/interpreter/message-helpers.d.ts +1 -1
- package/dist/interpreter/message-helpers.js +1 -1
- package/dist/interpreter/step-dispatcher.d.ts +2 -5
- package/dist/interpreter/typeguards.d.ts +3 -4
- package/dist/interpreter/typeguards.js +1 -1
- package/dist/observability/span-impl.d.ts +1 -1
- package/dist/observability/trace-exporter.d.ts +1 -1
- package/dist/patterns/dynamic-workflow.d.ts +2 -5
- package/dist/patterns/dynamic-workflow.js +1 -2
- package/dist/patterns/flow.d.ts +2 -2
- package/dist/patterns/flow.js +1 -1
- package/dist/patterns/interview.d.ts +2 -2
- package/dist/patterns/interview.js +1 -1
- package/dist/patterns/plans.d.ts +2 -3
- package/dist/patterns/plans.js +1 -1
- package/dist/patterns/ralph-wiggum.d.ts +2 -3
- package/dist/patterns/react.d.ts +2 -3
- package/dist/portable/memory-layers.d.ts +1 -7
- package/dist/portable/memory-layers.js +1 -7
- package/dist/portable.d.ts +2 -2
- package/dist/portable.js +2 -2
- package/dist/runtime/broadcaster-utils.d.ts +1 -1
- package/dist/runtime/channel-store.d.ts +43 -3
- package/dist/runtime/channel-store.js +233 -37
- package/dist/runtime/context-deps.d.ts +3 -3
- package/dist/runtime/context-deps.js +2 -2
- package/dist/runtime/context-impl.d.ts +17 -1
- package/dist/runtime/context-impl.js +30 -3
- package/dist/runtime/context-type-deps.d.ts +2 -11
- package/dist/runtime/cwd-helpers.d.ts +1 -1
- package/dist/runtime/detached-handle.d.ts +2 -3
- package/dist/runtime/detached-handle.js +1 -3
- package/dist/runtime/durable/checkpoint-store.d.ts +1 -1
- package/dist/runtime/durable/checkpoint-store.js +1 -1
- package/dist/runtime/durable/harness-checkpoints.d.ts +2 -5
- package/dist/runtime/durable/runnable-loop.d.ts +1 -3
- package/dist/runtime/durable/session-seed.d.ts +1 -1
- package/dist/runtime/durable/stall-nudge.d.ts +17 -10
- package/dist/runtime/durable/stall-nudge.js +29 -10
- package/dist/runtime/event-broadcaster.d.ts +10 -2
- package/dist/runtime/event-broadcaster.js +43 -19
- package/dist/runtime/in-memory-storage.d.ts +1 -1
- package/dist/runtime/in-memory-storage.js +1 -1
- package/dist/runtime/item-log-impl.d.ts +1 -3
- package/dist/runtime/item-log-impl.js +1 -1
- package/dist/runtime/message-queue.d.ts +1 -2
- package/dist/runtime/session-runner.d.ts +1 -4
- package/dist/runtime/session-streams.d.ts +1 -2
- package/dist/runtime/session-streams.js +1 -1
- package/dist/runtime/step-registry.d.ts +2 -2
- package/dist/runtime/step-registry.js +1 -2
- package/dist/runtime/tool-memory.d.ts +1 -3
- package/dist/types/mutable-context.d.ts +2 -3
- package/dist/types/step-registrar.d.ts +1 -1
- package/dist/unstable.d.ts +5 -9
- package/dist/unstable.js +3 -6
- package/dist/until/combinators.d.ts +1 -1
- package/dist/until/never.d.ts +1 -1
- package/dist/until/predicates.d.ts +2 -2
- package/package.json +4 -2
- package/dist/errors/noetic-config-error.d.ts +0 -41
- package/dist/errors/noetic-config-error.js +0 -44
- package/dist/errors/noetic-error.d.ts +0 -18
- package/dist/errors/noetic-error.js +0 -54
- package/dist/memory/budget.d.ts +0 -23
- package/dist/memory/budget.js +0 -117
- package/dist/memory/exec-context-factory.d.ts +0 -25
- package/dist/memory/exec-context-factory.js +0 -30
- package/dist/memory/flow-schema.d.ts +0 -52
- package/dist/memory/flow-schema.js +0 -102
- package/dist/memory/function-call-utils.d.ts +0 -6
- package/dist/memory/function-call-utils.js +0 -27
- package/dist/memory/layer-api.d.ts +0 -14
- package/dist/memory/layer-api.js +0 -91
- package/dist/memory/layer-lifecycle.d.ts +0 -160
- package/dist/memory/layer-lifecycle.js +0 -552
- package/dist/memory/layer-provides.d.ts +0 -32
- package/dist/memory/layer-provides.js +0 -33
- package/dist/memory/layer-usage.d.ts +0 -16
- package/dist/memory/layer-usage.js +0 -54
- package/dist/memory/layers/durable-task-state.d.ts +0 -79
- package/dist/memory/layers/durable-task-state.js +0 -118
- package/dist/memory/layers/file-reference.d.ts +0 -55
- package/dist/memory/layers/file-reference.js +0 -594
- package/dist/memory/layers/history-window.d.ts +0 -29
- package/dist/memory/layers/history-window.js +0 -114
- package/dist/memory/layers/observational-memory.d.ts +0 -53
- package/dist/memory/layers/observational-memory.js +0 -109
- package/dist/memory/layers/plan.d.ts +0 -55
- package/dist/memory/layers/plan.js +0 -374
- package/dist/memory/layers/static-content.d.ts +0 -27
- package/dist/memory/layers/static-content.js +0 -35
- package/dist/memory/layers/steering.d.ts +0 -51
- package/dist/memory/layers/steering.js +0 -258
- package/dist/memory/layers/temporal.d.ts +0 -91
- package/dist/memory/layers/temporal.js +0 -245
- package/dist/memory/layers/tool-memory-layer.d.ts +0 -16
- package/dist/memory/layers/tool-memory-layer.js +0 -43
- package/dist/memory/layers/working-memory.d.ts +0 -44
- package/dist/memory/layers/working-memory.js +0 -92
- package/dist/memory/projector.d.ts +0 -10
- package/dist/memory/projector.js +0 -19
- package/dist/memory/scope.d.ts +0 -3
- package/dist/memory/scope.js +0 -31
- package/dist/memory/strip-unresolved.d.ts +0 -13
- package/dist/memory/strip-unresolved.js +0 -35
- package/dist/schemas/item.d.ts +0 -35
- package/dist/schemas/item.js +0 -155
- package/dist/types/channel.d.ts +0 -29
- package/dist/types/channel.js +0 -1
- package/dist/types/common.d.ts +0 -68
- package/dist/types/common.js +0 -1
- package/dist/types/context-parts/item-log.d.ts +0 -6
- package/dist/types/context-parts/item-log.js +0 -1
- package/dist/types/context-parts/layer-usage.d.ts +0 -18
- package/dist/types/context-parts/layer-usage.js +0 -1
- package/dist/types/context.d.ts +0 -202
- package/dist/types/context.js +0 -1
- package/dist/types/detached.d.ts +0 -17
- package/dist/types/detached.js +0 -7
- package/dist/types/error.d.ts +0 -66
- package/dist/types/error.js +0 -1
- package/dist/types/fs-adapter.d.ts +0 -51
- package/dist/types/fs-adapter.js +0 -3
- package/dist/types/harness-result.d.ts +0 -43
- package/dist/types/harness-result.js +0 -2
- package/dist/types/items.d.ts +0 -131
- package/dist/types/items.js +0 -2
- package/dist/types/memory-context.d.ts +0 -39
- package/dist/types/memory-context.js +0 -1
- package/dist/types/memory.d.ts +0 -306
- package/dist/types/memory.js +0 -13
- package/dist/types/observability.d.ts +0 -30
- package/dist/types/observability.js +0 -1
- package/dist/types/runtime.d.ts +0 -256
- package/dist/types/runtime.js +0 -1
- package/dist/types/shell-adapter.d.ts +0 -34
- package/dist/types/shell-adapter.js +0 -10
- package/dist/types/steering.d.ts +0 -89
- package/dist/types/steering.js +0 -14
- package/dist/types/step.d.ts +0 -231
- package/dist/types/step.js +0 -1
- package/dist/types/subprocess-adapter.d.ts +0 -157
- package/dist/types/subprocess-adapter.js +0 -3
- package/dist/types/tool-context.d.ts +0 -34
- package/dist/types/tool-context.js +0 -1
- package/dist/types/tool.d.ts +0 -64
- package/dist/types/tool.js +0 -1
- package/dist/util/framework-cast.d.ts +0 -10
- package/dist/util/framework-cast.js +0 -16
- package/dist/util/message-helpers.d.ts +0 -17
- package/dist/util/message-helpers.js +0 -64
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { NoeticErrorImpl } from '
|
|
2
|
-
import { frameworkCast } from '../util/framework-cast.js';
|
|
3
|
-
function isExternalChannel(ch) {
|
|
4
|
-
return 'external' in ch && ch.external === true;
|
|
5
|
-
}
|
|
1
|
+
import { frameworkCast, NoeticErrorImpl } from '@noetic-tools/types';
|
|
6
2
|
const MAX_TOPIC_TIMEOUT = 300_000; // 5 minutes
|
|
3
|
+
const DEFAULT_SEND_TIMEOUT = 30_000;
|
|
4
|
+
const RESOLVED = Promise.resolve();
|
|
5
|
+
/** Build the `cancelled` error a blocked channel operation rejects with on abort. */
|
|
6
|
+
function cancelledError(signal) {
|
|
7
|
+
return new NoeticErrorImpl({
|
|
8
|
+
kind: 'cancelled',
|
|
9
|
+
reason: typeof signal.reason === 'string' ? signal.reason : undefined,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
7
12
|
export class ChannelStore {
|
|
8
13
|
channels = new Map();
|
|
9
14
|
closedExecutions = new Set();
|
|
@@ -17,6 +22,7 @@ export class ChannelStore {
|
|
|
17
22
|
queue: [],
|
|
18
23
|
capacity: channel.capacity ?? 1_000,
|
|
19
24
|
queueWaiters: [],
|
|
25
|
+
pendingSenders: [],
|
|
20
26
|
topicSubscribers: new Set(),
|
|
21
27
|
wakeSubscribers: new Set(),
|
|
22
28
|
};
|
|
@@ -40,14 +46,38 @@ export class ChannelStore {
|
|
|
40
46
|
subscribers.delete(callback);
|
|
41
47
|
};
|
|
42
48
|
}
|
|
43
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Internal-sender write with back-pressure (spec 06). Delivery is
|
|
51
|
+
* synchronous when possible; the returned promise only parks when a queue
|
|
52
|
+
* channel is at capacity:
|
|
53
|
+
*
|
|
54
|
+
* - **value**: store last-write-wins value, drain ALL parked recv waiters,
|
|
55
|
+
* resolve immediately.
|
|
56
|
+
* - **queue**: hand off to a parked recv waiter or push below capacity —
|
|
57
|
+
* resolve immediately. At capacity, park the sender FIFO until a consumer
|
|
58
|
+
* dequeues an item; reject `channel_timeout` after `opts.timeout`
|
|
59
|
+
* (default 30s) or `cancelled` when `opts.signal` aborts.
|
|
60
|
+
* - **topic**: deliver to current subscribers, resolve immediately.
|
|
61
|
+
*
|
|
62
|
+
* External callers use `ChannelHandle.send` (sync, drop-oldest) instead.
|
|
63
|
+
*/
|
|
64
|
+
send(channel, value, opts) {
|
|
44
65
|
const state = this.getOrCreate(channel);
|
|
66
|
+
let result = RESOLVED;
|
|
45
67
|
switch (state.mode) {
|
|
46
68
|
case 'value':
|
|
47
69
|
state.currentValue = value;
|
|
48
70
|
state.hasValue = true;
|
|
71
|
+
// Value-mode reads are non-consuming (last-write-wins), so once
|
|
72
|
+
// hasValue flips true EVERY parked waiter must be drained — waking
|
|
73
|
+
// only one would strand the rest until channel_timeout while new
|
|
74
|
+
// recv calls succeed instantly. Splice in place: timeout timers and
|
|
75
|
+
// abort listeners close over the array identity.
|
|
49
76
|
if (state.valueWaiters.length > 0) {
|
|
50
|
-
state.valueWaiters.
|
|
77
|
+
const drained = state.valueWaiters.splice(0, state.valueWaiters.length);
|
|
78
|
+
for (const waiter of drained) {
|
|
79
|
+
waiter.resolve(value);
|
|
80
|
+
}
|
|
51
81
|
}
|
|
52
82
|
break;
|
|
53
83
|
case 'queue':
|
|
@@ -58,13 +88,8 @@ export class ChannelStore {
|
|
|
58
88
|
state.queue.push(value);
|
|
59
89
|
}
|
|
60
90
|
else {
|
|
61
|
-
// At capacity
|
|
62
|
-
|
|
63
|
-
if (isExternalChannel(channel)) {
|
|
64
|
-
state.queue.shift();
|
|
65
|
-
state.queue.push(value);
|
|
66
|
-
}
|
|
67
|
-
// For internal, drop the new value
|
|
91
|
+
// At capacity — park the sender (back-pressure).
|
|
92
|
+
result = this.parkSender(state, channel.name, value, opts);
|
|
68
93
|
}
|
|
69
94
|
break;
|
|
70
95
|
case 'topic':
|
|
@@ -73,30 +98,128 @@ export class ChannelStore {
|
|
|
73
98
|
}
|
|
74
99
|
break;
|
|
75
100
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
101
|
+
this.fireWakes(state);
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
/** Park an internal sender on a full queue until a slot frees (FIFO). */
|
|
105
|
+
parkSender(state, channelName, value, opts) {
|
|
106
|
+
const timeout = opts?.timeout ?? DEFAULT_SEND_TIMEOUT;
|
|
107
|
+
const signal = opts?.signal;
|
|
108
|
+
if (signal?.aborted) {
|
|
109
|
+
return Promise.reject(cancelledError(signal));
|
|
110
|
+
}
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
let timer = null;
|
|
113
|
+
let removeAbortListener = null;
|
|
114
|
+
const cleanup = () => {
|
|
115
|
+
if (timer) {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
}
|
|
118
|
+
removeAbortListener?.();
|
|
119
|
+
};
|
|
120
|
+
const entry = {
|
|
121
|
+
value,
|
|
122
|
+
resolve: () => {
|
|
123
|
+
cleanup();
|
|
124
|
+
resolve();
|
|
125
|
+
},
|
|
126
|
+
reject: (e) => {
|
|
127
|
+
cleanup();
|
|
128
|
+
reject(e);
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
state.pendingSenders.push(entry);
|
|
132
|
+
const removeEntry = () => {
|
|
133
|
+
const idx = state.pendingSenders.indexOf(entry);
|
|
134
|
+
if (idx < 0) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
state.pendingSenders.splice(idx, 1);
|
|
138
|
+
return true;
|
|
139
|
+
};
|
|
140
|
+
if (timeout > 0) {
|
|
141
|
+
timer = setTimeout(() => {
|
|
142
|
+
if (removeEntry()) {
|
|
143
|
+
entry.reject(new NoeticErrorImpl({
|
|
144
|
+
kind: 'channel_timeout',
|
|
145
|
+
channelName,
|
|
146
|
+
timeout,
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
}, timeout);
|
|
84
150
|
}
|
|
151
|
+
if (signal) {
|
|
152
|
+
const onAbort = () => {
|
|
153
|
+
if (removeEntry()) {
|
|
154
|
+
entry.reject(cancelledError(signal));
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
signal.addEventListener('abort', onAbort, {
|
|
158
|
+
once: true,
|
|
159
|
+
});
|
|
160
|
+
removeAbortListener = () => {
|
|
161
|
+
signal.removeEventListener('abort', onAbort);
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A dequeue freed a queue slot — shift the oldest parked sender's value
|
|
168
|
+
* into it (FIFO) and resolve that sender's promise.
|
|
169
|
+
*/
|
|
170
|
+
promotePendingSender(state) {
|
|
171
|
+
if (state.pendingSenders.length === 0) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (state.queue.length >= state.capacity) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const sender = state.pendingSenders.shift();
|
|
178
|
+
state.queue.push(sender.value);
|
|
179
|
+
sender.resolve();
|
|
180
|
+
}
|
|
181
|
+
/** Fire-and-reset the one-shot wake subscribers for a channel state. */
|
|
182
|
+
fireWakes(state) {
|
|
183
|
+
if (state.wakeSubscribers.size === 0) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Swap in a fresh set before firing so a callback that re-subscribes
|
|
187
|
+
// (typical for `every` re-arming on its next iteration) lands on the
|
|
188
|
+
// new set and survives until the next send.
|
|
189
|
+
const wakers = state.wakeSubscribers;
|
|
190
|
+
state.wakeSubscribers = new Set();
|
|
191
|
+
for (const wake of wakers) {
|
|
192
|
+
wake();
|
|
85
193
|
}
|
|
86
194
|
}
|
|
87
|
-
async recv(channel, timeout = 30_000) {
|
|
195
|
+
async recv(channel, timeout = 30_000, signal) {
|
|
196
|
+
// An already-aborted context must reject promptly rather than park a
|
|
197
|
+
// waiter that nothing will ever wake (spec 09, Cancellation item 2).
|
|
198
|
+
if (signal?.aborted) {
|
|
199
|
+
throw cancelledError(signal);
|
|
200
|
+
}
|
|
88
201
|
const state = this.getOrCreate(channel);
|
|
89
202
|
switch (state.mode) {
|
|
90
203
|
case 'value':
|
|
91
204
|
if (state.hasValue) {
|
|
92
205
|
return state.currentValue;
|
|
93
206
|
}
|
|
94
|
-
return this.waitWithTimeout(state.valueWaiters, channel.name, timeout);
|
|
95
|
-
case 'queue':
|
|
207
|
+
return this.waitWithTimeout(state.valueWaiters, channel.name, timeout, signal);
|
|
208
|
+
case 'queue': {
|
|
96
209
|
if (state.queue.length > 0) {
|
|
97
|
-
|
|
210
|
+
const head = state.queue.shift();
|
|
211
|
+
this.promotePendingSender(state);
|
|
212
|
+
return head;
|
|
98
213
|
}
|
|
99
|
-
|
|
214
|
+
// capacity-0 edge: senders can be parked while the queue is empty —
|
|
215
|
+
// hand the oldest parked value straight to this receiver.
|
|
216
|
+
if (state.pendingSenders.length > 0) {
|
|
217
|
+
const sender = state.pendingSenders.shift();
|
|
218
|
+
sender.resolve();
|
|
219
|
+
return sender.value;
|
|
220
|
+
}
|
|
221
|
+
return this.waitWithTimeout(state.queueWaiters, channel.name, timeout, signal);
|
|
222
|
+
}
|
|
100
223
|
case 'topic': {
|
|
101
224
|
// Clamp timeout to prevent indefinite subscriber leaks
|
|
102
225
|
let effectiveTimeout = timeout;
|
|
@@ -105,8 +228,10 @@ export class ChannelStore {
|
|
|
105
228
|
effectiveTimeout = MAX_TOPIC_TIMEOUT;
|
|
106
229
|
}
|
|
107
230
|
return new Promise((resolve, reject) => {
|
|
231
|
+
let removeAbortListener = null;
|
|
108
232
|
const timer = setTimeout(() => {
|
|
109
233
|
state.topicSubscribers.delete(handler);
|
|
234
|
+
removeAbortListener?.();
|
|
110
235
|
reject(new NoeticErrorImpl({
|
|
111
236
|
kind: 'channel_timeout',
|
|
112
237
|
channelName: channel.name,
|
|
@@ -116,9 +241,23 @@ export class ChannelStore {
|
|
|
116
241
|
const handler = (value) => {
|
|
117
242
|
clearTimeout(timer);
|
|
118
243
|
state.topicSubscribers.delete(handler);
|
|
244
|
+
removeAbortListener?.();
|
|
119
245
|
resolve(value);
|
|
120
246
|
};
|
|
121
247
|
state.topicSubscribers.add(handler);
|
|
248
|
+
if (signal) {
|
|
249
|
+
const onAbort = () => {
|
|
250
|
+
clearTimeout(timer);
|
|
251
|
+
state.topicSubscribers.delete(handler);
|
|
252
|
+
reject(cancelledError(signal));
|
|
253
|
+
};
|
|
254
|
+
signal.addEventListener('abort', onAbort, {
|
|
255
|
+
once: true,
|
|
256
|
+
});
|
|
257
|
+
removeAbortListener = () => {
|
|
258
|
+
signal.removeEventListener('abort', onAbort);
|
|
259
|
+
};
|
|
260
|
+
}
|
|
122
261
|
});
|
|
123
262
|
}
|
|
124
263
|
}
|
|
@@ -128,32 +267,55 @@ export class ChannelStore {
|
|
|
128
267
|
switch (state.mode) {
|
|
129
268
|
case 'value':
|
|
130
269
|
return state.hasValue ? state.currentValue : null;
|
|
131
|
-
case 'queue':
|
|
132
|
-
|
|
270
|
+
case 'queue': {
|
|
271
|
+
if (state.queue.length > 0) {
|
|
272
|
+
const head = state.queue.shift();
|
|
273
|
+
this.promotePendingSender(state);
|
|
274
|
+
return head;
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
133
278
|
case 'topic':
|
|
134
279
|
return null;
|
|
135
280
|
}
|
|
136
281
|
}
|
|
137
|
-
waitWithTimeout(waiters, channelName, timeout) {
|
|
282
|
+
waitWithTimeout(waiters, channelName, timeout, signal) {
|
|
138
283
|
return new Promise((resolve, reject) => {
|
|
139
284
|
let timer = null;
|
|
140
|
-
|
|
285
|
+
let removeAbortListener = null;
|
|
286
|
+
// Symmetric cleanup: whichever path settles the waiter (delivery,
|
|
287
|
+
// timeout, abort) must release both the timer and the abort listener.
|
|
288
|
+
const cleanup = () => {
|
|
141
289
|
if (timer) {
|
|
142
290
|
clearTimeout(timer);
|
|
143
291
|
}
|
|
292
|
+
removeAbortListener?.();
|
|
293
|
+
};
|
|
294
|
+
const wrappedResolve = (v) => {
|
|
295
|
+
cleanup();
|
|
144
296
|
resolve(v);
|
|
145
297
|
};
|
|
298
|
+
const wrappedReject = (e) => {
|
|
299
|
+
cleanup();
|
|
300
|
+
reject(e);
|
|
301
|
+
};
|
|
146
302
|
const entry = {
|
|
147
303
|
resolve: wrappedResolve,
|
|
148
|
-
reject,
|
|
304
|
+
reject: wrappedReject,
|
|
149
305
|
};
|
|
150
306
|
waiters.push(entry);
|
|
307
|
+
const removeEntry = () => {
|
|
308
|
+
const idx = waiters.indexOf(entry);
|
|
309
|
+
if (idx < 0) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
waiters.splice(idx, 1);
|
|
313
|
+
return true;
|
|
314
|
+
};
|
|
151
315
|
if (timeout > 0) {
|
|
152
316
|
timer = setTimeout(() => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
waiters.splice(idx, 1);
|
|
156
|
-
reject(new NoeticErrorImpl({
|
|
317
|
+
if (removeEntry()) {
|
|
318
|
+
wrappedReject(new NoeticErrorImpl({
|
|
157
319
|
kind: 'channel_timeout',
|
|
158
320
|
channelName,
|
|
159
321
|
timeout,
|
|
@@ -161,8 +323,42 @@ export class ChannelStore {
|
|
|
161
323
|
}
|
|
162
324
|
}, timeout);
|
|
163
325
|
}
|
|
326
|
+
if (signal) {
|
|
327
|
+
const onAbort = () => {
|
|
328
|
+
if (removeEntry()) {
|
|
329
|
+
wrappedReject(cancelledError(signal));
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
signal.addEventListener('abort', onAbort, {
|
|
333
|
+
once: true,
|
|
334
|
+
});
|
|
335
|
+
removeAbortListener = () => {
|
|
336
|
+
signal.removeEventListener('abort', onAbort);
|
|
337
|
+
};
|
|
338
|
+
}
|
|
164
339
|
});
|
|
165
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* External-sender write: synchronous, never back-pressured (spec 06,
|
|
343
|
+
* External Sender Back-Pressure). At capacity the OLDEST queued item is
|
|
344
|
+
* dropped (with a warning) so external callers never block.
|
|
345
|
+
*/
|
|
346
|
+
sendExternal(channel, value) {
|
|
347
|
+
const state = this.getOrCreate(channel);
|
|
348
|
+
const isFullQueue = state.mode === 'queue' &&
|
|
349
|
+
state.queueWaiters.length === 0 &&
|
|
350
|
+
state.queue.length >= state.capacity;
|
|
351
|
+
if (isFullQueue) {
|
|
352
|
+
console.warn(`[noetic] Channel '${channel.name}': queue at capacity (${state.capacity}), dropping oldest message (external sender).`);
|
|
353
|
+
state.queue.shift();
|
|
354
|
+
state.queue.push(value);
|
|
355
|
+
this.fireWakes(state);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
// Below capacity (or non-queue mode) the internal path resolves
|
|
359
|
+
// synchronously — nothing can park here.
|
|
360
|
+
void this.send(channel, value);
|
|
361
|
+
}
|
|
166
362
|
getHandle(channel, executionId) {
|
|
167
363
|
const store = this;
|
|
168
364
|
return {
|
|
@@ -177,7 +373,7 @@ export class ChannelStore {
|
|
|
177
373
|
channelName: channel.name,
|
|
178
374
|
});
|
|
179
375
|
}
|
|
180
|
-
store.
|
|
376
|
+
store.sendExternal(channel, value);
|
|
181
377
|
},
|
|
182
378
|
};
|
|
183
379
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { buildContextMemory } from '
|
|
2
|
-
export type { ItemSchemaRegistry } from '
|
|
3
|
-
export { defaultItemSchemaRegistry } from '
|
|
1
|
+
export { buildContextMemory } from '@noetic-tools/memory';
|
|
2
|
+
export type { ItemSchemaRegistry } from '@noetic-tools/types';
|
|
3
|
+
export { defaultItemSchemaRegistry } from '@noetic-tools/types';
|
|
4
4
|
export type { AgentHarnessContract, Channel, ChannelStore, Context, ContextMemory, CwdState, EventBroadcaster, FrontierFrame, FsAdapter, Item, ItemLog, LastLayerUsage, MemoryLayer, Span, StepMeta, TokenUsage, Tool, } from './context-type-deps.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { buildContextMemory } from '
|
|
2
|
-
export { defaultItemSchemaRegistry } from '
|
|
1
|
+
export { buildContextMemory } from '@noetic-tools/memory';
|
|
2
|
+
export { defaultItemSchemaRegistry } from '@noetic-tools/types';
|
|
@@ -34,6 +34,15 @@ export declare class ContextImpl implements Context<ContextMemory> {
|
|
|
34
34
|
private _completed;
|
|
35
35
|
private _aborted;
|
|
36
36
|
private _abortReason?;
|
|
37
|
+
/**
|
|
38
|
+
* Per-context abort fan-out. `abort()` fires it so operations blocked on
|
|
39
|
+
* this context (channel `recv` waiters, parked back-pressure senders)
|
|
40
|
+
* reject promptly with `{ kind: 'cancelled' }` instead of hanging until
|
|
41
|
+
* their timeout (spec 09, Cancellation item 2). Each fork/spawn child
|
|
42
|
+
* constructs its own ContextImpl and therefore its own controller —
|
|
43
|
+
* aborting a child never rejects the parent's waiters.
|
|
44
|
+
*/
|
|
45
|
+
private readonly _abortController;
|
|
37
46
|
private _memory?;
|
|
38
47
|
/**
|
|
39
48
|
* Stack of steps currently in flight on this context, most-recent last.
|
|
@@ -77,7 +86,7 @@ export declare class ContextImpl implements Context<ContextMemory> {
|
|
|
77
86
|
recv<T>(ch: Channel<T>, opts?: {
|
|
78
87
|
timeout?: number;
|
|
79
88
|
}): Promise<T>;
|
|
80
|
-
send<T>(ch: Channel<T>, value: T): void
|
|
89
|
+
send<T>(ch: Channel<T>, value: T): Promise<void>;
|
|
81
90
|
tryRecv<T>(ch: Channel<T>): T | null;
|
|
82
91
|
checkpoint(): Promise<void>;
|
|
83
92
|
complete<T>(value: T): void;
|
|
@@ -85,6 +94,13 @@ export declare class ContextImpl implements Context<ContextMemory> {
|
|
|
85
94
|
get completionValue(): unknown;
|
|
86
95
|
get abortReason(): string | undefined;
|
|
87
96
|
abort(reason?: string): void;
|
|
97
|
+
/**
|
|
98
|
+
* @internal
|
|
99
|
+
* Abort signal scoped to this context. Channel operations register on it
|
|
100
|
+
* so `abort()` rejects them promptly. Not part of the public Context
|
|
101
|
+
* interface.
|
|
102
|
+
*/
|
|
103
|
+
get abortSignal(): AbortSignal;
|
|
88
104
|
/**
|
|
89
105
|
* @internal
|
|
90
106
|
* Push a frame onto the execution frontier. Called by `execute()` at the
|
|
@@ -48,6 +48,15 @@ export class ContextImpl {
|
|
|
48
48
|
_completed = false;
|
|
49
49
|
_aborted = false;
|
|
50
50
|
_abortReason;
|
|
51
|
+
/**
|
|
52
|
+
* Per-context abort fan-out. `abort()` fires it so operations blocked on
|
|
53
|
+
* this context (channel `recv` waiters, parked back-pressure senders)
|
|
54
|
+
* reject promptly with `{ kind: 'cancelled' }` instead of hanging until
|
|
55
|
+
* their timeout (spec 09, Cancellation item 2). Each fork/spawn child
|
|
56
|
+
* constructs its own ContextImpl and therefore its own controller —
|
|
57
|
+
* aborting a child never rejects the parent's waiters.
|
|
58
|
+
*/
|
|
59
|
+
_abortController = new AbortController();
|
|
51
60
|
_memory;
|
|
52
61
|
/**
|
|
53
62
|
* Stack of steps currently in flight on this context, most-recent last.
|
|
@@ -110,13 +119,18 @@ export class ContextImpl {
|
|
|
110
119
|
if (!this.channelStore) {
|
|
111
120
|
return Promise.reject(new Error('No channel store configured'));
|
|
112
121
|
}
|
|
113
|
-
return this.channelStore.recv(ch, opts?.timeout);
|
|
122
|
+
return this.channelStore.recv(ch, opts?.timeout, this._abortController.signal);
|
|
114
123
|
}
|
|
115
124
|
send(ch, value) {
|
|
116
125
|
if (!this.channelStore) {
|
|
117
|
-
|
|
126
|
+
return Promise.reject(new Error('No channel store configured'));
|
|
118
127
|
}
|
|
119
|
-
|
|
128
|
+
// Internal sender: back-pressured on full queue channels (default 30s →
|
|
129
|
+
// channel_timeout); aborting this context rejects a parked send with
|
|
130
|
+
// 'cancelled' via the shared abort signal.
|
|
131
|
+
return this.channelStore.send(ch, value, {
|
|
132
|
+
signal: this._abortController.signal,
|
|
133
|
+
});
|
|
120
134
|
}
|
|
121
135
|
tryRecv(ch) {
|
|
122
136
|
if (!this.channelStore) {
|
|
@@ -145,6 +159,19 @@ export class ContextImpl {
|
|
|
145
159
|
abort(reason) {
|
|
146
160
|
this._aborted = true;
|
|
147
161
|
this._abortReason = reason;
|
|
162
|
+
// Reject everything blocked on this context (channel recv waiters,
|
|
163
|
+
// parked back-pressure senders) with { kind: 'cancelled' }. Idempotent —
|
|
164
|
+
// a second abort() leaves the already-aborted signal untouched.
|
|
165
|
+
this._abortController.abort(reason ?? 'context aborted');
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* @internal
|
|
169
|
+
* Abort signal scoped to this context. Channel operations register on it
|
|
170
|
+
* so `abort()` rejects them promptly. Not part of the public Context
|
|
171
|
+
* interface.
|
|
172
|
+
*/
|
|
173
|
+
get abortSignal() {
|
|
174
|
+
return this._abortController.signal;
|
|
148
175
|
}
|
|
149
176
|
/**
|
|
150
177
|
* @internal
|
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type { ContextMemory, MemoryLayer } from '@noetic-tools/memory';
|
|
2
|
+
export type { AgentHarnessContract, Channel, Context, CwdState, FsAdapter, Item, ItemLog, LastLayerUsage, Span, StepMeta, TokenUsage, Tool, } from '@noetic-tools/types';
|
|
2
3
|
export type { FrontierFrame } from '../types/checkpoint.js';
|
|
3
|
-
export type { StepMeta, TokenUsage } from '../types/common.js';
|
|
4
|
-
export type { Context, CwdState } from '../types/context.js';
|
|
5
|
-
export type { ItemLog } from '../types/context-parts/item-log.js';
|
|
6
|
-
export type { LastLayerUsage } from '../types/context-parts/layer-usage.js';
|
|
7
|
-
export type { FsAdapter } from '../types/fs-adapter.js';
|
|
8
|
-
export type { Item } from '../types/items.js';
|
|
9
|
-
export type { ContextMemory, MemoryLayer } from '../types/memory.js';
|
|
10
|
-
export type { Span } from '../types/observability.js';
|
|
11
|
-
export type { AgentHarnessContract } from '../types/runtime.js';
|
|
12
|
-
export type { Tool } from '../types/tool.js';
|
|
13
4
|
export type { ChannelStore } from './channel-store.js';
|
|
14
5
|
export type { EventBroadcaster } from './event-broadcaster.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Context, CwdState } from '
|
|
1
|
+
import type { Context, CwdState } from '@noetic-tools/types';
|
|
2
2
|
/**
|
|
3
3
|
* @public Resolve the live cwd for a tool execution. Falls back to the
|
|
4
4
|
* tool's factory-time cwd (then `process.cwd()`) so partial test contexts
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { DetachedHandle } from '
|
|
2
|
-
import { DetachedStatus } from '
|
|
3
|
-
import type { SubprocessAdapter, SubprocessHandle } from '../types/subprocess-adapter.js';
|
|
1
|
+
import type { DetachedHandle, SubprocessAdapter, SubprocessHandle } from '@noetic-tools/types';
|
|
2
|
+
import { DetachedStatus } from '@noetic-tools/types';
|
|
4
3
|
interface DetachedHandleOpts {
|
|
5
4
|
/** Id surfaced to callers — usually the execution id (childCtx.id in
|
|
6
5
|
* `detachedSpawn`) so consumers can correlate it with their own state. */
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { NoeticErrorImpl } from '
|
|
2
|
-
import { DetachedStatus } from '../types/detached.js';
|
|
3
|
-
import { frameworkCast } from '../util/framework-cast.js';
|
|
1
|
+
import { DetachedStatus, frameworkCast, NoeticErrorImpl } from '@noetic-tools/types';
|
|
4
2
|
//#endregion
|
|
5
3
|
//#region Helpers
|
|
6
4
|
const INITIAL_POLL_MS = 10;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { StorageAdapter } from '@noetic-tools/memory';
|
|
1
2
|
import type { CheckpointSnapshot } from '../../types/checkpoint.js';
|
|
2
|
-
import type { StorageAdapter } from '../../types/memory.js';
|
|
3
3
|
export declare const CheckpointKeys: {
|
|
4
4
|
readonly ExecPrefix: "execution:";
|
|
5
5
|
readonly SnapshotSuffix: ":snapshot";
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import type { LayerStateStore } from '
|
|
2
|
-
import type { ItemSchemaRegistry } from '
|
|
3
|
-
import type { Context } from '../../types/context.js';
|
|
4
|
-
import type { Item } from '../../types/items.js';
|
|
5
|
-
import type { MemoryLayer } from '../../types/memory.js';
|
|
1
|
+
import type { LayerStateStore, MemoryLayer } from '@noetic-tools/memory';
|
|
2
|
+
import type { Context, Item, ItemSchemaRegistry } from '@noetic-tools/types';
|
|
6
3
|
import type { CheckpointStore } from './checkpoint-store.js';
|
|
7
4
|
/**
|
|
8
5
|
* Minimum harness surface `captureCheckpoint` / `restoreFromCheckpoint`
|
|
@@ -30,9 +30,7 @@
|
|
|
30
30
|
* escalation) lives in the caller and composes via the `afterFirstTurn`
|
|
31
31
|
* hook.
|
|
32
32
|
*/
|
|
33
|
-
import type { HarnessResponse } from '
|
|
34
|
-
import type { ExecuteInput, Item } from '../../types/items.js';
|
|
35
|
-
import type { ExecuteOptions, SessionScope } from '../../types/runtime.js';
|
|
33
|
+
import type { ExecuteInput, ExecuteOptions, HarnessResponse, Item, SessionScope } from '@noetic-tools/types';
|
|
36
34
|
import type { DetachedSignal } from './detached-signal.js';
|
|
37
35
|
/**
|
|
38
36
|
* Minimum harness surface the loop calls into. Defining this
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* restore a prior session) has one documented, named entrypoint rather
|
|
11
11
|
* than reaching for the harness method inline.
|
|
12
12
|
*/
|
|
13
|
-
import type { Item } from '
|
|
13
|
+
import type { Item } from '@noetic-tools/types';
|
|
14
14
|
/**
|
|
15
15
|
* Minimum harness surface `seedFromItems` requires. Defined structurally
|
|
16
16
|
* so tests (and out-of-process children) can pass a stub without casts;
|
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
* and without the agent asking for external input — i.e. the agent
|
|
6
6
|
* stopped without calling either a terminal tool or an ask-user-style
|
|
7
7
|
* tool. That's a soft failure: rather than wait forever, we nudge the
|
|
8
|
-
* agent once to remind it of its options
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* agent once to remind it of its options and wait for the nudged turn
|
|
9
|
+
* to actually complete (`harness.execute` is enqueue-only, so the hook
|
|
10
|
+
* awaits `getAgentResponse` — raced against the signal — before
|
|
11
|
+
* re-checking). If the second turn also ends in the same stalled shape
|
|
12
|
+
* we escalate: the caller-supplied `onStall` callback records the
|
|
13
|
+
* escalation (task pause, metrics, whatever the domain demands) and we
|
|
14
|
+
* settle the signal ourselves with a canonical "stalled" outcome so
|
|
15
|
+
* the parent unwinds cleanly.
|
|
13
16
|
*
|
|
14
17
|
* The helper factors the state machine out of the loop so callers can
|
|
15
18
|
* opt in per-runner. Task-specific concerns (how "pending external
|
|
@@ -17,7 +20,7 @@
|
|
|
17
20
|
* represents a stalled failure) stay with the caller and flow in via
|
|
18
21
|
* the config.
|
|
19
22
|
*/
|
|
20
|
-
import type { InputMessageItem } from '
|
|
23
|
+
import type { InputMessageItem } from '@noetic-tools/types';
|
|
21
24
|
import type { DetachedSignal } from './detached-signal.js';
|
|
22
25
|
import type { AfterFirstTurnContext, RunnableLoopHarness } from './runnable-loop.js';
|
|
23
26
|
/**
|
|
@@ -74,9 +77,13 @@ export declare function createNudgeMessage(opts: CreateNudgeMessageOpts): InputM
|
|
|
74
77
|
* - the caller reports pending external input (`hasPendingExternal`
|
|
75
78
|
* returns true — e.g. an outstanding ask-user request).
|
|
76
79
|
*
|
|
77
|
-
* Otherwise it sends the nudge message via `harness.execute
|
|
78
|
-
* for
|
|
79
|
-
* the
|
|
80
|
-
*
|
|
80
|
+
* Otherwise it sends the nudge message via `harness.execute` and waits
|
|
81
|
+
* for the nudged turn to actually complete. `execute()` only enqueues
|
|
82
|
+
* the message — the turn runs asynchronously through the session
|
|
83
|
+
* runner — so the hook awaits `harness.getAgentResponse(threadId)`
|
|
84
|
+
* (raced against `signal.done` so a terminal tool that settles the
|
|
85
|
+
* signal mid-turn short-circuits the wait) before re-checking the same
|
|
86
|
+
* two conditions. If the agent still hasn't progressed, the hook fires
|
|
87
|
+
* `onStall` and resolves the signal with `buildStalledOutcome()`.
|
|
81
88
|
*/
|
|
82
89
|
export declare function createStallNudgeHook<TOutcome>(opts: StallNudgeOpts<TOutcome>): (ctx: AfterFirstTurnContext) => Promise<void>;
|