@cosmonapse/sdk 0.1.3 → 0.1.4
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/index.cjs +554 -200
- package/dist/index.d.cts +118 -1
- package/dist/index.d.ts +118 -1
- package/dist/index.js +551 -200
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -55,7 +55,13 @@ var SignalType = {
|
|
|
55
55
|
IMPRINT: "IMPRINT",
|
|
56
56
|
IMPRINTED: "IMPRINTED",
|
|
57
57
|
// Discovery [C]
|
|
58
|
-
DISCOVER: "DISCOVER"
|
|
58
|
+
DISCOVER: "DISCOVER",
|
|
59
|
+
// Workflow control [C] - cooperative cancellation of a whole trace.
|
|
60
|
+
// STOP is broadcast on the trace; every Dendrite filters by trace_id,
|
|
61
|
+
// cancels its in-flight work + engram I/O, optionally rolls back Engram
|
|
62
|
+
// writes via the saga journal, then acks with STOPPED.
|
|
63
|
+
STOP: "STOP",
|
|
64
|
+
STOPPED: "STOPPED"
|
|
59
65
|
};
|
|
60
66
|
var AXON_TYPES = /* @__PURE__ */ new Set([
|
|
61
67
|
SignalType.AGENT_OUTPUT,
|
|
@@ -92,7 +98,11 @@ var SYNAPSE_TYPES = /* @__PURE__ */ new Set([
|
|
|
92
98
|
SignalType.RECALL,
|
|
93
99
|
SignalType.RECALLED,
|
|
94
100
|
SignalType.IMPRINT,
|
|
95
|
-
SignalType.IMPRINTED
|
|
101
|
+
SignalType.IMPRINTED,
|
|
102
|
+
// Workflow control - STOP is orchestrator-gated (see Dendrite role gate);
|
|
103
|
+
// STOPPED is the per-Dendrite ack.
|
|
104
|
+
SignalType.STOP,
|
|
105
|
+
SignalType.STOPPED
|
|
96
106
|
]);
|
|
97
107
|
function normalizeDirected(d) {
|
|
98
108
|
if (d === null || d === void 0) return null;
|
|
@@ -554,6 +564,237 @@ function imprintedSignal(args) {
|
|
|
554
564
|
meta: args.meta ?? {}
|
|
555
565
|
});
|
|
556
566
|
}
|
|
567
|
+
function stopSignal(args) {
|
|
568
|
+
const payload = { rollback: Boolean(args.rollback) };
|
|
569
|
+
if (args.reason !== void 0) payload["reason"] = args.reason;
|
|
570
|
+
return createSignal({
|
|
571
|
+
type: SignalType.STOP,
|
|
572
|
+
trace_id: args.traceId,
|
|
573
|
+
parent_id: args.parentId ?? null,
|
|
574
|
+
directed: args.directed ?? null,
|
|
575
|
+
payload,
|
|
576
|
+
meta: args.meta ?? {}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
function stoppedSignal(args) {
|
|
580
|
+
const payload = {
|
|
581
|
+
rolled_back: Boolean(args.rolledBack),
|
|
582
|
+
cancelled: args.cancelled ?? 0,
|
|
583
|
+
compensated: args.compensated ?? 0
|
|
584
|
+
};
|
|
585
|
+
if (args.node !== void 0) payload["node"] = args.node;
|
|
586
|
+
return createSignal({
|
|
587
|
+
type: SignalType.STOPPED,
|
|
588
|
+
trace_id: args.traceId,
|
|
589
|
+
parent_id: args.parentId ?? null,
|
|
590
|
+
directed: args.directed ?? null,
|
|
591
|
+
payload,
|
|
592
|
+
meta: args.meta ?? {}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/pathway.ts
|
|
597
|
+
var TERMINAL_TYPES = /* @__PURE__ */ new Set([
|
|
598
|
+
SignalType.FINAL,
|
|
599
|
+
SignalType.ERROR
|
|
600
|
+
]);
|
|
601
|
+
var WAIT_TYPES = /* @__PURE__ */ new Set([
|
|
602
|
+
SignalType.AGENT_OUTPUT,
|
|
603
|
+
SignalType.CLARIFICATION,
|
|
604
|
+
SignalType.PERMISSION,
|
|
605
|
+
SignalType.ERROR,
|
|
606
|
+
SignalType.FINAL
|
|
607
|
+
]);
|
|
608
|
+
var SCOPE_TERMINAL_TYPES = /* @__PURE__ */ new Set([
|
|
609
|
+
SignalType.FINAL,
|
|
610
|
+
SignalType.ERROR,
|
|
611
|
+
SignalType.CLARIFICATION,
|
|
612
|
+
SignalType.PERMISSION
|
|
613
|
+
]);
|
|
614
|
+
var PATHWAY_TYPES = new Set(
|
|
615
|
+
Object.values(SignalType).filter(
|
|
616
|
+
(t) => t !== SignalType.TASK && t !== SignalType.REGISTER && t !== SignalType.DEREGISTER && t !== SignalType.HEARTBEAT && t !== SignalType.DISCOVER
|
|
617
|
+
)
|
|
618
|
+
);
|
|
619
|
+
var PathwayClosedError = class extends Error {
|
|
620
|
+
constructor(message) {
|
|
621
|
+
super(message);
|
|
622
|
+
this.name = "PathwayClosedError";
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
var Pathway = class {
|
|
626
|
+
traceId;
|
|
627
|
+
parentId;
|
|
628
|
+
role;
|
|
629
|
+
scope;
|
|
630
|
+
scopeFilter;
|
|
631
|
+
onCloseHook;
|
|
632
|
+
handlers = /* @__PURE__ */ new Map();
|
|
633
|
+
waiters = [];
|
|
634
|
+
buffered = [];
|
|
635
|
+
closed_ = false;
|
|
636
|
+
// Async iteration: a pull queue of pending `next()` resolvers and a push
|
|
637
|
+
// queue of undelivered values. `null` is the close sentinel.
|
|
638
|
+
iterPush = [];
|
|
639
|
+
iterPull = [];
|
|
640
|
+
constructor(opts) {
|
|
641
|
+
const scope = opts.scope ?? "all";
|
|
642
|
+
if (scope !== "all" && scope !== "terminal") {
|
|
643
|
+
throw new Error(`scope must be 'all' or 'terminal', got '${scope}'`);
|
|
644
|
+
}
|
|
645
|
+
this.traceId = opts.traceId;
|
|
646
|
+
this.parentId = opts.parentId ?? null;
|
|
647
|
+
this.role = opts.role ?? "originator";
|
|
648
|
+
this.scope = scope;
|
|
649
|
+
this.scopeFilter = scope === "terminal" ? SCOPE_TERMINAL_TYPES : null;
|
|
650
|
+
this.onCloseHook = opts.onClose;
|
|
651
|
+
}
|
|
652
|
+
get closed() {
|
|
653
|
+
return this.closed_;
|
|
654
|
+
}
|
|
655
|
+
// -- consumer shape #1: wait ---------------------------------------
|
|
656
|
+
/** Resolve on the next AGENT_OUTPUT, CLARIFICATION, PERMISSION, ERROR or
|
|
657
|
+
* FINAL. Rejects with PathwayClosedError if the Pathway closes first, and
|
|
658
|
+
* with a TimeoutError-named Error if `timeoutMs` elapses. */
|
|
659
|
+
async wait(timeoutMs) {
|
|
660
|
+
return this.waitForTypes(WAIT_TYPES, timeoutMs);
|
|
661
|
+
}
|
|
662
|
+
/** Resolve on the next Signal of the given type. */
|
|
663
|
+
async waitFor(type, timeoutMs) {
|
|
664
|
+
return this.waitForTypes(/* @__PURE__ */ new Set([type]), timeoutMs);
|
|
665
|
+
}
|
|
666
|
+
async waitForTypes(types, timeoutMs) {
|
|
667
|
+
for (let i = 0; i < this.buffered.length; i++) {
|
|
668
|
+
const sig = this.buffered[i];
|
|
669
|
+
if (types.has(sig.type)) {
|
|
670
|
+
this.buffered.splice(i, 1);
|
|
671
|
+
return sig;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (this.closed_) {
|
|
675
|
+
throw new PathwayClosedError(`Pathway for trace '${this.traceId}' is closed`);
|
|
676
|
+
}
|
|
677
|
+
return new Promise((resolve, reject) => {
|
|
678
|
+
const waiter = { types, resolve, reject, settled: false };
|
|
679
|
+
let timer = null;
|
|
680
|
+
const settle = (fn) => (a) => {
|
|
681
|
+
if (waiter.settled) return;
|
|
682
|
+
waiter.settled = true;
|
|
683
|
+
if (timer !== null) clearTimeout(timer);
|
|
684
|
+
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
685
|
+
fn(a);
|
|
686
|
+
};
|
|
687
|
+
waiter.resolve = settle(resolve);
|
|
688
|
+
waiter.reject = settle(reject);
|
|
689
|
+
if (timeoutMs !== void 0) {
|
|
690
|
+
timer = setTimeout(() => {
|
|
691
|
+
const err = new Error(
|
|
692
|
+
`Pathway.wait timed out after ${timeoutMs}ms on trace '${this.traceId}'`
|
|
693
|
+
);
|
|
694
|
+
err.name = "TimeoutError";
|
|
695
|
+
waiter.reject(err);
|
|
696
|
+
}, timeoutMs);
|
|
697
|
+
}
|
|
698
|
+
this.waiters.push(waiter);
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
// -- consumer shape #2: callbacks ----------------------------------
|
|
702
|
+
/** Register a callback fired for each Signal of the given type. */
|
|
703
|
+
on(type, fn) {
|
|
704
|
+
const list = this.handlers.get(type) ?? [];
|
|
705
|
+
list.push(fn);
|
|
706
|
+
this.handlers.set(type, list);
|
|
707
|
+
return fn;
|
|
708
|
+
}
|
|
709
|
+
// -- consumer shape #3: async iteration ----------------------------
|
|
710
|
+
[Symbol.asyncIterator]() {
|
|
711
|
+
return {
|
|
712
|
+
next: () => {
|
|
713
|
+
if (this.iterPush.length > 0) {
|
|
714
|
+
const v = this.iterPush.shift();
|
|
715
|
+
return Promise.resolve(
|
|
716
|
+
v === null ? { value: void 0, done: true } : { value: v, done: false }
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
if (this.closed_) {
|
|
720
|
+
return Promise.resolve({ value: void 0, done: true });
|
|
721
|
+
}
|
|
722
|
+
return new Promise((resolve) => this.iterPull.push(resolve));
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
iterEmit(v) {
|
|
727
|
+
const pull = this.iterPull.shift();
|
|
728
|
+
if (pull) {
|
|
729
|
+
pull(v === null ? { value: void 0, done: true } : { value: v, done: false });
|
|
730
|
+
} else {
|
|
731
|
+
this.iterPush.push(v);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
// -- lifecycle ------------------------------------------------------
|
|
735
|
+
/** Close the Pathway. Idempotent. Pending waits reject with
|
|
736
|
+
* PathwayClosedError; iteration completes; the onClose hook fires once. */
|
|
737
|
+
async close() {
|
|
738
|
+
if (this.closed_) return;
|
|
739
|
+
this.closed_ = true;
|
|
740
|
+
for (const w of [...this.waiters]) {
|
|
741
|
+
w.reject(
|
|
742
|
+
new PathwayClosedError(
|
|
743
|
+
`Pathway for trace '${this.traceId}' closed before a matching Signal arrived`
|
|
744
|
+
)
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
this.waiters = [];
|
|
748
|
+
this.iterEmit(null);
|
|
749
|
+
if (this.onCloseHook) {
|
|
750
|
+
try {
|
|
751
|
+
await this.onCloseHook(this);
|
|
752
|
+
} catch {
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/** `await using pathway = ...` support. */
|
|
757
|
+
async [Symbol.asyncDispose]() {
|
|
758
|
+
await this.close();
|
|
759
|
+
}
|
|
760
|
+
// -- internal: signal delivery (called by the owning Dendrite) ------
|
|
761
|
+
/** @internal */
|
|
762
|
+
async _deliver(signal) {
|
|
763
|
+
if (this.closed_) return;
|
|
764
|
+
if (this.scopeFilter !== null && !this.scopeFilter.has(signal.type)) {
|
|
765
|
+
await this.fireHandlers(signal);
|
|
766
|
+
if (TERMINAL_TYPES.has(signal.type)) await this.close();
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
let consumed = false;
|
|
770
|
+
for (const w of [...this.waiters]) {
|
|
771
|
+
if (w.types.has(signal.type)) {
|
|
772
|
+
w.resolve(signal);
|
|
773
|
+
consumed = true;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (!consumed) this.buffered.push(signal);
|
|
777
|
+
await this.fireHandlers(signal);
|
|
778
|
+
this.iterEmit(signal);
|
|
779
|
+
if (TERMINAL_TYPES.has(signal.type)) await this.close();
|
|
780
|
+
}
|
|
781
|
+
async fireHandlers(signal) {
|
|
782
|
+
for (const h of this.handlers.get(signal.type) ?? []) {
|
|
783
|
+
try {
|
|
784
|
+
await h(signal);
|
|
785
|
+
} catch {
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
// src/retry.ts
|
|
792
|
+
function defaultRetryOn(outcome) {
|
|
793
|
+
if (outcome instanceof PathwayClosedError) return true;
|
|
794
|
+
if (outcome instanceof Error) return outcome.name === "TimeoutError";
|
|
795
|
+
const sig = outcome;
|
|
796
|
+
return sig.type === SignalType.ERROR && Boolean(sig.payload?.["recoverable"]);
|
|
797
|
+
}
|
|
557
798
|
|
|
558
799
|
// src/synapse.ts
|
|
559
800
|
var MemorySubscription = class {
|
|
@@ -1911,6 +2152,49 @@ var EngramOverloaded = class extends EngramError {
|
|
|
1911
2152
|
};
|
|
1912
2153
|
var Engram = class {
|
|
1913
2154
|
version = null;
|
|
2155
|
+
// ----------------------------------------------------------------------
|
|
2156
|
+
// Saga / compensating-log rollback
|
|
2157
|
+
// ----------------------------------------------------------------------
|
|
2158
|
+
// A backend opts in by calling `sagaRecord` from inside `imprint` with the
|
|
2159
|
+
// inverse op needed to undo the write it is about to apply. `compensate`
|
|
2160
|
+
// then replays those inverses in reverse (LIFO) through the public
|
|
2161
|
+
// `imprint` path with no traceId/imprintId (so they neither re-journal nor
|
|
2162
|
+
// consume idempotency keys). Every inverse is itself a valid
|
|
2163
|
+
// add/upsert/delete, so this is fully backend-agnostic.
|
|
2164
|
+
sagaJournal = /* @__PURE__ */ new Map();
|
|
2165
|
+
sagaRecord(traceId, op, entry, mergeKey) {
|
|
2166
|
+
if (!traceId) return;
|
|
2167
|
+
let j = this.sagaJournal.get(traceId);
|
|
2168
|
+
if (!j) {
|
|
2169
|
+
j = [];
|
|
2170
|
+
this.sagaJournal.set(traceId, j);
|
|
2171
|
+
}
|
|
2172
|
+
j.push({ op, entry, mergeKey });
|
|
2173
|
+
}
|
|
2174
|
+
/** Reverse every journaled write for `traceId` (LIFO) and discard the
|
|
2175
|
+
* journal. Returns the number of inverse ops applied. Best-effort. Only
|
|
2176
|
+
* Engram state is reversed - external side effects are out of scope. */
|
|
2177
|
+
async compensate(traceId) {
|
|
2178
|
+
const inverses = this.sagaJournal.get(traceId);
|
|
2179
|
+
if (!inverses) return 0;
|
|
2180
|
+
this.sagaJournal.delete(traceId);
|
|
2181
|
+
let applied = 0;
|
|
2182
|
+
for (let i = inverses.length - 1; i >= 0; i--) {
|
|
2183
|
+
const inv = inverses[i];
|
|
2184
|
+
try {
|
|
2185
|
+
const opts = inv.mergeKey !== void 0 ? { mergeKey: inv.mergeKey } : {};
|
|
2186
|
+
await this.imprint(inv.op, inv.entry, opts);
|
|
2187
|
+
applied++;
|
|
2188
|
+
} catch {
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
return applied;
|
|
2192
|
+
}
|
|
2193
|
+
/** Discard the trace's saga journal without reversing anything. Called at
|
|
2194
|
+
* the workflow commit point (FINAL/ERROR on the trace). */
|
|
2195
|
+
async commit(traceId) {
|
|
2196
|
+
this.sagaJournal.delete(traceId);
|
|
2197
|
+
}
|
|
1914
2198
|
/** Return false if this Engram cannot satisfy the query. Default: serve all. */
|
|
1915
2199
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1916
2200
|
async canServe(_query) {
|
|
@@ -2009,6 +2293,7 @@ var InMemoryEngram = class extends Engram {
|
|
|
2009
2293
|
async imprint(op, entry, opts = {}) {
|
|
2010
2294
|
const t0 = Date.now();
|
|
2011
2295
|
const mergeKey = opts.mergeKey ?? null;
|
|
2296
|
+
const traceId = opts.traceId;
|
|
2012
2297
|
const tookMs = () => Date.now() - t0;
|
|
2013
2298
|
if (opts.imprintId !== void 0) {
|
|
2014
2299
|
const seen = this.imprintSeen.get(opts.imprintId);
|
|
@@ -2031,6 +2316,7 @@ var InMemoryEngram = class extends Engram {
|
|
|
2031
2316
|
this.store(ent);
|
|
2032
2317
|
resultingId = ent.id;
|
|
2033
2318
|
version = ent.version;
|
|
2319
|
+
this.sagaRecord(traceId, "delete", { id: ent.id });
|
|
2034
2320
|
} else if (op === "append") {
|
|
2035
2321
|
let ent = this.makeEntry(entry, mergeKey);
|
|
2036
2322
|
while (this.entries.has(ent.id)) {
|
|
@@ -2039,11 +2325,18 @@ var InMemoryEngram = class extends Engram {
|
|
|
2039
2325
|
this.store(ent);
|
|
2040
2326
|
resultingId = ent.id;
|
|
2041
2327
|
version = ent.version;
|
|
2328
|
+
this.sagaRecord(traceId, "delete", { id: ent.id });
|
|
2042
2329
|
} else if (op === "upsert") {
|
|
2043
2330
|
const existingIds = this.byMergeKey.get(mergeKey ?? "") ?? [];
|
|
2044
2331
|
const targetId = existingIds[existingIds.length - 1];
|
|
2045
2332
|
const old = targetId !== void 0 ? this.entries.get(targetId) : void 0;
|
|
2046
2333
|
if (old !== void 0) {
|
|
2334
|
+
this.sagaRecord(
|
|
2335
|
+
traceId,
|
|
2336
|
+
"upsert",
|
|
2337
|
+
{ id: old.id, content: structuredClone(old.content), tags: [...old.tags], meta: structuredClone(old.extra) },
|
|
2338
|
+
old.mergeKey ?? void 0
|
|
2339
|
+
);
|
|
2047
2340
|
const next = this.makeEntry({ ...entry, id: old.id }, mergeKey);
|
|
2048
2341
|
next.createdAt = old.createdAt;
|
|
2049
2342
|
next.version = old.version + 1;
|
|
@@ -2055,6 +2348,7 @@ var InMemoryEngram = class extends Engram {
|
|
|
2055
2348
|
this.store(ent);
|
|
2056
2349
|
resultingId = ent.id;
|
|
2057
2350
|
version = ent.version;
|
|
2351
|
+
this.sagaRecord(traceId, "delete", { id: ent.id });
|
|
2058
2352
|
}
|
|
2059
2353
|
} else if (op === "merge") {
|
|
2060
2354
|
const existingIds = this.byMergeKey.get(mergeKey ?? "") ?? [];
|
|
@@ -2063,6 +2357,12 @@ var InMemoryEngram = class extends Engram {
|
|
|
2063
2357
|
if (old === void 0) {
|
|
2064
2358
|
return receipt(this.engramId, op, { error: `no entry for merge_key='${mergeKey}'`, tookMs: tookMs() });
|
|
2065
2359
|
}
|
|
2360
|
+
this.sagaRecord(
|
|
2361
|
+
traceId,
|
|
2362
|
+
"upsert",
|
|
2363
|
+
{ id: old.id, content: structuredClone(old.content), tags: [...old.tags], meta: structuredClone(old.extra) },
|
|
2364
|
+
old.mergeKey ?? void 0
|
|
2365
|
+
);
|
|
2066
2366
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2067
2367
|
const next = {
|
|
2068
2368
|
id: old.id,
|
|
@@ -2089,6 +2389,13 @@ var InMemoryEngram = class extends Engram {
|
|
|
2089
2389
|
if (targetId === null || !this.entries.has(targetId)) {
|
|
2090
2390
|
return receipt(this.engramId, op, { tookMs: tookMs() });
|
|
2091
2391
|
}
|
|
2392
|
+
const old = this.entries.get(targetId);
|
|
2393
|
+
this.sagaRecord(
|
|
2394
|
+
traceId,
|
|
2395
|
+
"add",
|
|
2396
|
+
{ id: old.id, content: structuredClone(old.content), tags: [...old.tags], meta: structuredClone(old.extra) },
|
|
2397
|
+
old.mergeKey ?? void 0
|
|
2398
|
+
);
|
|
2092
2399
|
this.evict(targetId);
|
|
2093
2400
|
resultingId = targetId;
|
|
2094
2401
|
version = null;
|
|
@@ -2995,201 +3302,6 @@ function parseMcpIntents(raw) {
|
|
|
2995
3302
|
return raw;
|
|
2996
3303
|
}
|
|
2997
3304
|
|
|
2998
|
-
// src/pathway.ts
|
|
2999
|
-
var TERMINAL_TYPES = /* @__PURE__ */ new Set([
|
|
3000
|
-
SignalType.FINAL,
|
|
3001
|
-
SignalType.ERROR
|
|
3002
|
-
]);
|
|
3003
|
-
var WAIT_TYPES = /* @__PURE__ */ new Set([
|
|
3004
|
-
SignalType.AGENT_OUTPUT,
|
|
3005
|
-
SignalType.CLARIFICATION,
|
|
3006
|
-
SignalType.PERMISSION,
|
|
3007
|
-
SignalType.ERROR,
|
|
3008
|
-
SignalType.FINAL
|
|
3009
|
-
]);
|
|
3010
|
-
var SCOPE_TERMINAL_TYPES = /* @__PURE__ */ new Set([
|
|
3011
|
-
SignalType.FINAL,
|
|
3012
|
-
SignalType.ERROR,
|
|
3013
|
-
SignalType.CLARIFICATION,
|
|
3014
|
-
SignalType.PERMISSION
|
|
3015
|
-
]);
|
|
3016
|
-
var PATHWAY_TYPES = new Set(
|
|
3017
|
-
Object.values(SignalType).filter(
|
|
3018
|
-
(t) => t !== SignalType.TASK && t !== SignalType.REGISTER && t !== SignalType.DEREGISTER && t !== SignalType.HEARTBEAT && t !== SignalType.DISCOVER
|
|
3019
|
-
)
|
|
3020
|
-
);
|
|
3021
|
-
var PathwayClosedError = class extends Error {
|
|
3022
|
-
constructor(message) {
|
|
3023
|
-
super(message);
|
|
3024
|
-
this.name = "PathwayClosedError";
|
|
3025
|
-
}
|
|
3026
|
-
};
|
|
3027
|
-
var Pathway = class {
|
|
3028
|
-
traceId;
|
|
3029
|
-
parentId;
|
|
3030
|
-
role;
|
|
3031
|
-
scope;
|
|
3032
|
-
scopeFilter;
|
|
3033
|
-
onCloseHook;
|
|
3034
|
-
handlers = /* @__PURE__ */ new Map();
|
|
3035
|
-
waiters = [];
|
|
3036
|
-
buffered = [];
|
|
3037
|
-
closed_ = false;
|
|
3038
|
-
// Async iteration: a pull queue of pending `next()` resolvers and a push
|
|
3039
|
-
// queue of undelivered values. `null` is the close sentinel.
|
|
3040
|
-
iterPush = [];
|
|
3041
|
-
iterPull = [];
|
|
3042
|
-
constructor(opts) {
|
|
3043
|
-
const scope = opts.scope ?? "all";
|
|
3044
|
-
if (scope !== "all" && scope !== "terminal") {
|
|
3045
|
-
throw new Error(`scope must be 'all' or 'terminal', got '${scope}'`);
|
|
3046
|
-
}
|
|
3047
|
-
this.traceId = opts.traceId;
|
|
3048
|
-
this.parentId = opts.parentId ?? null;
|
|
3049
|
-
this.role = opts.role ?? "originator";
|
|
3050
|
-
this.scope = scope;
|
|
3051
|
-
this.scopeFilter = scope === "terminal" ? SCOPE_TERMINAL_TYPES : null;
|
|
3052
|
-
this.onCloseHook = opts.onClose;
|
|
3053
|
-
}
|
|
3054
|
-
get closed() {
|
|
3055
|
-
return this.closed_;
|
|
3056
|
-
}
|
|
3057
|
-
// -- consumer shape #1: wait ---------------------------------------
|
|
3058
|
-
/** Resolve on the next AGENT_OUTPUT, CLARIFICATION, PERMISSION, ERROR or
|
|
3059
|
-
* FINAL. Rejects with PathwayClosedError if the Pathway closes first, and
|
|
3060
|
-
* with a TimeoutError-named Error if `timeoutMs` elapses. */
|
|
3061
|
-
async wait(timeoutMs) {
|
|
3062
|
-
return this.waitForTypes(WAIT_TYPES, timeoutMs);
|
|
3063
|
-
}
|
|
3064
|
-
/** Resolve on the next Signal of the given type. */
|
|
3065
|
-
async waitFor(type, timeoutMs) {
|
|
3066
|
-
return this.waitForTypes(/* @__PURE__ */ new Set([type]), timeoutMs);
|
|
3067
|
-
}
|
|
3068
|
-
async waitForTypes(types, timeoutMs) {
|
|
3069
|
-
for (let i = 0; i < this.buffered.length; i++) {
|
|
3070
|
-
const sig = this.buffered[i];
|
|
3071
|
-
if (types.has(sig.type)) {
|
|
3072
|
-
this.buffered.splice(i, 1);
|
|
3073
|
-
return sig;
|
|
3074
|
-
}
|
|
3075
|
-
}
|
|
3076
|
-
if (this.closed_) {
|
|
3077
|
-
throw new PathwayClosedError(`Pathway for trace '${this.traceId}' is closed`);
|
|
3078
|
-
}
|
|
3079
|
-
return new Promise((resolve, reject) => {
|
|
3080
|
-
const waiter = { types, resolve, reject, settled: false };
|
|
3081
|
-
let timer = null;
|
|
3082
|
-
const settle = (fn) => (a) => {
|
|
3083
|
-
if (waiter.settled) return;
|
|
3084
|
-
waiter.settled = true;
|
|
3085
|
-
if (timer !== null) clearTimeout(timer);
|
|
3086
|
-
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
3087
|
-
fn(a);
|
|
3088
|
-
};
|
|
3089
|
-
waiter.resolve = settle(resolve);
|
|
3090
|
-
waiter.reject = settle(reject);
|
|
3091
|
-
if (timeoutMs !== void 0) {
|
|
3092
|
-
timer = setTimeout(() => {
|
|
3093
|
-
const err = new Error(
|
|
3094
|
-
`Pathway.wait timed out after ${timeoutMs}ms on trace '${this.traceId}'`
|
|
3095
|
-
);
|
|
3096
|
-
err.name = "TimeoutError";
|
|
3097
|
-
waiter.reject(err);
|
|
3098
|
-
}, timeoutMs);
|
|
3099
|
-
}
|
|
3100
|
-
this.waiters.push(waiter);
|
|
3101
|
-
});
|
|
3102
|
-
}
|
|
3103
|
-
// -- consumer shape #2: callbacks ----------------------------------
|
|
3104
|
-
/** Register a callback fired for each Signal of the given type. */
|
|
3105
|
-
on(type, fn) {
|
|
3106
|
-
const list = this.handlers.get(type) ?? [];
|
|
3107
|
-
list.push(fn);
|
|
3108
|
-
this.handlers.set(type, list);
|
|
3109
|
-
return fn;
|
|
3110
|
-
}
|
|
3111
|
-
// -- consumer shape #3: async iteration ----------------------------
|
|
3112
|
-
[Symbol.asyncIterator]() {
|
|
3113
|
-
return {
|
|
3114
|
-
next: () => {
|
|
3115
|
-
if (this.iterPush.length > 0) {
|
|
3116
|
-
const v = this.iterPush.shift();
|
|
3117
|
-
return Promise.resolve(
|
|
3118
|
-
v === null ? { value: void 0, done: true } : { value: v, done: false }
|
|
3119
|
-
);
|
|
3120
|
-
}
|
|
3121
|
-
if (this.closed_) {
|
|
3122
|
-
return Promise.resolve({ value: void 0, done: true });
|
|
3123
|
-
}
|
|
3124
|
-
return new Promise((resolve) => this.iterPull.push(resolve));
|
|
3125
|
-
}
|
|
3126
|
-
};
|
|
3127
|
-
}
|
|
3128
|
-
iterEmit(v) {
|
|
3129
|
-
const pull = this.iterPull.shift();
|
|
3130
|
-
if (pull) {
|
|
3131
|
-
pull(v === null ? { value: void 0, done: true } : { value: v, done: false });
|
|
3132
|
-
} else {
|
|
3133
|
-
this.iterPush.push(v);
|
|
3134
|
-
}
|
|
3135
|
-
}
|
|
3136
|
-
// -- lifecycle ------------------------------------------------------
|
|
3137
|
-
/** Close the Pathway. Idempotent. Pending waits reject with
|
|
3138
|
-
* PathwayClosedError; iteration completes; the onClose hook fires once. */
|
|
3139
|
-
async close() {
|
|
3140
|
-
if (this.closed_) return;
|
|
3141
|
-
this.closed_ = true;
|
|
3142
|
-
for (const w of [...this.waiters]) {
|
|
3143
|
-
w.reject(
|
|
3144
|
-
new PathwayClosedError(
|
|
3145
|
-
`Pathway for trace '${this.traceId}' closed before a matching Signal arrived`
|
|
3146
|
-
)
|
|
3147
|
-
);
|
|
3148
|
-
}
|
|
3149
|
-
this.waiters = [];
|
|
3150
|
-
this.iterEmit(null);
|
|
3151
|
-
if (this.onCloseHook) {
|
|
3152
|
-
try {
|
|
3153
|
-
await this.onCloseHook(this);
|
|
3154
|
-
} catch {
|
|
3155
|
-
}
|
|
3156
|
-
}
|
|
3157
|
-
}
|
|
3158
|
-
/** `await using pathway = ...` support. */
|
|
3159
|
-
async [Symbol.asyncDispose]() {
|
|
3160
|
-
await this.close();
|
|
3161
|
-
}
|
|
3162
|
-
// -- internal: signal delivery (called by the owning Dendrite) ------
|
|
3163
|
-
/** @internal */
|
|
3164
|
-
async _deliver(signal) {
|
|
3165
|
-
if (this.closed_) return;
|
|
3166
|
-
if (this.scopeFilter !== null && !this.scopeFilter.has(signal.type)) {
|
|
3167
|
-
await this.fireHandlers(signal);
|
|
3168
|
-
if (TERMINAL_TYPES.has(signal.type)) await this.close();
|
|
3169
|
-
return;
|
|
3170
|
-
}
|
|
3171
|
-
let consumed = false;
|
|
3172
|
-
for (const w of [...this.waiters]) {
|
|
3173
|
-
if (w.types.has(signal.type)) {
|
|
3174
|
-
w.resolve(signal);
|
|
3175
|
-
consumed = true;
|
|
3176
|
-
}
|
|
3177
|
-
}
|
|
3178
|
-
if (!consumed) this.buffered.push(signal);
|
|
3179
|
-
await this.fireHandlers(signal);
|
|
3180
|
-
this.iterEmit(signal);
|
|
3181
|
-
if (TERMINAL_TYPES.has(signal.type)) await this.close();
|
|
3182
|
-
}
|
|
3183
|
-
async fireHandlers(signal) {
|
|
3184
|
-
for (const h of this.handlers.get(signal.type) ?? []) {
|
|
3185
|
-
try {
|
|
3186
|
-
await h(signal);
|
|
3187
|
-
} catch {
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
}
|
|
3191
|
-
};
|
|
3192
|
-
|
|
3193
3305
|
// src/engram-client.ts
|
|
3194
3306
|
function deferred() {
|
|
3195
3307
|
let resolve;
|
|
@@ -3449,6 +3561,11 @@ var Dendrite = class _Dendrite {
|
|
|
3449
3561
|
/** Hosted Engrams keyed by engramId, plus a kind index so RECALL/IMPRINT
|
|
3450
3562
|
* addressed by engramKind reach every matching host. */
|
|
3451
3563
|
_engrams = /* @__PURE__ */ new Map();
|
|
3564
|
+
// In-flight neuron work keyed by trace_id so a STOP can abandon exactly
|
|
3565
|
+
// one workflow. JS can't force-kill a running async body, so abort means
|
|
3566
|
+
// 'stop awaiting + suppress the reply'; the neuron should also check the
|
|
3567
|
+
// AbortSignal cooperatively where it can.
|
|
3568
|
+
traceAborts = /* @__PURE__ */ new Map();
|
|
3452
3569
|
engramKindIndex = /* @__PURE__ */ new Map();
|
|
3453
3570
|
/** Engrams learned from peer REGISTER signals (possibly out-of-process). */
|
|
3454
3571
|
_engramRegistrations = /* @__PURE__ */ new Map();
|
|
@@ -3902,6 +4019,7 @@ var Dendrite = class _Dendrite {
|
|
|
3902
4019
|
}
|
|
3903
4020
|
await this.ensureInboundSub(SignalType.TASK_AWARDED);
|
|
3904
4021
|
await this.ensureInboundSub(SignalType.DISCOVER);
|
|
4022
|
+
await this.ensureInboundSub(SignalType.STOP);
|
|
3905
4023
|
if (this.autoBid) await this.ensureInboundSub(SignalType.TASK_OFFER);
|
|
3906
4024
|
for (const axon of this._axons.values()) {
|
|
3907
4025
|
await this.mirrorToStore(axon, "registered");
|
|
@@ -3917,6 +4035,8 @@ var Dendrite = class _Dendrite {
|
|
|
3917
4035
|
}
|
|
3918
4036
|
await this.ensureInboundSub(SignalType.RECALL);
|
|
3919
4037
|
await this.ensureInboundSub(SignalType.IMPRINT);
|
|
4038
|
+
await this.ensureInboundSub(SignalType.FINAL);
|
|
4039
|
+
await this.ensureInboundSub(SignalType.ERROR);
|
|
3920
4040
|
await this.ensureInboundSub(SignalType.REGISTER);
|
|
3921
4041
|
for (const engram of this._engrams.values()) {
|
|
3922
4042
|
try {
|
|
@@ -3935,6 +4055,7 @@ var Dendrite = class _Dendrite {
|
|
|
3935
4055
|
await this.ensureInboundSub(t);
|
|
3936
4056
|
}
|
|
3937
4057
|
}
|
|
4058
|
+
await this.ensureInboundSub(SignalType.STOP);
|
|
3938
4059
|
this.running = true;
|
|
3939
4060
|
if (this._axons.size > 0 && this.heartbeatMs > 0) {
|
|
3940
4061
|
this.startHeartbeatLoop();
|
|
@@ -4162,7 +4283,10 @@ var Dendrite = class _Dendrite {
|
|
|
4162
4283
|
* Pathway, return the Signal. Use `scope: "terminal"` to wait only for
|
|
4163
4284
|
* FINAL / ERROR / CLARIFICATION / PERMISSION. */
|
|
4164
4285
|
async dispatchAndWait(args) {
|
|
4165
|
-
const { timeoutMs, ...rest } = args;
|
|
4286
|
+
const { timeoutMs, retry, ...rest } = args;
|
|
4287
|
+
if (retry) {
|
|
4288
|
+
return this.runWithRetry({ ...rest, retry, ...timeoutMs !== void 0 ? { timeoutMs } : {} });
|
|
4289
|
+
}
|
|
4166
4290
|
const pathway = await this.dispatch(rest);
|
|
4167
4291
|
try {
|
|
4168
4292
|
return await pathway.wait(timeoutMs ?? 3e4);
|
|
@@ -4761,9 +4885,11 @@ var Dendrite = class _Dendrite {
|
|
|
4761
4885
|
if (!axon) return;
|
|
4762
4886
|
target = axon.neuronId;
|
|
4763
4887
|
}
|
|
4888
|
+
const ac = new AbortController();
|
|
4889
|
+
this.registerTraceAbort(task.trace_id, ac);
|
|
4764
4890
|
let reply2;
|
|
4765
4891
|
try {
|
|
4766
|
-
reply2 = await axon.handleTask(task);
|
|
4892
|
+
reply2 = await this.raceAbort(axon.handleTask(task), ac.signal);
|
|
4767
4893
|
} catch (err) {
|
|
4768
4894
|
reply2 = errorSignal({
|
|
4769
4895
|
traceId: task.trace_id,
|
|
@@ -4773,6 +4899,11 @@ var Dendrite = class _Dendrite {
|
|
|
4773
4899
|
message: err instanceof Error ? err.message : String(err),
|
|
4774
4900
|
recoverable: false
|
|
4775
4901
|
});
|
|
4902
|
+
} finally {
|
|
4903
|
+
this.unregisterTraceAbort(task.trace_id, ac);
|
|
4904
|
+
}
|
|
4905
|
+
if (reply2 === null) {
|
|
4906
|
+
return;
|
|
4776
4907
|
}
|
|
4777
4908
|
await this.publish(reply2);
|
|
4778
4909
|
if (reply2.type === SignalType.AGENT_OUTPUT && task.payload["finalize"]) {
|
|
@@ -4789,6 +4920,200 @@ var Dendrite = class _Dendrite {
|
|
|
4789
4920
|
}
|
|
4790
4921
|
}
|
|
4791
4922
|
}
|
|
4923
|
+
// -- workflow control: STOP / STOPPED -------------------------------
|
|
4924
|
+
registerTraceAbort(traceId, ac) {
|
|
4925
|
+
let set = this.traceAborts.get(traceId);
|
|
4926
|
+
if (!set) {
|
|
4927
|
+
set = /* @__PURE__ */ new Set();
|
|
4928
|
+
this.traceAborts.set(traceId, set);
|
|
4929
|
+
}
|
|
4930
|
+
set.add(ac);
|
|
4931
|
+
}
|
|
4932
|
+
unregisterTraceAbort(traceId, ac) {
|
|
4933
|
+
const set = this.traceAborts.get(traceId);
|
|
4934
|
+
if (set) {
|
|
4935
|
+
set.delete(ac);
|
|
4936
|
+
if (set.size === 0) this.traceAborts.delete(traceId);
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
/** Resolve to the promise's value, or to null if the signal aborts first. */
|
|
4940
|
+
raceAbort(p, signal) {
|
|
4941
|
+
if (signal.aborted) return Promise.resolve(null);
|
|
4942
|
+
return new Promise((resolve, reject) => {
|
|
4943
|
+
const onAbort = () => resolve(null);
|
|
4944
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4945
|
+
p.then(
|
|
4946
|
+
(v) => {
|
|
4947
|
+
signal.removeEventListener("abort", onAbort);
|
|
4948
|
+
resolve(v);
|
|
4949
|
+
},
|
|
4950
|
+
(e) => {
|
|
4951
|
+
signal.removeEventListener("abort", onAbort);
|
|
4952
|
+
reject(e);
|
|
4953
|
+
}
|
|
4954
|
+
);
|
|
4955
|
+
});
|
|
4956
|
+
}
|
|
4957
|
+
async onStop(signal) {
|
|
4958
|
+
const traceId = signal.trace_id;
|
|
4959
|
+
if (!traceId) return;
|
|
4960
|
+
const rollback = Boolean(signal.payload["rollback"]);
|
|
4961
|
+
let cancelled = 0;
|
|
4962
|
+
let compensated = 0;
|
|
4963
|
+
let didWork = false;
|
|
4964
|
+
const acs = this.traceAborts.get(traceId);
|
|
4965
|
+
if (acs) {
|
|
4966
|
+
for (const ac of acs) {
|
|
4967
|
+
if (!ac.signal.aborted) {
|
|
4968
|
+
ac.abort();
|
|
4969
|
+
cancelled++;
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
this.traceAborts.delete(traceId);
|
|
4973
|
+
didWork = true;
|
|
4974
|
+
}
|
|
4975
|
+
try {
|
|
4976
|
+
await this.cancelOpPathways(traceId);
|
|
4977
|
+
this.engramClient.cancelTrace(traceId);
|
|
4978
|
+
} catch {
|
|
4979
|
+
}
|
|
4980
|
+
for (const engram of this._engrams.values()) {
|
|
4981
|
+
try {
|
|
4982
|
+
if (rollback) {
|
|
4983
|
+
const n = await engram.compensate(traceId);
|
|
4984
|
+
if (n > 0) {
|
|
4985
|
+
compensated += n;
|
|
4986
|
+
didWork = true;
|
|
4987
|
+
}
|
|
4988
|
+
} else {
|
|
4989
|
+
await engram.commit(traceId);
|
|
4990
|
+
}
|
|
4991
|
+
} catch {
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
const pw = this.pathways.get(traceId);
|
|
4995
|
+
if (pw && !pw.closed) {
|
|
4996
|
+
didWork = true;
|
|
4997
|
+
try {
|
|
4998
|
+
await pw.close();
|
|
4999
|
+
} catch {
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
if (didWork) {
|
|
5003
|
+
try {
|
|
5004
|
+
await this.publish(
|
|
5005
|
+
stoppedSignal({
|
|
5006
|
+
traceId,
|
|
5007
|
+
parentId: signal.id,
|
|
5008
|
+
node: this.namespace,
|
|
5009
|
+
rolledBack: rollback,
|
|
5010
|
+
cancelled,
|
|
5011
|
+
compensated
|
|
5012
|
+
})
|
|
5013
|
+
);
|
|
5014
|
+
} catch {
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
5018
|
+
/** Broadcast a STOP for `traceId` (orchestrator-gated). Best-effort and
|
|
5019
|
+
* idempotent. */
|
|
5020
|
+
async emitStop(args) {
|
|
5021
|
+
this.requireOrchestrator("emitStop");
|
|
5022
|
+
await this.ensureInboundSub(SignalType.STOP);
|
|
5023
|
+
const sig = stopSignal({
|
|
5024
|
+
traceId: args.traceId,
|
|
5025
|
+
...args.rollback !== void 0 ? { rollback: args.rollback } : {},
|
|
5026
|
+
...args.reason !== void 0 ? { reason: args.reason } : {}
|
|
5027
|
+
});
|
|
5028
|
+
await this.publish(sig);
|
|
5029
|
+
return sig;
|
|
5030
|
+
}
|
|
5031
|
+
/** Stop a whole workflow. With `collectAcks` returns the STOPPED acks seen
|
|
5032
|
+
* within `timeoutMs` (best effort). */
|
|
5033
|
+
async stopTrace(traceId, opts = {}) {
|
|
5034
|
+
if (!opts.collectAcks) {
|
|
5035
|
+
await this.emitStop({
|
|
5036
|
+
traceId,
|
|
5037
|
+
...opts.rollback !== void 0 ? { rollback: opts.rollback } : {},
|
|
5038
|
+
...opts.reason !== void 0 ? { reason: opts.reason } : {}
|
|
5039
|
+
});
|
|
5040
|
+
return [];
|
|
5041
|
+
}
|
|
5042
|
+
const acks = [];
|
|
5043
|
+
const collect = async (sig) => {
|
|
5044
|
+
if (sig.trace_id === traceId) acks.push(sig);
|
|
5045
|
+
};
|
|
5046
|
+
const list = this.handlers.get(SignalType.STOPPED) ?? [];
|
|
5047
|
+
list.push(collect);
|
|
5048
|
+
this.handlers.set(SignalType.STOPPED, list);
|
|
5049
|
+
await this.ensureInboundSub(SignalType.STOPPED);
|
|
5050
|
+
try {
|
|
5051
|
+
await this.emitStop({
|
|
5052
|
+
traceId,
|
|
5053
|
+
...opts.rollback !== void 0 ? { rollback: opts.rollback } : {},
|
|
5054
|
+
...opts.reason !== void 0 ? { reason: opts.reason } : {}
|
|
5055
|
+
});
|
|
5056
|
+
await new Promise((r) => setTimeout(r, opts.timeoutMs ?? 1e3));
|
|
5057
|
+
} finally {
|
|
5058
|
+
const idx = (this.handlers.get(SignalType.STOPPED) ?? []).indexOf(collect);
|
|
5059
|
+
if (idx >= 0) (this.handlers.get(SignalType.STOPPED) ?? []).splice(idx, 1);
|
|
5060
|
+
}
|
|
5061
|
+
return acks;
|
|
5062
|
+
}
|
|
5063
|
+
// -- retry ----------------------------------------------------------
|
|
5064
|
+
async safeStop(traceId, retry) {
|
|
5065
|
+
try {
|
|
5066
|
+
await this.emitStop({
|
|
5067
|
+
traceId,
|
|
5068
|
+
rollback: Boolean(retry.rollbackOnRetry),
|
|
5069
|
+
reason: retry.reason ?? "retry"
|
|
5070
|
+
});
|
|
5071
|
+
} catch {
|
|
5072
|
+
}
|
|
5073
|
+
}
|
|
5074
|
+
/** Dispatch and wait, retrying per `retry` until a non-retryable outcome or
|
|
5075
|
+
* attempts are exhausted. Returns the resolved Signal; re-throws the last
|
|
5076
|
+
* error when every attempt failed with an exception. */
|
|
5077
|
+
async runWithRetry(args) {
|
|
5078
|
+
const { retry, timeoutMs, traceId: callerTrace, ...rest } = args;
|
|
5079
|
+
const maxAttempts = retry.maxAttempts ?? 3;
|
|
5080
|
+
const retryOn = retry.retryOn ?? defaultRetryOn;
|
|
5081
|
+
const newTrace = retry.newTrace ?? true;
|
|
5082
|
+
const perTimeout = retry.timeoutMs ?? timeoutMs ?? 3e4;
|
|
5083
|
+
let outcome = null;
|
|
5084
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
5085
|
+
const tid = callerTrace && !newTrace ? callerTrace : newTraceId();
|
|
5086
|
+
const meta = { ...rest.meta ?? {}, attempt };
|
|
5087
|
+
try {
|
|
5088
|
+
const pathway = await this.dispatch({ ...rest, traceId: tid, meta });
|
|
5089
|
+
try {
|
|
5090
|
+
outcome = await pathway.wait(perTimeout);
|
|
5091
|
+
} finally {
|
|
5092
|
+
await pathway.close();
|
|
5093
|
+
}
|
|
5094
|
+
} catch (err) {
|
|
5095
|
+
outcome = err instanceof Error ? err : new Error(String(err));
|
|
5096
|
+
}
|
|
5097
|
+
if (!retryOn(outcome)) {
|
|
5098
|
+
if (outcome instanceof Error) throw outcome;
|
|
5099
|
+
return outcome;
|
|
5100
|
+
}
|
|
5101
|
+
if (newTrace) await this.safeStop(tid, retry);
|
|
5102
|
+
if (attempt + 1 >= maxAttempts) {
|
|
5103
|
+
if (outcome instanceof Error) throw outcome;
|
|
5104
|
+
return outcome;
|
|
5105
|
+
}
|
|
5106
|
+
if (retry.onRetry) {
|
|
5107
|
+
try {
|
|
5108
|
+
retry.onRetry(attempt, outcome);
|
|
5109
|
+
} catch {
|
|
5110
|
+
}
|
|
5111
|
+
}
|
|
5112
|
+
const delay = retry.backoffMs ? retry.backoffMs(attempt) : 0;
|
|
5113
|
+
if (delay > 0) await new Promise((r) => setTimeout(r, delay));
|
|
5114
|
+
}
|
|
5115
|
+
throw new Error("runWithRetry: exhausted attempts unexpectedly");
|
|
5116
|
+
}
|
|
4792
5117
|
async emitRegister(axon) {
|
|
4793
5118
|
await this.publish(
|
|
4794
5119
|
registerSignal({
|
|
@@ -4912,6 +5237,21 @@ var Dendrite = class _Dendrite {
|
|
|
4912
5237
|
if (hs.length) await Promise.allSettled(hs.map((h) => h(signal)));
|
|
4913
5238
|
return;
|
|
4914
5239
|
}
|
|
5240
|
+
if (signal.type === SignalType.STOP) {
|
|
5241
|
+
if (signal.trace_id) {
|
|
5242
|
+
const pw = this.pathways.get(signal.trace_id);
|
|
5243
|
+
if (pw) {
|
|
5244
|
+
try {
|
|
5245
|
+
await pw._deliver(signal);
|
|
5246
|
+
} catch {
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
await this.onStop(signal);
|
|
5251
|
+
const hs = this.handlers.get(SignalType.STOP) ?? [];
|
|
5252
|
+
if (hs.length) await Promise.allSettled(hs.map((h) => h(signal)));
|
|
5253
|
+
return;
|
|
5254
|
+
}
|
|
4915
5255
|
if (signal.type === SignalType.RECALLED || signal.type === SignalType.IMPRINTED) {
|
|
4916
5256
|
this.engramClient.deliver(signal);
|
|
4917
5257
|
}
|
|
@@ -4938,6 +5278,13 @@ var Dendrite = class _Dendrite {
|
|
|
4938
5278
|
if ((signal.type === SignalType.FINAL || signal.type === SignalType.ERROR) && signal.trace_id) {
|
|
4939
5279
|
await this.cancelOpPathways(signal.trace_id);
|
|
4940
5280
|
this.engramClient.cancelTrace(signal.trace_id);
|
|
5281
|
+
for (const engram of this._engrams.values()) {
|
|
5282
|
+
try {
|
|
5283
|
+
await engram.commit(signal.trace_id);
|
|
5284
|
+
} catch {
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
5287
|
+
this.traceAborts.delete(signal.trace_id);
|
|
4941
5288
|
}
|
|
4942
5289
|
if (signal.type === SignalType.TASK_AWARDED) {
|
|
4943
5290
|
const target = signal.directed?.id ?? null;
|
|
@@ -5038,6 +5385,7 @@ var Dendrite = class _Dendrite {
|
|
|
5038
5385
|
try {
|
|
5039
5386
|
const receipt2 = await engram.imprint(op, entry, {
|
|
5040
5387
|
imprintId: signal.id,
|
|
5388
|
+
traceId: signal.trace_id,
|
|
5041
5389
|
...mergeKey !== void 0 ? { mergeKey } : {}
|
|
5042
5390
|
});
|
|
5043
5391
|
reply2 = imprintedSignal({
|
|
@@ -5725,7 +6073,7 @@ var PostgresEngram = class extends Engram {
|
|
|
5725
6073
|
};
|
|
5726
6074
|
|
|
5727
6075
|
// src/index.ts
|
|
5728
|
-
var VERSION = true ? "0.1.
|
|
6076
|
+
var VERSION = true ? "0.1.4" : "0.0.0-dev";
|
|
5729
6077
|
export {
|
|
5730
6078
|
AXON_TYPES,
|
|
5731
6079
|
Axon,
|
|
@@ -5775,6 +6123,7 @@ export {
|
|
|
5775
6123
|
critiqueSignal,
|
|
5776
6124
|
decode,
|
|
5777
6125
|
deepMerge,
|
|
6126
|
+
defaultRetryOn,
|
|
5778
6127
|
deregisterSignal,
|
|
5779
6128
|
directedTo,
|
|
5780
6129
|
discoverSignal,
|
|
@@ -5813,6 +6162,8 @@ export {
|
|
|
5813
6162
|
reply,
|
|
5814
6163
|
runWithTraceContext,
|
|
5815
6164
|
standardMcpServers,
|
|
6165
|
+
stopSignal,
|
|
6166
|
+
stoppedSignal,
|
|
5816
6167
|
synapseFromUrl,
|
|
5817
6168
|
taskAwardedSignal,
|
|
5818
6169
|
taskDeclinedSignal,
|