@cosmonapse/sdk 0.1.2 → 0.1.3
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 +2757 -1059
- package/dist/index.d.cts +917 -285
- package/dist/index.d.ts +917 -285
- package/dist/index.js +2747 -1059
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -125,6 +125,12 @@ function createSignal(input) {
|
|
|
125
125
|
return signal;
|
|
126
126
|
}
|
|
127
127
|
function validateSignal(signal) {
|
|
128
|
+
const major = signal.v.split(".", 1)[0];
|
|
129
|
+
if (major !== "1") {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`unsupported protocol version '${signal.v}': this SDK speaks major version 1 (accepts '1' or '1.x')`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
128
134
|
if (!signal.id.startsWith("evt_")) {
|
|
129
135
|
throw new Error(`Signal id must start with 'evt_', got: ${signal.id}`);
|
|
130
136
|
}
|
|
@@ -162,6 +168,7 @@ function taskSignal(args) {
|
|
|
162
168
|
const payload = { input: args.input };
|
|
163
169
|
if (args.contextRef) payload["context_ref"] = args.contextRef;
|
|
164
170
|
if (args.capabilities) payload["capabilities"] = args.capabilities;
|
|
171
|
+
if (args.finalize) payload["finalize"] = true;
|
|
165
172
|
return createSignal({
|
|
166
173
|
type: SignalType.TASK,
|
|
167
174
|
trace_id: args.traceId ?? newTraceId(),
|
|
@@ -258,9 +265,10 @@ function errorSignal(args) {
|
|
|
258
265
|
}
|
|
259
266
|
function registerSignal(args) {
|
|
260
267
|
const caps = args.capabilities ?? args.directed?.capabilities ?? [];
|
|
261
|
-
const
|
|
268
|
+
const role = args.role ?? (args.engram ? "engram" : "neuron");
|
|
269
|
+
const payload = { role, capabilities: caps };
|
|
262
270
|
if (args.version) payload["version"] = args.version;
|
|
263
|
-
if (args.engram) payload["engram"] = true;
|
|
271
|
+
if (args.engram || role === "engram") payload["engram"] = true;
|
|
264
272
|
return createSignal({
|
|
265
273
|
type: SignalType.REGISTER,
|
|
266
274
|
trace_id: newTraceId(),
|
|
@@ -325,6 +333,32 @@ function bidSignal(args) {
|
|
|
325
333
|
meta: args.meta ?? {}
|
|
326
334
|
});
|
|
327
335
|
}
|
|
336
|
+
function taskAwardedSignal(args) {
|
|
337
|
+
const payload = { input: args.input };
|
|
338
|
+
if (args.winningBid !== void 0) payload["winning_bid"] = args.winningBid;
|
|
339
|
+
if (args.contextRef !== void 0) payload["context_ref"] = args.contextRef;
|
|
340
|
+
if (args.finalize) payload["finalize"] = true;
|
|
341
|
+
return createSignal({
|
|
342
|
+
type: SignalType.TASK_AWARDED,
|
|
343
|
+
trace_id: args.traceId,
|
|
344
|
+
parent_id: args.parentId,
|
|
345
|
+
directed: args.directed ?? null,
|
|
346
|
+
payload,
|
|
347
|
+
meta: args.meta ?? {}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
function taskDeclinedSignal(args) {
|
|
351
|
+
const payload = {};
|
|
352
|
+
if (args.reason !== void 0) payload["reason"] = args.reason;
|
|
353
|
+
return createSignal({
|
|
354
|
+
type: SignalType.TASK_DECLINED,
|
|
355
|
+
trace_id: args.traceId,
|
|
356
|
+
parent_id: args.parentId,
|
|
357
|
+
directed: args.directed ?? null,
|
|
358
|
+
payload,
|
|
359
|
+
meta: args.meta ?? {}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
328
362
|
function critiqueSignal(args) {
|
|
329
363
|
return createSignal({
|
|
330
364
|
type: SignalType.CRITIQUE,
|
|
@@ -1796,6 +1830,16 @@ var LifecycleHooks = class {
|
|
|
1796
1830
|
}
|
|
1797
1831
|
};
|
|
1798
1832
|
|
|
1833
|
+
// src/trace-context.ts
|
|
1834
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
1835
|
+
var storage = new AsyncLocalStorage();
|
|
1836
|
+
function ambientTrace() {
|
|
1837
|
+
return storage.getStore() ?? null;
|
|
1838
|
+
}
|
|
1839
|
+
function runWithTraceContext(traceId, parentId, fn) {
|
|
1840
|
+
return storage.run([traceId, parentId], fn);
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1799
1843
|
// src/neuron.ts
|
|
1800
1844
|
function clarify(question, context) {
|
|
1801
1845
|
return context === void 0 ? { __clarification__: true, question } : { __clarification__: true, question, context };
|
|
@@ -1827,182 +1871,524 @@ function isErrorOutput(output) {
|
|
|
1827
1871
|
return typeof output === "object" && output !== null && output["__error__"] === true;
|
|
1828
1872
|
}
|
|
1829
1873
|
|
|
1830
|
-
// src/
|
|
1831
|
-
var
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
args: ["-y", "@modelcontextprotocol/server-everything"],
|
|
1845
|
-
note: "Reference server exercising every MCP feature; handy for tests."
|
|
1846
|
-
},
|
|
1847
|
-
sequentialthinking: {
|
|
1848
|
-
command: "npx",
|
|
1849
|
-
args: ["-y", "@modelcontextprotocol/server-sequential-thinking"],
|
|
1850
|
-
note: "Structured step-by-step reasoning tool."
|
|
1851
|
-
},
|
|
1852
|
-
fetch: {
|
|
1853
|
-
command: "uvx",
|
|
1854
|
-
args: ["mcp-server-fetch"],
|
|
1855
|
-
note: "Fetch a URL and return its content as markdown/text."
|
|
1856
|
-
},
|
|
1857
|
-
git: {
|
|
1858
|
-
command: "uvx",
|
|
1859
|
-
args: ["mcp-server-git"],
|
|
1860
|
-
note: "Read/inspect a git repo. Append --repository <path>."
|
|
1861
|
-
},
|
|
1862
|
-
time: {
|
|
1863
|
-
command: "uvx",
|
|
1864
|
-
args: ["mcp-server-time"],
|
|
1865
|
-
note: "Current time and timezone conversions."
|
|
1866
|
-
}
|
|
1867
|
-
};
|
|
1868
|
-
var CONTROL_KEYS = /* @__PURE__ */ new Set(["tool", "arguments", "args", "__list_tools__"]);
|
|
1869
|
-
function resolveLaunch(opts) {
|
|
1870
|
-
const extra = opts.args ?? [];
|
|
1871
|
-
if (opts.server != null) {
|
|
1872
|
-
const preset = standardMcpServers[opts.server];
|
|
1873
|
-
if (!preset) {
|
|
1874
|
-
const available = Object.keys(standardMcpServers).sort().join(", ");
|
|
1874
|
+
// src/engram.ts
|
|
1875
|
+
var EngramBinding = class {
|
|
1876
|
+
name;
|
|
1877
|
+
directedId;
|
|
1878
|
+
directedType;
|
|
1879
|
+
defaultDeadlineMs;
|
|
1880
|
+
defaultRecallMode;
|
|
1881
|
+
constructor(init) {
|
|
1882
|
+
this.name = init.name;
|
|
1883
|
+
this.directedId = init.directedId ?? null;
|
|
1884
|
+
this.directedType = init.directedType ?? null;
|
|
1885
|
+
this.defaultDeadlineMs = init.defaultDeadlineMs ?? null;
|
|
1886
|
+
this.defaultRecallMode = init.defaultRecallMode ?? "first";
|
|
1887
|
+
if (!this.directedId && !this.directedType) {
|
|
1875
1888
|
throw new Error(
|
|
1876
|
-
`
|
|
1889
|
+
`EngramBinding '${this.name}' requires directedId (engram_id) or directedType (engram_kind), or both`
|
|
1877
1890
|
);
|
|
1878
1891
|
}
|
|
1879
|
-
return { command: opts.command ?? preset.command, args: [...preset.args, ...extra] };
|
|
1880
1892
|
}
|
|
1881
|
-
|
|
1882
|
-
|
|
1893
|
+
/** Build the `Directed` addressing this Engram. */
|
|
1894
|
+
toDirected() {
|
|
1895
|
+
return { id: this.directedId, type: this.directedType, capabilities: [] };
|
|
1883
1896
|
}
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
let connecting = null;
|
|
1890
|
-
async function ensure() {
|
|
1891
|
-
if (client) return client;
|
|
1892
|
-
if (connecting) return connecting;
|
|
1893
|
-
connecting = (async () => {
|
|
1894
|
-
const clientSpec = "@modelcontextprotocol/sdk/client/index.js";
|
|
1895
|
-
const stdioSpec = "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1896
|
-
const clientMod = await import(clientSpec);
|
|
1897
|
-
const stdioMod = await import(stdioSpec);
|
|
1898
|
-
const Client = clientMod.Client;
|
|
1899
|
-
const StdioClientTransport = stdioMod.StdioClientTransport;
|
|
1900
|
-
const transport = new StdioClientTransport({
|
|
1901
|
-
command,
|
|
1902
|
-
args,
|
|
1903
|
-
...opts.env ? { env: opts.env } : {},
|
|
1904
|
-
...opts.cwd ? { cwd: opts.cwd } : {}
|
|
1905
|
-
});
|
|
1906
|
-
const c = new Client(
|
|
1907
|
-
{ name: opts.clientName ?? "cosmonapse", version: opts.clientVersion ?? "0.2.0" },
|
|
1908
|
-
{ capabilities: {} }
|
|
1909
|
-
);
|
|
1910
|
-
await c.connect(transport);
|
|
1911
|
-
client = c;
|
|
1912
|
-
return c;
|
|
1913
|
-
})();
|
|
1914
|
-
return connecting;
|
|
1897
|
+
};
|
|
1898
|
+
var EngramError = class extends Error {
|
|
1899
|
+
constructor(message) {
|
|
1900
|
+
super(message);
|
|
1901
|
+
this.name = new.target.name;
|
|
1915
1902
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
throw new Error(
|
|
1944
|
-
`MCP Neuron could not determine which tool to call. Pass tool=... (server exposes: ${JSON.stringify(names)}).`
|
|
1945
|
-
);
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
const res = await c.callTool({ name: tool, arguments: toolArgs });
|
|
1949
|
-
const content = res.content ?? [];
|
|
1950
|
-
const texts = content.filter((x) => x?.text != null).map((x) => x.text);
|
|
1951
|
-
return {
|
|
1952
|
-
response: texts.join("\n"),
|
|
1953
|
-
result: res.structuredContent ?? null,
|
|
1954
|
-
is_error: Boolean(res.isError),
|
|
1955
|
-
content,
|
|
1956
|
-
meta: { tool, server: opts.server ?? null, command }
|
|
1957
|
-
};
|
|
1958
|
-
});
|
|
1959
|
-
fn.close = async () => {
|
|
1960
|
-
const c = client;
|
|
1961
|
-
client = null;
|
|
1962
|
-
connecting = null;
|
|
1963
|
-
if (c) {
|
|
1964
|
-
try {
|
|
1965
|
-
await c.close();
|
|
1966
|
-
} catch {
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1903
|
+
};
|
|
1904
|
+
var EngramTimeout = class extends EngramError {
|
|
1905
|
+
};
|
|
1906
|
+
var EngramCancelled = class extends EngramError {
|
|
1907
|
+
};
|
|
1908
|
+
var EngramNotBound = class extends EngramError {
|
|
1909
|
+
};
|
|
1910
|
+
var EngramOverloaded = class extends EngramError {
|
|
1911
|
+
};
|
|
1912
|
+
var Engram = class {
|
|
1913
|
+
version = null;
|
|
1914
|
+
/** Return false if this Engram cannot satisfy the query. Default: serve all. */
|
|
1915
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1916
|
+
async canServe(_query) {
|
|
1917
|
+
return true;
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
function receipt(engramId, op, fields = {}) {
|
|
1921
|
+
const error = fields.error ?? null;
|
|
1922
|
+
return {
|
|
1923
|
+
engramId,
|
|
1924
|
+
op,
|
|
1925
|
+
id: fields.id ?? null,
|
|
1926
|
+
version: fields.version ?? null,
|
|
1927
|
+
tookMs: fields.tookMs ?? null,
|
|
1928
|
+
error,
|
|
1929
|
+
ok: error === null
|
|
1969
1930
|
};
|
|
1970
|
-
return fn;
|
|
1971
1931
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1932
|
+
function entryToDict(e) {
|
|
1933
|
+
const out = {
|
|
1934
|
+
id: e.id,
|
|
1935
|
+
content: e.content,
|
|
1936
|
+
tags: [...e.tags],
|
|
1937
|
+
version: e.version,
|
|
1938
|
+
created_at: e.createdAt,
|
|
1939
|
+
updated_at: e.updatedAt
|
|
1940
|
+
};
|
|
1941
|
+
if (e.mergeKey !== null) out["merge_key"] = e.mergeKey;
|
|
1942
|
+
if (Object.keys(e.extra).length > 0) out["meta"] = { ...e.extra };
|
|
1943
|
+
return out;
|
|
1977
1944
|
}
|
|
1978
|
-
function
|
|
1979
|
-
|
|
1980
|
-
return Array.isArray(m) ? m : null;
|
|
1945
|
+
function asStringArray(v) {
|
|
1946
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
1981
1947
|
}
|
|
1982
|
-
function
|
|
1983
|
-
|
|
1984
|
-
const messages = readMessages(input);
|
|
1985
|
-
if (!prompt && !messages) {
|
|
1986
|
-
throw new Error(
|
|
1987
|
-
`${provider} Neuron expects 'prompt' or 'messages' in the input dict. Got keys: ${Object.keys(input).join(", ")}`
|
|
1988
|
-
);
|
|
1989
|
-
}
|
|
1990
|
-
return { prompt, messages };
|
|
1948
|
+
function asObject(v) {
|
|
1949
|
+
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
1991
1950
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
1951
|
+
var InMemoryEngram = class extends Engram {
|
|
1952
|
+
engramId;
|
|
1953
|
+
engramKind;
|
|
1954
|
+
capabilities;
|
|
1955
|
+
entries = /* @__PURE__ */ new Map();
|
|
1956
|
+
byMergeKey = /* @__PURE__ */ new Map();
|
|
1957
|
+
imprintSeen = /* @__PURE__ */ new Map();
|
|
1958
|
+
constructor(init = {}) {
|
|
1959
|
+
super();
|
|
1960
|
+
this.engramId = init.engramId ?? "engram-memory";
|
|
1961
|
+
this.engramKind = init.engramKind ?? "keyvalue";
|
|
1962
|
+
this.capabilities = init.capabilities ?? ["substring", "tags", "merge_key"];
|
|
1963
|
+
this.version = init.version ?? "0.0.1";
|
|
1964
|
+
}
|
|
1965
|
+
async connect() {
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
async close() {
|
|
1969
|
+
this.entries.clear();
|
|
1970
|
+
this.byMergeKey.clear();
|
|
1971
|
+
this.imprintSeen.clear();
|
|
1972
|
+
}
|
|
1973
|
+
async recall(query, opts = {}) {
|
|
1974
|
+
const q = query ?? {};
|
|
1975
|
+
const text = typeof q["text"] === "string" ? q["text"].toLowerCase() : "";
|
|
1976
|
+
const tagQ = typeof q["tag"] === "string" ? q["tag"] : null;
|
|
1977
|
+
const mergeKey = typeof q["merge_key"] === "string" ? q["merge_key"] : null;
|
|
1978
|
+
const topK = typeof q["top_k"] === "number" ? q["top_k"] : 50;
|
|
1979
|
+
const filters = opts.filters ?? {};
|
|
1980
|
+
const requireTags = asStringArray(filters["tags"]);
|
|
1981
|
+
const since = typeof filters["since"] === "string" ? Date.parse(filters["since"]) : NaN;
|
|
1982
|
+
const until = typeof filters["until"] === "string" ? Date.parse(filters["until"]) : NaN;
|
|
1983
|
+
let candidates;
|
|
1984
|
+
if (mergeKey !== null) {
|
|
1985
|
+
const ids = this.byMergeKey.get(mergeKey) ?? [];
|
|
1986
|
+
candidates = ids.map((i) => this.entries.get(i)).filter((e) => e !== void 0);
|
|
1987
|
+
} else {
|
|
1988
|
+
candidates = [...this.entries.values()];
|
|
1989
|
+
}
|
|
1990
|
+
const hits = [];
|
|
1991
|
+
for (const ent of candidates) {
|
|
1992
|
+
if (requireTags.length > 0 && !requireTags.every((t) => ent.tags.includes(t))) continue;
|
|
1993
|
+
const updated = Date.parse(ent.updatedAt);
|
|
1994
|
+
if (!Number.isNaN(since) && updated < since) continue;
|
|
1995
|
+
if (!Number.isNaN(until) && updated > until) continue;
|
|
1996
|
+
if (tagQ !== null && !ent.tags.includes(tagQ)) continue;
|
|
1997
|
+
let score = 1;
|
|
1998
|
+
if (text) {
|
|
1999
|
+
const hay = String(ent.content).toLowerCase();
|
|
2000
|
+
if (!hay.includes(text)) continue;
|
|
2001
|
+
score = Math.min(1, text.length / Math.max(1, hay.length));
|
|
2002
|
+
}
|
|
2003
|
+
if (opts.minConfidence !== void 0 && score < opts.minConfidence) continue;
|
|
2004
|
+
hits.push({ id: ent.id, entry: entryToDict(ent), score });
|
|
2005
|
+
}
|
|
2006
|
+
hits.sort((a, b) => b.score - a.score);
|
|
2007
|
+
return hits.slice(0, topK);
|
|
2008
|
+
}
|
|
2009
|
+
async imprint(op, entry, opts = {}) {
|
|
2010
|
+
const t0 = Date.now();
|
|
2011
|
+
const mergeKey = opts.mergeKey ?? null;
|
|
2012
|
+
const tookMs = () => Date.now() - t0;
|
|
2013
|
+
if (opts.imprintId !== void 0) {
|
|
2014
|
+
const seen = this.imprintSeen.get(opts.imprintId);
|
|
2015
|
+
if (seen !== void 0) {
|
|
2016
|
+
const existing = this.entries.get(seen);
|
|
2017
|
+
return receipt(this.engramId, op, {
|
|
2018
|
+
id: seen,
|
|
2019
|
+
version: existing ? existing.version : null,
|
|
2020
|
+
tookMs: tookMs()
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
let resultingId = null;
|
|
2025
|
+
let version = null;
|
|
2026
|
+
if (op === "add") {
|
|
2027
|
+
const ent = this.makeEntry(entry, mergeKey);
|
|
2028
|
+
if (this.entries.has(ent.id)) {
|
|
2029
|
+
return receipt(this.engramId, op, { error: `entry id '${ent.id}' already exists`, tookMs: tookMs() });
|
|
2030
|
+
}
|
|
2031
|
+
this.store(ent);
|
|
2032
|
+
resultingId = ent.id;
|
|
2033
|
+
version = ent.version;
|
|
2034
|
+
} else if (op === "append") {
|
|
2035
|
+
let ent = this.makeEntry(entry, mergeKey);
|
|
2036
|
+
while (this.entries.has(ent.id)) {
|
|
2037
|
+
ent = this.makeEntry({ ...entry, id: newEngramId() }, mergeKey);
|
|
2038
|
+
}
|
|
2039
|
+
this.store(ent);
|
|
2040
|
+
resultingId = ent.id;
|
|
2041
|
+
version = ent.version;
|
|
2042
|
+
} else if (op === "upsert") {
|
|
2043
|
+
const existingIds = this.byMergeKey.get(mergeKey ?? "") ?? [];
|
|
2044
|
+
const targetId = existingIds[existingIds.length - 1];
|
|
2045
|
+
const old = targetId !== void 0 ? this.entries.get(targetId) : void 0;
|
|
2046
|
+
if (old !== void 0) {
|
|
2047
|
+
const next = this.makeEntry({ ...entry, id: old.id }, mergeKey);
|
|
2048
|
+
next.createdAt = old.createdAt;
|
|
2049
|
+
next.version = old.version + 1;
|
|
2050
|
+
this.store(next, true);
|
|
2051
|
+
resultingId = next.id;
|
|
2052
|
+
version = next.version;
|
|
2053
|
+
} else {
|
|
2054
|
+
const ent = this.makeEntry(entry, mergeKey);
|
|
2055
|
+
this.store(ent);
|
|
2056
|
+
resultingId = ent.id;
|
|
2057
|
+
version = ent.version;
|
|
2058
|
+
}
|
|
2059
|
+
} else if (op === "merge") {
|
|
2060
|
+
const existingIds = this.byMergeKey.get(mergeKey ?? "") ?? [];
|
|
2061
|
+
const targetId = existingIds[existingIds.length - 1];
|
|
2062
|
+
const old = targetId !== void 0 ? this.entries.get(targetId) : void 0;
|
|
2063
|
+
if (old === void 0) {
|
|
2064
|
+
return receipt(this.engramId, op, { error: `no entry for merge_key='${mergeKey}'`, tookMs: tookMs() });
|
|
2065
|
+
}
|
|
2066
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2067
|
+
const next = {
|
|
2068
|
+
id: old.id,
|
|
2069
|
+
content: deepMerge(old.content, entry["content"]),
|
|
2070
|
+
tags: [.../* @__PURE__ */ new Set([...old.tags, ...asStringArray(entry["tags"])])],
|
|
2071
|
+
mergeKey: old.mergeKey,
|
|
2072
|
+
version: old.version + 1,
|
|
2073
|
+
createdAt: old.createdAt,
|
|
2074
|
+
updatedAt: now,
|
|
2075
|
+
extra: asObject(deepMerge(old.extra, entry["meta"]))
|
|
2076
|
+
};
|
|
2077
|
+
this.store(next, true);
|
|
2078
|
+
resultingId = next.id;
|
|
2079
|
+
version = next.version;
|
|
2080
|
+
} else if (op === "delete") {
|
|
2081
|
+
let targetId = null;
|
|
2082
|
+
const entId = entry["id"];
|
|
2083
|
+
if (typeof entId === "string") {
|
|
2084
|
+
targetId = entId;
|
|
2085
|
+
} else if (mergeKey !== null) {
|
|
2086
|
+
const ids = this.byMergeKey.get(mergeKey) ?? [];
|
|
2087
|
+
targetId = ids[ids.length - 1] ?? null;
|
|
2088
|
+
}
|
|
2089
|
+
if (targetId === null || !this.entries.has(targetId)) {
|
|
2090
|
+
return receipt(this.engramId, op, { tookMs: tookMs() });
|
|
2091
|
+
}
|
|
2092
|
+
this.evict(targetId);
|
|
2093
|
+
resultingId = targetId;
|
|
2094
|
+
version = null;
|
|
2095
|
+
} else {
|
|
2096
|
+
return receipt(this.engramId, op, { error: `unknown op '${op}'`, tookMs: tookMs() });
|
|
2097
|
+
}
|
|
2098
|
+
if (opts.imprintId !== void 0 && resultingId !== null) {
|
|
2099
|
+
this.imprintSeen.set(opts.imprintId, resultingId);
|
|
2100
|
+
}
|
|
2101
|
+
return receipt(this.engramId, op, { id: resultingId, version, tookMs: tookMs() });
|
|
2102
|
+
}
|
|
2103
|
+
/** Test/debug helper - NOT part of the Engram contract. */
|
|
2104
|
+
snapshot() {
|
|
2105
|
+
return [...this.entries.values()].map(entryToDict);
|
|
2106
|
+
}
|
|
2107
|
+
makeEntry(entry, mergeKey) {
|
|
2108
|
+
const id = typeof entry["id"] === "string" ? entry["id"] : newEngramId();
|
|
2109
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2110
|
+
return {
|
|
2111
|
+
id,
|
|
2112
|
+
content: entry["content"],
|
|
2113
|
+
tags: asStringArray(entry["tags"]),
|
|
2114
|
+
mergeKey,
|
|
2115
|
+
version: 1,
|
|
2116
|
+
createdAt: now,
|
|
2117
|
+
updatedAt: now,
|
|
2118
|
+
extra: asObject(entry["meta"])
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
store(ent, replace = false) {
|
|
2122
|
+
if (replace) {
|
|
2123
|
+
const old = this.entries.get(ent.id);
|
|
2124
|
+
if (old !== void 0 && old.mergeKey) {
|
|
2125
|
+
const bucket = this.byMergeKey.get(old.mergeKey);
|
|
2126
|
+
if (bucket) {
|
|
2127
|
+
const idx = bucket.indexOf(ent.id);
|
|
2128
|
+
if (idx >= 0) bucket.splice(idx, 1);
|
|
2129
|
+
if (bucket.length === 0) this.byMergeKey.delete(old.mergeKey);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
this.entries.set(ent.id, ent);
|
|
2134
|
+
if (ent.mergeKey) {
|
|
2135
|
+
const bucket = this.byMergeKey.get(ent.mergeKey);
|
|
2136
|
+
if (bucket) bucket.push(ent.id);
|
|
2137
|
+
else this.byMergeKey.set(ent.mergeKey, [ent.id]);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
evict(entryId) {
|
|
2141
|
+
const ent = this.entries.get(entryId);
|
|
2142
|
+
this.entries.delete(entryId);
|
|
2143
|
+
if (ent === void 0) return;
|
|
2144
|
+
if (ent.mergeKey) {
|
|
2145
|
+
const bucket = this.byMergeKey.get(ent.mergeKey);
|
|
2146
|
+
if (bucket) {
|
|
2147
|
+
const idx = bucket.indexOf(entryId);
|
|
2148
|
+
if (idx >= 0) bucket.splice(idx, 1);
|
|
2149
|
+
if (bucket.length === 0) this.byMergeKey.delete(ent.mergeKey);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
};
|
|
2154
|
+
function deepMerge(base, incoming) {
|
|
2155
|
+
if (incoming === void 0 || incoming === null) return base;
|
|
2156
|
+
const bothObjects = base !== null && typeof base === "object" && !Array.isArray(base) && typeof incoming === "object" && !Array.isArray(incoming);
|
|
2157
|
+
if (bothObjects) {
|
|
2158
|
+
const out = { ...base };
|
|
2159
|
+
for (const [k, v] of Object.entries(incoming)) {
|
|
2160
|
+
out[k] = k in out ? deepMerge(out[k], v) : v;
|
|
2161
|
+
}
|
|
2162
|
+
return out;
|
|
2163
|
+
}
|
|
2164
|
+
if (Array.isArray(base) && Array.isArray(incoming)) {
|
|
2165
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2166
|
+
const out = [];
|
|
2167
|
+
for (const item of [...base, ...incoming]) {
|
|
2168
|
+
const key = JSON.stringify(item);
|
|
2169
|
+
if (seen.has(key)) continue;
|
|
2170
|
+
seen.add(key);
|
|
2171
|
+
out.push(item);
|
|
2172
|
+
}
|
|
2173
|
+
return out;
|
|
2174
|
+
}
|
|
2175
|
+
return incoming;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// src/neuron-mcp.ts
|
|
2179
|
+
var standardMcpServers = {
|
|
2180
|
+
filesystem: {
|
|
2181
|
+
command: "npx",
|
|
2182
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem"],
|
|
2183
|
+
note: "Append one or more allowed directories, e.g. args=['/data']."
|
|
2184
|
+
},
|
|
2185
|
+
memory: {
|
|
2186
|
+
command: "npx",
|
|
2187
|
+
args: ["-y", "@modelcontextprotocol/server-memory"],
|
|
2188
|
+
note: "Knowledge-graph memory store."
|
|
2189
|
+
},
|
|
2190
|
+
everything: {
|
|
2191
|
+
command: "npx",
|
|
2192
|
+
args: ["-y", "@modelcontextprotocol/server-everything"],
|
|
2193
|
+
note: "Reference server exercising every MCP feature; handy for tests."
|
|
2194
|
+
},
|
|
2195
|
+
sequentialthinking: {
|
|
2196
|
+
command: "npx",
|
|
2197
|
+
args: ["-y", "@modelcontextprotocol/server-sequential-thinking"],
|
|
2198
|
+
note: "Structured step-by-step reasoning tool."
|
|
2199
|
+
},
|
|
2200
|
+
fetch: {
|
|
2201
|
+
command: "uvx",
|
|
2202
|
+
args: ["mcp-server-fetch"],
|
|
2203
|
+
note: "Fetch a URL and return its content as markdown/text."
|
|
2204
|
+
},
|
|
2205
|
+
git: {
|
|
2206
|
+
command: "uvx",
|
|
2207
|
+
args: ["mcp-server-git"],
|
|
2208
|
+
note: "Read/inspect a git repo. Append --repository <path>."
|
|
2209
|
+
},
|
|
2210
|
+
time: {
|
|
2211
|
+
command: "uvx",
|
|
2212
|
+
args: ["mcp-server-time"],
|
|
2213
|
+
note: "Current time and timezone conversions."
|
|
2214
|
+
}
|
|
2215
|
+
};
|
|
2216
|
+
var CONTROL_KEYS = /* @__PURE__ */ new Set(["tool", "arguments", "args", "__list_tools__"]);
|
|
2217
|
+
function resolveLaunch(opts) {
|
|
2218
|
+
const extra = opts.args ?? [];
|
|
2219
|
+
if (opts.server != null) {
|
|
2220
|
+
const preset = standardMcpServers[opts.server];
|
|
2221
|
+
if (!preset) {
|
|
2222
|
+
const available = Object.keys(standardMcpServers).sort().join(", ");
|
|
2223
|
+
throw new Error(
|
|
2224
|
+
`Unknown MCP server preset '${opts.server}'. Available: ${available}. (Or pass command/args to wrap any other stdio MCP server.)`
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
return { command: opts.command ?? preset.command, args: [...preset.args, ...extra] };
|
|
2228
|
+
}
|
|
2229
|
+
if (opts.command == null) {
|
|
2230
|
+
throw new Error("mcpNeuron(...) needs either `command` (+optional `args`) or a `server` preset name.");
|
|
2231
|
+
}
|
|
2232
|
+
return { command: opts.command, args: extra };
|
|
2233
|
+
}
|
|
2234
|
+
function mcpNeuron(opts) {
|
|
2235
|
+
const { command, args } = resolveLaunch(opts);
|
|
2236
|
+
let client = null;
|
|
2237
|
+
let connecting = null;
|
|
2238
|
+
async function ensure() {
|
|
2239
|
+
if (client) return client;
|
|
2240
|
+
if (connecting) return connecting;
|
|
2241
|
+
connecting = (async () => {
|
|
2242
|
+
const clientSpec = "@modelcontextprotocol/sdk/client/index.js";
|
|
2243
|
+
const stdioSpec = "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2244
|
+
const clientMod = await import(clientSpec);
|
|
2245
|
+
const stdioMod = await import(stdioSpec);
|
|
2246
|
+
const Client = clientMod.Client;
|
|
2247
|
+
const StdioClientTransport = stdioMod.StdioClientTransport;
|
|
2248
|
+
const transport = new StdioClientTransport({
|
|
2249
|
+
command,
|
|
2250
|
+
args,
|
|
2251
|
+
...opts.env ? { env: opts.env } : {},
|
|
2252
|
+
...opts.cwd ? { cwd: opts.cwd } : {}
|
|
2253
|
+
});
|
|
2254
|
+
const c = new Client(
|
|
2255
|
+
{ name: opts.clientName ?? "cosmonapse", version: opts.clientVersion ?? "0.2.0" },
|
|
2256
|
+
{ capabilities: {} }
|
|
2257
|
+
);
|
|
2258
|
+
await c.connect(transport);
|
|
2259
|
+
client = c;
|
|
2260
|
+
return c;
|
|
2261
|
+
})();
|
|
2262
|
+
return connecting;
|
|
2263
|
+
}
|
|
2264
|
+
const fn = (async (input, _context) => {
|
|
2265
|
+
const c = await ensure();
|
|
2266
|
+
const inp = input ?? {};
|
|
2267
|
+
if (inp.__list_tools__) {
|
|
2268
|
+
const res2 = await c.listTools();
|
|
2269
|
+
return {
|
|
2270
|
+
tools: (res2.tools ?? []).map((t) => ({
|
|
2271
|
+
name: t.name,
|
|
2272
|
+
description: t.description ?? null,
|
|
2273
|
+
input_schema: t.inputSchema ?? null
|
|
2274
|
+
}))
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
let tool = inp.tool ?? opts.tool;
|
|
2278
|
+
let toolArgs = inp.arguments ?? inp.args;
|
|
2279
|
+
if (toolArgs == null) {
|
|
2280
|
+
toolArgs = {};
|
|
2281
|
+
for (const [k, v] of Object.entries(inp)) {
|
|
2282
|
+
if (!CONTROL_KEYS.has(k)) toolArgs[k] = v;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
if (!tool) {
|
|
2286
|
+
const res2 = await c.listTools();
|
|
2287
|
+
const names = (res2.tools ?? []).map((t) => t.name);
|
|
2288
|
+
if (names.length === 1) {
|
|
2289
|
+
tool = names[0];
|
|
2290
|
+
} else {
|
|
2291
|
+
throw new Error(
|
|
2292
|
+
`MCP Neuron could not determine which tool to call. Pass tool=... (server exposes: ${JSON.stringify(names)}).`
|
|
2293
|
+
);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
const res = await c.callTool({ name: tool, arguments: toolArgs });
|
|
2297
|
+
const content = res.content ?? [];
|
|
2298
|
+
const texts = content.filter((x) => x?.text != null).map((x) => x.text);
|
|
2299
|
+
return {
|
|
2300
|
+
response: texts.join("\n"),
|
|
2301
|
+
result: res.structuredContent ?? null,
|
|
2302
|
+
is_error: Boolean(res.isError),
|
|
2303
|
+
content,
|
|
2304
|
+
meta: { tool, server: opts.server ?? null, command }
|
|
2305
|
+
};
|
|
2306
|
+
});
|
|
2307
|
+
fn.close = async () => {
|
|
2308
|
+
const c = client;
|
|
2309
|
+
client = null;
|
|
2310
|
+
connecting = null;
|
|
2311
|
+
if (c) {
|
|
2312
|
+
try {
|
|
2313
|
+
await c.close();
|
|
2314
|
+
} catch {
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
return fn;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
// src/neuron-http.ts
|
|
2322
|
+
function readPrompt(input) {
|
|
2323
|
+
const v = input["prompt"] ?? input["text"] ?? input["query"] ?? input["content"];
|
|
2324
|
+
return typeof v === "string" && v ? v : null;
|
|
2325
|
+
}
|
|
2326
|
+
function readMessages(input) {
|
|
2327
|
+
const m = input["messages"];
|
|
2328
|
+
return Array.isArray(m) ? m : null;
|
|
2329
|
+
}
|
|
2330
|
+
function followupPrompt(input) {
|
|
2331
|
+
const c = input["clarification"];
|
|
2332
|
+
if (c !== null && typeof c === "object" && !Array.isArray(c)) {
|
|
2333
|
+
const cd = c;
|
|
2334
|
+
const lines = ["You previously asked a clarifying question while working on a task."];
|
|
2335
|
+
if (cd["question"] !== void 0 && cd["question"] !== null) {
|
|
2336
|
+
lines.push(`Your question: ${String(cd["question"])}`);
|
|
2337
|
+
}
|
|
2338
|
+
if ("answer" in cd) lines.push(`The answer: ${JSON.stringify(cd["answer"])}`);
|
|
2339
|
+
const extra = Object.fromEntries(
|
|
2340
|
+
Object.entries(cd).filter(([k]) => k !== "question" && k !== "answer")
|
|
2341
|
+
);
|
|
2342
|
+
if (Object.keys(extra).length) lines.push(`Additional context: ${JSON.stringify(extra)}`);
|
|
2343
|
+
lines.push("Continue the original task using this answer.");
|
|
2344
|
+
return lines.join("\n");
|
|
2345
|
+
}
|
|
2346
|
+
const p = input["permission"];
|
|
2347
|
+
if (p !== null && typeof p === "object" && !Array.isArray(p)) {
|
|
2348
|
+
const pd = p;
|
|
2349
|
+
const granted = Boolean(pd["granted"]);
|
|
2350
|
+
const lines = ["You previously requested permission while working on a task."];
|
|
2351
|
+
if (pd["action"] !== void 0 && pd["action"] !== null) {
|
|
2352
|
+
lines.push(`Requested action: ${String(pd["action"])}`);
|
|
2353
|
+
}
|
|
2354
|
+
lines.push(`The decision: ${granted ? "GRANTED" : "DENIED"}.`);
|
|
2355
|
+
if (pd["reason"] !== void 0 && pd["reason"] !== null) {
|
|
2356
|
+
lines.push(`Reason: ${String(pd["reason"])}`);
|
|
2357
|
+
}
|
|
2358
|
+
if (pd["ttl_ms"] !== void 0 && pd["ttl_ms"] !== null) {
|
|
2359
|
+
lines.push(`The grant is valid for ${String(pd["ttl_ms"])} ms.`);
|
|
2360
|
+
}
|
|
2361
|
+
lines.push(
|
|
2362
|
+
granted ? "Proceed with the action and continue the original task." : "Do not perform the action. Continue the task another way, or explain why you cannot."
|
|
2363
|
+
);
|
|
2364
|
+
return lines.join("\n");
|
|
2365
|
+
}
|
|
2366
|
+
return null;
|
|
2367
|
+
}
|
|
2368
|
+
function requireInput(input, provider) {
|
|
2369
|
+
const prompt = readPrompt(input) ?? followupPrompt(input);
|
|
2370
|
+
const messages = readMessages(input);
|
|
2371
|
+
if (!prompt && !messages) {
|
|
2372
|
+
throw new Error(
|
|
2373
|
+
`${provider} Neuron expects 'prompt' or 'messages' in the input dict. Got keys: ${Object.keys(input).join(", ")}`
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
return { prompt, messages };
|
|
2377
|
+
}
|
|
2378
|
+
async function postJson(url, body, headers, timeoutMs) {
|
|
2379
|
+
const ctrl = new AbortController();
|
|
2380
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
2381
|
+
try {
|
|
2382
|
+
const res = await fetch(url, {
|
|
2383
|
+
method: "POST",
|
|
2384
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
2385
|
+
body: JSON.stringify(body),
|
|
2386
|
+
signal: ctrl.signal
|
|
2387
|
+
});
|
|
2388
|
+
if (!res.ok) {
|
|
2389
|
+
const text = await res.text().catch(() => "");
|
|
2390
|
+
throw new Error(`HTTP ${res.status} from ${url}: ${text.slice(0, 200)}`);
|
|
2391
|
+
}
|
|
2006
2392
|
return await res.json();
|
|
2007
2393
|
} finally {
|
|
2008
2394
|
clearTimeout(timer);
|
|
@@ -2191,9 +2577,11 @@ var Axon = class _Axon {
|
|
|
2191
2577
|
neuronId;
|
|
2192
2578
|
capabilities;
|
|
2193
2579
|
version;
|
|
2580
|
+
neuronKind;
|
|
2194
2581
|
fn;
|
|
2195
2582
|
contextFetcher;
|
|
2196
2583
|
outputParser;
|
|
2584
|
+
engramBindings = /* @__PURE__ */ new Map();
|
|
2197
2585
|
dendrite = null;
|
|
2198
2586
|
/**
|
|
2199
2587
|
* Decorator-registered recognisers, one bucket per capability (the asking
|
|
@@ -2202,48 +2590,139 @@ var Axon = class _Axon {
|
|
|
2202
2590
|
* output by {@link applyRecognisers}.
|
|
2203
2591
|
*/
|
|
2204
2592
|
recognisers = { error: [], clarification: [], permission: [], output: [] };
|
|
2593
|
+
/** Pre-task hooks (beforeTask): transform/validate/reject the TASK input
|
|
2594
|
+
* before the Neuron runs. */
|
|
2595
|
+
beforeTaskHooks = [];
|
|
2205
2596
|
/** @internal - lifecycle hooks, driven by the hosting Dendrite. */
|
|
2206
2597
|
hooks = new LifecycleHooks(this);
|
|
2207
2598
|
constructor(opts) {
|
|
2208
2599
|
this.neuronId = opts.neuronId;
|
|
2209
2600
|
this.capabilities = opts.capabilities ?? [];
|
|
2210
2601
|
this.version = opts.version;
|
|
2602
|
+
this.neuronKind = opts.neuronKind ?? "neuron";
|
|
2211
2603
|
this.fn = opts.neuronFn;
|
|
2212
2604
|
this.contextFetcher = opts.contextFetcher ?? noopContextFetcher;
|
|
2213
2605
|
this.outputParser = opts.outputParser;
|
|
2606
|
+
for (const b of opts.engrams ?? []) {
|
|
2607
|
+
if (this.engramBindings.has(b.name)) {
|
|
2608
|
+
throw new Error(`Axon '${opts.neuronId}': duplicate EngramBinding name '${b.name}'`);
|
|
2609
|
+
}
|
|
2610
|
+
this.engramBindings.set(b.name, b);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
/** Declared Engram bindings, keyed by name. */
|
|
2614
|
+
get engrams() {
|
|
2615
|
+
return new Map(this.engramBindings);
|
|
2616
|
+
}
|
|
2617
|
+
resolveBinding(name) {
|
|
2618
|
+
const binding = this.engramBindings.get(name);
|
|
2619
|
+
if (!binding) {
|
|
2620
|
+
throw new EngramNotBound(
|
|
2621
|
+
`Axon '${this.neuronId}': no Engram binding named '${name}'; available: ${[...this.engramBindings.keys()].sort().join(", ")}`
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
return binding;
|
|
2625
|
+
}
|
|
2626
|
+
/** Build the per-task helpers object handed to the Neuron as its third
|
|
2627
|
+
* argument. Helpers throw EngramNotBound for undeclared names and
|
|
2628
|
+
* require a hosting Dendrite (the only thing the Axon pulls from it). */
|
|
2629
|
+
buildHelpers(traceId, parentId) {
|
|
2630
|
+
const requireClient = () => {
|
|
2631
|
+
if (this.dendrite === null) {
|
|
2632
|
+
throw new Error(
|
|
2633
|
+
`Axon '${this.neuronId}': not attached to a Dendrite; engram helpers require a hosting Dendrite`
|
|
2634
|
+
);
|
|
2635
|
+
}
|
|
2636
|
+
return this.dendrite.engramClient;
|
|
2637
|
+
};
|
|
2638
|
+
return {
|
|
2639
|
+
recall: async (name, args) => {
|
|
2640
|
+
const binding = this.resolveBinding(name);
|
|
2641
|
+
return requireClient().recall({
|
|
2642
|
+
binding,
|
|
2643
|
+
query: args.query,
|
|
2644
|
+
traceId,
|
|
2645
|
+
parentId,
|
|
2646
|
+
...args.filters !== void 0 ? { filters: args.filters } : {},
|
|
2647
|
+
...args.contextRef !== void 0 ? { contextRef: args.contextRef } : {},
|
|
2648
|
+
...args.deadlineMs !== void 0 ? { deadlineMs: args.deadlineMs } : {},
|
|
2649
|
+
...args.recallMode !== void 0 ? { recallMode: args.recallMode } : {},
|
|
2650
|
+
...args.minConfidence !== void 0 ? { minConfidence: args.minConfidence } : {},
|
|
2651
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
2652
|
+
});
|
|
2653
|
+
},
|
|
2654
|
+
imprint: async (name, args) => {
|
|
2655
|
+
const binding = this.resolveBinding(name);
|
|
2656
|
+
return requireClient().imprint({
|
|
2657
|
+
binding,
|
|
2658
|
+
op: args.op,
|
|
2659
|
+
entry: args.entry,
|
|
2660
|
+
traceId,
|
|
2661
|
+
parentId,
|
|
2662
|
+
...args.mergeKey !== void 0 ? { mergeKey: args.mergeKey } : {},
|
|
2663
|
+
...args.awaitAck !== void 0 ? { awaitAck: args.awaitAck } : {},
|
|
2664
|
+
...args.deadlineMs !== void 0 ? { deadlineMs: args.deadlineMs } : {},
|
|
2665
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
// -- source-paired factories --------------------------------------
|
|
2671
|
+
// Build an Axon already paired with one of the `neuron(source, ...)`
|
|
2672
|
+
// providers AND wired with the matching recogniser. No new class: the
|
|
2673
|
+
// result is a plain Axon.
|
|
2674
|
+
/** Resolve the teach-intents decision and return (possibly augmented) source opts. */
|
|
2675
|
+
static applyTeachIntents(source, opts, extra) {
|
|
2676
|
+
const recognize = extra.recognize ?? true;
|
|
2677
|
+
const teach = extra.teachIntents ?? (recognize && SYSTEM_CAPABLE_SOURCES.has(source.toLowerCase()));
|
|
2678
|
+
if (!teach) return opts;
|
|
2679
|
+
if (!SYSTEM_CAPABLE_SOURCES.has(source.toLowerCase())) {
|
|
2680
|
+
throw new Error(
|
|
2681
|
+
`teachIntents: true is not supported for source '${source}': its Neuron wrapper accepts no system option. Embed the convention in the prompt yourself (COSMO_INTENT_SYSTEM_PROMPT).`
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2684
|
+
const existing = opts?.system;
|
|
2685
|
+
return {
|
|
2686
|
+
...opts ?? {},
|
|
2687
|
+
system: existing ? `${existing}
|
|
2688
|
+
|
|
2689
|
+
${COSMO_INTENT_SYSTEM_PROMPT}` : COSMO_INTENT_SYSTEM_PROMPT
|
|
2690
|
+
};
|
|
2214
2691
|
}
|
|
2215
|
-
// -- source-paired factories --------------------------------------
|
|
2216
|
-
// Build an Axon already paired with one of the `neuron(source, ...)`
|
|
2217
|
-
// providers AND wired with the matching recogniser. No new class: the
|
|
2218
|
-
// result is a plain Axon.
|
|
2219
2692
|
static build(neuronId, neuronFn, source, extra) {
|
|
2220
2693
|
const recognize = extra.recognize ?? true;
|
|
2221
2694
|
const o = { neuronId, neuronFn };
|
|
2222
2695
|
if (extra.capabilities) o.capabilities = extra.capabilities;
|
|
2223
2696
|
if (extra.version !== void 0) o.version = extra.version;
|
|
2697
|
+
if (extra.neuronKind !== void 0) o.neuronKind = extra.neuronKind;
|
|
2224
2698
|
if (extra.contextFetcher) o.contextFetcher = extra.contextFetcher;
|
|
2225
2699
|
if (recognize) o.outputParser = source === "mcp" ? parseMcpIntents : parseLlmIntents;
|
|
2226
2700
|
return new _Axon(o);
|
|
2227
2701
|
}
|
|
2228
2702
|
/** Axon paired with any registered Neuron source + its recogniser. */
|
|
2229
2703
|
static fromSource(source, neuronId, opts, extra = {}) {
|
|
2230
|
-
|
|
2704
|
+
const o = _Axon.applyTeachIntents(source, opts, extra);
|
|
2705
|
+
return _Axon.build(neuronId, neuron(source, o), source, extra);
|
|
2231
2706
|
}
|
|
2232
2707
|
/** Axon paired with the OpenAI Chat Completions API. */
|
|
2233
2708
|
static openai(neuronId, opts, extra = {}) {
|
|
2234
|
-
|
|
2709
|
+
const o = _Axon.applyTeachIntents("openai", opts, extra);
|
|
2710
|
+
return _Axon.build(neuronId, neuron("openai", o), "openai", extra);
|
|
2235
2711
|
}
|
|
2236
2712
|
/** Axon paired with the Anthropic Messages API. */
|
|
2237
2713
|
static anthropic(neuronId, opts, extra = {}) {
|
|
2238
|
-
|
|
2714
|
+
const o = _Axon.applyTeachIntents("anthropic", opts, extra);
|
|
2715
|
+
return _Axon.build(neuronId, neuron("anthropic", o), "anthropic", extra);
|
|
2239
2716
|
}
|
|
2240
2717
|
/** Axon paired with a local Ollama daemon. */
|
|
2241
2718
|
static ollama(neuronId, opts, extra = {}) {
|
|
2242
|
-
|
|
2719
|
+
const o = _Axon.applyTeachIntents("ollama", opts, extra);
|
|
2720
|
+
return _Axon.build(neuronId, neuron("ollama", o), "ollama", extra);
|
|
2243
2721
|
}
|
|
2244
2722
|
/** Axon paired with a HuggingFace TGI / OpenAI-compatible endpoint. */
|
|
2245
2723
|
static huggingface(neuronId, opts, extra = {}) {
|
|
2246
|
-
|
|
2724
|
+
const o = _Axon.applyTeachIntents("huggingface", opts, extra);
|
|
2725
|
+
return _Axon.build(neuronId, neuron("huggingface", o), "huggingface", extra);
|
|
2247
2726
|
}
|
|
2248
2727
|
/** Axon paired with a stdio MCP server. */
|
|
2249
2728
|
static mcp(neuronId, opts, extra = {}) {
|
|
@@ -2255,6 +2734,26 @@ var Axon = class _Axon {
|
|
|
2255
2734
|
// Signals). Return the intent's fields to match, or null/undefined to fall
|
|
2256
2735
|
// through. Sync or async; multiple per capability tried in order. These run
|
|
2257
2736
|
// after `outputParser` and before the literal `__marker__` checks.
|
|
2737
|
+
/**
|
|
2738
|
+
* Register a pre-task hook over the TASK's `input`. Runs before the Neuron.
|
|
2739
|
+
* Sync or async; multiple hooks run in registration order, each receiving
|
|
2740
|
+
* the previous one's result. Return a (new) object to replace the input,
|
|
2741
|
+
* return null/undefined to pass through unchanged, or throw to reject the
|
|
2742
|
+
* TASK (surfaces as an ERROR Signal, code NEURON_EXCEPTION). The natural
|
|
2743
|
+
* place for input normalisation or per-Axon policy checks.
|
|
2744
|
+
*/
|
|
2745
|
+
beforeTask(fn) {
|
|
2746
|
+
this.beforeTaskHooks.push(fn);
|
|
2747
|
+
return fn;
|
|
2748
|
+
}
|
|
2749
|
+
async applyBeforeTask(input) {
|
|
2750
|
+
let current = input;
|
|
2751
|
+
for (const fn of this.beforeTaskHooks) {
|
|
2752
|
+
const r = await fn(current);
|
|
2753
|
+
if (r !== null && r !== void 0) current = r;
|
|
2754
|
+
}
|
|
2755
|
+
return current;
|
|
2756
|
+
}
|
|
2258
2757
|
/** Detector returning the AGENT_OUTPUT payload, or null to wrap verbatim. */
|
|
2259
2758
|
detectsOutput(fn) {
|
|
2260
2759
|
this.recognisers.output.push(fn);
|
|
@@ -2325,8 +2824,21 @@ var Axon = class _Axon {
|
|
|
2325
2824
|
[DETACH]() {
|
|
2326
2825
|
this.dendrite = null;
|
|
2327
2826
|
}
|
|
2328
|
-
/** Run the Neuron and return AGENT_OUTPUT / CLARIFICATION / ERROR.
|
|
2827
|
+
/** Run the Neuron and return AGENT_OUTPUT / CLARIFICATION / ERROR.
|
|
2828
|
+
*
|
|
2829
|
+
* Binds the TASK's (traceId, parentId=task.id) as the ambient trace
|
|
2830
|
+
* context for the whole handling pass - neuronFn, detectors, and hooks
|
|
2831
|
+
* included - so engram calls made without explicit trace plumbing (e.g.
|
|
2832
|
+
* `dendrite.imprint` from a `detectsOutput` hook) are attributed to this
|
|
2833
|
+
* task's trace. */
|
|
2329
2834
|
async handleTask(task) {
|
|
2835
|
+
return runWithTraceContext(
|
|
2836
|
+
task.trace_id,
|
|
2837
|
+
task.id,
|
|
2838
|
+
() => this.handleTaskInner(task)
|
|
2839
|
+
);
|
|
2840
|
+
}
|
|
2841
|
+
async handleTaskInner(task) {
|
|
2330
2842
|
const traceId = task.trace_id;
|
|
2331
2843
|
const parentId = task.id;
|
|
2332
2844
|
const input = task.payload["input"] ?? {};
|
|
@@ -2339,136 +2851,569 @@ var Axon = class _Axon {
|
|
|
2339
2851
|
context = [];
|
|
2340
2852
|
}
|
|
2341
2853
|
}
|
|
2342
|
-
let rawOutput;
|
|
2854
|
+
let rawOutput;
|
|
2855
|
+
try {
|
|
2856
|
+
const effectiveInput = this.beforeTaskHooks.length ? await this.applyBeforeTask(input) : input;
|
|
2857
|
+
const helpers = this.engramBindings.size ? this.buildHelpers(traceId, parentId) : void 0;
|
|
2858
|
+
rawOutput = await this.fn(effectiveInput, context, helpers);
|
|
2859
|
+
if (this.outputParser) rawOutput = this.outputParser(rawOutput);
|
|
2860
|
+
rawOutput = await this.applyRecognisers(rawOutput);
|
|
2861
|
+
} catch (err) {
|
|
2862
|
+
return errorSignal({
|
|
2863
|
+
traceId,
|
|
2864
|
+
parentId,
|
|
2865
|
+
directed: { id: this.neuronId },
|
|
2866
|
+
code: "NEURON_EXCEPTION",
|
|
2867
|
+
message: err instanceof Error ? err.message : String(err),
|
|
2868
|
+
recoverable: false
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
if (isErrorOutput(rawOutput)) {
|
|
2872
|
+
return errorSignal({
|
|
2873
|
+
traceId,
|
|
2874
|
+
parentId,
|
|
2875
|
+
directed: { id: this.neuronId },
|
|
2876
|
+
code: rawOutput.code ?? "NEURON_ERROR",
|
|
2877
|
+
message: rawOutput.message ?? "",
|
|
2878
|
+
recoverable: Boolean(rawOutput.recoverable)
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
if (isClarification(rawOutput)) {
|
|
2882
|
+
return clarificationSignal({
|
|
2883
|
+
traceId,
|
|
2884
|
+
parentId,
|
|
2885
|
+
directed: { id: this.neuronId },
|
|
2886
|
+
question: rawOutput.question,
|
|
2887
|
+
...rawOutput.context !== void 0 ? { context: rawOutput.context } : {}
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
if (isPermissionRequest(rawOutput)) {
|
|
2891
|
+
return permissionSignal({
|
|
2892
|
+
traceId,
|
|
2893
|
+
parentId,
|
|
2894
|
+
directed: { id: this.neuronId },
|
|
2895
|
+
action: rawOutput.action,
|
|
2896
|
+
...rawOutput.scope !== void 0 ? { scope: rawOutput.scope } : {},
|
|
2897
|
+
...rawOutput.reason !== void 0 ? { reason: rawOutput.reason } : {},
|
|
2898
|
+
...rawOutput.context !== void 0 ? { context: rawOutput.context } : {}
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
const output = typeof rawOutput === "object" && rawOutput !== null ? rawOutput : { value: rawOutput };
|
|
2902
|
+
return agentOutputSignal({ traceId, parentId, directed: { id: this.neuronId }, output });
|
|
2903
|
+
}
|
|
2904
|
+
};
|
|
2905
|
+
var COSMO_INTENT_SYSTEM_PROMPT = 'You can control the surrounding agent protocol by replying with a single JSON object carrying a "cosmo" key (either as your whole reply or inside a ```json fenced block):\n{"cosmo": "clarification", "question": "<what you need to know>"} - ask the orchestrator a question when the task is ambiguous.\n{"cosmo": "permission", "action": "<action>", "scope": {...}, "reason": "<why>"} - request approval before a sensitive action.\n{"cosmo": "error", "code": "<CODE>", "message": "<details>"} - report a structured failure.\n{"cosmo": "output", "output": {...}} - return a structured result.\nFor a normal answer, just reply with plain text - do not wrap ordinary answers in a cosmo object.';
|
|
2906
|
+
var SYSTEM_CAPABLE_SOURCES = /* @__PURE__ */ new Set([
|
|
2907
|
+
"ollama",
|
|
2908
|
+
"openai",
|
|
2909
|
+
"anthropic",
|
|
2910
|
+
"groq",
|
|
2911
|
+
"openrouter",
|
|
2912
|
+
"together",
|
|
2913
|
+
"mistral"
|
|
2914
|
+
]);
|
|
2915
|
+
var INTENT_KEY = "cosmo";
|
|
2916
|
+
var FENCED_JSON = /```(?:json)?\s*(\{[\s\S]*?\})\s*```/g;
|
|
2917
|
+
function extractCosmoIntent(text) {
|
|
2918
|
+
if (!text) return null;
|
|
2919
|
+
const candidates = [text.trim()];
|
|
2920
|
+
FENCED_JSON.lastIndex = 0;
|
|
2921
|
+
let m;
|
|
2922
|
+
while ((m = FENCED_JSON.exec(text)) !== null) candidates.push(m[1]);
|
|
2923
|
+
for (const cand of candidates) {
|
|
2924
|
+
let obj;
|
|
2925
|
+
try {
|
|
2926
|
+
obj = JSON.parse(cand);
|
|
2927
|
+
} catch {
|
|
2928
|
+
continue;
|
|
2929
|
+
}
|
|
2930
|
+
if (obj !== null && typeof obj === "object" && typeof obj[INTENT_KEY] === "string") {
|
|
2931
|
+
return obj;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
return null;
|
|
2935
|
+
}
|
|
2936
|
+
function intentToMarker(intent) {
|
|
2937
|
+
const kind = intent[INTENT_KEY];
|
|
2938
|
+
if (kind === "clarification") {
|
|
2939
|
+
return {
|
|
2940
|
+
__clarification__: true,
|
|
2941
|
+
question: intent["question"] ?? "",
|
|
2942
|
+
...intent["context"] !== void 0 ? { context: intent["context"] } : {}
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
if (kind === "permission") {
|
|
2946
|
+
return {
|
|
2947
|
+
__permission__: true,
|
|
2948
|
+
action: intent["action"] ?? "",
|
|
2949
|
+
...intent["scope"] !== void 0 ? { scope: intent["scope"] } : {},
|
|
2950
|
+
...intent["reason"] !== void 0 ? { reason: intent["reason"] } : {},
|
|
2951
|
+
...intent["context"] !== void 0 ? { context: intent["context"] } : {}
|
|
2952
|
+
};
|
|
2953
|
+
}
|
|
2954
|
+
if (kind === "error") {
|
|
2955
|
+
return {
|
|
2956
|
+
__error__: true,
|
|
2957
|
+
code: intent["code"] ?? "NEURON_ERROR",
|
|
2958
|
+
message: intent["message"] ?? "",
|
|
2959
|
+
recoverable: Boolean(intent["recoverable"])
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2962
|
+
if (kind === "output") {
|
|
2963
|
+
const out = intent["output"];
|
|
2964
|
+
return out !== null && typeof out === "object" ? out : { value: out };
|
|
2965
|
+
}
|
|
2966
|
+
return null;
|
|
2967
|
+
}
|
|
2968
|
+
function parseLlmIntents(raw) {
|
|
2969
|
+
if (raw === null || typeof raw !== "object") return { value: raw };
|
|
2970
|
+
const text = raw["response"];
|
|
2971
|
+
if (typeof text === "string") {
|
|
2972
|
+
const intent = extractCosmoIntent(text);
|
|
2973
|
+
if (intent) {
|
|
2974
|
+
const marker = intentToMarker(intent);
|
|
2975
|
+
if (marker) return marker;
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
return raw;
|
|
2979
|
+
}
|
|
2980
|
+
function parseMcpIntents(raw) {
|
|
2981
|
+
if (raw === null || typeof raw !== "object") return { value: raw };
|
|
2982
|
+
const r = raw;
|
|
2983
|
+
if (r["is_error"]) {
|
|
2984
|
+
const msg = r["response"] ?? r["content"] ?? "MCP tool returned is_error";
|
|
2985
|
+
return { __error__: true, code: "MCP_TOOL_ERROR", message: String(msg) };
|
|
2986
|
+
}
|
|
2987
|
+
const text = r["response"];
|
|
2988
|
+
if (typeof text === "string") {
|
|
2989
|
+
const intent = extractCosmoIntent(text);
|
|
2990
|
+
if (intent) {
|
|
2991
|
+
const marker = intentToMarker(intent);
|
|
2992
|
+
if (marker) return marker;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
return raw;
|
|
2996
|
+
}
|
|
2997
|
+
|
|
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
|
+
// src/engram-client.ts
|
|
3194
|
+
function deferred() {
|
|
3195
|
+
let resolve;
|
|
3196
|
+
let reject;
|
|
3197
|
+
const promise = new Promise((res, rej) => {
|
|
3198
|
+
resolve = res;
|
|
3199
|
+
reject = rej;
|
|
3200
|
+
});
|
|
3201
|
+
return { promise, resolve, reject };
|
|
3202
|
+
}
|
|
3203
|
+
var EngramClient = class {
|
|
3204
|
+
constructor(publisher) {
|
|
3205
|
+
this.publisher = publisher;
|
|
3206
|
+
}
|
|
3207
|
+
publisher;
|
|
3208
|
+
pendingRecalls = /* @__PURE__ */ new Map();
|
|
3209
|
+
pendingImprints = /* @__PURE__ */ new Map();
|
|
3210
|
+
byTrace = /* @__PURE__ */ new Map();
|
|
3211
|
+
async recall(args) {
|
|
3212
|
+
let engramId = args.engramId;
|
|
3213
|
+
let engramKind = args.engramKind;
|
|
3214
|
+
let deadlineMs = args.deadlineMs;
|
|
3215
|
+
let recallMode = args.recallMode;
|
|
3216
|
+
if (args.binding) {
|
|
3217
|
+
engramId = engramId ?? args.binding.directedId ?? void 0;
|
|
3218
|
+
engramKind = engramKind ?? args.binding.directedType ?? void 0;
|
|
3219
|
+
if (deadlineMs === void 0) deadlineMs = args.binding.defaultDeadlineMs ?? void 0;
|
|
3220
|
+
if (recallMode === void 0) recallMode = args.binding.defaultRecallMode;
|
|
3221
|
+
}
|
|
3222
|
+
const mode = recallMode ?? "first";
|
|
3223
|
+
const sig = recallSignal({
|
|
3224
|
+
traceId: args.traceId,
|
|
3225
|
+
parentId: args.parentId,
|
|
3226
|
+
directed: directedTo(engramId ?? null, { type: engramKind ?? null }),
|
|
3227
|
+
query: args.query,
|
|
3228
|
+
...args.filters !== void 0 ? { filters: args.filters } : {},
|
|
3229
|
+
...args.contextRef !== void 0 ? { contextRef: args.contextRef } : {},
|
|
3230
|
+
...deadlineMs !== void 0 ? { deadlineMs } : {},
|
|
3231
|
+
...args.minConfidence !== void 0 ? { minConfidence: args.minConfidence } : {},
|
|
3232
|
+
recallMode: mode,
|
|
3233
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
3234
|
+
});
|
|
3235
|
+
const d = deferred();
|
|
3236
|
+
const pending = { deferred: d, mode, timer: null, done: false, hitsSoFar: [], engrams: [] };
|
|
3237
|
+
this.pendingRecalls.set(sig.id, pending);
|
|
3238
|
+
this.track(args.traceId, sig.id);
|
|
3239
|
+
if (deadlineMs !== void 0 && deadlineMs > 0) {
|
|
3240
|
+
pending.timer = setTimeout(() => this.onRecallDeadline(sig.id), deadlineMs);
|
|
3241
|
+
}
|
|
2343
3242
|
try {
|
|
2344
|
-
|
|
2345
|
-
if (this.outputParser) rawOutput = this.outputParser(rawOutput);
|
|
2346
|
-
rawOutput = await this.applyRecognisers(rawOutput);
|
|
3243
|
+
await this.publisher.publish(sig);
|
|
2347
3244
|
} catch (err) {
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
parentId,
|
|
2351
|
-
directed: { id: this.neuronId },
|
|
2352
|
-
code: "NEURON_EXCEPTION",
|
|
2353
|
-
message: err instanceof Error ? err.message : String(err),
|
|
2354
|
-
recoverable: false
|
|
2355
|
-
});
|
|
3245
|
+
this.cleanupRecall(args.traceId, sig.id);
|
|
3246
|
+
throw err;
|
|
2356
3247
|
}
|
|
2357
|
-
|
|
2358
|
-
return
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
directed: { id: this.neuronId },
|
|
2362
|
-
code: rawOutput.code ?? "NEURON_ERROR",
|
|
2363
|
-
message: rawOutput.message ?? "",
|
|
2364
|
-
recoverable: Boolean(rawOutput.recoverable)
|
|
2365
|
-
});
|
|
3248
|
+
try {
|
|
3249
|
+
return await d.promise;
|
|
3250
|
+
} finally {
|
|
3251
|
+
this.cleanupRecall(args.traceId, sig.id);
|
|
2366
3252
|
}
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
});
|
|
3253
|
+
}
|
|
3254
|
+
async imprint(args) {
|
|
3255
|
+
let engramId = args.engramId;
|
|
3256
|
+
let engramKind = args.engramKind;
|
|
3257
|
+
if (args.binding) {
|
|
3258
|
+
engramId = engramId ?? args.binding.directedId ?? void 0;
|
|
3259
|
+
engramKind = engramKind ?? args.binding.directedType ?? void 0;
|
|
2375
3260
|
}
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
3261
|
+
const sig = imprintSignal({
|
|
3262
|
+
traceId: args.traceId,
|
|
3263
|
+
parentId: args.parentId,
|
|
3264
|
+
directed: directedTo(engramId ?? null, { type: engramKind ?? null }),
|
|
3265
|
+
op: args.op,
|
|
3266
|
+
entry: args.entry,
|
|
3267
|
+
...args.mergeKey !== void 0 ? { mergeKey: args.mergeKey } : {},
|
|
3268
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
3269
|
+
});
|
|
3270
|
+
if (!args.awaitAck) {
|
|
3271
|
+
await this.publisher.publish(sig);
|
|
3272
|
+
return null;
|
|
3273
|
+
}
|
|
3274
|
+
const d = deferred();
|
|
3275
|
+
const pending = { deferred: d, timer: null, done: false };
|
|
3276
|
+
this.pendingImprints.set(sig.id, pending);
|
|
3277
|
+
this.track(args.traceId, sig.id);
|
|
3278
|
+
if (args.deadlineMs !== void 0 && args.deadlineMs > 0) {
|
|
3279
|
+
pending.timer = setTimeout(() => this.onImprintDeadline(sig.id), args.deadlineMs);
|
|
2386
3280
|
}
|
|
2387
|
-
const output = typeof rawOutput === "object" && rawOutput !== null ? rawOutput : { value: rawOutput };
|
|
2388
|
-
return agentOutputSignal({ traceId, parentId, directed: { id: this.neuronId }, output });
|
|
2389
|
-
}
|
|
2390
|
-
};
|
|
2391
|
-
var INTENT_KEY = "cosmo";
|
|
2392
|
-
var FENCED_JSON = /```(?:json)?\s*(\{[\s\S]*?\})\s*```/g;
|
|
2393
|
-
function extractCosmoIntent(text) {
|
|
2394
|
-
if (!text) return null;
|
|
2395
|
-
const candidates = [text.trim()];
|
|
2396
|
-
FENCED_JSON.lastIndex = 0;
|
|
2397
|
-
let m;
|
|
2398
|
-
while ((m = FENCED_JSON.exec(text)) !== null) candidates.push(m[1]);
|
|
2399
|
-
for (const cand of candidates) {
|
|
2400
|
-
let obj;
|
|
2401
3281
|
try {
|
|
2402
|
-
|
|
2403
|
-
} catch {
|
|
2404
|
-
|
|
3282
|
+
await this.publisher.publish(sig);
|
|
3283
|
+
} catch (err) {
|
|
3284
|
+
this.cleanupImprint(args.traceId, sig.id);
|
|
3285
|
+
throw err;
|
|
2405
3286
|
}
|
|
2406
|
-
|
|
2407
|
-
return
|
|
3287
|
+
try {
|
|
3288
|
+
return await d.promise;
|
|
3289
|
+
} finally {
|
|
3290
|
+
this.cleanupImprint(args.traceId, sig.id);
|
|
2408
3291
|
}
|
|
2409
3292
|
}
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
3293
|
+
/** Match RECALLED / IMPRINTED by parent_id and resolve pendings. */
|
|
3294
|
+
deliver(sig) {
|
|
3295
|
+
const pid = sig.parent_id;
|
|
3296
|
+
if (pid === null) return;
|
|
3297
|
+
if (sig.type === SignalType.RECALLED) {
|
|
3298
|
+
const pending = this.pendingRecalls.get(pid);
|
|
3299
|
+
if (pending === void 0) return;
|
|
3300
|
+
const hits = hitsFromPayload(sig.payload["hits"]);
|
|
3301
|
+
const engramId = typeof sig.payload["engram_id"] === "string" ? sig.payload["engram_id"] : "";
|
|
3302
|
+
const tookMs = typeof sig.payload["took_ms"] === "number" ? sig.payload["took_ms"] : null;
|
|
3303
|
+
const truncated = sig.payload["truncated"] === true;
|
|
3304
|
+
if (pending.mode === "first") {
|
|
3305
|
+
if (!pending.done) {
|
|
3306
|
+
pending.done = true;
|
|
3307
|
+
pending.deferred.resolve({
|
|
3308
|
+
hits,
|
|
3309
|
+
engramIds: engramId ? [engramId] : [],
|
|
3310
|
+
truncated,
|
|
3311
|
+
tookMs
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
} else {
|
|
3315
|
+
pending.hitsSoFar.push(...hits);
|
|
3316
|
+
if (engramId) pending.engrams.push(engramId);
|
|
3317
|
+
}
|
|
3318
|
+
} else if (sig.type === SignalType.IMPRINTED) {
|
|
3319
|
+
const pending = this.pendingImprints.get(pid);
|
|
3320
|
+
if (pending === void 0 || pending.done) return;
|
|
3321
|
+
pending.done = true;
|
|
3322
|
+
pending.deferred.resolve({
|
|
3323
|
+
engramId: typeof sig.payload["engram_id"] === "string" ? sig.payload["engram_id"] : "",
|
|
3324
|
+
op: typeof sig.payload["op"] === "string" ? sig.payload["op"] : "",
|
|
3325
|
+
id: typeof sig.payload["id"] === "string" ? sig.payload["id"] : null,
|
|
3326
|
+
version: typeof sig.payload["version"] === "number" ? sig.payload["version"] : null,
|
|
3327
|
+
tookMs: typeof sig.payload["took_ms"] === "number" ? sig.payload["took_ms"] : null,
|
|
3328
|
+
error: typeof sig.payload["error"] === "string" ? sig.payload["error"] : null,
|
|
3329
|
+
ok: !(typeof sig.payload["error"] === "string")
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
2429
3332
|
}
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
3333
|
+
/** Cancel every in-flight recall/imprint on a trace (FINAL/ERROR or shutdown). */
|
|
3334
|
+
cancelTrace(traceId) {
|
|
3335
|
+
const ids = this.byTrace.get(traceId);
|
|
3336
|
+
this.byTrace.delete(traceId);
|
|
3337
|
+
if (ids === void 0) return;
|
|
3338
|
+
for (const id of ids) {
|
|
3339
|
+
const pr = this.pendingRecalls.get(id);
|
|
3340
|
+
if (pr !== void 0 && !pr.done) {
|
|
3341
|
+
pr.done = true;
|
|
3342
|
+
if (pr.timer !== null) clearTimeout(pr.timer);
|
|
3343
|
+
pr.deferred.reject(new EngramCancelled(`trace ${traceId} terminated while recall ${id} in flight`));
|
|
3344
|
+
this.pendingRecalls.delete(id);
|
|
3345
|
+
}
|
|
3346
|
+
const pi = this.pendingImprints.get(id);
|
|
3347
|
+
if (pi !== void 0 && !pi.done) {
|
|
3348
|
+
pi.done = true;
|
|
3349
|
+
if (pi.timer !== null) clearTimeout(pi.timer);
|
|
3350
|
+
pi.deferred.reject(new EngramCancelled(`trace ${traceId} terminated while imprint ${id} in flight`));
|
|
3351
|
+
this.pendingImprints.delete(id);
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
2437
3354
|
}
|
|
2438
|
-
|
|
2439
|
-
const
|
|
2440
|
-
return out !== null && typeof out === "object" ? out : { value: out };
|
|
3355
|
+
cancelAll() {
|
|
3356
|
+
for (const traceId of [...this.byTrace.keys()]) this.cancelTrace(traceId);
|
|
2441
3357
|
}
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
3358
|
+
onRecallDeadline(id) {
|
|
3359
|
+
const pending = this.pendingRecalls.get(id);
|
|
3360
|
+
if (pending === void 0 || pending.done) return;
|
|
3361
|
+
pending.done = true;
|
|
3362
|
+
if (pending.mode === "first") {
|
|
3363
|
+
pending.deferred.reject(new EngramTimeout(`RECALL ${id} elapsed deadline without any responder`));
|
|
3364
|
+
} else {
|
|
3365
|
+
pending.deferred.resolve({
|
|
3366
|
+
hits: [...pending.hitsSoFar].sort((a, b) => b.score - a.score),
|
|
3367
|
+
engramIds: [...pending.engrams],
|
|
3368
|
+
truncated: false,
|
|
3369
|
+
tookMs: null
|
|
3370
|
+
});
|
|
2452
3371
|
}
|
|
2453
3372
|
}
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
if (r["is_error"]) {
|
|
2460
|
-
const msg = r["response"] ?? r["content"] ?? "MCP tool returned is_error";
|
|
2461
|
-
return { __error__: true, code: "MCP_TOOL_ERROR", message: String(msg) };
|
|
3373
|
+
onImprintDeadline(id) {
|
|
3374
|
+
const pending = this.pendingImprints.get(id);
|
|
3375
|
+
if (pending === void 0 || pending.done) return;
|
|
3376
|
+
pending.done = true;
|
|
3377
|
+
pending.deferred.reject(new EngramTimeout(`IMPRINT ${id} elapsed deadline without IMPRINTED`));
|
|
2462
3378
|
}
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
3379
|
+
track(traceId, id) {
|
|
3380
|
+
const bucket = this.byTrace.get(traceId);
|
|
3381
|
+
if (bucket) bucket.add(id);
|
|
3382
|
+
else this.byTrace.set(traceId, /* @__PURE__ */ new Set([id]));
|
|
3383
|
+
}
|
|
3384
|
+
cleanupRecall(traceId, id) {
|
|
3385
|
+
const p = this.pendingRecalls.get(id);
|
|
3386
|
+
if (p?.timer != null) clearTimeout(p.timer);
|
|
3387
|
+
this.pendingRecalls.delete(id);
|
|
3388
|
+
this.discardTrace(traceId, id);
|
|
3389
|
+
}
|
|
3390
|
+
cleanupImprint(traceId, id) {
|
|
3391
|
+
const p = this.pendingImprints.get(id);
|
|
3392
|
+
if (p?.timer != null) clearTimeout(p.timer);
|
|
3393
|
+
this.pendingImprints.delete(id);
|
|
3394
|
+
this.discardTrace(traceId, id);
|
|
3395
|
+
}
|
|
3396
|
+
discardTrace(traceId, id) {
|
|
3397
|
+
const bucket = this.byTrace.get(traceId);
|
|
3398
|
+
if (bucket === void 0) return;
|
|
3399
|
+
bucket.delete(id);
|
|
3400
|
+
if (bucket.size === 0) this.byTrace.delete(traceId);
|
|
2470
3401
|
}
|
|
2471
|
-
|
|
3402
|
+
};
|
|
3403
|
+
function hitsFromPayload(raw) {
|
|
3404
|
+
if (!Array.isArray(raw)) return [];
|
|
3405
|
+
const out = [];
|
|
3406
|
+
for (const h of raw) {
|
|
3407
|
+
if (h === null || typeof h !== "object") continue;
|
|
3408
|
+
const obj = h;
|
|
3409
|
+
const entryVal = obj["entry"];
|
|
3410
|
+
out.push({
|
|
3411
|
+
id: typeof obj["id"] === "string" ? obj["id"] : "",
|
|
3412
|
+
entry: entryVal !== null && typeof entryVal === "object" && !Array.isArray(entryVal) ? entryVal : { value: entryVal },
|
|
3413
|
+
score: typeof obj["score"] === "number" ? obj["score"] : 1
|
|
3414
|
+
});
|
|
3415
|
+
}
|
|
3416
|
+
return out;
|
|
2472
3417
|
}
|
|
2473
3418
|
|
|
2474
3419
|
// src/dendrite.ts
|
|
@@ -2479,33 +3424,67 @@ var DendriteProtocolError = class extends Error {
|
|
|
2479
3424
|
this.name = "DendriteProtocolError";
|
|
2480
3425
|
}
|
|
2481
3426
|
};
|
|
2482
|
-
var Dendrite = class {
|
|
3427
|
+
var Dendrite = class _Dendrite {
|
|
2483
3428
|
synapse;
|
|
2484
3429
|
registryStore;
|
|
2485
3430
|
namespace;
|
|
2486
3431
|
dendriteId;
|
|
3432
|
+
role;
|
|
2487
3433
|
heartbeatMs;
|
|
2488
3434
|
reregisterOnHeartbeat;
|
|
3435
|
+
autoBid;
|
|
3436
|
+
staleAfterMs;
|
|
2489
3437
|
_axons = /* @__PURE__ */ new Map();
|
|
2490
3438
|
handlers = /* @__PURE__ */ new Map();
|
|
2491
3439
|
taskSub = null;
|
|
3440
|
+
routedTaskSub = null;
|
|
2492
3441
|
inboundSubs = /* @__PURE__ */ new Map();
|
|
2493
|
-
|
|
3442
|
+
inflightSubs = /* @__PURE__ */ new Map();
|
|
3443
|
+
pendingSubs = /* @__PURE__ */ new Set();
|
|
3444
|
+
/** Recently seen CLARIFICATION_ANSWER / PERMISSION_DECISION signals keyed
|
|
3445
|
+
* by parent_id, so {@link awaitDecision} can serve an answer that arrived
|
|
3446
|
+
* before it was called (an in-process synapse can deliver the whole
|
|
3447
|
+
* request->answer chain within the original publish). Bounded FIFO. */
|
|
3448
|
+
recentDecisions = /* @__PURE__ */ new Map();
|
|
3449
|
+
/** Hosted Engrams keyed by engramId, plus a kind index so RECALL/IMPRINT
|
|
3450
|
+
* addressed by engramKind reach every matching host. */
|
|
3451
|
+
_engrams = /* @__PURE__ */ new Map();
|
|
3452
|
+
engramKindIndex = /* @__PURE__ */ new Map();
|
|
3453
|
+
/** Engrams learned from peer REGISTER signals (possibly out-of-process). */
|
|
3454
|
+
_engramRegistrations = /* @__PURE__ */ new Map();
|
|
3455
|
+
engramRegKindIndex = /* @__PURE__ */ new Map();
|
|
3456
|
+
/** Caller-side correlation table for RECALL/IMPRINT awaiting
|
|
3457
|
+
* RECALLED/IMPRINTED. The Dendrite owns the subscriptions and feeds it. */
|
|
3458
|
+
engramClient = new EngramClient(this);
|
|
2494
3459
|
heartbeatTimer = null;
|
|
2495
|
-
// Set true by stop() so an in-flight tick won't re-arm the loop.
|
|
2496
3460
|
heartbeatStopped = true;
|
|
2497
3461
|
running = false;
|
|
3462
|
+
/** Open Pathways keyed by trace_id (dispatch / observePathway). */
|
|
3463
|
+
pathways = /* @__PURE__ */ new Map();
|
|
3464
|
+
/** Per-operation Pathways keyed by the issuing request's id (matched
|
|
3465
|
+
* against inbound parent_id) - the generic request/reply primitive
|
|
3466
|
+
* behind awaitDecision (and a future EngramClient wiring). */
|
|
3467
|
+
opPathways = /* @__PURE__ */ new Map();
|
|
2498
3468
|
/** @internal - lifecycle hooks for this Dendrite. */
|
|
2499
3469
|
hooks = new LifecycleHooks(this);
|
|
2500
3470
|
constructor(opts) {
|
|
2501
3471
|
if (!opts.synapse) throw new TypeError("Dendrite requires a synapse");
|
|
3472
|
+
const role = opts.role ?? "orchestrator";
|
|
3473
|
+
if (role !== "orchestrator" && role !== "worker") {
|
|
3474
|
+
throw new Error(`role must be 'orchestrator' or 'worker', got '${role}'`);
|
|
3475
|
+
}
|
|
2502
3476
|
this.synapse = opts.synapse;
|
|
2503
3477
|
this.registryStore = opts.registryStore ?? null;
|
|
2504
3478
|
this.namespace = opts.namespace ?? "default";
|
|
2505
3479
|
this.dendriteId = opts.dendriteId ?? "dendrite";
|
|
2506
3480
|
this.heartbeatMs = opts.heartbeatMs ?? 3e4;
|
|
2507
3481
|
this.reregisterOnHeartbeat = opts.reregisterOnHeartbeat ?? true;
|
|
2508
|
-
|
|
3482
|
+
this.role = role;
|
|
3483
|
+
this.autoBid = opts.autoBid ?? true;
|
|
3484
|
+
this.staleAfterMs = opts.staleAfterMs ?? (this.heartbeatMs > 0 ? this.heartbeatMs * 3 : 0);
|
|
3485
|
+
for (const t of Object.values(SignalType)) {
|
|
3486
|
+
this.handlers.set(t, []);
|
|
3487
|
+
}
|
|
2509
3488
|
}
|
|
2510
3489
|
// -- properties ----------------------------------------------------
|
|
2511
3490
|
get axons() {
|
|
@@ -2514,70 +3493,393 @@ var Dendrite = class {
|
|
|
2514
3493
|
axon(neuronId) {
|
|
2515
3494
|
return this._axons.get(neuronId);
|
|
2516
3495
|
}
|
|
3496
|
+
/** Aggregate of every attached Axon's capabilities, deduplicated + sorted. */
|
|
3497
|
+
get capabilities() {
|
|
3498
|
+
const caps = /* @__PURE__ */ new Set();
|
|
3499
|
+
for (const ax of this._axons.values()) for (const c of ax.capabilities) caps.add(c);
|
|
3500
|
+
return [...caps].sort();
|
|
3501
|
+
}
|
|
3502
|
+
/** Canonical queue-group name for this Dendrite's aggregate caps, or null
|
|
3503
|
+
* when no Axons are attached. Identical Dendrites share a group. */
|
|
3504
|
+
capQueueGroup() {
|
|
3505
|
+
const caps = this.capabilities;
|
|
3506
|
+
return caps.length ? `caps:${caps.join(",")}` : null;
|
|
3507
|
+
}
|
|
3508
|
+
requireOrchestrator(op) {
|
|
3509
|
+
if (this.role !== "orchestrator") {
|
|
3510
|
+
throw new DendriteProtocolError(
|
|
3511
|
+
`Dendrite role='${this.role}' cannot perform '${op}': only role='orchestrator' Dendrites may dispatch TASK signals. Workers host Axons and emit replies / cognition signals freely.`
|
|
3512
|
+
);
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
2517
3515
|
// -- attachment ----------------------------------------------------
|
|
3516
|
+
/**
|
|
3517
|
+
* Attach an Axon to a *stopped* Dendrite. Throws if the Dendrite is
|
|
3518
|
+
* running - a running Dendrite needs the async activation path
|
|
3519
|
+
* (subscriptions, queue-group refresh, REGISTER): use
|
|
3520
|
+
* `await dendrite.addAxon(axon)` instead, which works in both states.
|
|
3521
|
+
*/
|
|
2518
3522
|
attachAxon(axon) {
|
|
3523
|
+
if (this.running) {
|
|
3524
|
+
throw new Error(
|
|
3525
|
+
"attachAxon on a running Dendrite would never receive TASKs (no subscription / REGISTER is set up after start). Use `await dendrite.addAxon(axon)` instead."
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
this.attachAxonRecord(axon);
|
|
3529
|
+
}
|
|
3530
|
+
attachAxonRecord(axon) {
|
|
2519
3531
|
if (this._axons.has(axon.neuronId)) {
|
|
2520
3532
|
throw new Error(`Dendrite already has an Axon for neuronId='${axon.neuronId}'`);
|
|
2521
3533
|
}
|
|
2522
3534
|
this._axons.set(axon.neuronId, axon);
|
|
2523
3535
|
axon[ATTACH](this);
|
|
2524
3536
|
}
|
|
3537
|
+
/**
|
|
3538
|
+
* Attach an Axon; if the Dendrite is running, activate it live: ensure the
|
|
3539
|
+
* addressed + routed TASK subscriptions exist (re-keying the routed queue
|
|
3540
|
+
* group for the new aggregate cap profile), subscribe TASK_AWARDED /
|
|
3541
|
+
* DISCOVER (and TASK_OFFER when autoBid), mirror to the registry store,
|
|
3542
|
+
* emit REGISTER, and fire the Axon's onConnect hooks.
|
|
3543
|
+
*/
|
|
3544
|
+
async addAxon(axon) {
|
|
3545
|
+
this.attachAxonRecord(axon);
|
|
3546
|
+
if (!this.running) return;
|
|
3547
|
+
if (this.taskSub === null) {
|
|
3548
|
+
this.taskSub = await this.synapse.subscribe(
|
|
3549
|
+
this.subject(SignalType.TASK),
|
|
3550
|
+
(s) => this.onTask(s)
|
|
3551
|
+
);
|
|
3552
|
+
}
|
|
3553
|
+
await this.refreshRoutedSub();
|
|
3554
|
+
await this.ensureInboundSub(SignalType.TASK_AWARDED);
|
|
3555
|
+
await this.ensureInboundSub(SignalType.DISCOVER);
|
|
3556
|
+
if (this.autoBid) await this.ensureInboundSub(SignalType.TASK_OFFER);
|
|
3557
|
+
await this.mirrorToStore(axon, "registered");
|
|
3558
|
+
await this.emitRegister(axon);
|
|
3559
|
+
await axon.hooks._fireConnect();
|
|
3560
|
+
axon.hooks._launchSchedule();
|
|
3561
|
+
}
|
|
3562
|
+
/** Detach an Axon. If running: deregister, tear down its hooks, and re-key
|
|
3563
|
+
* (or drop) the TASK subscriptions for the changed cap profile. */
|
|
3564
|
+
async detachAxon(neuronId, opts = {}) {
|
|
3565
|
+
const axon = this._axons.get(neuronId);
|
|
3566
|
+
if (!axon) {
|
|
3567
|
+
throw new Error(`Dendrite has no Axon for neuronId='${neuronId}'`);
|
|
3568
|
+
}
|
|
3569
|
+
if (this.running) {
|
|
3570
|
+
axon.hooks._stopHooks();
|
|
3571
|
+
if (this.registryStore !== null) {
|
|
3572
|
+
try {
|
|
3573
|
+
await this.registryStore.markDeregistered(neuronId);
|
|
3574
|
+
} catch {
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
await this.emitDeregister(axon, opts.reason);
|
|
3578
|
+
}
|
|
3579
|
+
this._axons.delete(neuronId);
|
|
3580
|
+
axon[DETACH]();
|
|
3581
|
+
if (this.running && this._axons.size === 0) {
|
|
3582
|
+
if (this.taskSub !== null) {
|
|
3583
|
+
try {
|
|
3584
|
+
await this.taskSub.unsubscribe();
|
|
3585
|
+
} catch {
|
|
3586
|
+
}
|
|
3587
|
+
this.taskSub = null;
|
|
3588
|
+
}
|
|
3589
|
+
if (this.routedTaskSub !== null) {
|
|
3590
|
+
try {
|
|
3591
|
+
await this.routedTaskSub.unsubscribe();
|
|
3592
|
+
} catch {
|
|
3593
|
+
}
|
|
3594
|
+
this.routedTaskSub = null;
|
|
3595
|
+
}
|
|
3596
|
+
} else if (this.running) {
|
|
3597
|
+
await this.refreshRoutedSub();
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
/**
|
|
3601
|
+
* Mount an Engram on this Dendrite. After attachment (and start), the
|
|
3602
|
+
* Dendrite subscribes to RECALL/IMPRINT, routes Signals addressed to
|
|
3603
|
+
* `engram.engramId` or matching `engram.engramKind` to the instance, and
|
|
3604
|
+
* announces it on the Synapse with an engram REGISTER. The Engram still
|
|
3605
|
+
* owns its backend lifecycle: `connect()` on start(), `close()` on stop().
|
|
3606
|
+
* When the Dendrite is already running, the backend is connected and the
|
|
3607
|
+
* subscriptions/REGISTER are established immediately.
|
|
3608
|
+
*/
|
|
3609
|
+
async attachEngram(engram) {
|
|
3610
|
+
if (this._engrams.has(engram.engramId)) {
|
|
3611
|
+
throw new Error(`Dendrite already hosts an Engram with engramId='${engram.engramId}'`);
|
|
3612
|
+
}
|
|
3613
|
+
this._engrams.set(engram.engramId, engram);
|
|
3614
|
+
const bucket = this.engramKindIndex.get(engram.engramKind) ?? [];
|
|
3615
|
+
bucket.push(engram.engramId);
|
|
3616
|
+
this.engramKindIndex.set(engram.engramKind, bucket);
|
|
3617
|
+
if (this.running) {
|
|
3618
|
+
await engram.connect();
|
|
3619
|
+
await this.ensureInboundSub(SignalType.RECALL);
|
|
3620
|
+
await this.ensureInboundSub(SignalType.IMPRINT);
|
|
3621
|
+
await this.ensureInboundSub(SignalType.REGISTER);
|
|
3622
|
+
await this.emitEngramRegister(engram);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
/** Remove a hosted Engram. Closes its backend if the Dendrite is running. */
|
|
3626
|
+
async detachEngram(engramId) {
|
|
3627
|
+
const engram = this._engrams.get(engramId);
|
|
3628
|
+
if (!engram) {
|
|
3629
|
+
throw new Error(`Dendrite has no Engram with engramId='${engramId}'`);
|
|
3630
|
+
}
|
|
3631
|
+
if (this.running) {
|
|
3632
|
+
try {
|
|
3633
|
+
await engram.close();
|
|
3634
|
+
} catch {
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
const bucket = this.engramKindIndex.get(engram.engramKind) ?? [];
|
|
3638
|
+
const kept = bucket.filter((id) => id !== engramId);
|
|
3639
|
+
if (kept.length) this.engramKindIndex.set(engram.engramKind, kept);
|
|
3640
|
+
else this.engramKindIndex.delete(engram.engramKind);
|
|
3641
|
+
this._engrams.delete(engramId);
|
|
3642
|
+
}
|
|
3643
|
+
get engrams() {
|
|
3644
|
+
return new Map(this._engrams);
|
|
3645
|
+
}
|
|
3646
|
+
/** Engrams learned via REGISTER, keyed by directed.id (or directed.type
|
|
3647
|
+
* when no id), including in-process ones. */
|
|
3648
|
+
get engramRegistrations() {
|
|
3649
|
+
return new Map(this._engramRegistrations);
|
|
3650
|
+
}
|
|
3651
|
+
/** True when an Engram with this id/kind is reachable - hosted
|
|
3652
|
+
* in-process or learned from a peer's REGISTER. */
|
|
3653
|
+
isEngramKnown(opts) {
|
|
3654
|
+
if (opts.engramId) {
|
|
3655
|
+
if (this._engrams.has(opts.engramId) || this._engramRegistrations.has(opts.engramId)) {
|
|
3656
|
+
return true;
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
if (opts.engramKind) {
|
|
3660
|
+
if (this.engramKindIndex.has(opts.engramKind) || this.engramRegKindIndex.has(opts.engramKind)) {
|
|
3661
|
+
return true;
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
return false;
|
|
3665
|
+
}
|
|
3666
|
+
/** (Re)subscribe the capability-routed TASK subscription so its queue
|
|
3667
|
+
* group matches the *current* aggregate cap profile. */
|
|
3668
|
+
async refreshRoutedSub() {
|
|
3669
|
+
const qgroup = this.capQueueGroup();
|
|
3670
|
+
if (this.routedTaskSub !== null) {
|
|
3671
|
+
try {
|
|
3672
|
+
await this.routedTaskSub.unsubscribe();
|
|
3673
|
+
} catch {
|
|
3674
|
+
}
|
|
3675
|
+
this.routedTaskSub = null;
|
|
3676
|
+
}
|
|
3677
|
+
if (qgroup !== null) {
|
|
3678
|
+
this.routedTaskSub = await this.synapse.subscribe(
|
|
3679
|
+
this.routedSubject(),
|
|
3680
|
+
(s) => this.onTask(s),
|
|
3681
|
+
{ queueGroup: qgroup }
|
|
3682
|
+
);
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
2525
3685
|
// -- inbound handler registration ----------------------------------
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
throw new DendriteProtocolError(`Cannot handle non-Axon type '${type}'`);
|
|
3686
|
+
wrapWithFilter(fn, filter) {
|
|
3687
|
+
if (!filter || filter.neuron === void 0 && filter.capability === void 0 && filter.traceId === void 0) {
|
|
3688
|
+
return fn;
|
|
2530
3689
|
}
|
|
2531
|
-
|
|
3690
|
+
return async (sig) => {
|
|
3691
|
+
const sigNeuron = sig.directed?.id ?? null;
|
|
3692
|
+
if (filter.neuron !== void 0 && sigNeuron !== filter.neuron) return;
|
|
3693
|
+
if (filter.traceId !== void 0 && sig.trace_id !== filter.traceId) return;
|
|
3694
|
+
if (filter.capability !== void 0) {
|
|
3695
|
+
if (!await this.neuronHasCapability(sigNeuron, filter.capability)) return;
|
|
3696
|
+
}
|
|
3697
|
+
await fn(sig);
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
async neuronHasCapability(neuronId, capability) {
|
|
3701
|
+
if (!neuronId) return false;
|
|
3702
|
+
const axon = this._axons.get(neuronId);
|
|
3703
|
+
if (axon) return axon.capabilities.includes(capability);
|
|
3704
|
+
if (this.registryStore !== null) {
|
|
3705
|
+
try {
|
|
3706
|
+
const recs = await this.registryStore.list({ includeDeregistered: true });
|
|
3707
|
+
const rec = recs.find((r) => r.neuron_id === neuronId);
|
|
3708
|
+
return rec ? rec.capabilities.includes(capability) : false;
|
|
3709
|
+
} catch {
|
|
3710
|
+
return false;
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
return false;
|
|
3714
|
+
}
|
|
3715
|
+
on(type, fn, filter) {
|
|
3716
|
+
const list = this.handlers.get(type);
|
|
3717
|
+
list.push(this.wrapWithFilter(fn, filter));
|
|
2532
3718
|
if (this.running && !this.inboundSubs.has(type)) {
|
|
2533
|
-
|
|
3719
|
+
const p = this.ensureInboundSub(type).finally(() => this.pendingSubs.delete(p));
|
|
3720
|
+
this.pendingSubs.add(p);
|
|
3721
|
+
p.catch(() => {
|
|
3722
|
+
});
|
|
2534
3723
|
}
|
|
2535
3724
|
return fn;
|
|
2536
3725
|
}
|
|
2537
|
-
|
|
2538
|
-
|
|
3726
|
+
/**
|
|
3727
|
+
* Generic handler registration for *any* SignalType - the escape hatch
|
|
3728
|
+
* behind every named `on*` helper. New protocol types are observable the
|
|
3729
|
+
* day they exist. Supports the same filters as the named helpers.
|
|
3730
|
+
*/
|
|
3731
|
+
onSignal(type, fn, filter) {
|
|
3732
|
+
return this.on(type, fn, filter);
|
|
3733
|
+
}
|
|
3734
|
+
/** Await until inbound subscriptions exist for `types` - removes the
|
|
3735
|
+
* late-registration race deterministically. Idempotent. */
|
|
3736
|
+
async ensureSubscribed(...types) {
|
|
3737
|
+
for (const t of types) await this.ensureInboundSub(t);
|
|
2539
3738
|
}
|
|
2540
|
-
|
|
2541
|
-
|
|
3739
|
+
// -- lifecycle / reply handlers --
|
|
3740
|
+
onAgentOutput(fn, filter) {
|
|
3741
|
+
return this.on(SignalType.AGENT_OUTPUT, fn, filter);
|
|
3742
|
+
}
|
|
3743
|
+
onClarification(fn, filter) {
|
|
3744
|
+
return this.on(SignalType.CLARIFICATION, fn, filter);
|
|
2542
3745
|
}
|
|
2543
3746
|
/**
|
|
2544
3747
|
* Register a handler fired on inbound PERMISSION requests - the *answering*
|
|
2545
|
-
* side.
|
|
2546
|
-
*
|
|
2547
|
-
*
|
|
2548
|
-
* {@link grantPermission} / {@link denyPermission} (emit a discrete
|
|
2549
|
-
* PERMISSION_DECISION). It may also imprint the decision into an Engram so
|
|
2550
|
-
* future recalls hit.
|
|
3748
|
+
* side. Reply via {@link respondToPermission} (re-dispatch a TASK with the
|
|
3749
|
+
* verdict) or {@link grantPermission} / {@link denyPermission} (emit a
|
|
3750
|
+
* discrete PERMISSION_DECISION).
|
|
2551
3751
|
*/
|
|
2552
|
-
onPermission(fn) {
|
|
2553
|
-
return this.on(SignalType.PERMISSION, fn);
|
|
2554
|
-
}
|
|
2555
|
-
onErrorSignal(fn) {
|
|
2556
|
-
return this.on(SignalType.ERROR, fn);
|
|
3752
|
+
onPermission(fn, filter) {
|
|
3753
|
+
return this.on(SignalType.PERMISSION, fn, filter);
|
|
2557
3754
|
}
|
|
2558
|
-
|
|
2559
|
-
return this.on(SignalType.
|
|
3755
|
+
onErrorSignal(fn, filter) {
|
|
3756
|
+
return this.on(SignalType.ERROR, fn, filter);
|
|
2560
3757
|
}
|
|
2561
|
-
|
|
2562
|
-
|
|
3758
|
+
/** Register a handler fired on FINAL - workflow conclusion. */
|
|
3759
|
+
onFinal(fn, filter) {
|
|
3760
|
+
return this.on(SignalType.FINAL, fn, filter);
|
|
2563
3761
|
}
|
|
2564
|
-
|
|
2565
|
-
|
|
3762
|
+
/** Observe inbound TASKs (audit/logging). Observation only - Axon routing
|
|
3763
|
+
* happens on its own subscription and is unaffected. */
|
|
3764
|
+
onTaskSignal(fn, filter) {
|
|
3765
|
+
return this.on(SignalType.TASK, fn, filter);
|
|
3766
|
+
}
|
|
3767
|
+
onRegister(fn, filter) {
|
|
3768
|
+
return this.on(SignalType.REGISTER, fn, filter);
|
|
3769
|
+
}
|
|
3770
|
+
onDeregister(fn, filter) {
|
|
3771
|
+
return this.on(SignalType.DEREGISTER, fn, filter);
|
|
3772
|
+
}
|
|
3773
|
+
onHeartbeat(fn, filter) {
|
|
3774
|
+
return this.on(SignalType.HEARTBEAT, fn, filter);
|
|
3775
|
+
}
|
|
3776
|
+
onDiscover(fn, filter) {
|
|
3777
|
+
return this.on(SignalType.DISCOVER, fn, filter);
|
|
3778
|
+
}
|
|
3779
|
+
// -- cognition handlers --
|
|
3780
|
+
onPlan(fn, filter) {
|
|
3781
|
+
return this.on(SignalType.PLAN, fn, filter);
|
|
3782
|
+
}
|
|
3783
|
+
onThoughtDelta(fn, filter) {
|
|
3784
|
+
return this.on(SignalType.THOUGHT_DELTA, fn, filter);
|
|
3785
|
+
}
|
|
3786
|
+
onToolCall(fn, filter) {
|
|
3787
|
+
return this.on(SignalType.TOOL_CALL, fn, filter);
|
|
3788
|
+
}
|
|
3789
|
+
onToolResult(fn, filter) {
|
|
3790
|
+
return this.on(SignalType.TOOL_RESULT, fn, filter);
|
|
3791
|
+
}
|
|
3792
|
+
onMemoryAppend(fn, filter) {
|
|
3793
|
+
return this.on(SignalType.MEMORY_APPEND, fn, filter);
|
|
3794
|
+
}
|
|
3795
|
+
onCritique(fn, filter) {
|
|
3796
|
+
return this.on(SignalType.CRITIQUE, fn, filter);
|
|
3797
|
+
}
|
|
3798
|
+
onEscalation(fn, filter) {
|
|
3799
|
+
return this.on(SignalType.ESCALATION, fn, filter);
|
|
3800
|
+
}
|
|
3801
|
+
onConsensus(fn, filter) {
|
|
3802
|
+
return this.on(SignalType.CONSENSUS, fn, filter);
|
|
3803
|
+
}
|
|
3804
|
+
onContextSync(fn, filter) {
|
|
3805
|
+
return this.on(SignalType.CONTEXT_SYNC, fn, filter);
|
|
3806
|
+
}
|
|
3807
|
+
// -- routing / market handlers --
|
|
3808
|
+
/** Workers use this to evaluate offers and call {@link bid} to compete.
|
|
3809
|
+
* Registering it suppresses the default auto-bidder entirely. */
|
|
3810
|
+
onTaskOffer(fn, filter) {
|
|
3811
|
+
return this.on(SignalType.TASK_OFFER, fn, filter);
|
|
3812
|
+
}
|
|
3813
|
+
/** Observe BIDs (market observability). dispatchOffer collects its own. */
|
|
3814
|
+
onBid(fn, filter) {
|
|
3815
|
+
return this.on(SignalType.BID, fn, filter);
|
|
3816
|
+
}
|
|
3817
|
+
/** Observe TASK_AWARDED. The hosting Dendrite's award-to-TASK synthesis is
|
|
3818
|
+
* unaffected by handlers here. */
|
|
3819
|
+
onTaskAwarded(fn, filter) {
|
|
3820
|
+
return this.on(SignalType.TASK_AWARDED, fn, filter);
|
|
3821
|
+
}
|
|
3822
|
+
/** e.g. release a reservation made while bidding. */
|
|
3823
|
+
onTaskDeclined(fn, filter) {
|
|
3824
|
+
return this.on(SignalType.TASK_DECLINED, fn, filter);
|
|
3825
|
+
}
|
|
3826
|
+
// -- discrete answer-path consumers --
|
|
3827
|
+
/** Fired on CLARIFICATION_ANSWER - correlate by `sig.parent_id === the
|
|
3828
|
+
* CLARIFICATION's id`, or use {@link awaitDecision}. */
|
|
3829
|
+
onClarificationAnswer(fn, filter) {
|
|
3830
|
+
return this.on(SignalType.CLARIFICATION_ANSWER, fn, filter);
|
|
3831
|
+
}
|
|
3832
|
+
/** Fired on PERMISSION_DECISION - correlate by parent_id, or use
|
|
3833
|
+
* {@link awaitDecision}. */
|
|
3834
|
+
onPermissionDecision(fn, filter) {
|
|
3835
|
+
return this.on(SignalType.PERMISSION_DECISION, fn, filter);
|
|
3836
|
+
}
|
|
3837
|
+
// -- memory-traffic observers --
|
|
3838
|
+
onRecalled(fn, filter) {
|
|
3839
|
+
return this.on(SignalType.RECALLED, fn, filter);
|
|
3840
|
+
}
|
|
3841
|
+
onImprinted(fn, filter) {
|
|
3842
|
+
return this.on(SignalType.IMPRINTED, fn, filter);
|
|
3843
|
+
}
|
|
3844
|
+
onRecallSignal(fn, filter) {
|
|
3845
|
+
return this.on(SignalType.RECALL, fn, filter);
|
|
3846
|
+
}
|
|
3847
|
+
onImprintSignal(fn, filter) {
|
|
3848
|
+
return this.on(SignalType.IMPRINT, fn, filter);
|
|
3849
|
+
}
|
|
3850
|
+
// -- trace-scoped helper --
|
|
3851
|
+
static TRACE_DEFAULT_TYPES = [
|
|
3852
|
+
SignalType.AGENT_OUTPUT,
|
|
3853
|
+
SignalType.FINAL,
|
|
3854
|
+
SignalType.ERROR,
|
|
3855
|
+
SignalType.PLAN,
|
|
3856
|
+
SignalType.THOUGHT_DELTA,
|
|
3857
|
+
SignalType.TOOL_CALL,
|
|
3858
|
+
SignalType.TOOL_RESULT,
|
|
3859
|
+
SignalType.MEMORY_APPEND,
|
|
3860
|
+
SignalType.CRITIQUE,
|
|
3861
|
+
SignalType.ESCALATION,
|
|
3862
|
+
SignalType.CONSENSUS,
|
|
3863
|
+
SignalType.CONTEXT_SYNC,
|
|
3864
|
+
SignalType.CLARIFICATION
|
|
3865
|
+
];
|
|
3866
|
+
/** Register one handler for multiple types narrowed to a single workflow. */
|
|
3867
|
+
onTrace(traceId, fn, types) {
|
|
3868
|
+
for (const t of types ?? _Dendrite.TRACE_DEFAULT_TYPES) {
|
|
3869
|
+
this.on(t, fn, { traceId });
|
|
3870
|
+
}
|
|
3871
|
+
return fn;
|
|
2566
3872
|
}
|
|
2567
3873
|
// -- lifecycle hooks ----------------------------------------------
|
|
2568
|
-
/** Register a fire-once handler called after start() completes. */
|
|
2569
3874
|
onConnect(fn) {
|
|
2570
3875
|
return this.hooks.onConnect(fn);
|
|
2571
3876
|
}
|
|
2572
|
-
/** Register a handler called whenever this Dendrite's state refreshes. */
|
|
2573
3877
|
onRefresh(fn) {
|
|
2574
3878
|
return this.hooks.onRefresh(fn);
|
|
2575
3879
|
}
|
|
2576
|
-
/** Register a periodic handler that runs every `everyMs` until stop(). */
|
|
2577
3880
|
onSchedule(everyMs, fn) {
|
|
2578
3881
|
return this.hooks.onSchedule(everyMs, fn);
|
|
2579
3882
|
}
|
|
2580
|
-
/** Manually fire a refresh event (reason defaults to "manual"). */
|
|
2581
3883
|
async refresh(opts = {}) {
|
|
2582
3884
|
await this.hooks.refresh(opts);
|
|
2583
3885
|
}
|
|
@@ -2590,11 +3892,41 @@ var Dendrite = class {
|
|
|
2590
3892
|
this.subject(SignalType.TASK),
|
|
2591
3893
|
(s) => this.onTask(s)
|
|
2592
3894
|
);
|
|
3895
|
+
const qgroup = this.capQueueGroup();
|
|
3896
|
+
if (qgroup !== null) {
|
|
3897
|
+
this.routedTaskSub = await this.synapse.subscribe(
|
|
3898
|
+
this.routedSubject(),
|
|
3899
|
+
(s) => this.onTask(s),
|
|
3900
|
+
{ queueGroup: qgroup }
|
|
3901
|
+
);
|
|
3902
|
+
}
|
|
3903
|
+
await this.ensureInboundSub(SignalType.TASK_AWARDED);
|
|
3904
|
+
await this.ensureInboundSub(SignalType.DISCOVER);
|
|
3905
|
+
if (this.autoBid) await this.ensureInboundSub(SignalType.TASK_OFFER);
|
|
2593
3906
|
for (const axon of this._axons.values()) {
|
|
2594
3907
|
await this.mirrorToStore(axon, "registered");
|
|
2595
3908
|
await this.emitRegister(axon);
|
|
2596
3909
|
}
|
|
2597
3910
|
}
|
|
3911
|
+
if (this._engrams.size > 0) {
|
|
3912
|
+
for (const engram of this._engrams.values()) {
|
|
3913
|
+
try {
|
|
3914
|
+
await engram.connect();
|
|
3915
|
+
} catch {
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
await this.ensureInboundSub(SignalType.RECALL);
|
|
3919
|
+
await this.ensureInboundSub(SignalType.IMPRINT);
|
|
3920
|
+
await this.ensureInboundSub(SignalType.REGISTER);
|
|
3921
|
+
for (const engram of this._engrams.values()) {
|
|
3922
|
+
try {
|
|
3923
|
+
await this.emitEngramRegister(engram);
|
|
3924
|
+
} catch {
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
await this.ensureInboundSub(SignalType.RECALLED);
|
|
3929
|
+
await this.ensureInboundSub(SignalType.IMPRINTED);
|
|
2598
3930
|
for (const [type, hs] of this.handlers) {
|
|
2599
3931
|
if (hs.length) await this.ensureInboundSub(type);
|
|
2600
3932
|
}
|
|
@@ -2614,17 +3946,6 @@ var Dendrite = class {
|
|
|
2614
3946
|
axon.hooks._launchSchedule();
|
|
2615
3947
|
}
|
|
2616
3948
|
}
|
|
2617
|
-
/**
|
|
2618
|
-
* Heartbeat as a self-scheduling async loop rather than `setInterval`.
|
|
2619
|
-
*
|
|
2620
|
-
* Why not setInterval: it fires on a fixed wall-clock cadence regardless of
|
|
2621
|
-
* whether the previous tick finished, so under load ticks overlap and the
|
|
2622
|
-
* effective interval drifts; and because the callback is sync, any rejection
|
|
2623
|
-
* from the async work inside is an unhandled rejection that setInterval
|
|
2624
|
-
* silently drops. Here each tick is fully awaited, its errors are caught, and
|
|
2625
|
-
* only then is the next tick scheduled - matching the Python SDK's
|
|
2626
|
-
* asyncio.Task semantics (structured error handling + clean cancellation).
|
|
2627
|
-
*/
|
|
2628
3949
|
startHeartbeatLoop() {
|
|
2629
3950
|
this.heartbeatStopped = false;
|
|
2630
3951
|
const schedule = () => {
|
|
@@ -2644,8 +3965,29 @@ var Dendrite = class {
|
|
|
2644
3965
|
schedule();
|
|
2645
3966
|
}
|
|
2646
3967
|
async stop(reason) {
|
|
3968
|
+
for (const pw of [...this.pathways.values()]) {
|
|
3969
|
+
try {
|
|
3970
|
+
await pw.close();
|
|
3971
|
+
} catch {
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
this.pathways.clear();
|
|
3975
|
+
for (const pw of [...this.opPathways.values()]) {
|
|
3976
|
+
try {
|
|
3977
|
+
await pw.close();
|
|
3978
|
+
} catch {
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
this.opPathways.clear();
|
|
3982
|
+
this.engramClient.cancelAll();
|
|
2647
3983
|
if (!this.running) return;
|
|
2648
3984
|
this.running = false;
|
|
3985
|
+
for (const engram of this._engrams.values()) {
|
|
3986
|
+
try {
|
|
3987
|
+
await engram.close();
|
|
3988
|
+
} catch {
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
2649
3991
|
this.hooks._stopHooks();
|
|
2650
3992
|
for (const axon of this._axons.values()) axon.hooks._stopHooks();
|
|
2651
3993
|
this.heartbeatStopped = true;
|
|
@@ -2657,6 +3999,10 @@ var Dendrite = class {
|
|
|
2657
3999
|
await this.taskSub.unsubscribe();
|
|
2658
4000
|
this.taskSub = null;
|
|
2659
4001
|
}
|
|
4002
|
+
if (this.routedTaskSub !== null) {
|
|
4003
|
+
await this.routedTaskSub.unsubscribe();
|
|
4004
|
+
this.routedTaskSub = null;
|
|
4005
|
+
}
|
|
2660
4006
|
for (const sub of this.inboundSubs.values()) {
|
|
2661
4007
|
try {
|
|
2662
4008
|
await sub.unsubscribe();
|
|
@@ -2674,20 +4020,6 @@ var Dendrite = class {
|
|
|
2674
4020
|
await this.emitDeregister(axon, reason);
|
|
2675
4021
|
}
|
|
2676
4022
|
}
|
|
2677
|
-
/**
|
|
2678
|
-
* Explicit-resource-management hook so a Dendrite can be used with
|
|
2679
|
-
* `await using` - the TS equivalent of Python's `async with dendrite:`.
|
|
2680
|
-
*
|
|
2681
|
-
* ```ts
|
|
2682
|
-
* await using dendrite = new Dendrite({ synapse });
|
|
2683
|
-
* dendrite.attachAxon(axon);
|
|
2684
|
-
* await dendrite.start();
|
|
2685
|
-
* // ... stop() runs automatically when this scope exits, even on throw.
|
|
2686
|
-
* ```
|
|
2687
|
-
*
|
|
2688
|
-
* Idempotent: stop() is a no-op if the Dendrite was never started or already
|
|
2689
|
-
* stopped. As with stop(), the caller still owns the Synapse/registry store.
|
|
2690
|
-
*/
|
|
2691
4023
|
async [Symbol.asyncDispose]() {
|
|
2692
4024
|
await this.stop();
|
|
2693
4025
|
}
|
|
@@ -2702,29 +4034,387 @@ var Dendrite = class {
|
|
|
2702
4034
|
}
|
|
2703
4035
|
/** All known records, optionally filtered (live records only by default). */
|
|
2704
4036
|
async registrySnapshot(opts = {}) {
|
|
2705
|
-
|
|
4037
|
+
const { maxAgeMs, ...listOpts } = opts;
|
|
4038
|
+
const records = await this.requireStore().list(listOpts);
|
|
4039
|
+
return maxAgeMs !== void 0 ? _Dendrite.filterFresh(records, maxAgeMs) : records;
|
|
2706
4040
|
}
|
|
2707
|
-
/** Live (non-deregistered) records, optionally filtered by capability.
|
|
4041
|
+
/** Live (non-deregistered) records, optionally filtered by capability.
|
|
4042
|
+
* `maxAgeMs` additionally drops records whose last heartbeat is older -
|
|
4043
|
+
* a read-side freshness guard when the background sweep can't be relied on. */
|
|
2708
4044
|
async findNeurons(opts = {}) {
|
|
2709
|
-
|
|
4045
|
+
const records = await this.requireStore().list({
|
|
2710
4046
|
...opts.capability !== void 0 ? { capability: opts.capability } : {},
|
|
2711
4047
|
includeDeregistered: false
|
|
2712
4048
|
});
|
|
4049
|
+
return opts.maxAgeMs !== void 0 ? _Dendrite.filterFresh(records, opts.maxAgeMs) : records;
|
|
4050
|
+
}
|
|
4051
|
+
static filterFresh(records, maxAgeMs) {
|
|
4052
|
+
const now = Date.now();
|
|
4053
|
+
return records.filter((r) => {
|
|
4054
|
+
const seen = r.last_heartbeat ?? r.registered_at;
|
|
4055
|
+
if (!seen) return false;
|
|
4056
|
+
return now - Date.parse(seen) <= maxAgeMs;
|
|
4057
|
+
});
|
|
2713
4058
|
}
|
|
2714
4059
|
// -- outbound primitives ------------------------------------------
|
|
4060
|
+
/**
|
|
4061
|
+
* Emit a TASK. Addressed (`neuron`) or capability-routed (`capabilities`)
|
|
4062
|
+
* - at least one must be set. `finalize: true` tags the TASK so the
|
|
4063
|
+
* handling worker Dendrite promotes a successful AGENT_OUTPUT to FINAL
|
|
4064
|
+
* (terminal-handler finalize - see {@link dispatch}). Only
|
|
4065
|
+
* orchestrator-role Dendrites may dispatch.
|
|
4066
|
+
*/
|
|
2715
4067
|
async dispatchTask(args) {
|
|
4068
|
+
this.requireOrchestrator("dispatchTask");
|
|
4069
|
+
if (!args.neuron && !args.capabilities?.length) {
|
|
4070
|
+
throw new Error(
|
|
4071
|
+
"dispatchTask requires either neuron (addressed) or capabilities (capability-routed)"
|
|
4072
|
+
);
|
|
4073
|
+
}
|
|
2716
4074
|
const sig = taskSignal({
|
|
2717
|
-
directed: { id: args.neuron },
|
|
2718
4075
|
input: args.input,
|
|
4076
|
+
...args.neuron ? { directed: { id: args.neuron } } : {},
|
|
2719
4077
|
...args.traceId !== void 0 ? { traceId: args.traceId } : {},
|
|
2720
4078
|
...args.parentId !== void 0 ? { parentId: args.parentId } : {},
|
|
2721
4079
|
...args.contextRef !== void 0 ? { contextRef: args.contextRef } : {},
|
|
2722
4080
|
...args.capabilities !== void 0 ? { capabilities: args.capabilities } : {},
|
|
4081
|
+
...args.finalize !== void 0 ? { finalize: args.finalize } : {},
|
|
2723
4082
|
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
2724
4083
|
});
|
|
2725
|
-
await this.
|
|
4084
|
+
await this.publishTask(sig);
|
|
4085
|
+
return sig;
|
|
4086
|
+
}
|
|
4087
|
+
/** Publish a TASK to the right subject for its routing mode. Addressed ->
|
|
4088
|
+
* broadcast subject; capability-routed -> queue-grouped routed subject. */
|
|
4089
|
+
async publishTask(sig) {
|
|
4090
|
+
const subject = sig.directed?.id ? this.subject(SignalType.TASK) : sig.payload["capabilities"]?.length ? this.routedSubject() : this.subject(SignalType.TASK);
|
|
4091
|
+
await this.synapse.publish(subject, sig);
|
|
4092
|
+
}
|
|
4093
|
+
// -- Pathway-based dispatch ----------------------------------------
|
|
4094
|
+
/**
|
|
4095
|
+
* Dispatch a TASK and return a {@link Pathway} scoped to its trace -
|
|
4096
|
+
* await it, attach callbacks, or iterate:
|
|
4097
|
+
*
|
|
4098
|
+
* ```ts
|
|
4099
|
+
* // 1) sequential / request-reply
|
|
4100
|
+
* const pw = await orch.dispatch({ neuron: "summarize", input });
|
|
4101
|
+
* const out = await pw.wait();
|
|
4102
|
+
*
|
|
4103
|
+
* // 2) reactive
|
|
4104
|
+
* pw.on(SignalType.PLAN, (sig) => { ... });
|
|
4105
|
+
*
|
|
4106
|
+
* // 3) streaming
|
|
4107
|
+
* for await (const sig of pw) { ... }
|
|
4108
|
+
* ```
|
|
4109
|
+
*
|
|
4110
|
+
* `capabilities` instead of `neuron` gives event-driven dispatch. Delivery
|
|
4111
|
+
* is exactly-once within a queue group (identical cap profiles) but
|
|
4112
|
+
* **at-least-once across heterogeneous groups** - use
|
|
4113
|
+
* {@link dispatchOffer} when overlapping profiles need an atomic claim.
|
|
4114
|
+
*
|
|
4115
|
+
* `scope: "terminal"` filters delivery to FINAL / ERROR / CLARIFICATION /
|
|
4116
|
+
* PERMISSION. `finalize` (default: true exactly when scope is "terminal")
|
|
4117
|
+
* tags the TASK for terminal-handler finalize: the worker Dendrite promotes
|
|
4118
|
+
* a successful AGENT_OUTPUT by also emitting FINAL - a default Axon never
|
|
4119
|
+
* emits FINAL itself, so a terminal-scoped Pathway would otherwise never
|
|
4120
|
+
* resolve against stock workers.
|
|
4121
|
+
*/
|
|
4122
|
+
async dispatch(args) {
|
|
4123
|
+
this.requireOrchestrator("dispatch");
|
|
4124
|
+
if (!args.neuron && !args.capabilities?.length) {
|
|
4125
|
+
throw new Error(
|
|
4126
|
+
"dispatch requires either neuron (addressed) or capabilities (capability-routed)"
|
|
4127
|
+
);
|
|
4128
|
+
}
|
|
4129
|
+
const scope = args.scope ?? "all";
|
|
4130
|
+
const finalize = args.finalize ?? scope === "terminal";
|
|
4131
|
+
const tid = args.traceId ?? newTraceId();
|
|
4132
|
+
await this.ensurePathwaySubs();
|
|
4133
|
+
const pathway = new Pathway({
|
|
4134
|
+
traceId: tid,
|
|
4135
|
+
role: "originator",
|
|
4136
|
+
scope,
|
|
4137
|
+
onClose: (pw) => {
|
|
4138
|
+
this.pathways.delete(pw.traceId);
|
|
4139
|
+
}
|
|
4140
|
+
});
|
|
4141
|
+
this.pathways.set(tid, pathway);
|
|
4142
|
+
const sig = taskSignal({
|
|
4143
|
+
input: args.input,
|
|
4144
|
+
traceId: tid,
|
|
4145
|
+
...args.neuron ? { directed: { id: args.neuron } } : {},
|
|
4146
|
+
...args.parentId !== void 0 ? { parentId: args.parentId } : {},
|
|
4147
|
+
...args.contextRef !== void 0 ? { contextRef: args.contextRef } : {},
|
|
4148
|
+
...args.capabilities !== void 0 ? { capabilities: args.capabilities } : {},
|
|
4149
|
+
finalize,
|
|
4150
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
4151
|
+
});
|
|
4152
|
+
try {
|
|
4153
|
+
await this.publishTask(sig);
|
|
4154
|
+
} catch (err) {
|
|
4155
|
+
this.pathways.delete(tid);
|
|
4156
|
+
await pathway.close();
|
|
4157
|
+
throw err;
|
|
4158
|
+
}
|
|
4159
|
+
return pathway;
|
|
4160
|
+
}
|
|
4161
|
+
/** Sync-shape sugar: dispatch, await the first matching Signal, close the
|
|
4162
|
+
* Pathway, return the Signal. Use `scope: "terminal"` to wait only for
|
|
4163
|
+
* FINAL / ERROR / CLARIFICATION / PERMISSION. */
|
|
4164
|
+
async dispatchAndWait(args) {
|
|
4165
|
+
const { timeoutMs, ...rest } = args;
|
|
4166
|
+
const pathway = await this.dispatch(rest);
|
|
4167
|
+
try {
|
|
4168
|
+
return await pathway.wait(timeoutMs ?? 3e4);
|
|
4169
|
+
} finally {
|
|
4170
|
+
await pathway.close();
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
/** Async-shape sugar: dispatch, return the live Pathway immediately. The
|
|
4174
|
+
* caller attaches `pw.on(...)` callbacks or iterates. */
|
|
4175
|
+
async dispatchAndSubscribe(args) {
|
|
4176
|
+
return this.dispatch(args);
|
|
4177
|
+
}
|
|
4178
|
+
/** Open a Pathway in *observer* role for a trace another peer started. */
|
|
4179
|
+
async observePathway(traceId) {
|
|
4180
|
+
if (this.pathways.has(traceId)) {
|
|
4181
|
+
throw new Error(`Dendrite already has a Pathway open for trace '${traceId}'`);
|
|
4182
|
+
}
|
|
4183
|
+
await this.ensurePathwaySubs();
|
|
4184
|
+
const pathway = new Pathway({
|
|
4185
|
+
traceId,
|
|
4186
|
+
role: "observer",
|
|
4187
|
+
onClose: (pw) => {
|
|
4188
|
+
this.pathways.delete(pw.traceId);
|
|
4189
|
+
}
|
|
4190
|
+
});
|
|
4191
|
+
this.pathways.set(traceId, pathway);
|
|
4192
|
+
return pathway;
|
|
4193
|
+
}
|
|
4194
|
+
async ensurePathwaySubs() {
|
|
4195
|
+
for (const t of PATHWAY_TYPES) await this.ensureInboundSub(t);
|
|
4196
|
+
}
|
|
4197
|
+
// -- per-operation (request/reply) Pathways -------------------------
|
|
4198
|
+
openOpPathway(opId, traceId) {
|
|
4199
|
+
const pathway = new Pathway({
|
|
4200
|
+
traceId,
|
|
4201
|
+
parentId: opId,
|
|
4202
|
+
role: "originator",
|
|
4203
|
+
onClose: (pw) => {
|
|
4204
|
+
if (pw.parentId !== null) this.opPathways.delete(pw.parentId);
|
|
4205
|
+
}
|
|
4206
|
+
});
|
|
4207
|
+
this.opPathways.set(opId, pathway);
|
|
4208
|
+
return pathway;
|
|
4209
|
+
}
|
|
4210
|
+
async cancelOpPathways(traceId) {
|
|
4211
|
+
for (const pw of [...this.opPathways.values()].filter((p) => p.traceId === traceId)) {
|
|
4212
|
+
try {
|
|
4213
|
+
await pw.close();
|
|
4214
|
+
} catch {
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
/**
|
|
4219
|
+
* Await the discrete answer to a CLARIFICATION or PERMISSION request.
|
|
4220
|
+
*
|
|
4221
|
+
* Opens a per-operation Pathway keyed on `request.id` and resolves on the
|
|
4222
|
+
* first CLARIFICATION_ANSWER / PERMISSION_DECISION whose parent_id matches.
|
|
4223
|
+
* The awaitable counterpart to {@link onClarificationAnswer} /
|
|
4224
|
+
* {@link onPermissionDecision}.
|
|
4225
|
+
*/
|
|
4226
|
+
async awaitDecision(request, opts = {}) {
|
|
4227
|
+
let expected;
|
|
4228
|
+
if (request.type === SignalType.CLARIFICATION) {
|
|
4229
|
+
expected = SignalType.CLARIFICATION_ANSWER;
|
|
4230
|
+
} else if (request.type === SignalType.PERMISSION) {
|
|
4231
|
+
expected = SignalType.PERMISSION_DECISION;
|
|
4232
|
+
} else {
|
|
4233
|
+
throw new DendriteProtocolError(
|
|
4234
|
+
`awaitDecision expects a CLARIFICATION or PERMISSION signal, got '${request.type}'`
|
|
4235
|
+
);
|
|
4236
|
+
}
|
|
4237
|
+
await this.ensureInboundSub(expected);
|
|
4238
|
+
const cached = this.recentDecisions.get(request.id);
|
|
4239
|
+
if (cached && cached.type === expected) {
|
|
4240
|
+
this.recentDecisions.delete(request.id);
|
|
4241
|
+
return cached;
|
|
4242
|
+
}
|
|
4243
|
+
const pathway = this.openOpPathway(request.id, request.trace_id);
|
|
4244
|
+
try {
|
|
4245
|
+
return await pathway.waitFor(expected, opts.timeoutMs ?? 3e4);
|
|
4246
|
+
} finally {
|
|
4247
|
+
await pathway.close();
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
// -- competitive bidding: TASK_OFFER / BID / TASK_AWARDED ------------
|
|
4251
|
+
/**
|
|
4252
|
+
* Broadcast a TASK_OFFER, collect BIDs for `deadlineMs`, award the winner
|
|
4253
|
+
* per `select` ("first_bid" | "lowest_cost" | "highest_confidence"), and
|
|
4254
|
+
* return a Pathway scoped to the resulting workflow. Losers get
|
|
4255
|
+
* TASK_DECLINED. Throws a TimeoutError-named Error when no BID arrives.
|
|
4256
|
+
* `finalize` follows the same rule as {@link dispatch}.
|
|
4257
|
+
*/
|
|
4258
|
+
async dispatchOffer(args) {
|
|
4259
|
+
this.requireOrchestrator("dispatchOffer");
|
|
4260
|
+
const select = args.select ?? "first_bid";
|
|
4261
|
+
if (!["first_bid", "lowest_cost", "highest_confidence"].includes(select)) {
|
|
4262
|
+
throw new Error(
|
|
4263
|
+
`select must be 'first_bid' / 'lowest_cost' / 'highest_confidence', got '${select}'`
|
|
4264
|
+
);
|
|
4265
|
+
}
|
|
4266
|
+
const deadlineMs = args.deadlineMs ?? 250;
|
|
4267
|
+
const scope = args.scope ?? "all";
|
|
4268
|
+
const finalize = args.finalize ?? scope === "terminal";
|
|
4269
|
+
const tid = args.traceId ?? newTraceId();
|
|
4270
|
+
await this.ensurePathwaySubs();
|
|
4271
|
+
await this.ensureInboundSub(SignalType.BID);
|
|
4272
|
+
const pathway = new Pathway({
|
|
4273
|
+
traceId: tid,
|
|
4274
|
+
role: "originator",
|
|
4275
|
+
scope,
|
|
4276
|
+
onClose: (pw) => {
|
|
4277
|
+
this.pathways.delete(pw.traceId);
|
|
4278
|
+
}
|
|
4279
|
+
});
|
|
4280
|
+
this.pathways.set(tid, pathway);
|
|
4281
|
+
const offer = taskOfferSignal({
|
|
4282
|
+
traceId: tid,
|
|
4283
|
+
input: args.input,
|
|
4284
|
+
deadlineMs,
|
|
4285
|
+
...args.parentId !== void 0 ? { parentId: args.parentId } : {},
|
|
4286
|
+
...args.capabilities !== void 0 ? { capabilities: args.capabilities } : {},
|
|
4287
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
4288
|
+
});
|
|
4289
|
+
const bids = [];
|
|
4290
|
+
let firstBid = null;
|
|
4291
|
+
const firstBidArrived = new Promise((resolve) => {
|
|
4292
|
+
firstBid = resolve;
|
|
4293
|
+
});
|
|
4294
|
+
pathway.on(SignalType.BID, (sig) => {
|
|
4295
|
+
bids.push(sig);
|
|
4296
|
+
if (select === "first_bid") firstBid?.(sig);
|
|
4297
|
+
});
|
|
4298
|
+
try {
|
|
4299
|
+
await this.emit(offer);
|
|
4300
|
+
} catch (err) {
|
|
4301
|
+
this.pathways.delete(tid);
|
|
4302
|
+
await pathway.close();
|
|
4303
|
+
throw err;
|
|
4304
|
+
}
|
|
4305
|
+
const sleep = (ms) => new Promise((r) => setTimeout(() => r(null), ms));
|
|
4306
|
+
if (select === "first_bid") {
|
|
4307
|
+
const winnerOrNull = await Promise.race([firstBidArrived, sleep(deadlineMs)]);
|
|
4308
|
+
if (winnerOrNull === null && bids.length === 0) {
|
|
4309
|
+
await pathway.close();
|
|
4310
|
+
const err = new Error(`dispatchOffer: no BID arrived within ${deadlineMs}ms`);
|
|
4311
|
+
err.name = "TimeoutError";
|
|
4312
|
+
throw err;
|
|
4313
|
+
}
|
|
4314
|
+
} else {
|
|
4315
|
+
await sleep(deadlineMs);
|
|
4316
|
+
if (bids.length === 0) {
|
|
4317
|
+
await pathway.close();
|
|
4318
|
+
const err = new Error(`dispatchOffer: no BID arrived within ${deadlineMs}ms`);
|
|
4319
|
+
err.name = "TimeoutError";
|
|
4320
|
+
throw err;
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
let winner;
|
|
4324
|
+
if (select === "first_bid") {
|
|
4325
|
+
winner = bids[0];
|
|
4326
|
+
} else if (select === "lowest_cost") {
|
|
4327
|
+
winner = bids.reduce(
|
|
4328
|
+
(a, b) => (b.payload["cost"] ?? Infinity) < (a.payload["cost"] ?? Infinity) ? b : a
|
|
4329
|
+
);
|
|
4330
|
+
} else {
|
|
4331
|
+
winner = bids.reduce(
|
|
4332
|
+
(a, b) => (b.payload["confidence"] ?? -Infinity) > (a.payload["confidence"] ?? -Infinity) ? b : a
|
|
4333
|
+
);
|
|
4334
|
+
}
|
|
4335
|
+
for (const b of bids) {
|
|
4336
|
+
if (b.id === winner.id) continue;
|
|
4337
|
+
const bNeuron = b.directed?.id ?? null;
|
|
4338
|
+
try {
|
|
4339
|
+
await this.emit(
|
|
4340
|
+
taskDeclinedSignal({
|
|
4341
|
+
traceId: tid,
|
|
4342
|
+
parentId: b.id,
|
|
4343
|
+
reason: "not selected",
|
|
4344
|
+
...bNeuron ? { directed: { id: bNeuron } } : {}
|
|
4345
|
+
})
|
|
4346
|
+
);
|
|
4347
|
+
} catch {
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
const winnerNeuron = winner.directed?.id ?? null;
|
|
4351
|
+
const winningBid = {};
|
|
4352
|
+
for (const k of ["cost", "eta_ms", "confidence"]) {
|
|
4353
|
+
if (k in winner.payload) winningBid[k] = winner.payload[k];
|
|
4354
|
+
}
|
|
4355
|
+
const awarded = taskAwardedSignal({
|
|
4356
|
+
traceId: tid,
|
|
4357
|
+
parentId: winner.id,
|
|
4358
|
+
input: args.input,
|
|
4359
|
+
winningBid,
|
|
4360
|
+
finalize,
|
|
4361
|
+
...winnerNeuron ? { directed: { id: winnerNeuron } } : {},
|
|
4362
|
+
...args.contextRef !== void 0 ? { contextRef: args.contextRef } : {}
|
|
4363
|
+
});
|
|
4364
|
+
try {
|
|
4365
|
+
await this.emit(awarded);
|
|
4366
|
+
} catch (err) {
|
|
4367
|
+
await pathway.close();
|
|
4368
|
+
throw err;
|
|
4369
|
+
}
|
|
4370
|
+
return pathway;
|
|
4371
|
+
}
|
|
4372
|
+
/**
|
|
4373
|
+
* Emit a BID in response to a TASK_OFFER, on behalf of the local Axon named
|
|
4374
|
+
* by `neuron`. Bypasses the role guard - a worker bidding announces
|
|
4375
|
+
* capability, not orchestration.
|
|
4376
|
+
*/
|
|
4377
|
+
async bid(offer, args) {
|
|
4378
|
+
if (offer.type !== SignalType.TASK_OFFER) {
|
|
4379
|
+
throw new DendriteProtocolError(
|
|
4380
|
+
`bid() expects a TASK_OFFER signal, got '${offer.type}'`
|
|
4381
|
+
);
|
|
4382
|
+
}
|
|
4383
|
+
const sig = bidSignal({
|
|
4384
|
+
traceId: offer.trace_id,
|
|
4385
|
+
parentId: offer.id,
|
|
4386
|
+
directed: { id: args.neuron },
|
|
4387
|
+
cost: args.cost,
|
|
4388
|
+
...args.etaMs !== void 0 ? { etaMs: args.etaMs } : {},
|
|
4389
|
+
...args.confidence !== void 0 ? { confidence: args.confidence } : {},
|
|
4390
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
4391
|
+
});
|
|
4392
|
+
await this.publish(sig);
|
|
2726
4393
|
return sig;
|
|
2727
4394
|
}
|
|
4395
|
+
/** Default bidder: first hosted Axon whose caps cover the offer answers
|
|
4396
|
+
* (cost 0, confidence 1). No-op when nothing matches. */
|
|
4397
|
+
async maybeAutoBid(offer) {
|
|
4398
|
+
const requested = new Set(
|
|
4399
|
+
offer.payload["capabilities"] ?? []
|
|
4400
|
+
);
|
|
4401
|
+
for (const axon of this._axons.values()) {
|
|
4402
|
+
if (requested.size && ![...requested].every((c) => axon.capabilities.includes(c))) {
|
|
4403
|
+
continue;
|
|
4404
|
+
}
|
|
4405
|
+
try {
|
|
4406
|
+
await this.bid(offer, {
|
|
4407
|
+
neuron: axon.neuronId,
|
|
4408
|
+
cost: 0,
|
|
4409
|
+
confidence: 1,
|
|
4410
|
+
meta: { auto_bid: true }
|
|
4411
|
+
});
|
|
4412
|
+
} catch {
|
|
4413
|
+
}
|
|
4414
|
+
return;
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
// -- reply / cognition emit helpers ---------------------------------
|
|
2728
4418
|
async emitFinal(args) {
|
|
2729
4419
|
const sig = finalSignal({
|
|
2730
4420
|
traceId: args.traceId,
|
|
@@ -2749,767 +4439,755 @@ var Dendrite = class {
|
|
|
2749
4439
|
await this.emit(sig);
|
|
2750
4440
|
return sig;
|
|
2751
4441
|
}
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
* recall it next time). New TASK input: `{ permission: { action, granted,
|
|
2761
|
-
* reason?, ttlMs?, ...extra } }`.
|
|
2762
|
-
*/
|
|
2763
|
-
async respondToPermission(request, opts) {
|
|
2764
|
-
if (request.type !== SignalType.PERMISSION) {
|
|
2765
|
-
throw new DendriteProtocolError(
|
|
2766
|
-
`respondToPermission expects a PERMISSION signal, got '${request.type}'`
|
|
2767
|
-
);
|
|
2768
|
-
}
|
|
2769
|
-
const target = opts.neuron ?? request.directed?.id ?? null;
|
|
2770
|
-
if (!target) {
|
|
2771
|
-
throw new DendriteProtocolError(
|
|
2772
|
-
"respondToPermission: signal has no neuron and no neuron override - nowhere to dispatch the follow-up TASK"
|
|
2773
|
-
);
|
|
2774
|
-
}
|
|
2775
|
-
const permission = {
|
|
2776
|
-
action: request.payload["action"] ?? null,
|
|
2777
|
-
granted: opts.granted
|
|
2778
|
-
};
|
|
2779
|
-
if (opts.reason !== void 0) permission["reason"] = opts.reason;
|
|
2780
|
-
if (opts.ttlMs !== void 0) permission["ttl_ms"] = opts.ttlMs;
|
|
2781
|
-
if (opts.extra !== void 0) Object.assign(permission, opts.extra);
|
|
2782
|
-
return this.dispatchTask({
|
|
2783
|
-
neuron: target,
|
|
2784
|
-
input: { permission },
|
|
2785
|
-
traceId: request.trace_id,
|
|
2786
|
-
parentId: request.id,
|
|
2787
|
-
...opts.meta !== void 0 ? { meta: opts.meta } : {}
|
|
4442
|
+
async emitPlan(args) {
|
|
4443
|
+
const sig = planSignal({
|
|
4444
|
+
traceId: args.traceId,
|
|
4445
|
+
parentId: args.parentId,
|
|
4446
|
+
steps: args.steps,
|
|
4447
|
+
directed: { id: args.neuron ?? this.dendriteId },
|
|
4448
|
+
...args.rationale !== void 0 ? { rationale: args.rationale } : {},
|
|
4449
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
2788
4450
|
});
|
|
4451
|
+
await this.emit(sig);
|
|
4452
|
+
return sig;
|
|
2789
4453
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
* grant is valid so the requester can cache it (e.g. in an Engram). */
|
|
2799
|
-
async grantPermission(request, opts = {}) {
|
|
2800
|
-
return this.decidePermission(request, true, opts);
|
|
2801
|
-
}
|
|
2802
|
-
/** Reject a PERMISSION request. */
|
|
2803
|
-
async denyPermission(request, opts = {}) {
|
|
2804
|
-
return this.decidePermission(request, false, opts);
|
|
2805
|
-
}
|
|
2806
|
-
async decidePermission(request, granted, opts) {
|
|
2807
|
-
if (request.type !== SignalType.PERMISSION) {
|
|
2808
|
-
throw new DendriteProtocolError(
|
|
2809
|
-
`grant/denyPermission expects a PERMISSION signal, got '${request.type}'`
|
|
2810
|
-
);
|
|
2811
|
-
}
|
|
2812
|
-
const sig = permissionDecisionSignal({
|
|
2813
|
-
traceId: request.trace_id,
|
|
2814
|
-
parentId: request.id,
|
|
2815
|
-
granted,
|
|
2816
|
-
directed: { id: this.dendriteId },
|
|
2817
|
-
...opts.reason !== void 0 ? { reason: opts.reason } : {},
|
|
2818
|
-
...opts.ttlMs !== void 0 ? { ttlMs: opts.ttlMs } : {},
|
|
2819
|
-
...opts.meta !== void 0 ? { meta: opts.meta } : {}
|
|
4454
|
+
async emitThoughtDelta(args) {
|
|
4455
|
+
const sig = thoughtDeltaSignal({
|
|
4456
|
+
traceId: args.traceId,
|
|
4457
|
+
parentId: args.parentId,
|
|
4458
|
+
delta: args.delta,
|
|
4459
|
+
directed: { id: args.neuron ?? this.dendriteId },
|
|
4460
|
+
...args.seq !== void 0 ? { seq: args.seq } : {},
|
|
4461
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
2820
4462
|
});
|
|
2821
|
-
await this.
|
|
4463
|
+
await this.emit(sig);
|
|
2822
4464
|
return sig;
|
|
2823
4465
|
}
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
traceId: request.trace_id,
|
|
2834
|
-
parentId: request.id,
|
|
2835
|
-
answer,
|
|
2836
|
-
directed: { id: this.dendriteId },
|
|
2837
|
-
...opts.meta !== void 0 ? { meta: opts.meta } : {}
|
|
4466
|
+
async emitToolCall(args) {
|
|
4467
|
+
const sig = toolCallSignal({
|
|
4468
|
+
traceId: args.traceId,
|
|
4469
|
+
parentId: args.parentId,
|
|
4470
|
+
tool: args.tool,
|
|
4471
|
+
args: args.args_,
|
|
4472
|
+
directed: { id: args.neuron ?? this.dendriteId },
|
|
4473
|
+
...args.callId !== void 0 ? { callId: args.callId } : {},
|
|
4474
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
2838
4475
|
});
|
|
2839
|
-
await this.
|
|
4476
|
+
await this.emit(sig);
|
|
2840
4477
|
return sig;
|
|
2841
4478
|
}
|
|
2842
|
-
async
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
return
|
|
2855
|
-
}
|
|
2856
|
-
// -- internal ------------------------------------------------------
|
|
2857
|
-
subject(type) {
|
|
2858
|
-
return `cosmonapse.${this.namespace}.${type}`;
|
|
2859
|
-
}
|
|
2860
|
-
async ensureInboundSub(type) {
|
|
2861
|
-
if (this.inboundSubs.has(type)) return;
|
|
2862
|
-
const sub = await this.subscribe(type, (s) => this.dispatchInbound(s));
|
|
2863
|
-
this.inboundSubs.set(type, sub);
|
|
2864
|
-
}
|
|
2865
|
-
async onTask(task) {
|
|
2866
|
-
const target = task.directed?.id ?? null;
|
|
2867
|
-
if (!target) return;
|
|
2868
|
-
const axon = this._axons.get(target);
|
|
2869
|
-
if (!axon) return;
|
|
2870
|
-
let reply2;
|
|
2871
|
-
try {
|
|
2872
|
-
reply2 = await axon.handleTask(task);
|
|
2873
|
-
} catch (err) {
|
|
2874
|
-
reply2 = errorSignal({
|
|
2875
|
-
traceId: task.trace_id,
|
|
2876
|
-
parentId: task.id,
|
|
2877
|
-
directed: { id: target },
|
|
2878
|
-
code: "AXON_EXCEPTION",
|
|
2879
|
-
message: err instanceof Error ? err.message : String(err),
|
|
2880
|
-
recoverable: false
|
|
2881
|
-
});
|
|
2882
|
-
}
|
|
2883
|
-
await this.publish(reply2);
|
|
2884
|
-
}
|
|
2885
|
-
async emitRegister(axon) {
|
|
2886
|
-
await this.publish(
|
|
2887
|
-
registerSignal({
|
|
2888
|
-
directed: { id: axon.neuronId, capabilities: [...axon.capabilities] },
|
|
2889
|
-
capabilities: axon.capabilities,
|
|
2890
|
-
...axon.version !== void 0 ? { version: axon.version } : {}
|
|
2891
|
-
})
|
|
2892
|
-
);
|
|
2893
|
-
}
|
|
2894
|
-
async emitDeregister(axon, reason) {
|
|
2895
|
-
await this.publish(
|
|
2896
|
-
deregisterSignal({
|
|
2897
|
-
directed: { id: axon.neuronId },
|
|
2898
|
-
...reason !== void 0 ? { reason } : {}
|
|
2899
|
-
})
|
|
2900
|
-
);
|
|
2901
|
-
}
|
|
2902
|
-
async heartbeatTick() {
|
|
2903
|
-
if (!this.running) return;
|
|
2904
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2905
|
-
for (const axon of this._axons.values()) {
|
|
2906
|
-
try {
|
|
2907
|
-
if (this.reregisterOnHeartbeat) await this.emitRegister(axon);
|
|
2908
|
-
await this.synapse.publish(
|
|
2909
|
-
this.subject(SignalType.HEARTBEAT),
|
|
2910
|
-
heartbeatSignal({ directed: { id: axon.neuronId } })
|
|
2911
|
-
);
|
|
2912
|
-
} catch {
|
|
2913
|
-
}
|
|
2914
|
-
if (this.registryStore !== null) {
|
|
2915
|
-
try {
|
|
2916
|
-
await this.registryStore.touchHeartbeat(axon.neuronId, now);
|
|
2917
|
-
} catch {
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
await this.hooks._fireRefresh({ reason: "heartbeat", neuronId: axon.neuronId, extra: {} });
|
|
2921
|
-
await axon.hooks._fireRefresh({ reason: "heartbeat", neuronId: axon.neuronId, extra: {} });
|
|
2922
|
-
}
|
|
2923
|
-
}
|
|
2924
|
-
async mirrorToStore(axon, status) {
|
|
2925
|
-
if (this.registryStore === null) return;
|
|
2926
|
-
try {
|
|
2927
|
-
await this.registryStore.upsert(
|
|
2928
|
-
neuronRecord({
|
|
2929
|
-
neuron_id: axon.neuronId,
|
|
2930
|
-
capabilities: [...axon.capabilities],
|
|
2931
|
-
version: axon.version ?? null,
|
|
2932
|
-
status,
|
|
2933
|
-
last_heartbeat: (/* @__PURE__ */ new Date()).toISOString()
|
|
2934
|
-
})
|
|
2935
|
-
);
|
|
2936
|
-
} catch {
|
|
2937
|
-
}
|
|
2938
|
-
}
|
|
2939
|
-
async dispatchInbound(signal) {
|
|
2940
|
-
if (!AXON_TYPES.has(signal.type)) return;
|
|
2941
|
-
if (this.registryStore !== null) {
|
|
2942
|
-
try {
|
|
2943
|
-
await this.updateRegistry(signal);
|
|
2944
|
-
} catch {
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
const handlers = this.handlers.get(signal.type) ?? [];
|
|
2948
|
-
if (!handlers.length) return;
|
|
2949
|
-
await Promise.allSettled(handlers.map((h) => h(signal)));
|
|
2950
|
-
}
|
|
2951
|
-
async updateRegistry(signal) {
|
|
2952
|
-
if (this.registryStore === null) return;
|
|
2953
|
-
if (signal.payload["engram"]) return;
|
|
2954
|
-
const neuronId = signal.directed?.id ?? null;
|
|
2955
|
-
if (!neuronId) return;
|
|
2956
|
-
let reason = null;
|
|
2957
|
-
if (signal.type === SignalType.REGISTER) {
|
|
2958
|
-
await this.registryStore.upsert(
|
|
2959
|
-
neuronRecord({
|
|
2960
|
-
neuron_id: neuronId,
|
|
2961
|
-
capabilities: signal.payload["capabilities"] ?? [],
|
|
2962
|
-
version: signal.payload["version"] ?? null,
|
|
2963
|
-
status: "registered",
|
|
2964
|
-
last_heartbeat: signal.ts
|
|
2965
|
-
})
|
|
2966
|
-
);
|
|
2967
|
-
reason = "register";
|
|
2968
|
-
} else if (signal.type === SignalType.DEREGISTER) {
|
|
2969
|
-
await this.registryStore.markDeregistered(neuronId);
|
|
2970
|
-
reason = "deregister";
|
|
2971
|
-
} else if (signal.type === SignalType.HEARTBEAT) {
|
|
2972
|
-
const status = signal.payload["status"];
|
|
2973
|
-
if (status) await this.registryStore.touchHeartbeat(neuronId, signal.ts, status);
|
|
2974
|
-
else await this.registryStore.touchHeartbeat(neuronId, signal.ts);
|
|
2975
|
-
reason = "heartbeat";
|
|
2976
|
-
}
|
|
2977
|
-
if (reason !== null) {
|
|
2978
|
-
await this.hooks._fireRefresh({ reason, neuronId, extra: {} });
|
|
2979
|
-
}
|
|
2980
|
-
}
|
|
2981
|
-
};
|
|
2982
|
-
var Cortex = Dendrite;
|
|
2983
|
-
|
|
2984
|
-
// src/engram.ts
|
|
2985
|
-
var EngramBinding = class {
|
|
2986
|
-
name;
|
|
2987
|
-
directedId;
|
|
2988
|
-
directedType;
|
|
2989
|
-
defaultDeadlineMs;
|
|
2990
|
-
defaultRecallMode;
|
|
2991
|
-
constructor(init) {
|
|
2992
|
-
this.name = init.name;
|
|
2993
|
-
this.directedId = init.directedId ?? null;
|
|
2994
|
-
this.directedType = init.directedType ?? null;
|
|
2995
|
-
this.defaultDeadlineMs = init.defaultDeadlineMs ?? null;
|
|
2996
|
-
this.defaultRecallMode = init.defaultRecallMode ?? "first";
|
|
2997
|
-
if (!this.directedId && !this.directedType) {
|
|
2998
|
-
throw new Error(
|
|
2999
|
-
`EngramBinding '${this.name}' requires directedId (engram_id) or directedType (engram_kind), or both`
|
|
3000
|
-
);
|
|
3001
|
-
}
|
|
3002
|
-
}
|
|
3003
|
-
/** Build the `Directed` addressing this Engram. */
|
|
3004
|
-
toDirected() {
|
|
3005
|
-
return { id: this.directedId, type: this.directedType, capabilities: [] };
|
|
3006
|
-
}
|
|
3007
|
-
};
|
|
3008
|
-
var EngramError = class extends Error {
|
|
3009
|
-
constructor(message) {
|
|
3010
|
-
super(message);
|
|
3011
|
-
this.name = new.target.name;
|
|
3012
|
-
}
|
|
3013
|
-
};
|
|
3014
|
-
var EngramTimeout = class extends EngramError {
|
|
3015
|
-
};
|
|
3016
|
-
var EngramCancelled = class extends EngramError {
|
|
3017
|
-
};
|
|
3018
|
-
var EngramNotBound = class extends EngramError {
|
|
3019
|
-
};
|
|
3020
|
-
var EngramOverloaded = class extends EngramError {
|
|
3021
|
-
};
|
|
3022
|
-
var Engram = class {
|
|
3023
|
-
version = null;
|
|
3024
|
-
/** Return false if this Engram cannot satisfy the query. Default: serve all. */
|
|
3025
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3026
|
-
async canServe(_query) {
|
|
3027
|
-
return true;
|
|
4479
|
+
async emitToolResult(args) {
|
|
4480
|
+
const sig = toolResultSignal({
|
|
4481
|
+
traceId: args.traceId,
|
|
4482
|
+
parentId: args.parentId,
|
|
4483
|
+
tool: args.tool,
|
|
4484
|
+
directed: { id: args.neuron ?? this.dendriteId },
|
|
4485
|
+
...args.result !== void 0 ? { result: args.result } : {},
|
|
4486
|
+
...args.error !== void 0 ? { error: args.error } : {},
|
|
4487
|
+
...args.callId !== void 0 ? { callId: args.callId } : {},
|
|
4488
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
4489
|
+
});
|
|
4490
|
+
await this.emit(sig);
|
|
4491
|
+
return sig;
|
|
3028
4492
|
}
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
};
|
|
3041
|
-
}
|
|
3042
|
-
function entryToDict(e) {
|
|
3043
|
-
const out = {
|
|
3044
|
-
id: e.id,
|
|
3045
|
-
content: e.content,
|
|
3046
|
-
tags: [...e.tags],
|
|
3047
|
-
version: e.version,
|
|
3048
|
-
created_at: e.createdAt,
|
|
3049
|
-
updated_at: e.updatedAt
|
|
3050
|
-
};
|
|
3051
|
-
if (e.mergeKey !== null) out["merge_key"] = e.mergeKey;
|
|
3052
|
-
if (Object.keys(e.extra).length > 0) out["meta"] = { ...e.extra };
|
|
3053
|
-
return out;
|
|
3054
|
-
}
|
|
3055
|
-
function asStringArray(v) {
|
|
3056
|
-
return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
3057
|
-
}
|
|
3058
|
-
function asObject(v) {
|
|
3059
|
-
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
3060
|
-
}
|
|
3061
|
-
var InMemoryEngram = class extends Engram {
|
|
3062
|
-
engramId;
|
|
3063
|
-
engramKind;
|
|
3064
|
-
capabilities;
|
|
3065
|
-
entries = /* @__PURE__ */ new Map();
|
|
3066
|
-
byMergeKey = /* @__PURE__ */ new Map();
|
|
3067
|
-
imprintSeen = /* @__PURE__ */ new Map();
|
|
3068
|
-
constructor(init = {}) {
|
|
3069
|
-
super();
|
|
3070
|
-
this.engramId = init.engramId ?? "engram-memory";
|
|
3071
|
-
this.engramKind = init.engramKind ?? "keyvalue";
|
|
3072
|
-
this.capabilities = init.capabilities ?? ["substring", "tags", "merge_key"];
|
|
3073
|
-
this.version = init.version ?? "0.0.1";
|
|
4493
|
+
async emitMemoryAppend(args) {
|
|
4494
|
+
const sig = memoryAppendSignal({
|
|
4495
|
+
traceId: args.traceId,
|
|
4496
|
+
parentId: args.parentId,
|
|
4497
|
+
key: args.key,
|
|
4498
|
+
value: args.value,
|
|
4499
|
+
directed: { id: args.neuron ?? this.dendriteId },
|
|
4500
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
4501
|
+
});
|
|
4502
|
+
await this.emit(sig);
|
|
4503
|
+
return sig;
|
|
3074
4504
|
}
|
|
3075
|
-
async
|
|
3076
|
-
|
|
4505
|
+
async emitCritique(args) {
|
|
4506
|
+
const sig = critiqueSignal({
|
|
4507
|
+
traceId: args.traceId,
|
|
4508
|
+
parentId: args.parentId,
|
|
4509
|
+
targetEventId: args.targetEventId,
|
|
4510
|
+
issues: args.issues,
|
|
4511
|
+
verdict: args.verdict,
|
|
4512
|
+
directed: { id: args.neuron ?? this.dendriteId },
|
|
4513
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
4514
|
+
});
|
|
4515
|
+
await this.emit(sig);
|
|
4516
|
+
return sig;
|
|
3077
4517
|
}
|
|
3078
|
-
async
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
4518
|
+
async emitEscalation(args) {
|
|
4519
|
+
const sig = escalationSignal({
|
|
4520
|
+
traceId: args.traceId,
|
|
4521
|
+
parentId: args.parentId,
|
|
4522
|
+
reason: args.reason,
|
|
4523
|
+
directed: { id: args.neuron ?? this.dendriteId },
|
|
4524
|
+
...args.target !== void 0 ? { target: args.target } : {},
|
|
4525
|
+
...args.context !== void 0 ? { context: args.context } : {},
|
|
4526
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
4527
|
+
});
|
|
4528
|
+
await this.emit(sig);
|
|
4529
|
+
return sig;
|
|
3082
4530
|
}
|
|
3083
|
-
async
|
|
3084
|
-
const
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
4531
|
+
async emitConsensus(args) {
|
|
4532
|
+
const sig = consensusSignal({
|
|
4533
|
+
traceId: args.traceId,
|
|
4534
|
+
parentId: args.parentId,
|
|
4535
|
+
members: args.members,
|
|
4536
|
+
verdict: args.verdict,
|
|
4537
|
+
directed: { id: args.neuron ?? this.dendriteId },
|
|
4538
|
+
...args.votes !== void 0 ? { votes: args.votes } : {},
|
|
4539
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
4540
|
+
});
|
|
4541
|
+
await this.emit(sig);
|
|
4542
|
+
return sig;
|
|
4543
|
+
}
|
|
4544
|
+
async emitContextSync(args) {
|
|
4545
|
+
const sig = contextSyncSignal({
|
|
4546
|
+
traceId: args.traceId,
|
|
4547
|
+
parentId: args.parentId,
|
|
4548
|
+
snapshot: args.snapshot,
|
|
4549
|
+
directed: { id: args.neuron ?? this.dendriteId },
|
|
4550
|
+
...args.version !== void 0 ? { version: args.version } : {},
|
|
4551
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
4552
|
+
});
|
|
4553
|
+
await this.emit(sig);
|
|
4554
|
+
return sig;
|
|
4555
|
+
}
|
|
4556
|
+
// -- close-the-loop helpers ------------------------------------------
|
|
4557
|
+
/**
|
|
4558
|
+
* Reply to a CLARIFICATION by re-dispatching a TASK with the answer. The
|
|
4559
|
+
* new TASK is addressed by default to the asking Neuron, with parentId =
|
|
4560
|
+
* the clarification's id and the original traceId carried over. Input
|
|
4561
|
+
* shape: `{ clarification: { question, answer, ...extra } }`.
|
|
4562
|
+
*/
|
|
4563
|
+
async respondToClarification(request, opts) {
|
|
4564
|
+
if (request.type !== SignalType.CLARIFICATION) {
|
|
4565
|
+
throw new DendriteProtocolError(
|
|
4566
|
+
`respondToClarification expects a CLARIFICATION signal, got '${request.type}'`
|
|
4567
|
+
);
|
|
3099
4568
|
}
|
|
3100
|
-
const
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
if (!Number.isNaN(until) && updated > until) continue;
|
|
3106
|
-
if (tagQ !== null && !ent.tags.includes(tagQ)) continue;
|
|
3107
|
-
let score = 1;
|
|
3108
|
-
if (text) {
|
|
3109
|
-
const hay = String(ent.content).toLowerCase();
|
|
3110
|
-
if (!hay.includes(text)) continue;
|
|
3111
|
-
score = Math.min(1, text.length / Math.max(1, hay.length));
|
|
3112
|
-
}
|
|
3113
|
-
if (opts.minConfidence !== void 0 && score < opts.minConfidence) continue;
|
|
3114
|
-
hits.push({ id: ent.id, entry: entryToDict(ent), score });
|
|
4569
|
+
const target = opts.neuron ?? request.directed?.id ?? null;
|
|
4570
|
+
if (!target) {
|
|
4571
|
+
throw new DendriteProtocolError(
|
|
4572
|
+
"respondToClarification: signal has no neuron and no neuron override - nowhere to dispatch the follow-up TASK"
|
|
4573
|
+
);
|
|
3115
4574
|
}
|
|
3116
|
-
|
|
3117
|
-
|
|
4575
|
+
const clarification = {
|
|
4576
|
+
question: request.payload["question"] ?? null,
|
|
4577
|
+
answer: opts.answer
|
|
4578
|
+
};
|
|
4579
|
+
if (opts.extra !== void 0) Object.assign(clarification, opts.extra);
|
|
4580
|
+
return this.dispatchTask({
|
|
4581
|
+
neuron: target,
|
|
4582
|
+
input: { clarification },
|
|
4583
|
+
traceId: request.trace_id,
|
|
4584
|
+
parentId: request.id,
|
|
4585
|
+
...opts.meta !== void 0 ? { meta: opts.meta } : {}
|
|
4586
|
+
});
|
|
3118
4587
|
}
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
version: existing ? existing.version : null,
|
|
3130
|
-
tookMs: tookMs()
|
|
3131
|
-
});
|
|
3132
|
-
}
|
|
4588
|
+
/**
|
|
4589
|
+
* Reply to an ESCALATION by dispatching a TASK to the escalation target
|
|
4590
|
+
* (default: `payload.target`). Default input:
|
|
4591
|
+
* `{ escalation: { reason, context, from } }`.
|
|
4592
|
+
*/
|
|
4593
|
+
async respondToEscalation(request, opts = {}) {
|
|
4594
|
+
if (request.type !== SignalType.ESCALATION) {
|
|
4595
|
+
throw new DendriteProtocolError(
|
|
4596
|
+
`respondToEscalation expects an ESCALATION signal, got '${request.type}'`
|
|
4597
|
+
);
|
|
3133
4598
|
}
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
let ent = this.makeEntry(entry, mergeKey);
|
|
3146
|
-
while (this.entries.has(ent.id)) {
|
|
3147
|
-
ent = this.makeEntry({ ...entry, id: newEngramId() }, mergeKey);
|
|
3148
|
-
}
|
|
3149
|
-
this.store(ent);
|
|
3150
|
-
resultingId = ent.id;
|
|
3151
|
-
version = ent.version;
|
|
3152
|
-
} else if (op === "upsert") {
|
|
3153
|
-
const existingIds = this.byMergeKey.get(mergeKey ?? "") ?? [];
|
|
3154
|
-
const targetId = existingIds[existingIds.length - 1];
|
|
3155
|
-
const old = targetId !== void 0 ? this.entries.get(targetId) : void 0;
|
|
3156
|
-
if (old !== void 0) {
|
|
3157
|
-
const next = this.makeEntry({ ...entry, id: old.id }, mergeKey);
|
|
3158
|
-
next.createdAt = old.createdAt;
|
|
3159
|
-
next.version = old.version + 1;
|
|
3160
|
-
this.store(next, true);
|
|
3161
|
-
resultingId = next.id;
|
|
3162
|
-
version = next.version;
|
|
3163
|
-
} else {
|
|
3164
|
-
const ent = this.makeEntry(entry, mergeKey);
|
|
3165
|
-
this.store(ent);
|
|
3166
|
-
resultingId = ent.id;
|
|
3167
|
-
version = ent.version;
|
|
3168
|
-
}
|
|
3169
|
-
} else if (op === "merge") {
|
|
3170
|
-
const existingIds = this.byMergeKey.get(mergeKey ?? "") ?? [];
|
|
3171
|
-
const targetId = existingIds[existingIds.length - 1];
|
|
3172
|
-
const old = targetId !== void 0 ? this.entries.get(targetId) : void 0;
|
|
3173
|
-
if (old === void 0) {
|
|
3174
|
-
return receipt(this.engramId, op, { error: `no entry for merge_key='${mergeKey}'`, tookMs: tookMs() });
|
|
3175
|
-
}
|
|
3176
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3177
|
-
const next = {
|
|
3178
|
-
id: old.id,
|
|
3179
|
-
content: deepMerge(old.content, entry["content"]),
|
|
3180
|
-
tags: [.../* @__PURE__ */ new Set([...old.tags, ...asStringArray(entry["tags"])])],
|
|
3181
|
-
mergeKey: old.mergeKey,
|
|
3182
|
-
version: old.version + 1,
|
|
3183
|
-
createdAt: old.createdAt,
|
|
3184
|
-
updatedAt: now,
|
|
3185
|
-
extra: asObject(deepMerge(old.extra, entry["meta"]))
|
|
3186
|
-
};
|
|
3187
|
-
this.store(next, true);
|
|
3188
|
-
resultingId = next.id;
|
|
3189
|
-
version = next.version;
|
|
3190
|
-
} else if (op === "delete") {
|
|
3191
|
-
let targetId = null;
|
|
3192
|
-
const entId = entry["id"];
|
|
3193
|
-
if (typeof entId === "string") {
|
|
3194
|
-
targetId = entId;
|
|
3195
|
-
} else if (mergeKey !== null) {
|
|
3196
|
-
const ids = this.byMergeKey.get(mergeKey) ?? [];
|
|
3197
|
-
targetId = ids[ids.length - 1] ?? null;
|
|
3198
|
-
}
|
|
3199
|
-
if (targetId === null || !this.entries.has(targetId)) {
|
|
3200
|
-
return receipt(this.engramId, op, { tookMs: tookMs() });
|
|
4599
|
+
const target = opts.neuron ?? request.payload["target"] ?? null;
|
|
4600
|
+
if (!target) {
|
|
4601
|
+
throw new DendriteProtocolError(
|
|
4602
|
+
"respondToEscalation: signal has no payload.target and no neuron override - nowhere to dispatch the follow-up TASK"
|
|
4603
|
+
);
|
|
4604
|
+
}
|
|
4605
|
+
const input = opts.input ?? {
|
|
4606
|
+
escalation: {
|
|
4607
|
+
reason: request.payload["reason"] ?? null,
|
|
4608
|
+
context: request.payload["context"] ?? null,
|
|
4609
|
+
from: request.directed?.id ?? null
|
|
3201
4610
|
}
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
4611
|
+
};
|
|
4612
|
+
return this.dispatchTask({
|
|
4613
|
+
neuron: target,
|
|
4614
|
+
input,
|
|
4615
|
+
traceId: request.trace_id,
|
|
4616
|
+
parentId: request.id,
|
|
4617
|
+
...opts.meta !== void 0 ? { meta: opts.meta } : {}
|
|
4618
|
+
});
|
|
4619
|
+
}
|
|
4620
|
+
/**
|
|
4621
|
+
* Reply to a PERMISSION by re-dispatching a TASK carrying the verdict.
|
|
4622
|
+
* Input shape: `{ permission: { action, granted, reason?, ttl_ms?, ...extra } }`.
|
|
4623
|
+
*/
|
|
4624
|
+
async respondToPermission(request, opts) {
|
|
4625
|
+
if (request.type !== SignalType.PERMISSION) {
|
|
4626
|
+
throw new DendriteProtocolError(
|
|
4627
|
+
`respondToPermission expects a PERMISSION signal, got '${request.type}'`
|
|
4628
|
+
);
|
|
4629
|
+
}
|
|
4630
|
+
const target = opts.neuron ?? request.directed?.id ?? null;
|
|
4631
|
+
if (!target) {
|
|
4632
|
+
throw new DendriteProtocolError(
|
|
4633
|
+
"respondToPermission: signal has no neuron and no neuron override - nowhere to dispatch the follow-up TASK"
|
|
4634
|
+
);
|
|
4635
|
+
}
|
|
4636
|
+
const permission = {
|
|
4637
|
+
action: request.payload["action"] ?? null,
|
|
4638
|
+
granted: opts.granted
|
|
4639
|
+
};
|
|
4640
|
+
if (opts.reason !== void 0) permission["reason"] = opts.reason;
|
|
4641
|
+
if (opts.ttlMs !== void 0) permission["ttl_ms"] = opts.ttlMs;
|
|
4642
|
+
if (opts.extra !== void 0) Object.assign(permission, opts.extra);
|
|
4643
|
+
return this.dispatchTask({
|
|
4644
|
+
neuron: target,
|
|
4645
|
+
input: { permission },
|
|
4646
|
+
traceId: request.trace_id,
|
|
4647
|
+
parentId: request.id,
|
|
4648
|
+
...opts.meta !== void 0 ? { meta: opts.meta } : {}
|
|
4649
|
+
});
|
|
4650
|
+
}
|
|
4651
|
+
// -- cognition decision signals (discrete, decentralised option) -----
|
|
4652
|
+
/** Approve a PERMISSION request. */
|
|
4653
|
+
async grantPermission(request, opts = {}) {
|
|
4654
|
+
return this.decidePermission(request, true, opts);
|
|
4655
|
+
}
|
|
4656
|
+
/** Reject a PERMISSION request. */
|
|
4657
|
+
async denyPermission(request, opts = {}) {
|
|
4658
|
+
return this.decidePermission(request, false, opts);
|
|
4659
|
+
}
|
|
4660
|
+
async decidePermission(request, granted, opts) {
|
|
4661
|
+
if (request.type !== SignalType.PERMISSION) {
|
|
4662
|
+
throw new DendriteProtocolError(
|
|
4663
|
+
`grant/denyPermission expects a PERMISSION signal, got '${request.type}'`
|
|
4664
|
+
);
|
|
3207
4665
|
}
|
|
3208
|
-
|
|
3209
|
-
|
|
4666
|
+
const sig = permissionDecisionSignal({
|
|
4667
|
+
traceId: request.trace_id,
|
|
4668
|
+
parentId: request.id,
|
|
4669
|
+
granted,
|
|
4670
|
+
directed: { id: this.dendriteId },
|
|
4671
|
+
...opts.reason !== void 0 ? { reason: opts.reason } : {},
|
|
4672
|
+
...opts.ttlMs !== void 0 ? { ttlMs: opts.ttlMs } : {},
|
|
4673
|
+
...opts.meta !== void 0 ? { meta: opts.meta } : {}
|
|
4674
|
+
});
|
|
4675
|
+
await this.publish(sig);
|
|
4676
|
+
return sig;
|
|
4677
|
+
}
|
|
4678
|
+
/** Answer a CLARIFICATION with a discrete CLARIFICATION_ANSWER signal
|
|
4679
|
+
* (parent_id = the request's id). Consumers pick it up via
|
|
4680
|
+
* {@link onClarificationAnswer} or {@link awaitDecision}. Distinct from
|
|
4681
|
+
* {@link respondToClarification}, which re-dispatches a TASK. */
|
|
4682
|
+
async answerClarification(request, answer, opts = {}) {
|
|
4683
|
+
if (request.type !== SignalType.CLARIFICATION) {
|
|
4684
|
+
throw new DendriteProtocolError(
|
|
4685
|
+
`answerClarification expects a CLARIFICATION signal, got '${request.type}'`
|
|
4686
|
+
);
|
|
3210
4687
|
}
|
|
3211
|
-
|
|
4688
|
+
const sig = clarificationAnswerSignal({
|
|
4689
|
+
traceId: request.trace_id,
|
|
4690
|
+
parentId: request.id,
|
|
4691
|
+
answer,
|
|
4692
|
+
directed: { id: this.dendriteId },
|
|
4693
|
+
...opts.meta !== void 0 ? { meta: opts.meta } : {}
|
|
4694
|
+
});
|
|
4695
|
+
await this.publish(sig);
|
|
4696
|
+
return sig;
|
|
3212
4697
|
}
|
|
3213
|
-
/**
|
|
3214
|
-
|
|
3215
|
-
|
|
4698
|
+
/** Emit a synapse-side Signal. Refuses Axon-owned types; TASK initiation
|
|
4699
|
+
* additionally requires orchestrator role. */
|
|
4700
|
+
async emit(signal) {
|
|
4701
|
+
if (signal.type === SignalType.TASK) {
|
|
4702
|
+
this.requireOrchestrator(`emit(${signal.type})`);
|
|
4703
|
+
}
|
|
4704
|
+
if (!SYNAPSE_TYPES.has(signal.type)) {
|
|
4705
|
+
throw new DendriteProtocolError(
|
|
4706
|
+
`Dendrite refuses to emit '${signal.type}': only synapse-side types may be emitted this way. '${signal.type}' is an Axon-owned type.`
|
|
4707
|
+
);
|
|
4708
|
+
}
|
|
4709
|
+
await this.publish(signal);
|
|
3216
4710
|
}
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3220
|
-
return {
|
|
3221
|
-
id,
|
|
3222
|
-
content: entry["content"],
|
|
3223
|
-
tags: asStringArray(entry["tags"]),
|
|
3224
|
-
mergeKey,
|
|
3225
|
-
version: 1,
|
|
3226
|
-
createdAt: now,
|
|
3227
|
-
updatedAt: now,
|
|
3228
|
-
extra: asObject(entry["meta"])
|
|
3229
|
-
};
|
|
4711
|
+
async publish(signal) {
|
|
4712
|
+
await this.synapse.publish(this.subject(signal.type), signal);
|
|
3230
4713
|
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
4714
|
+
async subscribe(type, handler, opts) {
|
|
4715
|
+
return this.synapse.subscribe(this.subject(type), handler, opts);
|
|
4716
|
+
}
|
|
4717
|
+
// -- internal ------------------------------------------------------
|
|
4718
|
+
subject(type) {
|
|
4719
|
+
return `cosmonapse.${this.namespace}.${type}`;
|
|
4720
|
+
}
|
|
4721
|
+
/** Subject for capability-routed TASKs (queue-grouped consumers). */
|
|
4722
|
+
routedSubject() {
|
|
4723
|
+
return `cosmonapse.${this.namespace}.${SignalType.TASK}.routed`;
|
|
4724
|
+
}
|
|
4725
|
+
ensureInboundSub(type) {
|
|
4726
|
+
if (this.inboundSubs.has(type)) return Promise.resolve();
|
|
4727
|
+
let p = this.inflightSubs.get(type);
|
|
4728
|
+
if (!p) {
|
|
4729
|
+
p = (async () => {
|
|
4730
|
+
const sub = await this.subscribe(type, (s) => this.dispatchInbound(s));
|
|
4731
|
+
this.inboundSubs.set(type, sub);
|
|
4732
|
+
})().finally(() => this.inflightSubs.delete(type));
|
|
4733
|
+
this.inflightSubs.set(type, p);
|
|
4734
|
+
}
|
|
4735
|
+
return p;
|
|
4736
|
+
}
|
|
4737
|
+
/**
|
|
4738
|
+
* Route an inbound TASK to a local Axon. Addressed: by neuron_id (drop if
|
|
4739
|
+
* not hosted here). Capability-routed: first local Axon whose caps superset
|
|
4740
|
+
* the request. After publishing the reply, apply terminal-handler finalize:
|
|
4741
|
+
* a TASK tagged `payload.finalize` promotes a successful AGENT_OUTPUT by
|
|
4742
|
+
* also emitting FINAL (parented to the AGENT_OUTPUT, attributed to the
|
|
4743
|
+
* producing Neuron). Only AGENT_OUTPUT is promoted - CLARIFICATION /
|
|
4744
|
+
* PERMISSION pause the workflow and ERROR is already terminal.
|
|
4745
|
+
*/
|
|
4746
|
+
async onTask(task) {
|
|
4747
|
+
let target = task.directed?.id ?? null;
|
|
4748
|
+
let axon;
|
|
4749
|
+
if (target) {
|
|
4750
|
+
axon = this._axons.get(target);
|
|
4751
|
+
if (!axon) return;
|
|
4752
|
+
} else {
|
|
4753
|
+
const requested = task.payload["capabilities"] ?? [];
|
|
4754
|
+
if (!requested.length) return;
|
|
4755
|
+
for (const candidate of this._axons.values()) {
|
|
4756
|
+
if (requested.every((c) => candidate.capabilities.includes(c))) {
|
|
4757
|
+
axon = candidate;
|
|
4758
|
+
break;
|
|
3240
4759
|
}
|
|
3241
4760
|
}
|
|
4761
|
+
if (!axon) return;
|
|
4762
|
+
target = axon.neuronId;
|
|
3242
4763
|
}
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
4764
|
+
let reply2;
|
|
4765
|
+
try {
|
|
4766
|
+
reply2 = await axon.handleTask(task);
|
|
4767
|
+
} catch (err) {
|
|
4768
|
+
reply2 = errorSignal({
|
|
4769
|
+
traceId: task.trace_id,
|
|
4770
|
+
parentId: task.id,
|
|
4771
|
+
directed: { id: target },
|
|
4772
|
+
code: "AXON_EXCEPTION",
|
|
4773
|
+
message: err instanceof Error ? err.message : String(err),
|
|
4774
|
+
recoverable: false
|
|
4775
|
+
});
|
|
3248
4776
|
}
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
4777
|
+
await this.publish(reply2);
|
|
4778
|
+
if (reply2.type === SignalType.AGENT_OUTPUT && task.payload["finalize"]) {
|
|
4779
|
+
try {
|
|
4780
|
+
await this.publish(
|
|
4781
|
+
finalSignal({
|
|
4782
|
+
traceId: reply2.trace_id,
|
|
4783
|
+
parentId: reply2.id,
|
|
4784
|
+
directed: { id: target },
|
|
4785
|
+
result: reply2.payload["output"] ?? {}
|
|
4786
|
+
})
|
|
4787
|
+
);
|
|
4788
|
+
} catch {
|
|
3260
4789
|
}
|
|
3261
4790
|
}
|
|
3262
4791
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
const out = [];
|
|
3277
|
-
for (const item of [...base, ...incoming]) {
|
|
3278
|
-
const key = JSON.stringify(item);
|
|
3279
|
-
if (seen.has(key)) continue;
|
|
3280
|
-
seen.add(key);
|
|
3281
|
-
out.push(item);
|
|
3282
|
-
}
|
|
3283
|
-
return out;
|
|
4792
|
+
async emitRegister(axon) {
|
|
4793
|
+
await this.publish(
|
|
4794
|
+
registerSignal({
|
|
4795
|
+
directed: {
|
|
4796
|
+
id: axon.neuronId,
|
|
4797
|
+
type: axon.neuronKind ?? "neuron",
|
|
4798
|
+
capabilities: [...axon.capabilities]
|
|
4799
|
+
},
|
|
4800
|
+
capabilities: axon.capabilities,
|
|
4801
|
+
role: "neuron",
|
|
4802
|
+
...axon.version !== void 0 ? { version: axon.version } : {}
|
|
4803
|
+
})
|
|
4804
|
+
);
|
|
3284
4805
|
}
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
const promise = new Promise((res, rej) => {
|
|
3293
|
-
resolve = res;
|
|
3294
|
-
reject = rej;
|
|
3295
|
-
});
|
|
3296
|
-
return { promise, resolve, reject };
|
|
3297
|
-
}
|
|
3298
|
-
var EngramClient = class {
|
|
3299
|
-
constructor(publisher) {
|
|
3300
|
-
this.publisher = publisher;
|
|
4806
|
+
async emitDeregister(axon, reason) {
|
|
4807
|
+
await this.publish(
|
|
4808
|
+
deregisterSignal({
|
|
4809
|
+
directed: { id: axon.neuronId },
|
|
4810
|
+
...reason !== void 0 ? { reason } : {}
|
|
4811
|
+
})
|
|
4812
|
+
);
|
|
3301
4813
|
}
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
if (
|
|
3315
|
-
|
|
4814
|
+
async heartbeatTick() {
|
|
4815
|
+
if (!this.running) return;
|
|
4816
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4817
|
+
for (const axon of this._axons.values()) {
|
|
4818
|
+
try {
|
|
4819
|
+
if (this.reregisterOnHeartbeat) await this.emitRegister(axon);
|
|
4820
|
+
await this.synapse.publish(
|
|
4821
|
+
this.subject(SignalType.HEARTBEAT),
|
|
4822
|
+
heartbeatSignal({ directed: { id: axon.neuronId } })
|
|
4823
|
+
);
|
|
4824
|
+
} catch {
|
|
4825
|
+
}
|
|
4826
|
+
if (this.registryStore !== null) {
|
|
4827
|
+
try {
|
|
4828
|
+
await this.registryStore.touchHeartbeat(axon.neuronId, now);
|
|
4829
|
+
} catch {
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
await this.hooks._fireRefresh({ reason: "heartbeat", neuronId: axon.neuronId, extra: {} });
|
|
4833
|
+
await axon.hooks._fireRefresh({ reason: "heartbeat", neuronId: axon.neuronId, extra: {} });
|
|
3316
4834
|
}
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
const
|
|
3331
|
-
const
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
4835
|
+
if (this.registryStore !== null && this.staleAfterMs > 0) {
|
|
4836
|
+
try {
|
|
4837
|
+
await this.sweepStaleNeurons(Date.now());
|
|
4838
|
+
} catch {
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
/** Mark Neurons deregistered when their last heartbeat is older than
|
|
4843
|
+
* `staleAfterMs`. Own hosted Axons were touched immediately before the
|
|
4844
|
+
* sweep, so they never qualify. */
|
|
4845
|
+
async sweepStaleNeurons(nowMs) {
|
|
4846
|
+
const store = this.registryStore;
|
|
4847
|
+
if (store === null) return;
|
|
4848
|
+
const records = await store.list({ includeDeregistered: false });
|
|
4849
|
+
for (const rec of records) {
|
|
4850
|
+
const seen = rec.last_heartbeat ?? rec.registered_at;
|
|
4851
|
+
if (!seen) continue;
|
|
4852
|
+
if (nowMs - Date.parse(seen) > this.staleAfterMs) {
|
|
4853
|
+
try {
|
|
4854
|
+
await store.markDeregistered(rec.neuron_id);
|
|
4855
|
+
await this.hooks._fireRefresh({
|
|
4856
|
+
reason: "stale",
|
|
4857
|
+
neuronId: rec.neuron_id,
|
|
4858
|
+
extra: {}
|
|
4859
|
+
});
|
|
4860
|
+
} catch {
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
3336
4863
|
}
|
|
4864
|
+
}
|
|
4865
|
+
async mirrorToStore(axon, status) {
|
|
4866
|
+
if (this.registryStore === null) return;
|
|
3337
4867
|
try {
|
|
3338
|
-
await this.
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
4868
|
+
await this.registryStore.upsert(
|
|
4869
|
+
neuronRecord({
|
|
4870
|
+
neuron_id: axon.neuronId,
|
|
4871
|
+
capabilities: [...axon.capabilities],
|
|
4872
|
+
version: axon.version ?? null,
|
|
4873
|
+
status,
|
|
4874
|
+
last_heartbeat: (/* @__PURE__ */ new Date()).toISOString()
|
|
4875
|
+
})
|
|
4876
|
+
);
|
|
4877
|
+
} catch {
|
|
3342
4878
|
}
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
4879
|
+
}
|
|
4880
|
+
/** Respond to a DISCOVER by re-emitting REGISTER for matching Axons. */
|
|
4881
|
+
async respondToDiscover(signal) {
|
|
4882
|
+
if (this._axons.size === 0) return;
|
|
4883
|
+
const target = signal.payload["neuron"];
|
|
4884
|
+
const capsFilter = signal.payload["capabilities"];
|
|
4885
|
+
for (const axon of this._axons.values()) {
|
|
4886
|
+
if (target && axon.neuronId !== target) continue;
|
|
4887
|
+
if (capsFilter?.length && !capsFilter.every((c) => axon.capabilities.includes(c))) {
|
|
4888
|
+
continue;
|
|
4889
|
+
}
|
|
4890
|
+
try {
|
|
4891
|
+
await this.emitRegister(axon);
|
|
4892
|
+
} catch {
|
|
4893
|
+
}
|
|
3347
4894
|
}
|
|
3348
4895
|
}
|
|
3349
|
-
async
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
4896
|
+
async dispatchInbound(signal) {
|
|
4897
|
+
if (signal.type === SignalType.DISCOVER) {
|
|
4898
|
+
const hs = this.handlers.get(SignalType.DISCOVER) ?? [];
|
|
4899
|
+
if (hs.length) await Promise.allSettled(hs.map((h) => h(signal)));
|
|
4900
|
+
else await this.respondToDiscover(signal);
|
|
4901
|
+
return;
|
|
3355
4902
|
}
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
entry: args.entry,
|
|
3362
|
-
...args.mergeKey !== void 0 ? { mergeKey: args.mergeKey } : {},
|
|
3363
|
-
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
3364
|
-
});
|
|
3365
|
-
if (!args.awaitAck) {
|
|
3366
|
-
await this.publisher.publish(sig);
|
|
3367
|
-
return null;
|
|
4903
|
+
if (signal.type === SignalType.RECALL) {
|
|
4904
|
+
await this.onRecall(signal);
|
|
4905
|
+
const hs = this.handlers.get(SignalType.RECALL) ?? [];
|
|
4906
|
+
if (hs.length) await Promise.allSettled(hs.map((h) => h(signal)));
|
|
4907
|
+
return;
|
|
3368
4908
|
}
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
pending.timer = setTimeout(() => this.onImprintDeadline(sig.id), args.deadlineMs);
|
|
4909
|
+
if (signal.type === SignalType.IMPRINT) {
|
|
4910
|
+
await this.onImprint(signal);
|
|
4911
|
+
const hs = this.handlers.get(SignalType.IMPRINT) ?? [];
|
|
4912
|
+
if (hs.length) await Promise.allSettled(hs.map((h) => h(signal)));
|
|
4913
|
+
return;
|
|
3375
4914
|
}
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
} catch (err) {
|
|
3379
|
-
this.cleanupImprint(args.traceId, sig.id);
|
|
3380
|
-
throw err;
|
|
4915
|
+
if (signal.type === SignalType.RECALLED || signal.type === SignalType.IMPRINTED) {
|
|
4916
|
+
this.engramClient.deliver(signal);
|
|
3381
4917
|
}
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
this.cleanupImprint(args.traceId, sig.id);
|
|
4918
|
+
if (signal.type === SignalType.REGISTER && this.isEngramRegister(signal)) {
|
|
4919
|
+
this.recordEngramRegistration(signal);
|
|
4920
|
+
return;
|
|
3386
4921
|
}
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
const pending = this.pendingRecalls.get(pid);
|
|
3394
|
-
if (pending === void 0) return;
|
|
3395
|
-
const hits = hitsFromPayload(sig.payload["hits"]);
|
|
3396
|
-
const engramId = typeof sig.payload["engram_id"] === "string" ? sig.payload["engram_id"] : "";
|
|
3397
|
-
const tookMs = typeof sig.payload["took_ms"] === "number" ? sig.payload["took_ms"] : null;
|
|
3398
|
-
const truncated = sig.payload["truncated"] === true;
|
|
3399
|
-
if (pending.mode === "first") {
|
|
3400
|
-
if (!pending.done) {
|
|
3401
|
-
pending.done = true;
|
|
3402
|
-
pending.deferred.resolve({
|
|
3403
|
-
hits,
|
|
3404
|
-
engramIds: engramId ? [engramId] : [],
|
|
3405
|
-
truncated,
|
|
3406
|
-
tookMs
|
|
3407
|
-
});
|
|
4922
|
+
if (signal.parent_id) {
|
|
4923
|
+
const opPw = this.opPathways.get(signal.parent_id);
|
|
4924
|
+
if (opPw) {
|
|
4925
|
+
try {
|
|
4926
|
+
await opPw._deliver(signal);
|
|
4927
|
+
} catch {
|
|
3408
4928
|
}
|
|
3409
|
-
} else {
|
|
3410
|
-
pending.hitsSoFar.push(...hits);
|
|
3411
|
-
if (engramId) pending.engrams.push(engramId);
|
|
3412
4929
|
}
|
|
3413
|
-
}
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
4930
|
+
}
|
|
4931
|
+
if ((signal.type === SignalType.CLARIFICATION_ANSWER || signal.type === SignalType.PERMISSION_DECISION) && signal.parent_id) {
|
|
4932
|
+
this.recentDecisions.set(signal.parent_id, signal);
|
|
4933
|
+
while (this.recentDecisions.size > 256) {
|
|
4934
|
+
const oldest = this.recentDecisions.keys().next().value;
|
|
4935
|
+
this.recentDecisions.delete(oldest);
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
if ((signal.type === SignalType.FINAL || signal.type === SignalType.ERROR) && signal.trace_id) {
|
|
4939
|
+
await this.cancelOpPathways(signal.trace_id);
|
|
4940
|
+
this.engramClient.cancelTrace(signal.trace_id);
|
|
4941
|
+
}
|
|
4942
|
+
if (signal.type === SignalType.TASK_AWARDED) {
|
|
4943
|
+
const target = signal.directed?.id ?? null;
|
|
4944
|
+
if (target && this._axons.has(target)) {
|
|
4945
|
+
const synthetic = taskSignal({
|
|
4946
|
+
traceId: signal.trace_id,
|
|
4947
|
+
parentId: signal.id,
|
|
4948
|
+
directed: { id: target },
|
|
4949
|
+
input: signal.payload["input"] ?? {},
|
|
4950
|
+
finalize: Boolean(signal.payload["finalize"]),
|
|
4951
|
+
...signal.payload["context_ref"] !== void 0 ? { contextRef: signal.payload["context_ref"] } : {},
|
|
4952
|
+
meta: signal.meta
|
|
4953
|
+
});
|
|
4954
|
+
await this.onTask(synthetic);
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
if (signal.trace_id && PATHWAY_TYPES.has(signal.type)) {
|
|
4958
|
+
const pathway = this.pathways.get(signal.trace_id);
|
|
4959
|
+
if (pathway) {
|
|
4960
|
+
try {
|
|
4961
|
+
await pathway._deliver(signal);
|
|
4962
|
+
} catch {
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
if (AXON_TYPES.has(signal.type) && this.registryStore !== null) {
|
|
4967
|
+
try {
|
|
4968
|
+
await this.updateRegistry(signal);
|
|
4969
|
+
} catch {
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
if (signal.type === SignalType.TASK_OFFER && this.autoBid && this._axons.size > 0 && (this.handlers.get(SignalType.TASK_OFFER) ?? []).length === 0) {
|
|
4973
|
+
await this.maybeAutoBid(signal);
|
|
4974
|
+
}
|
|
4975
|
+
const handlers = this.handlers.get(signal.type) ?? [];
|
|
4976
|
+
if (handlers.length) await Promise.allSettled(handlers.map((h) => h(signal)));
|
|
4977
|
+
}
|
|
4978
|
+
// -- Engram: hosted-side handlers -----------------------------------
|
|
4979
|
+
/** Pick the hosted Engrams that should respond to a RECALL/IMPRINT.
|
|
4980
|
+
* directed.id (engramId) wins over directed.type (engramKind). */
|
|
4981
|
+
resolveEngramTargets(signal) {
|
|
4982
|
+
const eid = signal.directed?.id ?? null;
|
|
4983
|
+
if (eid) {
|
|
4984
|
+
const ent = this._engrams.get(eid);
|
|
4985
|
+
return ent ? [ent] : [];
|
|
4986
|
+
}
|
|
4987
|
+
const ekind = signal.directed?.type ?? null;
|
|
4988
|
+
if (ekind) {
|
|
4989
|
+
return (this.engramKindIndex.get(ekind) ?? []).map((id) => this._engrams.get(id)).filter((e) => e !== void 0);
|
|
4990
|
+
}
|
|
4991
|
+
return [];
|
|
4992
|
+
}
|
|
4993
|
+
async onRecall(signal) {
|
|
4994
|
+
const targets = this.resolveEngramTargets(signal);
|
|
4995
|
+
if (!targets.length) return;
|
|
4996
|
+
const query = signal.payload["query"] ?? {};
|
|
4997
|
+
const filters = signal.payload["filters"];
|
|
4998
|
+
const contextRef = signal.payload["context_ref"];
|
|
4999
|
+
const deadlineMs = signal.payload["deadline_ms"];
|
|
5000
|
+
const minConfidence = signal.payload["min_confidence"];
|
|
5001
|
+
for (const engram of targets) {
|
|
5002
|
+
let hits;
|
|
5003
|
+
try {
|
|
5004
|
+
if (!await engram.canServe(query)) continue;
|
|
5005
|
+
hits = await engram.recall(query, {
|
|
5006
|
+
...filters !== void 0 ? { filters } : {},
|
|
5007
|
+
...contextRef !== void 0 ? { contextRef } : {},
|
|
5008
|
+
...deadlineMs !== void 0 ? { deadlineMs } : {},
|
|
5009
|
+
...minConfidence !== void 0 ? { minConfidence } : {}
|
|
5010
|
+
});
|
|
5011
|
+
} catch {
|
|
5012
|
+
continue;
|
|
5013
|
+
}
|
|
5014
|
+
try {
|
|
5015
|
+
await this.publish(
|
|
5016
|
+
recalledSignal({
|
|
5017
|
+
traceId: signal.trace_id,
|
|
5018
|
+
parentId: signal.id,
|
|
5019
|
+
engramId: engram.engramId,
|
|
5020
|
+
hits: hits.map((h) => ({ id: h.id, entry: h.entry, score: h.score })),
|
|
5021
|
+
// Attribute the reply to the Engram that answered, not the host
|
|
5022
|
+
// Dendrite, so observers classify it by the Engram's REGISTER.
|
|
5023
|
+
directed: { id: engram.engramId, type: engram.engramKind }
|
|
5024
|
+
})
|
|
5025
|
+
);
|
|
5026
|
+
} catch {
|
|
5027
|
+
}
|
|
3426
5028
|
}
|
|
3427
5029
|
}
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
5030
|
+
async onImprint(signal) {
|
|
5031
|
+
const targets = this.resolveEngramTargets(signal);
|
|
5032
|
+
if (!targets.length) return;
|
|
5033
|
+
const op = signal.payload["op"] ?? "add";
|
|
5034
|
+
const entry = signal.payload["entry"] ?? {};
|
|
5035
|
+
const mergeKey = signal.payload["merge_key"];
|
|
5036
|
+
for (const engram of targets) {
|
|
5037
|
+
let reply2;
|
|
5038
|
+
try {
|
|
5039
|
+
const receipt2 = await engram.imprint(op, entry, {
|
|
5040
|
+
imprintId: signal.id,
|
|
5041
|
+
...mergeKey !== void 0 ? { mergeKey } : {}
|
|
5042
|
+
});
|
|
5043
|
+
reply2 = imprintedSignal({
|
|
5044
|
+
traceId: signal.trace_id,
|
|
5045
|
+
parentId: signal.id,
|
|
5046
|
+
engramId: receipt2.engramId || engram.engramId,
|
|
5047
|
+
op: receipt2.op,
|
|
5048
|
+
...receipt2.id !== null ? { id: receipt2.id } : {},
|
|
5049
|
+
...receipt2.version !== null ? { version: receipt2.version } : {},
|
|
5050
|
+
...receipt2.tookMs !== null ? { tookMs: receipt2.tookMs } : {},
|
|
5051
|
+
...receipt2.error !== null ? { error: receipt2.error } : {},
|
|
5052
|
+
directed: { id: engram.engramId, type: engram.engramKind }
|
|
5053
|
+
});
|
|
5054
|
+
} catch (err) {
|
|
5055
|
+
reply2 = imprintedSignal({
|
|
5056
|
+
traceId: signal.trace_id,
|
|
5057
|
+
parentId: signal.id,
|
|
5058
|
+
engramId: engram.engramId,
|
|
5059
|
+
op,
|
|
5060
|
+
error: `engram_exception: ${err instanceof Error ? err.message : String(err)}`,
|
|
5061
|
+
directed: { id: engram.engramId, type: engram.engramKind }
|
|
5062
|
+
});
|
|
3440
5063
|
}
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
if (pi.timer !== null) clearTimeout(pi.timer);
|
|
3445
|
-
pi.deferred.reject(new EngramCancelled(`trace ${traceId} terminated while imprint ${id} in flight`));
|
|
3446
|
-
this.pendingImprints.delete(id);
|
|
5064
|
+
try {
|
|
5065
|
+
await this.publish(reply2);
|
|
5066
|
+
} catch {
|
|
3447
5067
|
}
|
|
3448
5068
|
}
|
|
3449
5069
|
}
|
|
3450
|
-
|
|
3451
|
-
|
|
5070
|
+
// -- Engram: registration (announce + learn) -------------------------
|
|
5071
|
+
async emitEngramRegister(engram) {
|
|
5072
|
+
await this.publish(
|
|
5073
|
+
registerSignal({
|
|
5074
|
+
directed: {
|
|
5075
|
+
id: engram.engramId,
|
|
5076
|
+
type: engram.engramKind,
|
|
5077
|
+
capabilities: [...engram.capabilities]
|
|
5078
|
+
},
|
|
5079
|
+
capabilities: engram.capabilities,
|
|
5080
|
+
role: "engram",
|
|
5081
|
+
...engram.version !== null ? { version: engram.version } : {}
|
|
5082
|
+
})
|
|
5083
|
+
);
|
|
3452
5084
|
}
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
5085
|
+
isEngramRegister(signal) {
|
|
5086
|
+
if (signal.payload["role"] === "engram" || signal.payload["engram"]) return true;
|
|
5087
|
+
const dtype = signal.directed?.type ?? null;
|
|
5088
|
+
if (dtype && (this.engramKindIndex.has(dtype) || this.engramRegKindIndex.has(dtype))) {
|
|
5089
|
+
return true;
|
|
5090
|
+
}
|
|
5091
|
+
return false;
|
|
5092
|
+
}
|
|
5093
|
+
recordEngramRegistration(signal) {
|
|
5094
|
+
const d = signal.directed;
|
|
5095
|
+
if (!d || !d.id && !d.type) return;
|
|
5096
|
+
let caps = [...d.capabilities];
|
|
5097
|
+
if (!caps.length) {
|
|
5098
|
+
caps = [...signal.payload["capabilities"] ?? []];
|
|
5099
|
+
}
|
|
5100
|
+
const key = d.id ?? d.type;
|
|
5101
|
+
this._engramRegistrations.set(key, { id: d.id, type: d.type, capabilities: caps });
|
|
5102
|
+
if (d.type) {
|
|
5103
|
+
const bucket = this.engramRegKindIndex.get(d.type) ?? /* @__PURE__ */ new Set();
|
|
5104
|
+
bucket.add(key);
|
|
5105
|
+
this.engramRegKindIndex.set(d.type, bucket);
|
|
3466
5106
|
}
|
|
3467
5107
|
}
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
5108
|
+
// -- Engram: caller-side helpers -------------------------------------
|
|
5109
|
+
/** Resolve (traceId, parentId) for a caller-side engram op: explicit ids
|
|
5110
|
+
* win, then the ambient task context (bound by Axon.handleTask), then a
|
|
5111
|
+
* freshly minted trace (the pre-task-hydration shape). */
|
|
5112
|
+
static resolveTrace(traceId, parentId) {
|
|
5113
|
+
let tid = traceId;
|
|
5114
|
+
let pid = parentId;
|
|
5115
|
+
if (tid === void 0) {
|
|
5116
|
+
const amb = ambientTrace();
|
|
5117
|
+
if (amb !== null) {
|
|
5118
|
+
tid = amb[0];
|
|
5119
|
+
if (pid === void 0) pid = amb[1];
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
return [tid ?? newTraceId(), pid ?? newEventId()];
|
|
3478
5123
|
}
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
this.
|
|
5124
|
+
/** Emit RECALL and await RECALLED. Trace attribution: explicit ids win,
|
|
5125
|
+
* then the ambient task context, then a fresh trace. */
|
|
5126
|
+
async recall(args) {
|
|
5127
|
+
const [tid, pid] = _Dendrite.resolveTrace(args.traceId, args.parentId);
|
|
5128
|
+
return this.engramClient.recall({
|
|
5129
|
+
query: args.query,
|
|
5130
|
+
traceId: tid,
|
|
5131
|
+
parentId: pid,
|
|
5132
|
+
...args.engramId !== void 0 ? { engramId: args.engramId } : {},
|
|
5133
|
+
...args.engramKind !== void 0 ? { engramKind: args.engramKind } : {},
|
|
5134
|
+
...args.filters !== void 0 ? { filters: args.filters } : {},
|
|
5135
|
+
...args.contextRef !== void 0 ? { contextRef: args.contextRef } : {},
|
|
5136
|
+
...args.deadlineMs !== void 0 ? { deadlineMs: args.deadlineMs } : {},
|
|
5137
|
+
...args.recallMode !== void 0 ? { recallMode: args.recallMode } : {},
|
|
5138
|
+
...args.minConfidence !== void 0 ? { minConfidence: args.minConfidence } : {},
|
|
5139
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
5140
|
+
});
|
|
3484
5141
|
}
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
this.
|
|
5142
|
+
/** Emit IMPRINT. Resolves null unless `awaitAck: true`. Trace attribution
|
|
5143
|
+
* as {@link recall}. */
|
|
5144
|
+
async imprint(args) {
|
|
5145
|
+
const [tid, pid] = _Dendrite.resolveTrace(args.traceId, args.parentId);
|
|
5146
|
+
return this.engramClient.imprint({
|
|
5147
|
+
op: args.op,
|
|
5148
|
+
entry: args.entry,
|
|
5149
|
+
traceId: tid,
|
|
5150
|
+
parentId: pid,
|
|
5151
|
+
...args.engramId !== void 0 ? { engramId: args.engramId } : {},
|
|
5152
|
+
...args.engramKind !== void 0 ? { engramKind: args.engramKind } : {},
|
|
5153
|
+
...args.mergeKey !== void 0 ? { mergeKey: args.mergeKey } : {},
|
|
5154
|
+
...args.awaitAck !== void 0 ? { awaitAck: args.awaitAck } : {},
|
|
5155
|
+
...args.deadlineMs !== void 0 ? { deadlineMs: args.deadlineMs } : {},
|
|
5156
|
+
...args.meta !== void 0 ? { meta: args.meta } : {}
|
|
5157
|
+
});
|
|
3490
5158
|
}
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
if (
|
|
3494
|
-
|
|
3495
|
-
if (
|
|
5159
|
+
async updateRegistry(signal) {
|
|
5160
|
+
if (this.registryStore === null) return;
|
|
5161
|
+
if (signal.payload["role"] === "engram" || signal.payload["engram"]) return;
|
|
5162
|
+
const neuronId = signal.directed?.id ?? null;
|
|
5163
|
+
if (!neuronId) return;
|
|
5164
|
+
let reason = null;
|
|
5165
|
+
if (signal.type === SignalType.REGISTER) {
|
|
5166
|
+
await this.registryStore.upsert(
|
|
5167
|
+
neuronRecord({
|
|
5168
|
+
neuron_id: neuronId,
|
|
5169
|
+
capabilities: signal.payload["capabilities"] ?? [],
|
|
5170
|
+
version: signal.payload["version"] ?? null,
|
|
5171
|
+
status: "registered",
|
|
5172
|
+
last_heartbeat: signal.ts
|
|
5173
|
+
})
|
|
5174
|
+
);
|
|
5175
|
+
reason = "register";
|
|
5176
|
+
} else if (signal.type === SignalType.DEREGISTER) {
|
|
5177
|
+
await this.registryStore.markDeregistered(neuronId);
|
|
5178
|
+
reason = "deregister";
|
|
5179
|
+
} else if (signal.type === SignalType.HEARTBEAT) {
|
|
5180
|
+
const status = signal.payload["status"];
|
|
5181
|
+
if (status) await this.registryStore.touchHeartbeat(neuronId, signal.ts, status);
|
|
5182
|
+
else await this.registryStore.touchHeartbeat(neuronId, signal.ts);
|
|
5183
|
+
reason = "heartbeat";
|
|
5184
|
+
}
|
|
5185
|
+
if (reason !== null) {
|
|
5186
|
+
await this.hooks._fireRefresh({ reason, neuronId, extra: {} });
|
|
5187
|
+
}
|
|
3496
5188
|
}
|
|
3497
5189
|
};
|
|
3498
|
-
|
|
3499
|
-
if (!Array.isArray(raw)) return [];
|
|
3500
|
-
const out = [];
|
|
3501
|
-
for (const h of raw) {
|
|
3502
|
-
if (h === null || typeof h !== "object") continue;
|
|
3503
|
-
const obj = h;
|
|
3504
|
-
const entryVal = obj["entry"];
|
|
3505
|
-
out.push({
|
|
3506
|
-
id: typeof obj["id"] === "string" ? obj["id"] : "",
|
|
3507
|
-
entry: entryVal !== null && typeof entryVal === "object" && !Array.isArray(entryVal) ? entryVal : { value: entryVal },
|
|
3508
|
-
score: typeof obj["score"] === "number" ? obj["score"] : 1
|
|
3509
|
-
});
|
|
3510
|
-
}
|
|
3511
|
-
return out;
|
|
3512
|
-
}
|
|
5190
|
+
var Cortex = Dendrite;
|
|
3513
5191
|
|
|
3514
5192
|
// src/engram-sqlite.ts
|
|
3515
5193
|
var SCHEMA3 = `
|
|
@@ -4047,10 +5725,11 @@ var PostgresEngram = class extends Engram {
|
|
|
4047
5725
|
};
|
|
4048
5726
|
|
|
4049
5727
|
// src/index.ts
|
|
4050
|
-
var VERSION = true ? "0.1.
|
|
5728
|
+
var VERSION = true ? "0.1.3" : "0.0.0-dev";
|
|
4051
5729
|
export {
|
|
4052
5730
|
AXON_TYPES,
|
|
4053
5731
|
Axon,
|
|
5732
|
+
COSMO_INTENT_SYSTEM_PROMPT,
|
|
4054
5733
|
Cortex,
|
|
4055
5734
|
DendriteProtocolError as CortexProtocolError,
|
|
4056
5735
|
Dendrite,
|
|
@@ -4071,14 +5750,19 @@ export {
|
|
|
4071
5750
|
MemoryRegistryStore,
|
|
4072
5751
|
MemorySynapse,
|
|
4073
5752
|
NatsSynapse,
|
|
5753
|
+
PATHWAY_TYPES,
|
|
5754
|
+
Pathway,
|
|
5755
|
+
PathwayClosedError,
|
|
4074
5756
|
PostgresEngram,
|
|
4075
5757
|
PostgresRegistryStore,
|
|
4076
5758
|
SYNAPSE_TYPES,
|
|
4077
5759
|
SignalType,
|
|
4078
5760
|
SqliteEngram,
|
|
4079
5761
|
SqliteRegistryStore,
|
|
5762
|
+
TERMINAL_TYPES,
|
|
4080
5763
|
VERSION,
|
|
4081
5764
|
agentOutputSignal,
|
|
5765
|
+
ambientTrace,
|
|
4082
5766
|
anthropicNeuron,
|
|
4083
5767
|
bidSignal,
|
|
4084
5768
|
clarificationAnswerSignal,
|
|
@@ -4099,6 +5783,7 @@ export {
|
|
|
4099
5783
|
errorSignal,
|
|
4100
5784
|
escalationSignal,
|
|
4101
5785
|
finalSignal,
|
|
5786
|
+
followupPrompt,
|
|
4102
5787
|
heartbeatSignal,
|
|
4103
5788
|
huggingFaceNeuron,
|
|
4104
5789
|
imprintSignal,
|
|
@@ -4126,8 +5811,11 @@ export {
|
|
|
4126
5811
|
recalledSignal,
|
|
4127
5812
|
registerSignal,
|
|
4128
5813
|
reply,
|
|
5814
|
+
runWithTraceContext,
|
|
4129
5815
|
standardMcpServers,
|
|
4130
5816
|
synapseFromUrl,
|
|
5817
|
+
taskAwardedSignal,
|
|
5818
|
+
taskDeclinedSignal,
|
|
4131
5819
|
taskOfferSignal,
|
|
4132
5820
|
taskSignal,
|
|
4133
5821
|
thoughtDeltaSignal,
|