@noetic-tools/core 3.2.0 → 5.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-subprocess/manifest-persistence.d.ts +1 -1
- package/dist/adapters/in-memory-subprocess/types.d.ts +1 -1
- package/dist/adapters/openrouter.d.ts +2 -2
- package/dist/adapters/openrouter.js +2 -2
- package/dist/builders/context-builder.d.ts +11 -0
- package/dist/builders/context-builder.js +15 -0
- package/dist/builders/context-option.d.ts +23 -0
- package/dist/builders/context-option.js +22 -0
- package/dist/builders/control-flow-builders.d.ts +19 -19
- package/dist/builders/every.d.ts +4 -4
- 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 +3 -3
- package/dist/builders/provide-builder.d.ts +14 -7
- package/dist/builders/provide-builder.js +15 -4
- package/dist/builders/spawn-builder.d.ts +8 -6
- package/dist/builders/spawn-builder.js +7 -2
- package/dist/builders/step-builders.d.ts +19 -20
- package/dist/builders/tool-builder.d.ts +9 -5
- package/dist/builders/tool-builder.js +3 -2
- package/dist/builders/workflow-hydrator.d.ts +17 -4
- package/dist/builders/workflow-hydrator.js +87 -8
- package/dist/builders/workflow-step.d.ts +61 -0
- package/dist/builders/workflow-step.js +126 -0
- package/dist/conditions/conditions.d.ts +17 -17
- package/dist/conditions/conditions.js +1 -1
- package/dist/deprecated.d.ts +30 -0
- package/dist/deprecated.js +28 -0
- package/dist/harness/agent-harness.d.ts +42 -26
- package/dist/harness/agent-harness.js +68 -21
- package/dist/harness/deps/{memory.d.ts → context.d.ts} +2 -2
- package/dist/harness/deps/{memory.js → context.js} +1 -1
- package/dist/harness/deps/types.d.ts +1 -1
- package/dist/harness/model-schema.d.ts +2 -2
- package/dist/index.d.ts +21 -17
- package/dist/index.js +14 -8
- package/dist/interpreter/action-deps.d.ts +3 -3
- package/dist/interpreter/action-deps.js +2 -2
- package/dist/interpreter/action-types.d.ts +1 -1
- package/dist/interpreter/collect-tools.d.ts +1 -1
- package/dist/interpreter/execute-action.d.ts +10 -10
- package/dist/interpreter/execute-action.js +13 -13
- package/dist/interpreter/execute-control.d.ts +4 -4
- package/dist/interpreter/execute-control.js +1 -1
- package/dist/interpreter/execute-sub-harness.d.ts +1 -1
- package/dist/interpreter/execute.d.ts +3 -3
- package/dist/interpreter/execute.js +78 -31
- package/dist/interpreter/step-dispatcher.d.ts +3 -3
- package/dist/interpreter/sub-harness-events.d.ts +2 -2
- package/dist/interpreter/typeguards.d.ts +4 -4
- package/dist/patterns/dynamic-workflow.d.ts +7 -4
- package/dist/patterns/dynamic-workflow.js +2 -0
- package/dist/patterns/interview.d.ts +2 -2
- package/dist/patterns/plans.d.ts +5 -5
- package/dist/patterns/ralph-wiggum.d.ts +2 -2
- package/dist/patterns/react.d.ts +7 -5
- package/dist/patterns/react.js +6 -4
- package/dist/portable/builders.js +2 -0
- package/dist/portable/context-layers.d.ts +8 -0
- package/dist/portable/context-layers.js +8 -0
- package/dist/portable.d.ts +2 -2
- package/dist/portable.js +2 -2
- package/dist/runtime/channel-store.d.ts +33 -0
- package/dist/runtime/channel-store.js +297 -11
- package/dist/runtime/context-deps.d.ts +2 -2
- package/dist/runtime/context-deps.js +1 -1
- package/dist/runtime/context-impl.d.ts +10 -8
- package/dist/runtime/context-impl.js +11 -7
- package/dist/runtime/context-type-deps.d.ts +1 -1
- package/dist/runtime/durable/checkpoint-store.d.ts +1 -1
- package/dist/runtime/durable/harness-checkpoints.d.ts +2 -3
- package/dist/runtime/durable/harness-checkpoints.js +2 -3
- package/dist/runtime/durable/step-ledger.d.ts +1 -1
- package/dist/runtime/durable/step-ledger.js +1 -1
- package/dist/runtime/in-memory-storage.d.ts +1 -1
- package/dist/runtime/session-runner.d.ts +1 -1
- package/dist/runtime/step-registry.d.ts +3 -3
- package/dist/runtime/{tool-memory.d.ts → tool-context.d.ts} +1 -2
- package/dist/runtime/{tool-memory.js → tool-context.js} +5 -2
- package/dist/runtime/tool-ui.d.ts +1 -1
- package/dist/runtime/tool-ui.js +1 -1
- package/dist/schemas/workflow.d.ts +5 -279
- package/dist/schemas/workflow.js +4 -376
- package/dist/types/mutable-context.d.ts +2 -2
- package/dist/types/step-registrar.d.ts +1 -1
- package/dist/unstable.d.ts +5 -5
- package/dist/unstable.js +3 -3
- package/dist/until/predicates.d.ts +1 -1
- package/package.json +4 -4
- package/schema/noetic-workflow.schema.json +28 -0
- package/dist/builders/memory-builder.d.ts +0 -11
- package/dist/builders/memory-builder.js +0 -15
- package/dist/patterns/flow.d.ts +0 -2
- package/dist/patterns/flow.js +0 -1
- package/dist/portable/memory-layers.d.ts +0 -8
- package/dist/portable/memory-layers.js +0 -8
|
@@ -12,6 +12,15 @@ export interface ChannelSendOpts {
|
|
|
12
12
|
export declare class ChannelStore {
|
|
13
13
|
private channels;
|
|
14
14
|
private closedExecutions;
|
|
15
|
+
/**
|
|
16
|
+
* Ids a `ChannelHandle` was issued for. Handles have no dispose lifecycle,
|
|
17
|
+
* so these are the only ids whose closure must be durably recorded (for
|
|
18
|
+
* `handle.closed` / `channel_closed`). Streams are closed directly through
|
|
19
|
+
* `subscribersByExecution`, so stream-only ids never accumulate here — a
|
|
20
|
+
* session harness closing one root id per turn stays bounded.
|
|
21
|
+
*/
|
|
22
|
+
private handleObserved;
|
|
23
|
+
private subscribersByExecution;
|
|
15
24
|
private getOrCreate;
|
|
16
25
|
/**
|
|
17
26
|
* Subscribe to wake notifications on a channel without consuming any value.
|
|
@@ -53,6 +62,30 @@ export declare class ChannelStore {
|
|
|
53
62
|
* dropped (with a warning) so external callers never block.
|
|
54
63
|
*/
|
|
55
64
|
private sendExternal;
|
|
65
|
+
/**
|
|
66
|
+
* External-reader subscription (spec 06, External Subscriptions). Delivery
|
|
67
|
+
* by mode: `topic` is a persistent non-lossy tap (per-subscriber bounded
|
|
68
|
+
* buffer, no replay of pre-subscribe values), `queue` competes with internal
|
|
69
|
+
* `recv` waiters FIFO (external consumption frees parked senders), `value`
|
|
70
|
+
* yields the current value then conflated updates.
|
|
71
|
+
*
|
|
72
|
+
* Channel state is keyed by NAME — `executionId` bounds only the
|
|
73
|
+
* subscription's lifetime (it ends when `closeExecution(executionId)`
|
|
74
|
+
* runs; queued values present at close drain first), never which values
|
|
75
|
+
* are delivered. An id that is never closed yields an unbounded stream
|
|
76
|
+
* the caller ends with `iterator.return()`. External waiters never time
|
|
77
|
+
* out — they end via close instead. The returned iterable owns a single
|
|
78
|
+
* iterator (generator semantics): subscribe again for a second consumer.
|
|
79
|
+
*/
|
|
80
|
+
subscribe<T>(channel: ExternalChannel<T>, executionId: string): AsyncIterable<T>;
|
|
81
|
+
/** Shift the queue head and promote the oldest parked sender into the freed slot. */
|
|
82
|
+
private dequeueHead;
|
|
56
83
|
getHandle<T>(channel: ExternalChannel<T>, executionId: string): ChannelHandle<T>;
|
|
57
84
|
closeExecution(executionId: string): void;
|
|
85
|
+
/**
|
|
86
|
+
* A root run is (re)starting on this execution id: clear any previous
|
|
87
|
+
* closure so checkpoint-restored and sequentially re-run root contexts get
|
|
88
|
+
* working channels again instead of a permanently poisoned id.
|
|
89
|
+
*/
|
|
90
|
+
openExecution(executionId: string): void;
|
|
58
91
|
}
|
|
@@ -12,6 +12,15 @@ function cancelledError(signal) {
|
|
|
12
12
|
export class ChannelStore {
|
|
13
13
|
channels = new Map();
|
|
14
14
|
closedExecutions = new Set();
|
|
15
|
+
/**
|
|
16
|
+
* Ids a `ChannelHandle` was issued for. Handles have no dispose lifecycle,
|
|
17
|
+
* so these are the only ids whose closure must be durably recorded (for
|
|
18
|
+
* `handle.closed` / `channel_closed`). Streams are closed directly through
|
|
19
|
+
* `subscribersByExecution`, so stream-only ids never accumulate here — a
|
|
20
|
+
* session harness closing one root id per turn stays bounded.
|
|
21
|
+
*/
|
|
22
|
+
handleObserved = new Set();
|
|
23
|
+
subscribersByExecution = new Map();
|
|
15
24
|
getOrCreate(channel) {
|
|
16
25
|
let state = frameworkCast(this.channels.get(channel.name));
|
|
17
26
|
if (!state) {
|
|
@@ -25,6 +34,7 @@ export class ChannelStore {
|
|
|
25
34
|
pendingSenders: [],
|
|
26
35
|
topicSubscribers: new Set(),
|
|
27
36
|
wakeSubscribers: new Set(),
|
|
37
|
+
externalSubscribers: new Set(),
|
|
28
38
|
};
|
|
29
39
|
this.channels.set(channel.name, frameworkCast(state));
|
|
30
40
|
}
|
|
@@ -79,6 +89,9 @@ export class ChannelStore {
|
|
|
79
89
|
waiter.resolve(value);
|
|
80
90
|
}
|
|
81
91
|
}
|
|
92
|
+
for (const sub of state.externalSubscribers) {
|
|
93
|
+
sub.push(value);
|
|
94
|
+
}
|
|
82
95
|
break;
|
|
83
96
|
case 'queue':
|
|
84
97
|
if (state.queueWaiters.length > 0) {
|
|
@@ -96,6 +109,9 @@ export class ChannelStore {
|
|
|
96
109
|
for (const sub of state.topicSubscribers) {
|
|
97
110
|
sub(value);
|
|
98
111
|
}
|
|
112
|
+
for (const sub of state.externalSubscribers) {
|
|
113
|
+
sub.push(value);
|
|
114
|
+
}
|
|
99
115
|
break;
|
|
100
116
|
}
|
|
101
117
|
this.fireWakes(state);
|
|
@@ -206,10 +222,9 @@ export class ChannelStore {
|
|
|
206
222
|
}
|
|
207
223
|
return this.waitWithTimeout(state.valueWaiters, channel.name, timeout, signal);
|
|
208
224
|
case 'queue': {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return head;
|
|
225
|
+
const head = this.dequeueHead(state);
|
|
226
|
+
if (head) {
|
|
227
|
+
return head.value;
|
|
213
228
|
}
|
|
214
229
|
// capacity-0 edge: senders can be parked while the queue is empty —
|
|
215
230
|
// hand the oldest parked value straight to this receiver.
|
|
@@ -268,12 +283,10 @@ export class ChannelStore {
|
|
|
268
283
|
case 'value':
|
|
269
284
|
return state.hasValue ? state.currentValue : null;
|
|
270
285
|
case 'queue': {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
return null;
|
|
286
|
+
// Explicit head check (not `?? null`): a stored `undefined` must come
|
|
287
|
+
// back as `undefined`, distinguishable from the empty-queue sentinel.
|
|
288
|
+
const head = this.dequeueHead(state);
|
|
289
|
+
return head ? head.value : null;
|
|
277
290
|
}
|
|
278
291
|
case 'topic':
|
|
279
292
|
return null;
|
|
@@ -359,7 +372,96 @@ export class ChannelStore {
|
|
|
359
372
|
// synchronously — nothing can park here.
|
|
360
373
|
void this.send(channel, value);
|
|
361
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* External-reader subscription (spec 06, External Subscriptions). Delivery
|
|
377
|
+
* by mode: `topic` is a persistent non-lossy tap (per-subscriber bounded
|
|
378
|
+
* buffer, no replay of pre-subscribe values), `queue` competes with internal
|
|
379
|
+
* `recv` waiters FIFO (external consumption frees parked senders), `value`
|
|
380
|
+
* yields the current value then conflated updates.
|
|
381
|
+
*
|
|
382
|
+
* Channel state is keyed by NAME — `executionId` bounds only the
|
|
383
|
+
* subscription's lifetime (it ends when `closeExecution(executionId)`
|
|
384
|
+
* runs; queued values present at close drain first), never which values
|
|
385
|
+
* are delivered. An id that is never closed yields an unbounded stream
|
|
386
|
+
* the caller ends with `iterator.return()`. External waiters never time
|
|
387
|
+
* out — they end via close instead. The returned iterable owns a single
|
|
388
|
+
* iterator (generator semantics): subscribe again for a second consumer.
|
|
389
|
+
*/
|
|
390
|
+
subscribe(channel, executionId) {
|
|
391
|
+
if (this.closedExecutions.has(executionId)) {
|
|
392
|
+
return CLOSED_ITERABLE;
|
|
393
|
+
}
|
|
394
|
+
const state = this.getOrCreate(channel);
|
|
395
|
+
const subscriber = new ExternalSubscriber({
|
|
396
|
+
channelName: channel.name,
|
|
397
|
+
conflate: state.mode === 'value',
|
|
398
|
+
takeShared: state.mode === 'queue'
|
|
399
|
+
? () => {
|
|
400
|
+
const head = this.dequeueHead(state);
|
|
401
|
+
if (head) {
|
|
402
|
+
return head;
|
|
403
|
+
}
|
|
404
|
+
// capacity-0 edge: hand a parked sender's value straight over.
|
|
405
|
+
const sender = state.pendingSenders.shift();
|
|
406
|
+
if (!sender) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
sender.resolve();
|
|
410
|
+
return {
|
|
411
|
+
value: sender.value,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
: undefined,
|
|
415
|
+
parkShared: state.mode === 'queue'
|
|
416
|
+
? (waiter) => {
|
|
417
|
+
state.queueWaiters.push(waiter);
|
|
418
|
+
return () => {
|
|
419
|
+
const idx = state.queueWaiters.indexOf(waiter);
|
|
420
|
+
if (idx >= 0) {
|
|
421
|
+
state.queueWaiters.splice(idx, 1);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
: undefined,
|
|
426
|
+
onFinish: () => {
|
|
427
|
+
state.externalSubscribers.delete(subscriber);
|
|
428
|
+
const byExecution = this.subscribersByExecution.get(executionId);
|
|
429
|
+
if (byExecution) {
|
|
430
|
+
byExecution.delete(erased);
|
|
431
|
+
if (byExecution.size === 0) {
|
|
432
|
+
this.subscribersByExecution.delete(executionId);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
const erased = frameworkCast(subscriber);
|
|
438
|
+
if (state.mode !== 'queue') {
|
|
439
|
+
state.externalSubscribers.add(subscriber);
|
|
440
|
+
}
|
|
441
|
+
if (state.mode === 'value' && state.hasValue) {
|
|
442
|
+
subscriber.push(state.currentValue);
|
|
443
|
+
}
|
|
444
|
+
let byExecution = this.subscribersByExecution.get(executionId);
|
|
445
|
+
if (!byExecution) {
|
|
446
|
+
byExecution = new Set();
|
|
447
|
+
this.subscribersByExecution.set(executionId, byExecution);
|
|
448
|
+
}
|
|
449
|
+
byExecution.add(erased);
|
|
450
|
+
return subscriber;
|
|
451
|
+
}
|
|
452
|
+
/** Shift the queue head and promote the oldest parked sender into the freed slot. */
|
|
453
|
+
dequeueHead(state) {
|
|
454
|
+
if (state.queue.length === 0) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
const head = state.queue.shift();
|
|
458
|
+
this.promotePendingSender(state);
|
|
459
|
+
return {
|
|
460
|
+
value: head,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
362
463
|
getHandle(channel, executionId) {
|
|
464
|
+
this.handleObserved.add(executionId);
|
|
363
465
|
const store = this;
|
|
364
466
|
return {
|
|
365
467
|
get closed() {
|
|
@@ -378,6 +480,190 @@ export class ChannelStore {
|
|
|
378
480
|
};
|
|
379
481
|
}
|
|
380
482
|
closeExecution(executionId) {
|
|
381
|
-
|
|
483
|
+
// Closure is durably recorded only for ids with issued handles — see
|
|
484
|
+
// `handleObserved`. Consequence: a handle taken AFTER an execution
|
|
485
|
+
// completed reads `closed === false`, and a stream subscribed after a
|
|
486
|
+
// stream-only execution completed waits instead of ending; take handles
|
|
487
|
+
// and subscribe before running.
|
|
488
|
+
if (this.handleObserved.has(executionId)) {
|
|
489
|
+
this.closedExecutions.add(executionId);
|
|
490
|
+
}
|
|
491
|
+
const subscribers = this.subscribersByExecution.get(executionId);
|
|
492
|
+
if (!subscribers) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
this.subscribersByExecution.delete(executionId);
|
|
496
|
+
for (const subscriber of subscribers) {
|
|
497
|
+
subscriber.close();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* A root run is (re)starting on this execution id: clear any previous
|
|
502
|
+
* closure so checkpoint-restored and sequentially re-run root contexts get
|
|
503
|
+
* working channels again instead of a permanently poisoned id.
|
|
504
|
+
*/
|
|
505
|
+
openExecution(executionId) {
|
|
506
|
+
this.closedExecutions.delete(executionId);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const MAX_EXTERNAL_BUFFER = 1_000;
|
|
510
|
+
const CLOSED_ITERABLE = {
|
|
511
|
+
[Symbol.asyncIterator]() {
|
|
512
|
+
return this;
|
|
513
|
+
},
|
|
514
|
+
next() {
|
|
515
|
+
return Promise.resolve({
|
|
516
|
+
value: undefined,
|
|
517
|
+
done: true,
|
|
518
|
+
});
|
|
519
|
+
},
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* The iterator behind `ChannelStore.subscribe`. Topic/value subscribers are
|
|
523
|
+
* push-fed via `push()`; queue subscribers pull from the shared queue state so
|
|
524
|
+
* they compete fairly with internal `recv` waiters. `close()` (execution
|
|
525
|
+
* completed) lets already-buffered values drain before ending; `return()`
|
|
526
|
+
* (consumer walked away) drops them.
|
|
527
|
+
*/
|
|
528
|
+
class ExternalSubscriber {
|
|
529
|
+
opts;
|
|
530
|
+
buffer = [];
|
|
531
|
+
waiters = [];
|
|
532
|
+
parked = [];
|
|
533
|
+
done = false;
|
|
534
|
+
/** Consumer called return(): stop pulling from the shared queue. */
|
|
535
|
+
detached = false;
|
|
536
|
+
constructor(opts) {
|
|
537
|
+
this.opts = opts;
|
|
538
|
+
}
|
|
539
|
+
[Symbol.asyncIterator]() {
|
|
540
|
+
return this;
|
|
541
|
+
}
|
|
542
|
+
/** Deliver a value to this subscriber (topic/value modes). */
|
|
543
|
+
push(value) {
|
|
544
|
+
if (this.done) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const waiter = this.waiters.shift();
|
|
548
|
+
if (waiter) {
|
|
549
|
+
waiter({
|
|
550
|
+
value,
|
|
551
|
+
done: false,
|
|
552
|
+
});
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (this.opts.conflate) {
|
|
556
|
+
this.buffer = [
|
|
557
|
+
value,
|
|
558
|
+
];
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (this.buffer.length >= MAX_EXTERNAL_BUFFER) {
|
|
562
|
+
console.warn(`[noetic] Channel '${this.opts.channelName}': external subscriber buffer at capacity (${MAX_EXTERNAL_BUFFER}), dropping oldest value.`);
|
|
563
|
+
this.buffer.shift();
|
|
564
|
+
}
|
|
565
|
+
this.buffer.push(value);
|
|
566
|
+
}
|
|
567
|
+
next() {
|
|
568
|
+
if (this.buffer.length > 0) {
|
|
569
|
+
return Promise.resolve({
|
|
570
|
+
value: this.buffer.shift(),
|
|
571
|
+
done: false,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
// Live shared-queue reads stop at close — close() snapshots the queue
|
|
575
|
+
// into this.buffer, so a closed iterator can never steal values sent to
|
|
576
|
+
// a later execution on the same channel name.
|
|
577
|
+
const taken = this.done ? null : this.opts.takeShared?.();
|
|
578
|
+
if (taken) {
|
|
579
|
+
return Promise.resolve({
|
|
580
|
+
value: taken.value,
|
|
581
|
+
done: false,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
if (this.done) {
|
|
585
|
+
return Promise.resolve({
|
|
586
|
+
value: undefined,
|
|
587
|
+
done: true,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
if (this.opts.parkShared) {
|
|
591
|
+
return this.parkInSharedQueue(this.opts.parkShared);
|
|
592
|
+
}
|
|
593
|
+
return new Promise((resolve) => {
|
|
594
|
+
this.waiters.push(resolve);
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
return() {
|
|
598
|
+
this.detached = true;
|
|
599
|
+
this.buffer = [];
|
|
600
|
+
this.close();
|
|
601
|
+
return Promise.resolve({
|
|
602
|
+
value: undefined,
|
|
603
|
+
done: true,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
/** End the iterator: settle every pending `next()` and unregister. Buffered values still drain. */
|
|
607
|
+
close() {
|
|
608
|
+
if (this.done) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
// Snapshot the shared queue's current contents into the private buffer
|
|
612
|
+
// ("buffered values drain first") — after this the iterator never touches
|
|
613
|
+
// live channel state again. A detached iterator (return()) walked away
|
|
614
|
+
// and drains nothing.
|
|
615
|
+
if (!this.detached && this.opts.takeShared) {
|
|
616
|
+
for (let taken = this.opts.takeShared(); taken; taken = this.opts.takeShared()) {
|
|
617
|
+
this.buffer.push(taken.value);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
this.done = true;
|
|
621
|
+
this.opts.onFinish();
|
|
622
|
+
for (const entry of this.parked.splice(0)) {
|
|
623
|
+
entry.unpark();
|
|
624
|
+
entry.settleDone();
|
|
625
|
+
}
|
|
626
|
+
for (const waiter of this.waiters.splice(0)) {
|
|
627
|
+
waiter({
|
|
628
|
+
value: undefined,
|
|
629
|
+
done: true,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Queue mode: wait in the channel's shared waiter FIFO. No timer attaches,
|
|
635
|
+
* so external waiters never reject with `channel_timeout` — `close()` ends
|
|
636
|
+
* them instead.
|
|
637
|
+
*/
|
|
638
|
+
parkInSharedQueue(parkShared) {
|
|
639
|
+
return new Promise((resolve) => {
|
|
640
|
+
const entry = {
|
|
641
|
+
unpark: () => { },
|
|
642
|
+
settleDone: () => resolve({
|
|
643
|
+
value: undefined,
|
|
644
|
+
done: true,
|
|
645
|
+
}),
|
|
646
|
+
};
|
|
647
|
+
const removeParked = () => {
|
|
648
|
+
const idx = this.parked.indexOf(entry);
|
|
649
|
+
if (idx >= 0) {
|
|
650
|
+
this.parked.splice(idx, 1);
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
entry.unpark = parkShared({
|
|
654
|
+
resolve: (value) => {
|
|
655
|
+
removeParked();
|
|
656
|
+
resolve({
|
|
657
|
+
value,
|
|
658
|
+
done: false,
|
|
659
|
+
});
|
|
660
|
+
},
|
|
661
|
+
reject: () => {
|
|
662
|
+
removeParked();
|
|
663
|
+
entry.settleDone();
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
this.parked.push(entry);
|
|
667
|
+
});
|
|
382
668
|
}
|
|
383
669
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { buildContextData } from '@noetic-tools/context';
|
|
2
2
|
export type { ItemSchemaRegistry } from '@noetic-tools/types';
|
|
3
3
|
export { defaultItemSchemaRegistry } from '@noetic-tools/types';
|
|
4
|
-
export type { AgentHarnessContract, Channel, ChannelStore, Context,
|
|
4
|
+
export type { AgentHarnessContract, Channel, ChannelStore, Context, ContextData, ContextLayer, CwdState, EventBroadcaster, FrontierFrame, FsAdapter, Item, ItemLog, LastLayerUsage, Span, StepMeta, TokenUsage, Tool, } from './context-type-deps.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { buildContextData } from '@noetic-tools/context';
|
|
2
2
|
export { defaultItemSchemaRegistry } from '@noetic-tools/types';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentHarnessContract, Channel, ChannelStore, Context,
|
|
1
|
+
import type { AgentHarnessContract, Channel, ChannelStore, Context, ContextData, ContextLayer, CwdState, EventBroadcaster, FrontierFrame, FsAdapter, Item, ItemLog, ItemSchemaRegistry, LastLayerUsage, Span, StepMeta, TokenUsage, Tool } from './context-deps.js';
|
|
2
2
|
import type { StepLedger } from './durable/step-ledger.js';
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
@@ -7,13 +7,13 @@ import type { StepLedger } from './durable/step-ledger.js';
|
|
|
7
7
|
* children, so a foreign `Context` implementation yields just itself.
|
|
8
8
|
*/
|
|
9
9
|
export declare function collectContextTree(ctx: Context): Context[];
|
|
10
|
-
export declare class ContextImpl implements Context<
|
|
10
|
+
export declare class ContextImpl implements Context<ContextData> {
|
|
11
11
|
readonly id: string;
|
|
12
12
|
stepCount: number;
|
|
13
13
|
tokens: TokenUsage;
|
|
14
14
|
cost: number;
|
|
15
15
|
state: unknown;
|
|
16
|
-
readonly parent: Context<
|
|
16
|
+
readonly parent: Context<ContextData> | null;
|
|
17
17
|
readonly depth: number;
|
|
18
18
|
readonly span: Span;
|
|
19
19
|
readonly threadId: string;
|
|
@@ -22,7 +22,7 @@ export declare class ContextImpl implements Context<ContextMemory> {
|
|
|
22
22
|
lastStepMeta: StepMeta | null;
|
|
23
23
|
lastLayerUsage: LastLayerUsage | undefined;
|
|
24
24
|
readonly harness: AgentHarnessContract;
|
|
25
|
-
readonly layers?:
|
|
25
|
+
readonly layers?: ContextLayer[];
|
|
26
26
|
unifiedTools?: ReadonlyArray<Tool>;
|
|
27
27
|
readonly itemSchemas?: ItemSchemaRegistry;
|
|
28
28
|
readonly cwdState: CwdState;
|
|
@@ -60,7 +60,7 @@ export declare class ContextImpl implements Context<ContextMemory> {
|
|
|
60
60
|
* @internal
|
|
61
61
|
*/
|
|
62
62
|
private readonly _children;
|
|
63
|
-
private
|
|
63
|
+
private _contextData?;
|
|
64
64
|
/**
|
|
65
65
|
* Stack of steps currently in flight on this context, most-recent last.
|
|
66
66
|
* Pushed by `enterStep` at the top of `execute()` and popped by
|
|
@@ -109,7 +109,7 @@ export declare class ContextImpl implements Context<ContextMemory> {
|
|
|
109
109
|
pathPrefix?: string;
|
|
110
110
|
/** The execution's completion ledger, shared with children by reference. */
|
|
111
111
|
ledger?: StepLedger;
|
|
112
|
-
layers?:
|
|
112
|
+
layers?: ContextLayer[];
|
|
113
113
|
unifiedTools?: ReadonlyArray<Tool>;
|
|
114
114
|
itemSchemas?: ItemSchemaRegistry;
|
|
115
115
|
cwdState?: CwdState;
|
|
@@ -118,7 +118,7 @@ export declare class ContextImpl implements Context<ContextMemory> {
|
|
|
118
118
|
* Pre-chosen context id. When set, the ContextImpl adopts this id instead
|
|
119
119
|
* of generating a fresh UUID. Used by `executeSpawn` so the child's
|
|
120
120
|
* `ctx.id` matches the `executionId` keyed into the layer-state store —
|
|
121
|
-
* otherwise writes via `ctx.
|
|
121
|
+
* otherwise writes via `ctx.context[layerId].state` land on one id while
|
|
122
122
|
* spawn's `onReturn` reads from another, silently losing the update.
|
|
123
123
|
*/
|
|
124
124
|
id?: string;
|
|
@@ -149,7 +149,9 @@ export declare class ContextImpl implements Context<ContextMemory> {
|
|
|
149
149
|
get fs(): FsAdapter;
|
|
150
150
|
get shell(): AgentHarnessContract['shell'];
|
|
151
151
|
get subprocess(): AgentHarnessContract['subprocess'];
|
|
152
|
-
get
|
|
152
|
+
get context(): ContextData;
|
|
153
|
+
/** @deprecated Renamed to `context`. Returns the same object. */
|
|
154
|
+
get memory(): ContextData;
|
|
153
155
|
recv<T>(ch: Channel<T>, opts?: {
|
|
154
156
|
timeout?: number;
|
|
155
157
|
}): Promise<T>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildContextData, defaultItemSchemaRegistry } from './context-deps.js';
|
|
2
2
|
import { ItemLogImpl } from './item-log-impl.js';
|
|
3
|
-
const
|
|
3
|
+
const EMPTY_CONTEXT = Object.freeze({});
|
|
4
4
|
class NoopSpan {
|
|
5
5
|
traceId = crypto.randomUUID();
|
|
6
6
|
spanId = crypto.randomUUID();
|
|
@@ -84,7 +84,7 @@ export class ContextImpl {
|
|
|
84
84
|
* @internal
|
|
85
85
|
*/
|
|
86
86
|
_children = new Set();
|
|
87
|
-
|
|
87
|
+
_contextData;
|
|
88
88
|
/**
|
|
89
89
|
* Stack of steps currently in flight on this context, most-recent last.
|
|
90
90
|
* Pushed by `enterStep` at the top of `execute()` and popped by
|
|
@@ -203,11 +203,15 @@ export class ContextImpl {
|
|
|
203
203
|
get subprocess() {
|
|
204
204
|
return this.harness.subprocess;
|
|
205
205
|
}
|
|
206
|
-
get
|
|
207
|
-
if (!this.
|
|
208
|
-
this.
|
|
206
|
+
get context() {
|
|
207
|
+
if (!this._contextData) {
|
|
208
|
+
this._contextData = this.layers ? buildContextData(this.layers, this) : EMPTY_CONTEXT;
|
|
209
209
|
}
|
|
210
|
-
return this.
|
|
210
|
+
return this._contextData;
|
|
211
|
+
}
|
|
212
|
+
/** @deprecated Renamed to `context`. Returns the same object. */
|
|
213
|
+
get memory() {
|
|
214
|
+
return this.context;
|
|
211
215
|
}
|
|
212
216
|
recv(ch, opts) {
|
|
213
217
|
if (!this.channelStore) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type { ContextData, ContextLayer } from '@noetic-tools/context';
|
|
2
2
|
export type { AgentHarnessContract, Channel, Context, CwdState, FsAdapter, Item, ItemLog, LastLayerUsage, Span, StepMeta, TokenUsage, Tool, } from '@noetic-tools/types';
|
|
3
3
|
export type { FrontierFrame } from '../types/checkpoint.js';
|
|
4
4
|
export type { ChannelStore } from './channel-store.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LayerStateStore
|
|
1
|
+
import type { LayerStateStore } from '@noetic-tools/context';
|
|
2
2
|
import type { Context, Item, ItemSchemaRegistry, RestoreContextOptions } from '@noetic-tools/types';
|
|
3
3
|
import type { EventBroadcaster } from '../event-broadcaster.js';
|
|
4
4
|
import type { CheckpointStore } from './checkpoint-store.js';
|
|
@@ -29,7 +29,6 @@ export interface CheckpointHarnessHandle {
|
|
|
29
29
|
readonly stepLedgerRetention?: StepLedgerRetention;
|
|
30
30
|
readonly layerStateStore: LayerStateStore;
|
|
31
31
|
readonly itemSchemas: ItemSchemaRegistry;
|
|
32
|
-
readonly _memory?: MemoryLayer[];
|
|
33
32
|
createContext(opts?: RestoreCheckpointOptions & {
|
|
34
33
|
items?: Item[];
|
|
35
34
|
threadId?: string;
|
|
@@ -52,7 +51,7 @@ export declare function captureCheckpoint(h: CheckpointHarnessHandle, ctx: Conte
|
|
|
52
51
|
* `null` if no snapshot is recorded for `executionId`. Layer state is
|
|
53
52
|
* replayed into `layerStateStore` keyed by the original executionId so
|
|
54
53
|
* the restored context observes the pre-crash state through
|
|
55
|
-
* `readLayerState` and the
|
|
54
|
+
* `readLayerState` and the context projectors.
|
|
56
55
|
*
|
|
57
56
|
* `opts` carries the host's own context wiring (broadcaster, parent, state,
|
|
58
57
|
* layer overrides) into the rebuilt context. A snapshot cannot round-trip live
|
|
@@ -63,7 +63,7 @@ export async function captureCheckpoint(h, ctx) {
|
|
|
63
63
|
* `null` if no snapshot is recorded for `executionId`. Layer state is
|
|
64
64
|
* replayed into `layerStateStore` keyed by the original executionId so
|
|
65
65
|
* the restored context observes the pre-crash state through
|
|
66
|
-
* `readLayerState` and the
|
|
66
|
+
* `readLayerState` and the context projectors.
|
|
67
67
|
*
|
|
68
68
|
* `opts` carries the host's own context wiring (broadcaster, parent, state,
|
|
69
69
|
* layer overrides) into the rebuilt context. A snapshot cannot round-trip live
|
|
@@ -93,7 +93,7 @@ export async function restoreFromCheckpoint(h, executionId, opts) {
|
|
|
93
93
|
}
|
|
94
94
|
const items = h.itemSchemas.parseMany(snapshot.itemLog.items);
|
|
95
95
|
const cwdInit = snapshot.cwd?.current ?? undefined;
|
|
96
|
-
/* Caller wiring first, snapshot second: a host may legitimately swap the
|
|
96
|
+
/* Caller wiring first, snapshot second: a host may legitimately swap the context
|
|
97
97
|
* layers or hang the restored execution under a new parent, but it must never be
|
|
98
98
|
* able to override the identity/history the snapshot is the record of. */
|
|
99
99
|
const ctx = h.createContext({
|
|
@@ -102,7 +102,6 @@ export async function restoreFromCheckpoint(h, executionId, opts) {
|
|
|
102
102
|
threadId: snapshot.threadId,
|
|
103
103
|
resourceId: snapshot.resourceId,
|
|
104
104
|
cwdInit,
|
|
105
|
-
memory: opts?.memory ?? h._memory,
|
|
106
105
|
});
|
|
107
106
|
if (ctx instanceof ContextImpl) {
|
|
108
107
|
Object.defineProperty(ctx, 'id', {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* construction — a missing entry re-runs its step, which costs work rather than
|
|
18
18
|
* correctness.
|
|
19
19
|
*/
|
|
20
|
-
import type { StorageAdapter } from '@noetic-tools/
|
|
20
|
+
import type { StorageAdapter } from '@noetic-tools/context';
|
|
21
21
|
import { z } from 'zod';
|
|
22
22
|
/** @public One completed step, keyed by its execution path. */
|
|
23
23
|
export declare const StepLedgerEntrySchema: z.ZodObject<{
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* construction — a missing entry re-runs its step, which costs work rather than
|
|
18
18
|
* correctness.
|
|
19
19
|
*/
|
|
20
|
-
import { storageGetMany } from '@noetic-tools/
|
|
20
|
+
import { storageGetMany } from '@noetic-tools/context';
|
|
21
21
|
import { NoeticConfigError } from '@noetic-tools/types';
|
|
22
22
|
import { z } from 'zod';
|
|
23
23
|
//#region Entry
|
|
@@ -16,7 +16,7 @@ export interface TurnContext {
|
|
|
16
16
|
* (so the runner is the single point of ExecuteInput → Item[] conversion),
|
|
17
17
|
* plus the messages being delivered this turn so the harness can honour
|
|
18
18
|
* per-turn `ExecuteOptions` (the first message's options establish
|
|
19
|
-
* resourceId/state/
|
|
19
|
+
* resourceId/state/context for the turn). */
|
|
20
20
|
export type CreateContextFn = (items: ReadonlyArray<Item>, turnId: string, messages: ReadonlyArray<QueuedMessage>) => Context;
|
|
21
21
|
export interface SessionRunnerOpts {
|
|
22
22
|
readonly threadId: string;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ContextData } from '@noetic-tools/context';
|
|
2
2
|
import type { Step } from '@noetic-tools/types';
|
|
3
3
|
/** Registry-wide step type — stored at the widest generic parameters so
|
|
4
4
|
* callers can register steps with narrower input/output types without
|
|
5
5
|
* fighting variance. The public `Step` default already uses `unknown` for
|
|
6
6
|
* the input and output parameters, matching this shape. */
|
|
7
|
-
type RegisteredStep = Step<
|
|
7
|
+
type RegisteredStep = Step<ContextData, unknown, unknown>;
|
|
8
8
|
/**
|
|
9
9
|
* @public
|
|
10
10
|
* Registers a step so the subprocess adapter can look it up by id.
|
|
@@ -18,7 +18,7 @@ type RegisteredStep = Step<ContextMemory, unknown, unknown>;
|
|
|
18
18
|
* `clearRegistry` wiring is deferred — see the tracked follow-up task on
|
|
19
19
|
* the team's board (strict-registry-plus-test-clear).
|
|
20
20
|
*/
|
|
21
|
-
export declare function registerStep<
|
|
21
|
+
export declare function registerStep<TContext, I, O>(step: Step<TContext, I, O>): void;
|
|
22
22
|
/**
|
|
23
23
|
* @public
|
|
24
24
|
* Looks up a registered step by id. Returns `null` when the id is not known
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { AgentHarnessContract, Context, ToolExecutionContext
|
|
1
|
+
import type { AgentHarnessContract, Context, ToolExecutionContext } from '@noetic-tools/types';
|
|
2
2
|
import type { TurnContext } from '@openrouter/agent';
|
|
3
|
-
export declare function buildToolMemory(harness: AgentHarnessContract, ctx: Context): ToolMemory;
|
|
4
3
|
export declare function buildToolExecutionContext(ctx: Context, harness: AgentHarnessContract, turnContext?: TurnContext): ToolExecutionContext;
|