@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.cjs
CHANGED
|
@@ -78,6 +78,7 @@ __export(index_exports, {
|
|
|
78
78
|
critiqueSignal: () => critiqueSignal,
|
|
79
79
|
decode: () => decode,
|
|
80
80
|
deepMerge: () => deepMerge,
|
|
81
|
+
defaultRetryOn: () => defaultRetryOn,
|
|
81
82
|
deregisterSignal: () => deregisterSignal,
|
|
82
83
|
directedTo: () => directedTo,
|
|
83
84
|
discoverSignal: () => discoverSignal,
|
|
@@ -116,6 +117,8 @@ __export(index_exports, {
|
|
|
116
117
|
reply: () => reply,
|
|
117
118
|
runWithTraceContext: () => runWithTraceContext,
|
|
118
119
|
standardMcpServers: () => standardMcpServers,
|
|
120
|
+
stopSignal: () => stopSignal,
|
|
121
|
+
stoppedSignal: () => stoppedSignal,
|
|
119
122
|
synapseFromUrl: () => synapseFromUrl,
|
|
120
123
|
taskAwardedSignal: () => taskAwardedSignal,
|
|
121
124
|
taskDeclinedSignal: () => taskDeclinedSignal,
|
|
@@ -185,7 +188,13 @@ var SignalType = {
|
|
|
185
188
|
IMPRINT: "IMPRINT",
|
|
186
189
|
IMPRINTED: "IMPRINTED",
|
|
187
190
|
// Discovery [C]
|
|
188
|
-
DISCOVER: "DISCOVER"
|
|
191
|
+
DISCOVER: "DISCOVER",
|
|
192
|
+
// Workflow control [C] - cooperative cancellation of a whole trace.
|
|
193
|
+
// STOP is broadcast on the trace; every Dendrite filters by trace_id,
|
|
194
|
+
// cancels its in-flight work + engram I/O, optionally rolls back Engram
|
|
195
|
+
// writes via the saga journal, then acks with STOPPED.
|
|
196
|
+
STOP: "STOP",
|
|
197
|
+
STOPPED: "STOPPED"
|
|
189
198
|
};
|
|
190
199
|
var AXON_TYPES = /* @__PURE__ */ new Set([
|
|
191
200
|
SignalType.AGENT_OUTPUT,
|
|
@@ -222,7 +231,11 @@ var SYNAPSE_TYPES = /* @__PURE__ */ new Set([
|
|
|
222
231
|
SignalType.RECALL,
|
|
223
232
|
SignalType.RECALLED,
|
|
224
233
|
SignalType.IMPRINT,
|
|
225
|
-
SignalType.IMPRINTED
|
|
234
|
+
SignalType.IMPRINTED,
|
|
235
|
+
// Workflow control - STOP is orchestrator-gated (see Dendrite role gate);
|
|
236
|
+
// STOPPED is the per-Dendrite ack.
|
|
237
|
+
SignalType.STOP,
|
|
238
|
+
SignalType.STOPPED
|
|
226
239
|
]);
|
|
227
240
|
function normalizeDirected(d) {
|
|
228
241
|
if (d === null || d === void 0) return null;
|
|
@@ -684,6 +697,237 @@ function imprintedSignal(args) {
|
|
|
684
697
|
meta: args.meta ?? {}
|
|
685
698
|
});
|
|
686
699
|
}
|
|
700
|
+
function stopSignal(args) {
|
|
701
|
+
const payload = { rollback: Boolean(args.rollback) };
|
|
702
|
+
if (args.reason !== void 0) payload["reason"] = args.reason;
|
|
703
|
+
return createSignal({
|
|
704
|
+
type: SignalType.STOP,
|
|
705
|
+
trace_id: args.traceId,
|
|
706
|
+
parent_id: args.parentId ?? null,
|
|
707
|
+
directed: args.directed ?? null,
|
|
708
|
+
payload,
|
|
709
|
+
meta: args.meta ?? {}
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
function stoppedSignal(args) {
|
|
713
|
+
const payload = {
|
|
714
|
+
rolled_back: Boolean(args.rolledBack),
|
|
715
|
+
cancelled: args.cancelled ?? 0,
|
|
716
|
+
compensated: args.compensated ?? 0
|
|
717
|
+
};
|
|
718
|
+
if (args.node !== void 0) payload["node"] = args.node;
|
|
719
|
+
return createSignal({
|
|
720
|
+
type: SignalType.STOPPED,
|
|
721
|
+
trace_id: args.traceId,
|
|
722
|
+
parent_id: args.parentId ?? null,
|
|
723
|
+
directed: args.directed ?? null,
|
|
724
|
+
payload,
|
|
725
|
+
meta: args.meta ?? {}
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/pathway.ts
|
|
730
|
+
var TERMINAL_TYPES = /* @__PURE__ */ new Set([
|
|
731
|
+
SignalType.FINAL,
|
|
732
|
+
SignalType.ERROR
|
|
733
|
+
]);
|
|
734
|
+
var WAIT_TYPES = /* @__PURE__ */ new Set([
|
|
735
|
+
SignalType.AGENT_OUTPUT,
|
|
736
|
+
SignalType.CLARIFICATION,
|
|
737
|
+
SignalType.PERMISSION,
|
|
738
|
+
SignalType.ERROR,
|
|
739
|
+
SignalType.FINAL
|
|
740
|
+
]);
|
|
741
|
+
var SCOPE_TERMINAL_TYPES = /* @__PURE__ */ new Set([
|
|
742
|
+
SignalType.FINAL,
|
|
743
|
+
SignalType.ERROR,
|
|
744
|
+
SignalType.CLARIFICATION,
|
|
745
|
+
SignalType.PERMISSION
|
|
746
|
+
]);
|
|
747
|
+
var PATHWAY_TYPES = new Set(
|
|
748
|
+
Object.values(SignalType).filter(
|
|
749
|
+
(t) => t !== SignalType.TASK && t !== SignalType.REGISTER && t !== SignalType.DEREGISTER && t !== SignalType.HEARTBEAT && t !== SignalType.DISCOVER
|
|
750
|
+
)
|
|
751
|
+
);
|
|
752
|
+
var PathwayClosedError = class extends Error {
|
|
753
|
+
constructor(message) {
|
|
754
|
+
super(message);
|
|
755
|
+
this.name = "PathwayClosedError";
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
var Pathway = class {
|
|
759
|
+
traceId;
|
|
760
|
+
parentId;
|
|
761
|
+
role;
|
|
762
|
+
scope;
|
|
763
|
+
scopeFilter;
|
|
764
|
+
onCloseHook;
|
|
765
|
+
handlers = /* @__PURE__ */ new Map();
|
|
766
|
+
waiters = [];
|
|
767
|
+
buffered = [];
|
|
768
|
+
closed_ = false;
|
|
769
|
+
// Async iteration: a pull queue of pending `next()` resolvers and a push
|
|
770
|
+
// queue of undelivered values. `null` is the close sentinel.
|
|
771
|
+
iterPush = [];
|
|
772
|
+
iterPull = [];
|
|
773
|
+
constructor(opts) {
|
|
774
|
+
const scope = opts.scope ?? "all";
|
|
775
|
+
if (scope !== "all" && scope !== "terminal") {
|
|
776
|
+
throw new Error(`scope must be 'all' or 'terminal', got '${scope}'`);
|
|
777
|
+
}
|
|
778
|
+
this.traceId = opts.traceId;
|
|
779
|
+
this.parentId = opts.parentId ?? null;
|
|
780
|
+
this.role = opts.role ?? "originator";
|
|
781
|
+
this.scope = scope;
|
|
782
|
+
this.scopeFilter = scope === "terminal" ? SCOPE_TERMINAL_TYPES : null;
|
|
783
|
+
this.onCloseHook = opts.onClose;
|
|
784
|
+
}
|
|
785
|
+
get closed() {
|
|
786
|
+
return this.closed_;
|
|
787
|
+
}
|
|
788
|
+
// -- consumer shape #1: wait ---------------------------------------
|
|
789
|
+
/** Resolve on the next AGENT_OUTPUT, CLARIFICATION, PERMISSION, ERROR or
|
|
790
|
+
* FINAL. Rejects with PathwayClosedError if the Pathway closes first, and
|
|
791
|
+
* with a TimeoutError-named Error if `timeoutMs` elapses. */
|
|
792
|
+
async wait(timeoutMs) {
|
|
793
|
+
return this.waitForTypes(WAIT_TYPES, timeoutMs);
|
|
794
|
+
}
|
|
795
|
+
/** Resolve on the next Signal of the given type. */
|
|
796
|
+
async waitFor(type, timeoutMs) {
|
|
797
|
+
return this.waitForTypes(/* @__PURE__ */ new Set([type]), timeoutMs);
|
|
798
|
+
}
|
|
799
|
+
async waitForTypes(types, timeoutMs) {
|
|
800
|
+
for (let i = 0; i < this.buffered.length; i++) {
|
|
801
|
+
const sig = this.buffered[i];
|
|
802
|
+
if (types.has(sig.type)) {
|
|
803
|
+
this.buffered.splice(i, 1);
|
|
804
|
+
return sig;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (this.closed_) {
|
|
808
|
+
throw new PathwayClosedError(`Pathway for trace '${this.traceId}' is closed`);
|
|
809
|
+
}
|
|
810
|
+
return new Promise((resolve, reject) => {
|
|
811
|
+
const waiter = { types, resolve, reject, settled: false };
|
|
812
|
+
let timer = null;
|
|
813
|
+
const settle = (fn) => (a) => {
|
|
814
|
+
if (waiter.settled) return;
|
|
815
|
+
waiter.settled = true;
|
|
816
|
+
if (timer !== null) clearTimeout(timer);
|
|
817
|
+
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
818
|
+
fn(a);
|
|
819
|
+
};
|
|
820
|
+
waiter.resolve = settle(resolve);
|
|
821
|
+
waiter.reject = settle(reject);
|
|
822
|
+
if (timeoutMs !== void 0) {
|
|
823
|
+
timer = setTimeout(() => {
|
|
824
|
+
const err = new Error(
|
|
825
|
+
`Pathway.wait timed out after ${timeoutMs}ms on trace '${this.traceId}'`
|
|
826
|
+
);
|
|
827
|
+
err.name = "TimeoutError";
|
|
828
|
+
waiter.reject(err);
|
|
829
|
+
}, timeoutMs);
|
|
830
|
+
}
|
|
831
|
+
this.waiters.push(waiter);
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
// -- consumer shape #2: callbacks ----------------------------------
|
|
835
|
+
/** Register a callback fired for each Signal of the given type. */
|
|
836
|
+
on(type, fn) {
|
|
837
|
+
const list = this.handlers.get(type) ?? [];
|
|
838
|
+
list.push(fn);
|
|
839
|
+
this.handlers.set(type, list);
|
|
840
|
+
return fn;
|
|
841
|
+
}
|
|
842
|
+
// -- consumer shape #3: async iteration ----------------------------
|
|
843
|
+
[Symbol.asyncIterator]() {
|
|
844
|
+
return {
|
|
845
|
+
next: () => {
|
|
846
|
+
if (this.iterPush.length > 0) {
|
|
847
|
+
const v = this.iterPush.shift();
|
|
848
|
+
return Promise.resolve(
|
|
849
|
+
v === null ? { value: void 0, done: true } : { value: v, done: false }
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
if (this.closed_) {
|
|
853
|
+
return Promise.resolve({ value: void 0, done: true });
|
|
854
|
+
}
|
|
855
|
+
return new Promise((resolve) => this.iterPull.push(resolve));
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
iterEmit(v) {
|
|
860
|
+
const pull = this.iterPull.shift();
|
|
861
|
+
if (pull) {
|
|
862
|
+
pull(v === null ? { value: void 0, done: true } : { value: v, done: false });
|
|
863
|
+
} else {
|
|
864
|
+
this.iterPush.push(v);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
// -- lifecycle ------------------------------------------------------
|
|
868
|
+
/** Close the Pathway. Idempotent. Pending waits reject with
|
|
869
|
+
* PathwayClosedError; iteration completes; the onClose hook fires once. */
|
|
870
|
+
async close() {
|
|
871
|
+
if (this.closed_) return;
|
|
872
|
+
this.closed_ = true;
|
|
873
|
+
for (const w of [...this.waiters]) {
|
|
874
|
+
w.reject(
|
|
875
|
+
new PathwayClosedError(
|
|
876
|
+
`Pathway for trace '${this.traceId}' closed before a matching Signal arrived`
|
|
877
|
+
)
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
this.waiters = [];
|
|
881
|
+
this.iterEmit(null);
|
|
882
|
+
if (this.onCloseHook) {
|
|
883
|
+
try {
|
|
884
|
+
await this.onCloseHook(this);
|
|
885
|
+
} catch {
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/** `await using pathway = ...` support. */
|
|
890
|
+
async [Symbol.asyncDispose]() {
|
|
891
|
+
await this.close();
|
|
892
|
+
}
|
|
893
|
+
// -- internal: signal delivery (called by the owning Dendrite) ------
|
|
894
|
+
/** @internal */
|
|
895
|
+
async _deliver(signal) {
|
|
896
|
+
if (this.closed_) return;
|
|
897
|
+
if (this.scopeFilter !== null && !this.scopeFilter.has(signal.type)) {
|
|
898
|
+
await this.fireHandlers(signal);
|
|
899
|
+
if (TERMINAL_TYPES.has(signal.type)) await this.close();
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
let consumed = false;
|
|
903
|
+
for (const w of [...this.waiters]) {
|
|
904
|
+
if (w.types.has(signal.type)) {
|
|
905
|
+
w.resolve(signal);
|
|
906
|
+
consumed = true;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (!consumed) this.buffered.push(signal);
|
|
910
|
+
await this.fireHandlers(signal);
|
|
911
|
+
this.iterEmit(signal);
|
|
912
|
+
if (TERMINAL_TYPES.has(signal.type)) await this.close();
|
|
913
|
+
}
|
|
914
|
+
async fireHandlers(signal) {
|
|
915
|
+
for (const h of this.handlers.get(signal.type) ?? []) {
|
|
916
|
+
try {
|
|
917
|
+
await h(signal);
|
|
918
|
+
} catch {
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
// src/retry.ts
|
|
925
|
+
function defaultRetryOn(outcome) {
|
|
926
|
+
if (outcome instanceof PathwayClosedError) return true;
|
|
927
|
+
if (outcome instanceof Error) return outcome.name === "TimeoutError";
|
|
928
|
+
const sig = outcome;
|
|
929
|
+
return sig.type === SignalType.ERROR && Boolean(sig.payload?.["recoverable"]);
|
|
930
|
+
}
|
|
687
931
|
|
|
688
932
|
// src/synapse.ts
|
|
689
933
|
var MemorySubscription = class {
|
|
@@ -2041,6 +2285,49 @@ var EngramOverloaded = class extends EngramError {
|
|
|
2041
2285
|
};
|
|
2042
2286
|
var Engram = class {
|
|
2043
2287
|
version = null;
|
|
2288
|
+
// ----------------------------------------------------------------------
|
|
2289
|
+
// Saga / compensating-log rollback
|
|
2290
|
+
// ----------------------------------------------------------------------
|
|
2291
|
+
// A backend opts in by calling `sagaRecord` from inside `imprint` with the
|
|
2292
|
+
// inverse op needed to undo the write it is about to apply. `compensate`
|
|
2293
|
+
// then replays those inverses in reverse (LIFO) through the public
|
|
2294
|
+
// `imprint` path with no traceId/imprintId (so they neither re-journal nor
|
|
2295
|
+
// consume idempotency keys). Every inverse is itself a valid
|
|
2296
|
+
// add/upsert/delete, so this is fully backend-agnostic.
|
|
2297
|
+
sagaJournal = /* @__PURE__ */ new Map();
|
|
2298
|
+
sagaRecord(traceId, op, entry, mergeKey) {
|
|
2299
|
+
if (!traceId) return;
|
|
2300
|
+
let j = this.sagaJournal.get(traceId);
|
|
2301
|
+
if (!j) {
|
|
2302
|
+
j = [];
|
|
2303
|
+
this.sagaJournal.set(traceId, j);
|
|
2304
|
+
}
|
|
2305
|
+
j.push({ op, entry, mergeKey });
|
|
2306
|
+
}
|
|
2307
|
+
/** Reverse every journaled write for `traceId` (LIFO) and discard the
|
|
2308
|
+
* journal. Returns the number of inverse ops applied. Best-effort. Only
|
|
2309
|
+
* Engram state is reversed - external side effects are out of scope. */
|
|
2310
|
+
async compensate(traceId) {
|
|
2311
|
+
const inverses = this.sagaJournal.get(traceId);
|
|
2312
|
+
if (!inverses) return 0;
|
|
2313
|
+
this.sagaJournal.delete(traceId);
|
|
2314
|
+
let applied = 0;
|
|
2315
|
+
for (let i = inverses.length - 1; i >= 0; i--) {
|
|
2316
|
+
const inv = inverses[i];
|
|
2317
|
+
try {
|
|
2318
|
+
const opts = inv.mergeKey !== void 0 ? { mergeKey: inv.mergeKey } : {};
|
|
2319
|
+
await this.imprint(inv.op, inv.entry, opts);
|
|
2320
|
+
applied++;
|
|
2321
|
+
} catch {
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
return applied;
|
|
2325
|
+
}
|
|
2326
|
+
/** Discard the trace's saga journal without reversing anything. Called at
|
|
2327
|
+
* the workflow commit point (FINAL/ERROR on the trace). */
|
|
2328
|
+
async commit(traceId) {
|
|
2329
|
+
this.sagaJournal.delete(traceId);
|
|
2330
|
+
}
|
|
2044
2331
|
/** Return false if this Engram cannot satisfy the query. Default: serve all. */
|
|
2045
2332
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2046
2333
|
async canServe(_query) {
|
|
@@ -2139,6 +2426,7 @@ var InMemoryEngram = class extends Engram {
|
|
|
2139
2426
|
async imprint(op, entry, opts = {}) {
|
|
2140
2427
|
const t0 = Date.now();
|
|
2141
2428
|
const mergeKey = opts.mergeKey ?? null;
|
|
2429
|
+
const traceId = opts.traceId;
|
|
2142
2430
|
const tookMs = () => Date.now() - t0;
|
|
2143
2431
|
if (opts.imprintId !== void 0) {
|
|
2144
2432
|
const seen = this.imprintSeen.get(opts.imprintId);
|
|
@@ -2161,6 +2449,7 @@ var InMemoryEngram = class extends Engram {
|
|
|
2161
2449
|
this.store(ent);
|
|
2162
2450
|
resultingId = ent.id;
|
|
2163
2451
|
version = ent.version;
|
|
2452
|
+
this.sagaRecord(traceId, "delete", { id: ent.id });
|
|
2164
2453
|
} else if (op === "append") {
|
|
2165
2454
|
let ent = this.makeEntry(entry, mergeKey);
|
|
2166
2455
|
while (this.entries.has(ent.id)) {
|
|
@@ -2169,11 +2458,18 @@ var InMemoryEngram = class extends Engram {
|
|
|
2169
2458
|
this.store(ent);
|
|
2170
2459
|
resultingId = ent.id;
|
|
2171
2460
|
version = ent.version;
|
|
2461
|
+
this.sagaRecord(traceId, "delete", { id: ent.id });
|
|
2172
2462
|
} else if (op === "upsert") {
|
|
2173
2463
|
const existingIds = this.byMergeKey.get(mergeKey ?? "") ?? [];
|
|
2174
2464
|
const targetId = existingIds[existingIds.length - 1];
|
|
2175
2465
|
const old = targetId !== void 0 ? this.entries.get(targetId) : void 0;
|
|
2176
2466
|
if (old !== void 0) {
|
|
2467
|
+
this.sagaRecord(
|
|
2468
|
+
traceId,
|
|
2469
|
+
"upsert",
|
|
2470
|
+
{ id: old.id, content: structuredClone(old.content), tags: [...old.tags], meta: structuredClone(old.extra) },
|
|
2471
|
+
old.mergeKey ?? void 0
|
|
2472
|
+
);
|
|
2177
2473
|
const next = this.makeEntry({ ...entry, id: old.id }, mergeKey);
|
|
2178
2474
|
next.createdAt = old.createdAt;
|
|
2179
2475
|
next.version = old.version + 1;
|
|
@@ -2185,6 +2481,7 @@ var InMemoryEngram = class extends Engram {
|
|
|
2185
2481
|
this.store(ent);
|
|
2186
2482
|
resultingId = ent.id;
|
|
2187
2483
|
version = ent.version;
|
|
2484
|
+
this.sagaRecord(traceId, "delete", { id: ent.id });
|
|
2188
2485
|
}
|
|
2189
2486
|
} else if (op === "merge") {
|
|
2190
2487
|
const existingIds = this.byMergeKey.get(mergeKey ?? "") ?? [];
|
|
@@ -2193,6 +2490,12 @@ var InMemoryEngram = class extends Engram {
|
|
|
2193
2490
|
if (old === void 0) {
|
|
2194
2491
|
return receipt(this.engramId, op, { error: `no entry for merge_key='${mergeKey}'`, tookMs: tookMs() });
|
|
2195
2492
|
}
|
|
2493
|
+
this.sagaRecord(
|
|
2494
|
+
traceId,
|
|
2495
|
+
"upsert",
|
|
2496
|
+
{ id: old.id, content: structuredClone(old.content), tags: [...old.tags], meta: structuredClone(old.extra) },
|
|
2497
|
+
old.mergeKey ?? void 0
|
|
2498
|
+
);
|
|
2196
2499
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2197
2500
|
const next = {
|
|
2198
2501
|
id: old.id,
|
|
@@ -2219,6 +2522,13 @@ var InMemoryEngram = class extends Engram {
|
|
|
2219
2522
|
if (targetId === null || !this.entries.has(targetId)) {
|
|
2220
2523
|
return receipt(this.engramId, op, { tookMs: tookMs() });
|
|
2221
2524
|
}
|
|
2525
|
+
const old = this.entries.get(targetId);
|
|
2526
|
+
this.sagaRecord(
|
|
2527
|
+
traceId,
|
|
2528
|
+
"add",
|
|
2529
|
+
{ id: old.id, content: structuredClone(old.content), tags: [...old.tags], meta: structuredClone(old.extra) },
|
|
2530
|
+
old.mergeKey ?? void 0
|
|
2531
|
+
);
|
|
2222
2532
|
this.evict(targetId);
|
|
2223
2533
|
resultingId = targetId;
|
|
2224
2534
|
version = null;
|
|
@@ -3125,201 +3435,6 @@ function parseMcpIntents(raw) {
|
|
|
3125
3435
|
return raw;
|
|
3126
3436
|
}
|
|
3127
3437
|
|
|
3128
|
-
// src/pathway.ts
|
|
3129
|
-
var TERMINAL_TYPES = /* @__PURE__ */ new Set([
|
|
3130
|
-
SignalType.FINAL,
|
|
3131
|
-
SignalType.ERROR
|
|
3132
|
-
]);
|
|
3133
|
-
var WAIT_TYPES = /* @__PURE__ */ new Set([
|
|
3134
|
-
SignalType.AGENT_OUTPUT,
|
|
3135
|
-
SignalType.CLARIFICATION,
|
|
3136
|
-
SignalType.PERMISSION,
|
|
3137
|
-
SignalType.ERROR,
|
|
3138
|
-
SignalType.FINAL
|
|
3139
|
-
]);
|
|
3140
|
-
var SCOPE_TERMINAL_TYPES = /* @__PURE__ */ new Set([
|
|
3141
|
-
SignalType.FINAL,
|
|
3142
|
-
SignalType.ERROR,
|
|
3143
|
-
SignalType.CLARIFICATION,
|
|
3144
|
-
SignalType.PERMISSION
|
|
3145
|
-
]);
|
|
3146
|
-
var PATHWAY_TYPES = new Set(
|
|
3147
|
-
Object.values(SignalType).filter(
|
|
3148
|
-
(t) => t !== SignalType.TASK && t !== SignalType.REGISTER && t !== SignalType.DEREGISTER && t !== SignalType.HEARTBEAT && t !== SignalType.DISCOVER
|
|
3149
|
-
)
|
|
3150
|
-
);
|
|
3151
|
-
var PathwayClosedError = class extends Error {
|
|
3152
|
-
constructor(message) {
|
|
3153
|
-
super(message);
|
|
3154
|
-
this.name = "PathwayClosedError";
|
|
3155
|
-
}
|
|
3156
|
-
};
|
|
3157
|
-
var Pathway = class {
|
|
3158
|
-
traceId;
|
|
3159
|
-
parentId;
|
|
3160
|
-
role;
|
|
3161
|
-
scope;
|
|
3162
|
-
scopeFilter;
|
|
3163
|
-
onCloseHook;
|
|
3164
|
-
handlers = /* @__PURE__ */ new Map();
|
|
3165
|
-
waiters = [];
|
|
3166
|
-
buffered = [];
|
|
3167
|
-
closed_ = false;
|
|
3168
|
-
// Async iteration: a pull queue of pending `next()` resolvers and a push
|
|
3169
|
-
// queue of undelivered values. `null` is the close sentinel.
|
|
3170
|
-
iterPush = [];
|
|
3171
|
-
iterPull = [];
|
|
3172
|
-
constructor(opts) {
|
|
3173
|
-
const scope = opts.scope ?? "all";
|
|
3174
|
-
if (scope !== "all" && scope !== "terminal") {
|
|
3175
|
-
throw new Error(`scope must be 'all' or 'terminal', got '${scope}'`);
|
|
3176
|
-
}
|
|
3177
|
-
this.traceId = opts.traceId;
|
|
3178
|
-
this.parentId = opts.parentId ?? null;
|
|
3179
|
-
this.role = opts.role ?? "originator";
|
|
3180
|
-
this.scope = scope;
|
|
3181
|
-
this.scopeFilter = scope === "terminal" ? SCOPE_TERMINAL_TYPES : null;
|
|
3182
|
-
this.onCloseHook = opts.onClose;
|
|
3183
|
-
}
|
|
3184
|
-
get closed() {
|
|
3185
|
-
return this.closed_;
|
|
3186
|
-
}
|
|
3187
|
-
// -- consumer shape #1: wait ---------------------------------------
|
|
3188
|
-
/** Resolve on the next AGENT_OUTPUT, CLARIFICATION, PERMISSION, ERROR or
|
|
3189
|
-
* FINAL. Rejects with PathwayClosedError if the Pathway closes first, and
|
|
3190
|
-
* with a TimeoutError-named Error if `timeoutMs` elapses. */
|
|
3191
|
-
async wait(timeoutMs) {
|
|
3192
|
-
return this.waitForTypes(WAIT_TYPES, timeoutMs);
|
|
3193
|
-
}
|
|
3194
|
-
/** Resolve on the next Signal of the given type. */
|
|
3195
|
-
async waitFor(type, timeoutMs) {
|
|
3196
|
-
return this.waitForTypes(/* @__PURE__ */ new Set([type]), timeoutMs);
|
|
3197
|
-
}
|
|
3198
|
-
async waitForTypes(types, timeoutMs) {
|
|
3199
|
-
for (let i = 0; i < this.buffered.length; i++) {
|
|
3200
|
-
const sig = this.buffered[i];
|
|
3201
|
-
if (types.has(sig.type)) {
|
|
3202
|
-
this.buffered.splice(i, 1);
|
|
3203
|
-
return sig;
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3206
|
-
if (this.closed_) {
|
|
3207
|
-
throw new PathwayClosedError(`Pathway for trace '${this.traceId}' is closed`);
|
|
3208
|
-
}
|
|
3209
|
-
return new Promise((resolve, reject) => {
|
|
3210
|
-
const waiter = { types, resolve, reject, settled: false };
|
|
3211
|
-
let timer = null;
|
|
3212
|
-
const settle = (fn) => (a) => {
|
|
3213
|
-
if (waiter.settled) return;
|
|
3214
|
-
waiter.settled = true;
|
|
3215
|
-
if (timer !== null) clearTimeout(timer);
|
|
3216
|
-
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
3217
|
-
fn(a);
|
|
3218
|
-
};
|
|
3219
|
-
waiter.resolve = settle(resolve);
|
|
3220
|
-
waiter.reject = settle(reject);
|
|
3221
|
-
if (timeoutMs !== void 0) {
|
|
3222
|
-
timer = setTimeout(() => {
|
|
3223
|
-
const err = new Error(
|
|
3224
|
-
`Pathway.wait timed out after ${timeoutMs}ms on trace '${this.traceId}'`
|
|
3225
|
-
);
|
|
3226
|
-
err.name = "TimeoutError";
|
|
3227
|
-
waiter.reject(err);
|
|
3228
|
-
}, timeoutMs);
|
|
3229
|
-
}
|
|
3230
|
-
this.waiters.push(waiter);
|
|
3231
|
-
});
|
|
3232
|
-
}
|
|
3233
|
-
// -- consumer shape #2: callbacks ----------------------------------
|
|
3234
|
-
/** Register a callback fired for each Signal of the given type. */
|
|
3235
|
-
on(type, fn) {
|
|
3236
|
-
const list = this.handlers.get(type) ?? [];
|
|
3237
|
-
list.push(fn);
|
|
3238
|
-
this.handlers.set(type, list);
|
|
3239
|
-
return fn;
|
|
3240
|
-
}
|
|
3241
|
-
// -- consumer shape #3: async iteration ----------------------------
|
|
3242
|
-
[Symbol.asyncIterator]() {
|
|
3243
|
-
return {
|
|
3244
|
-
next: () => {
|
|
3245
|
-
if (this.iterPush.length > 0) {
|
|
3246
|
-
const v = this.iterPush.shift();
|
|
3247
|
-
return Promise.resolve(
|
|
3248
|
-
v === null ? { value: void 0, done: true } : { value: v, done: false }
|
|
3249
|
-
);
|
|
3250
|
-
}
|
|
3251
|
-
if (this.closed_) {
|
|
3252
|
-
return Promise.resolve({ value: void 0, done: true });
|
|
3253
|
-
}
|
|
3254
|
-
return new Promise((resolve) => this.iterPull.push(resolve));
|
|
3255
|
-
}
|
|
3256
|
-
};
|
|
3257
|
-
}
|
|
3258
|
-
iterEmit(v) {
|
|
3259
|
-
const pull = this.iterPull.shift();
|
|
3260
|
-
if (pull) {
|
|
3261
|
-
pull(v === null ? { value: void 0, done: true } : { value: v, done: false });
|
|
3262
|
-
} else {
|
|
3263
|
-
this.iterPush.push(v);
|
|
3264
|
-
}
|
|
3265
|
-
}
|
|
3266
|
-
// -- lifecycle ------------------------------------------------------
|
|
3267
|
-
/** Close the Pathway. Idempotent. Pending waits reject with
|
|
3268
|
-
* PathwayClosedError; iteration completes; the onClose hook fires once. */
|
|
3269
|
-
async close() {
|
|
3270
|
-
if (this.closed_) return;
|
|
3271
|
-
this.closed_ = true;
|
|
3272
|
-
for (const w of [...this.waiters]) {
|
|
3273
|
-
w.reject(
|
|
3274
|
-
new PathwayClosedError(
|
|
3275
|
-
`Pathway for trace '${this.traceId}' closed before a matching Signal arrived`
|
|
3276
|
-
)
|
|
3277
|
-
);
|
|
3278
|
-
}
|
|
3279
|
-
this.waiters = [];
|
|
3280
|
-
this.iterEmit(null);
|
|
3281
|
-
if (this.onCloseHook) {
|
|
3282
|
-
try {
|
|
3283
|
-
await this.onCloseHook(this);
|
|
3284
|
-
} catch {
|
|
3285
|
-
}
|
|
3286
|
-
}
|
|
3287
|
-
}
|
|
3288
|
-
/** `await using pathway = ...` support. */
|
|
3289
|
-
async [Symbol.asyncDispose]() {
|
|
3290
|
-
await this.close();
|
|
3291
|
-
}
|
|
3292
|
-
// -- internal: signal delivery (called by the owning Dendrite) ------
|
|
3293
|
-
/** @internal */
|
|
3294
|
-
async _deliver(signal) {
|
|
3295
|
-
if (this.closed_) return;
|
|
3296
|
-
if (this.scopeFilter !== null && !this.scopeFilter.has(signal.type)) {
|
|
3297
|
-
await this.fireHandlers(signal);
|
|
3298
|
-
if (TERMINAL_TYPES.has(signal.type)) await this.close();
|
|
3299
|
-
return;
|
|
3300
|
-
}
|
|
3301
|
-
let consumed = false;
|
|
3302
|
-
for (const w of [...this.waiters]) {
|
|
3303
|
-
if (w.types.has(signal.type)) {
|
|
3304
|
-
w.resolve(signal);
|
|
3305
|
-
consumed = true;
|
|
3306
|
-
}
|
|
3307
|
-
}
|
|
3308
|
-
if (!consumed) this.buffered.push(signal);
|
|
3309
|
-
await this.fireHandlers(signal);
|
|
3310
|
-
this.iterEmit(signal);
|
|
3311
|
-
if (TERMINAL_TYPES.has(signal.type)) await this.close();
|
|
3312
|
-
}
|
|
3313
|
-
async fireHandlers(signal) {
|
|
3314
|
-
for (const h of this.handlers.get(signal.type) ?? []) {
|
|
3315
|
-
try {
|
|
3316
|
-
await h(signal);
|
|
3317
|
-
} catch {
|
|
3318
|
-
}
|
|
3319
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
};
|
|
3322
|
-
|
|
3323
3438
|
// src/engram-client.ts
|
|
3324
3439
|
function deferred() {
|
|
3325
3440
|
let resolve;
|
|
@@ -3579,6 +3694,11 @@ var Dendrite = class _Dendrite {
|
|
|
3579
3694
|
/** Hosted Engrams keyed by engramId, plus a kind index so RECALL/IMPRINT
|
|
3580
3695
|
* addressed by engramKind reach every matching host. */
|
|
3581
3696
|
_engrams = /* @__PURE__ */ new Map();
|
|
3697
|
+
// In-flight neuron work keyed by trace_id so a STOP can abandon exactly
|
|
3698
|
+
// one workflow. JS can't force-kill a running async body, so abort means
|
|
3699
|
+
// 'stop awaiting + suppress the reply'; the neuron should also check the
|
|
3700
|
+
// AbortSignal cooperatively where it can.
|
|
3701
|
+
traceAborts = /* @__PURE__ */ new Map();
|
|
3582
3702
|
engramKindIndex = /* @__PURE__ */ new Map();
|
|
3583
3703
|
/** Engrams learned from peer REGISTER signals (possibly out-of-process). */
|
|
3584
3704
|
_engramRegistrations = /* @__PURE__ */ new Map();
|
|
@@ -4032,6 +4152,7 @@ var Dendrite = class _Dendrite {
|
|
|
4032
4152
|
}
|
|
4033
4153
|
await this.ensureInboundSub(SignalType.TASK_AWARDED);
|
|
4034
4154
|
await this.ensureInboundSub(SignalType.DISCOVER);
|
|
4155
|
+
await this.ensureInboundSub(SignalType.STOP);
|
|
4035
4156
|
if (this.autoBid) await this.ensureInboundSub(SignalType.TASK_OFFER);
|
|
4036
4157
|
for (const axon of this._axons.values()) {
|
|
4037
4158
|
await this.mirrorToStore(axon, "registered");
|
|
@@ -4047,6 +4168,8 @@ var Dendrite = class _Dendrite {
|
|
|
4047
4168
|
}
|
|
4048
4169
|
await this.ensureInboundSub(SignalType.RECALL);
|
|
4049
4170
|
await this.ensureInboundSub(SignalType.IMPRINT);
|
|
4171
|
+
await this.ensureInboundSub(SignalType.FINAL);
|
|
4172
|
+
await this.ensureInboundSub(SignalType.ERROR);
|
|
4050
4173
|
await this.ensureInboundSub(SignalType.REGISTER);
|
|
4051
4174
|
for (const engram of this._engrams.values()) {
|
|
4052
4175
|
try {
|
|
@@ -4065,6 +4188,7 @@ var Dendrite = class _Dendrite {
|
|
|
4065
4188
|
await this.ensureInboundSub(t);
|
|
4066
4189
|
}
|
|
4067
4190
|
}
|
|
4191
|
+
await this.ensureInboundSub(SignalType.STOP);
|
|
4068
4192
|
this.running = true;
|
|
4069
4193
|
if (this._axons.size > 0 && this.heartbeatMs > 0) {
|
|
4070
4194
|
this.startHeartbeatLoop();
|
|
@@ -4292,7 +4416,10 @@ var Dendrite = class _Dendrite {
|
|
|
4292
4416
|
* Pathway, return the Signal. Use `scope: "terminal"` to wait only for
|
|
4293
4417
|
* FINAL / ERROR / CLARIFICATION / PERMISSION. */
|
|
4294
4418
|
async dispatchAndWait(args) {
|
|
4295
|
-
const { timeoutMs, ...rest } = args;
|
|
4419
|
+
const { timeoutMs, retry, ...rest } = args;
|
|
4420
|
+
if (retry) {
|
|
4421
|
+
return this.runWithRetry({ ...rest, retry, ...timeoutMs !== void 0 ? { timeoutMs } : {} });
|
|
4422
|
+
}
|
|
4296
4423
|
const pathway = await this.dispatch(rest);
|
|
4297
4424
|
try {
|
|
4298
4425
|
return await pathway.wait(timeoutMs ?? 3e4);
|
|
@@ -4891,9 +5018,11 @@ var Dendrite = class _Dendrite {
|
|
|
4891
5018
|
if (!axon) return;
|
|
4892
5019
|
target = axon.neuronId;
|
|
4893
5020
|
}
|
|
5021
|
+
const ac = new AbortController();
|
|
5022
|
+
this.registerTraceAbort(task.trace_id, ac);
|
|
4894
5023
|
let reply2;
|
|
4895
5024
|
try {
|
|
4896
|
-
reply2 = await axon.handleTask(task);
|
|
5025
|
+
reply2 = await this.raceAbort(axon.handleTask(task), ac.signal);
|
|
4897
5026
|
} catch (err) {
|
|
4898
5027
|
reply2 = errorSignal({
|
|
4899
5028
|
traceId: task.trace_id,
|
|
@@ -4903,6 +5032,11 @@ var Dendrite = class _Dendrite {
|
|
|
4903
5032
|
message: err instanceof Error ? err.message : String(err),
|
|
4904
5033
|
recoverable: false
|
|
4905
5034
|
});
|
|
5035
|
+
} finally {
|
|
5036
|
+
this.unregisterTraceAbort(task.trace_id, ac);
|
|
5037
|
+
}
|
|
5038
|
+
if (reply2 === null) {
|
|
5039
|
+
return;
|
|
4906
5040
|
}
|
|
4907
5041
|
await this.publish(reply2);
|
|
4908
5042
|
if (reply2.type === SignalType.AGENT_OUTPUT && task.payload["finalize"]) {
|
|
@@ -4919,6 +5053,200 @@ var Dendrite = class _Dendrite {
|
|
|
4919
5053
|
}
|
|
4920
5054
|
}
|
|
4921
5055
|
}
|
|
5056
|
+
// -- workflow control: STOP / STOPPED -------------------------------
|
|
5057
|
+
registerTraceAbort(traceId, ac) {
|
|
5058
|
+
let set = this.traceAborts.get(traceId);
|
|
5059
|
+
if (!set) {
|
|
5060
|
+
set = /* @__PURE__ */ new Set();
|
|
5061
|
+
this.traceAborts.set(traceId, set);
|
|
5062
|
+
}
|
|
5063
|
+
set.add(ac);
|
|
5064
|
+
}
|
|
5065
|
+
unregisterTraceAbort(traceId, ac) {
|
|
5066
|
+
const set = this.traceAborts.get(traceId);
|
|
5067
|
+
if (set) {
|
|
5068
|
+
set.delete(ac);
|
|
5069
|
+
if (set.size === 0) this.traceAborts.delete(traceId);
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
5072
|
+
/** Resolve to the promise's value, or to null if the signal aborts first. */
|
|
5073
|
+
raceAbort(p, signal) {
|
|
5074
|
+
if (signal.aborted) return Promise.resolve(null);
|
|
5075
|
+
return new Promise((resolve, reject) => {
|
|
5076
|
+
const onAbort = () => resolve(null);
|
|
5077
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5078
|
+
p.then(
|
|
5079
|
+
(v) => {
|
|
5080
|
+
signal.removeEventListener("abort", onAbort);
|
|
5081
|
+
resolve(v);
|
|
5082
|
+
},
|
|
5083
|
+
(e) => {
|
|
5084
|
+
signal.removeEventListener("abort", onAbort);
|
|
5085
|
+
reject(e);
|
|
5086
|
+
}
|
|
5087
|
+
);
|
|
5088
|
+
});
|
|
5089
|
+
}
|
|
5090
|
+
async onStop(signal) {
|
|
5091
|
+
const traceId = signal.trace_id;
|
|
5092
|
+
if (!traceId) return;
|
|
5093
|
+
const rollback = Boolean(signal.payload["rollback"]);
|
|
5094
|
+
let cancelled = 0;
|
|
5095
|
+
let compensated = 0;
|
|
5096
|
+
let didWork = false;
|
|
5097
|
+
const acs = this.traceAborts.get(traceId);
|
|
5098
|
+
if (acs) {
|
|
5099
|
+
for (const ac of acs) {
|
|
5100
|
+
if (!ac.signal.aborted) {
|
|
5101
|
+
ac.abort();
|
|
5102
|
+
cancelled++;
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
this.traceAborts.delete(traceId);
|
|
5106
|
+
didWork = true;
|
|
5107
|
+
}
|
|
5108
|
+
try {
|
|
5109
|
+
await this.cancelOpPathways(traceId);
|
|
5110
|
+
this.engramClient.cancelTrace(traceId);
|
|
5111
|
+
} catch {
|
|
5112
|
+
}
|
|
5113
|
+
for (const engram of this._engrams.values()) {
|
|
5114
|
+
try {
|
|
5115
|
+
if (rollback) {
|
|
5116
|
+
const n = await engram.compensate(traceId);
|
|
5117
|
+
if (n > 0) {
|
|
5118
|
+
compensated += n;
|
|
5119
|
+
didWork = true;
|
|
5120
|
+
}
|
|
5121
|
+
} else {
|
|
5122
|
+
await engram.commit(traceId);
|
|
5123
|
+
}
|
|
5124
|
+
} catch {
|
|
5125
|
+
}
|
|
5126
|
+
}
|
|
5127
|
+
const pw = this.pathways.get(traceId);
|
|
5128
|
+
if (pw && !pw.closed) {
|
|
5129
|
+
didWork = true;
|
|
5130
|
+
try {
|
|
5131
|
+
await pw.close();
|
|
5132
|
+
} catch {
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
if (didWork) {
|
|
5136
|
+
try {
|
|
5137
|
+
await this.publish(
|
|
5138
|
+
stoppedSignal({
|
|
5139
|
+
traceId,
|
|
5140
|
+
parentId: signal.id,
|
|
5141
|
+
node: this.namespace,
|
|
5142
|
+
rolledBack: rollback,
|
|
5143
|
+
cancelled,
|
|
5144
|
+
compensated
|
|
5145
|
+
})
|
|
5146
|
+
);
|
|
5147
|
+
} catch {
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5151
|
+
/** Broadcast a STOP for `traceId` (orchestrator-gated). Best-effort and
|
|
5152
|
+
* idempotent. */
|
|
5153
|
+
async emitStop(args) {
|
|
5154
|
+
this.requireOrchestrator("emitStop");
|
|
5155
|
+
await this.ensureInboundSub(SignalType.STOP);
|
|
5156
|
+
const sig = stopSignal({
|
|
5157
|
+
traceId: args.traceId,
|
|
5158
|
+
...args.rollback !== void 0 ? { rollback: args.rollback } : {},
|
|
5159
|
+
...args.reason !== void 0 ? { reason: args.reason } : {}
|
|
5160
|
+
});
|
|
5161
|
+
await this.publish(sig);
|
|
5162
|
+
return sig;
|
|
5163
|
+
}
|
|
5164
|
+
/** Stop a whole workflow. With `collectAcks` returns the STOPPED acks seen
|
|
5165
|
+
* within `timeoutMs` (best effort). */
|
|
5166
|
+
async stopTrace(traceId, opts = {}) {
|
|
5167
|
+
if (!opts.collectAcks) {
|
|
5168
|
+
await this.emitStop({
|
|
5169
|
+
traceId,
|
|
5170
|
+
...opts.rollback !== void 0 ? { rollback: opts.rollback } : {},
|
|
5171
|
+
...opts.reason !== void 0 ? { reason: opts.reason } : {}
|
|
5172
|
+
});
|
|
5173
|
+
return [];
|
|
5174
|
+
}
|
|
5175
|
+
const acks = [];
|
|
5176
|
+
const collect = async (sig) => {
|
|
5177
|
+
if (sig.trace_id === traceId) acks.push(sig);
|
|
5178
|
+
};
|
|
5179
|
+
const list = this.handlers.get(SignalType.STOPPED) ?? [];
|
|
5180
|
+
list.push(collect);
|
|
5181
|
+
this.handlers.set(SignalType.STOPPED, list);
|
|
5182
|
+
await this.ensureInboundSub(SignalType.STOPPED);
|
|
5183
|
+
try {
|
|
5184
|
+
await this.emitStop({
|
|
5185
|
+
traceId,
|
|
5186
|
+
...opts.rollback !== void 0 ? { rollback: opts.rollback } : {},
|
|
5187
|
+
...opts.reason !== void 0 ? { reason: opts.reason } : {}
|
|
5188
|
+
});
|
|
5189
|
+
await new Promise((r) => setTimeout(r, opts.timeoutMs ?? 1e3));
|
|
5190
|
+
} finally {
|
|
5191
|
+
const idx = (this.handlers.get(SignalType.STOPPED) ?? []).indexOf(collect);
|
|
5192
|
+
if (idx >= 0) (this.handlers.get(SignalType.STOPPED) ?? []).splice(idx, 1);
|
|
5193
|
+
}
|
|
5194
|
+
return acks;
|
|
5195
|
+
}
|
|
5196
|
+
// -- retry ----------------------------------------------------------
|
|
5197
|
+
async safeStop(traceId, retry) {
|
|
5198
|
+
try {
|
|
5199
|
+
await this.emitStop({
|
|
5200
|
+
traceId,
|
|
5201
|
+
rollback: Boolean(retry.rollbackOnRetry),
|
|
5202
|
+
reason: retry.reason ?? "retry"
|
|
5203
|
+
});
|
|
5204
|
+
} catch {
|
|
5205
|
+
}
|
|
5206
|
+
}
|
|
5207
|
+
/** Dispatch and wait, retrying per `retry` until a non-retryable outcome or
|
|
5208
|
+
* attempts are exhausted. Returns the resolved Signal; re-throws the last
|
|
5209
|
+
* error when every attempt failed with an exception. */
|
|
5210
|
+
async runWithRetry(args) {
|
|
5211
|
+
const { retry, timeoutMs, traceId: callerTrace, ...rest } = args;
|
|
5212
|
+
const maxAttempts = retry.maxAttempts ?? 3;
|
|
5213
|
+
const retryOn = retry.retryOn ?? defaultRetryOn;
|
|
5214
|
+
const newTrace = retry.newTrace ?? true;
|
|
5215
|
+
const perTimeout = retry.timeoutMs ?? timeoutMs ?? 3e4;
|
|
5216
|
+
let outcome = null;
|
|
5217
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
5218
|
+
const tid = callerTrace && !newTrace ? callerTrace : newTraceId();
|
|
5219
|
+
const meta = { ...rest.meta ?? {}, attempt };
|
|
5220
|
+
try {
|
|
5221
|
+
const pathway = await this.dispatch({ ...rest, traceId: tid, meta });
|
|
5222
|
+
try {
|
|
5223
|
+
outcome = await pathway.wait(perTimeout);
|
|
5224
|
+
} finally {
|
|
5225
|
+
await pathway.close();
|
|
5226
|
+
}
|
|
5227
|
+
} catch (err) {
|
|
5228
|
+
outcome = err instanceof Error ? err : new Error(String(err));
|
|
5229
|
+
}
|
|
5230
|
+
if (!retryOn(outcome)) {
|
|
5231
|
+
if (outcome instanceof Error) throw outcome;
|
|
5232
|
+
return outcome;
|
|
5233
|
+
}
|
|
5234
|
+
if (newTrace) await this.safeStop(tid, retry);
|
|
5235
|
+
if (attempt + 1 >= maxAttempts) {
|
|
5236
|
+
if (outcome instanceof Error) throw outcome;
|
|
5237
|
+
return outcome;
|
|
5238
|
+
}
|
|
5239
|
+
if (retry.onRetry) {
|
|
5240
|
+
try {
|
|
5241
|
+
retry.onRetry(attempt, outcome);
|
|
5242
|
+
} catch {
|
|
5243
|
+
}
|
|
5244
|
+
}
|
|
5245
|
+
const delay = retry.backoffMs ? retry.backoffMs(attempt) : 0;
|
|
5246
|
+
if (delay > 0) await new Promise((r) => setTimeout(r, delay));
|
|
5247
|
+
}
|
|
5248
|
+
throw new Error("runWithRetry: exhausted attempts unexpectedly");
|
|
5249
|
+
}
|
|
4922
5250
|
async emitRegister(axon) {
|
|
4923
5251
|
await this.publish(
|
|
4924
5252
|
registerSignal({
|
|
@@ -5042,6 +5370,21 @@ var Dendrite = class _Dendrite {
|
|
|
5042
5370
|
if (hs.length) await Promise.allSettled(hs.map((h) => h(signal)));
|
|
5043
5371
|
return;
|
|
5044
5372
|
}
|
|
5373
|
+
if (signal.type === SignalType.STOP) {
|
|
5374
|
+
if (signal.trace_id) {
|
|
5375
|
+
const pw = this.pathways.get(signal.trace_id);
|
|
5376
|
+
if (pw) {
|
|
5377
|
+
try {
|
|
5378
|
+
await pw._deliver(signal);
|
|
5379
|
+
} catch {
|
|
5380
|
+
}
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
await this.onStop(signal);
|
|
5384
|
+
const hs = this.handlers.get(SignalType.STOP) ?? [];
|
|
5385
|
+
if (hs.length) await Promise.allSettled(hs.map((h) => h(signal)));
|
|
5386
|
+
return;
|
|
5387
|
+
}
|
|
5045
5388
|
if (signal.type === SignalType.RECALLED || signal.type === SignalType.IMPRINTED) {
|
|
5046
5389
|
this.engramClient.deliver(signal);
|
|
5047
5390
|
}
|
|
@@ -5068,6 +5411,13 @@ var Dendrite = class _Dendrite {
|
|
|
5068
5411
|
if ((signal.type === SignalType.FINAL || signal.type === SignalType.ERROR) && signal.trace_id) {
|
|
5069
5412
|
await this.cancelOpPathways(signal.trace_id);
|
|
5070
5413
|
this.engramClient.cancelTrace(signal.trace_id);
|
|
5414
|
+
for (const engram of this._engrams.values()) {
|
|
5415
|
+
try {
|
|
5416
|
+
await engram.commit(signal.trace_id);
|
|
5417
|
+
} catch {
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
this.traceAborts.delete(signal.trace_id);
|
|
5071
5421
|
}
|
|
5072
5422
|
if (signal.type === SignalType.TASK_AWARDED) {
|
|
5073
5423
|
const target = signal.directed?.id ?? null;
|
|
@@ -5168,6 +5518,7 @@ var Dendrite = class _Dendrite {
|
|
|
5168
5518
|
try {
|
|
5169
5519
|
const receipt2 = await engram.imprint(op, entry, {
|
|
5170
5520
|
imprintId: signal.id,
|
|
5521
|
+
traceId: signal.trace_id,
|
|
5171
5522
|
...mergeKey !== void 0 ? { mergeKey } : {}
|
|
5172
5523
|
});
|
|
5173
5524
|
reply2 = imprintedSignal({
|
|
@@ -5855,7 +6206,7 @@ var PostgresEngram = class extends Engram {
|
|
|
5855
6206
|
};
|
|
5856
6207
|
|
|
5857
6208
|
// src/index.ts
|
|
5858
|
-
var VERSION = true ? "0.1.
|
|
6209
|
+
var VERSION = true ? "0.1.4" : "0.0.0-dev";
|
|
5859
6210
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5860
6211
|
0 && (module.exports = {
|
|
5861
6212
|
AXON_TYPES,
|
|
@@ -5906,6 +6257,7 @@ var VERSION = true ? "0.1.3" : "0.0.0-dev";
|
|
|
5906
6257
|
critiqueSignal,
|
|
5907
6258
|
decode,
|
|
5908
6259
|
deepMerge,
|
|
6260
|
+
defaultRetryOn,
|
|
5909
6261
|
deregisterSignal,
|
|
5910
6262
|
directedTo,
|
|
5911
6263
|
discoverSignal,
|
|
@@ -5944,6 +6296,8 @@ var VERSION = true ? "0.1.3" : "0.0.0-dev";
|
|
|
5944
6296
|
reply,
|
|
5945
6297
|
runWithTraceContext,
|
|
5946
6298
|
standardMcpServers,
|
|
6299
|
+
stopSignal,
|
|
6300
|
+
stoppedSignal,
|
|
5947
6301
|
synapseFromUrl,
|
|
5948
6302
|
taskAwardedSignal,
|
|
5949
6303
|
taskDeclinedSignal,
|