mindgraph-mcp 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +23 -3
  2. package/dist/cli.js +1055 -225
  3. package/dist/index.js +1008 -183
  4. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -60,6 +60,9 @@ function loadHookEnv(dir) {
60
60
  }
61
61
  }
62
62
 
63
+ // src/tools.ts
64
+ var import_node_crypto5 = require("crypto");
65
+
63
66
  // src/error-detail.ts
64
67
  var import_mindgraph = require("mindgraph");
65
68
  function errorDetail(e) {
@@ -238,29 +241,29 @@ async function runProcess(executable, args, timeoutMs, env, cwd) {
238
241
  child.stderr.on("data", collect(stderr));
239
242
  child.on("error", (cause) => {
240
243
  clearTimeout(timer);
241
- const error = new Error(cause.message);
242
- error.kind = cause.code === "ENOENT" ? "absent" : "command_failed";
243
- rejectPromise(error);
244
+ const error2 = new Error(cause.message);
245
+ error2.kind = cause.code === "ENOENT" ? "absent" : "command_failed";
246
+ rejectPromise(error2);
244
247
  });
245
248
  child.on("close", (code) => {
246
249
  clearTimeout(timer);
247
250
  if (timedOut) {
248
- const error = new Error(`command timed out after ${timeoutMs}ms`);
249
- error.kind = "timeout";
250
- rejectPromise(error);
251
+ const error2 = new Error(`command timed out after ${timeoutMs}ms`);
252
+ error2.kind = "timeout";
253
+ rejectPromise(error2);
251
254
  return;
252
255
  }
253
256
  if (outputBytes > MAX_OUTPUT_BYTES) {
254
- const error = new Error("codegraph output exceeded 5 MiB");
255
- error.kind = "command_failed";
256
- rejectPromise(error);
257
+ const error2 = new Error("codegraph output exceeded 5 MiB");
258
+ error2.kind = "command_failed";
259
+ rejectPromise(error2);
257
260
  return;
258
261
  }
259
262
  if (code !== 0) {
260
- const error = new Error(Buffer.concat(stderr).toString("utf8").trim());
261
- error.kind = "command_failed";
262
- error.exitCode = code;
263
- rejectPromise(error);
263
+ const error2 = new Error(Buffer.concat(stderr).toString("utf8").trim());
264
+ error2.kind = "command_failed";
265
+ error2.exitCode = code;
266
+ rejectPromise(error2);
264
267
  return;
265
268
  }
266
269
  resolvePromise(Buffer.concat(stdout).toString("utf8"));
@@ -813,9 +816,7 @@ function codeRefIdentityKey(ref) {
813
816
  qualified_name: ref.qualifiedName,
814
817
  disambiguator: {
815
818
  signature: ref.signature ?? "",
816
- path: ref.path,
817
- start_line: ref.startLine,
818
- end_line: ref.endLine
819
+ path: ref.path
819
820
  }
820
821
  };
821
822
  }
@@ -1193,7 +1194,7 @@ async function anchorCodeRef(client2, ref, repositoryUid, spaceUid, agentId) {
1193
1194
  identity_space_uid: spaceUid,
1194
1195
  agent_id: agentId
1195
1196
  });
1196
- if (response.uid) {
1197
+ if (response.uid && response.created !== false && response.status !== "existing") {
1197
1198
  await client2.entity({
1198
1199
  action: "relate",
1199
1200
  source_uid: response.uid,
@@ -1556,8 +1557,8 @@ async function handleSyncTool(client2, args) {
1556
1557
  }
1557
1558
  }
1558
1559
  return ok2(await remote.memorySync(request));
1559
- } catch (error) {
1560
- return err2("sync_failed", errorDetail(error));
1560
+ } catch (error2) {
1561
+ return err2("sync_failed", errorDetail(error2));
1561
1562
  }
1562
1563
  }
1563
1564
  async function scanRepository(remote, repository, requested, provider, agentId) {
@@ -1595,6 +1596,9 @@ async function scanRepository(remote, repository, requested, provider, agentId)
1595
1596
  };
1596
1597
  }
1597
1598
 
1599
+ // src/memory-tool.ts
1600
+ var import_node_crypto4 = require("crypto");
1601
+
1598
1602
  // src/render.ts
1599
1603
  var LABEL_MAX = 200;
1600
1604
  var SUMMARY_MAX = 400;
@@ -1603,6 +1607,8 @@ var ARTICLE_MAX = 6e3;
1603
1607
  var CHUNK_MAX = 6e3;
1604
1608
  var RELATIONSHIP_MAX = 40;
1605
1609
  var RENDER_CHAR_BUDGET = 24e3;
1610
+ var MIN_RENDER_CHAR_BUDGET = 512;
1611
+ var BOUNDED_MARKER_RESERVE = 220;
1606
1612
  var CURATION_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
1607
1613
  var INFRA_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
1608
1614
  function obj(value) {
@@ -1614,12 +1620,22 @@ function str(value) {
1614
1620
  function num(value) {
1615
1621
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1616
1622
  }
1623
+ function edgeTypeName(value) {
1624
+ return str(value) ?? str(obj(value)?.Custom);
1625
+ }
1626
+ function renderBudget(requested) {
1627
+ if (requested === void 0 || !Number.isFinite(requested)) return RENDER_CHAR_BUDGET;
1628
+ return Math.min(
1629
+ RENDER_CHAR_BUDGET,
1630
+ Math.max(MIN_RENDER_CHAR_BUDGET, Math.floor(requested))
1631
+ );
1632
+ }
1617
1633
  function clip(value, max, state) {
1618
- const text = str(value);
1619
- if (!text) return void 0;
1620
- if (text.length <= max) return text;
1634
+ const text2 = str(value);
1635
+ if (!text2) return void 0;
1636
+ if (text2.length <= max) return text2;
1621
1637
  state.bounded = true;
1622
- return `${text.slice(0, max - 1)}\u2026`;
1638
+ return `${text2.slice(0, max - 1)}\u2026`;
1623
1639
  }
1624
1640
  function isNodeShaped(value) {
1625
1641
  const node = obj(value);
@@ -1741,7 +1757,7 @@ function relationshipItems(edges, labelByUid, state, options) {
1741
1757
  for (const raw of edges) {
1742
1758
  const edge = obj(raw);
1743
1759
  if (!edge) continue;
1744
- const type = str(edge.edge_type) || "?";
1760
+ const type = edgeTypeName(edge.edge_type) || "?";
1745
1761
  const from = str(edge.from_uid);
1746
1762
  const to = str(edge.to_uid);
1747
1763
  if (CURATION_EDGE_TYPES.has(type)) continue;
@@ -1754,25 +1770,29 @@ function relationshipItems(edges, labelByUid, state, options) {
1754
1770
  if (lines.length > RELATIONSHIP_MAX) {
1755
1771
  state.bounded = true;
1756
1772
  const extra = lines.length - RELATIONSHIP_MAX;
1773
+ state.omitted += extra;
1757
1774
  return [...lines.slice(0, RELATIONSHIP_MAX), `- (+${extra} more relationships)`];
1758
1775
  }
1759
1776
  return lines;
1760
1777
  }
1761
- function assemble(header, sections, state) {
1778
+ function assemble(header, sections, state, maxChars) {
1779
+ const budget = renderBudget(maxChars);
1780
+ const packingBudget = Math.max(header.length, budget - BOUNDED_MARKER_RESERVE);
1762
1781
  const lines = [header];
1763
1782
  let used = header.length;
1764
1783
  for (const section of sections) {
1765
1784
  if (section.items.length === 0 && !section.heading) continue;
1766
1785
  const headingCost = section.heading.length + 2;
1767
- if (used + headingCost > RENDER_CHAR_BUDGET) {
1786
+ if (used + headingCost > packingBudget) {
1768
1787
  state.bounded = true;
1788
+ state.omitted += Math.max(1, section.items.length);
1769
1789
  continue;
1770
1790
  }
1771
1791
  let sectionUsed = 0;
1772
1792
  const kept = [];
1773
1793
  let dropped = 0;
1774
1794
  for (const item of section.items) {
1775
- if (used + headingCost + sectionUsed + item.length + 1 > RENDER_CHAR_BUDGET) {
1795
+ if (used + headingCost + sectionUsed + item.length + 1 > packingBudget) {
1776
1796
  dropped += 1;
1777
1797
  continue;
1778
1798
  }
@@ -1781,13 +1801,17 @@ function assemble(header, sections, state) {
1781
1801
  }
1782
1802
  if (kept.length === 0 && section.items.length > 0) {
1783
1803
  state.bounded = true;
1804
+ state.omitted += dropped;
1784
1805
  continue;
1785
1806
  }
1786
1807
  if (dropped > 0) {
1787
1808
  state.bounded = true;
1809
+ state.omitted += dropped;
1788
1810
  const marker = `(+${dropped} more not shown)`;
1789
- kept.push(marker);
1790
- sectionUsed += marker.length + 1;
1811
+ if (used + headingCost + sectionUsed + marker.length + 1 <= packingBudget) {
1812
+ kept.push(marker);
1813
+ sectionUsed += marker.length + 1;
1814
+ }
1791
1815
  }
1792
1816
  lines.push(
1793
1817
  section.items.length === 0 ? section.heading : section.heading ? `${section.heading}
@@ -1797,10 +1821,10 @@ ${kept.join("\n")}` : kept.join("\n")
1797
1821
  }
1798
1822
  if (state.bounded) {
1799
1823
  lines.push(
1800
- '\u2026[rendered context bounded \u2014 fetch full node content by uid, or pass format: "json" for the raw response]'
1824
+ `\u2026[rendered context bounded to ${budget} chars; ${state.omitted} item(s) omitted \u2014 fetch full node content by uid, narrow the query, raise max_output_chars, or pass format: "json" for the raw response]`
1801
1825
  );
1802
1826
  }
1803
- return lines.join("\n\n");
1827
+ return lines.join("\n\n").slice(0, budget);
1804
1828
  }
1805
1829
  function policySections(payload, state) {
1806
1830
  const policies = Array.isArray(payload.applicable_policies) ? payload.applicable_policies : [];
@@ -1821,7 +1845,7 @@ function hiddenSections(payload) {
1821
1845
  }
1822
1846
  ];
1823
1847
  }
1824
- function renderContext(response) {
1848
+ function renderContext(response, maxChars) {
1825
1849
  const payload = obj(response);
1826
1850
  if (!payload) return void 0;
1827
1851
  const graph = obj(payload.graph);
@@ -1831,7 +1855,7 @@ function renderContext(response) {
1831
1855
  const infraOnly = shaped.length - nodes.length;
1832
1856
  const articles = Array.isArray(payload.articles) ? payload.articles : [];
1833
1857
  const chunks = Array.isArray(payload.chunks) ? payload.chunks : [];
1834
- const state = { bounded: false };
1858
+ const state = { bounded: false, omitted: 0 };
1835
1859
  const sections = [];
1836
1860
  if (articles.length > 0) {
1837
1861
  sections.push({
@@ -1874,11 +1898,129 @@ ${clip(article.content, ARTICLE_MAX, state) || ""}`
1874
1898
  return "No results found for this search.";
1875
1899
  }
1876
1900
  if (nodes.length === 0 && articles.length === 0 && chunks.length === 0) {
1877
- return assemble("# Retrieved context\n\nNo graph results matched this search.", sections, state);
1901
+ return assemble(
1902
+ "# Retrieved context\n\nNo graph results matched this search.",
1903
+ sections,
1904
+ state,
1905
+ maxChars
1906
+ );
1907
+ }
1908
+ return assemble("# Retrieved context", sections, state, maxChars);
1909
+ }
1910
+ function resumeEntry(raw, state, fallbackType) {
1911
+ const wrapper = obj(raw);
1912
+ if (!wrapper) return void 0;
1913
+ const value = obj(wrapper.node) ?? wrapper;
1914
+ if (isNodeShaped(value)) return nodeLines(value, state);
1915
+ const uid = str(value.uid);
1916
+ const props = obj(value.props);
1917
+ const label = clip(value.label, LABEL_MAX, state) ?? clip(value.name, LABEL_MAX, state) ?? clip(value.title, LABEL_MAX, state) ?? clip(value.action, LABEL_MAX, state) ?? fallbackType;
1918
+ const status = str(value.status) ?? str(props?.status);
1919
+ const summary = clip(value.summary, SUMMARY_MAX, state) ?? clip(value.description, SUMMARY_MAX, state) ?? clip(value.content, SUMMARY_MAX, state) ?? clip(value.outcome_summary, SUMMARY_MAX, state) ?? clip(props?.summary, SUMMARY_MAX, state);
1920
+ const details = [
1921
+ status ? `status ${status}` : void 0,
1922
+ str(value.path) ? `path ${str(value.path)}` : void 0,
1923
+ str(value.symbol) ? `symbol ${str(value.symbol)}` : void 0
1924
+ ].filter(Boolean);
1925
+ const head = `- **${label}**${uid ? ` [${uid}]` : ""}${details.length > 0 ? ` (${details.join(", ")})` : ""}`;
1926
+ return summary && summary !== label ? `${head}: ${summary}` : head;
1927
+ }
1928
+ function resumeSection(heading, values, state, fallbackType) {
1929
+ const items = values.map((value) => resumeEntry(value, state, fallbackType)).filter((value) => Boolean(value));
1930
+ return items.length > 0 ? { heading, items } : void 0;
1931
+ }
1932
+ function renderResumeContext(response, maxChars) {
1933
+ const payload = obj(response);
1934
+ if (!payload) return void 0;
1935
+ const state = { bounded: false, omitted: 0 };
1936
+ const status = str(payload.status);
1937
+ if (!payload.task && status === "no_eligible_work") {
1938
+ const scopedOut = Array.isArray(payload.scoped_out_tasks) ? payload.scoped_out_tasks.length : 0;
1939
+ const note = scopedOut > 0 ? ` ${scopedOut} task(s) exist outside the requested scope.` : "";
1940
+ return assemble(
1941
+ "# Resume context",
1942
+ [{ heading: `No eligible work was found in the requested scope.${note}`, items: [] }],
1943
+ state,
1944
+ maxChars
1945
+ );
1946
+ }
1947
+ const sections = [];
1948
+ const push = (section) => {
1949
+ if (section) sections.push(section);
1950
+ };
1951
+ push(resumeSection("## Current task", [payload.task], state, "Task"));
1952
+ push(
1953
+ resumeSection(
1954
+ "## Goal and project",
1955
+ [payload.goal, payload.project].filter(Boolean),
1956
+ state,
1957
+ "Scope"
1958
+ )
1959
+ );
1960
+ push(resumeSection("## Plan", [payload.plan], state, "Plan"));
1961
+ push(
1962
+ resumeSection(
1963
+ "## Next steps",
1964
+ Array.isArray(payload.next_steps) ? payload.next_steps : [],
1965
+ state,
1966
+ "Next step"
1967
+ )
1968
+ );
1969
+ push(
1970
+ resumeSection(
1971
+ "## Blockers",
1972
+ Array.isArray(payload.blockers) ? payload.blockers : [],
1973
+ state,
1974
+ "Blocker"
1975
+ )
1976
+ );
1977
+ const knowledge = obj(payload.knowledge);
1978
+ push(
1979
+ resumeSection(
1980
+ "## Relevant lessons and risks",
1981
+ [
1982
+ ...Array.isArray(knowledge?.lessons) ? knowledge.lessons : [],
1983
+ ...Array.isArray(knowledge?.risks) ? knowledge.risks : []
1984
+ ],
1985
+ state,
1986
+ "Knowledge"
1987
+ )
1988
+ );
1989
+ push(
1990
+ resumeSection(
1991
+ "## Active execution",
1992
+ [payload.active_execution].filter(Boolean),
1993
+ state,
1994
+ "Execution"
1995
+ )
1996
+ );
1997
+ push(
1998
+ resumeSection(
1999
+ "## Recent executions",
2000
+ Array.isArray(payload.recent_executions) ? payload.recent_executions : [],
2001
+ state,
2002
+ "Execution"
2003
+ )
2004
+ );
2005
+ push(
2006
+ resumeSection(
2007
+ "## Code targets",
2008
+ Array.isArray(payload.code_targets) ? payload.code_targets : [],
2009
+ state,
2010
+ "Code target"
2011
+ )
2012
+ );
2013
+ push(resumeSection("## Lease", [payload.lease].filter(Boolean), state, "Lease"));
2014
+ const warning = clip(payload.warning, SUMMARY_MAX, state);
2015
+ if (warning) sections.push({ heading: "## Warning", items: [`- ${warning}`] });
2016
+ const selectionReason = clip(payload.selection_reason, SUMMARY_MAX, state);
2017
+ if (selectionReason) {
2018
+ sections.push({ heading: "## Selection", items: [`- ${selectionReason}`] });
1878
2019
  }
1879
- return assemble("# Retrieved context", sections, state);
2020
+ if (sections.length === 0) return void 0;
2021
+ return assemble("# Resume context", sections, state, maxChars);
1880
2022
  }
1881
- function renderNodeList(response, title, limit) {
2023
+ function renderNodeList(response, title, limit, maxChars) {
1882
2024
  let candidates = [];
1883
2025
  if (Array.isArray(response)) {
1884
2026
  candidates = response;
@@ -1896,8 +2038,9 @@ function renderNodeList(response, title, limit) {
1896
2038
  let nodes = candidates.map(unwrapSearchResult).filter((node) => Boolean(node));
1897
2039
  if (nodes.length === 0) return void 0;
1898
2040
  const total = nodes.length;
1899
- const state = { bounded: false };
2041
+ const state = { bounded: false, omitted: 0 };
1900
2042
  if (limit !== void 0 && limit > 0 && nodes.length > limit) {
2043
+ state.omitted += nodes.length - limit;
1901
2044
  nodes = nodes.slice(0, limit);
1902
2045
  state.bounded = true;
1903
2046
  }
@@ -1906,18 +2049,23 @@ function renderNodeList(response, title, limit) {
1906
2049
  return assemble(
1907
2050
  `# ${title} (${shown === total ? total : `${shown} of ${total}`})`,
1908
2051
  sections,
1909
- state
2052
+ state,
2053
+ maxChars
1910
2054
  );
1911
2055
  }
1912
- function renderTraversal(response) {
2056
+ function renderTraversal(response, maxChars) {
2057
+ const directSteps = Array.isArray(response) ? response : void 0;
1913
2058
  const payload = obj(response);
1914
- if (!payload) return void 0;
1915
- const container = obj(payload.graph) ?? payload;
2059
+ if (!payload && !directSteps) return void 0;
2060
+ const body = payload ?? {};
2061
+ const container = obj(body.graph) ?? body;
1916
2062
  const rawNodes = Array.isArray(container.nodes) ? container.nodes : [];
1917
2063
  const nodes = rawNodes.filter(isNodeShaped);
1918
- const paths = Array.isArray(payload.paths) ? payload.paths : [];
1919
- if (nodes.length === 0 && paths.length === 0) return void 0;
1920
- const state = { bounded: false };
2064
+ const paths = Array.isArray(body.paths) ? body.paths : [];
2065
+ const hasSteps = directSteps !== void 0 || Object.hasOwn(body, "steps");
2066
+ const rawSteps = directSteps ?? (Array.isArray(body.steps) ? body.steps : []);
2067
+ if (nodes.length === 0 && paths.length === 0 && !hasSteps) return void 0;
2068
+ const state = { bounded: false, omitted: 0 };
1921
2069
  const sections = [];
1922
2070
  const labelByUid = /* @__PURE__ */ new Map();
1923
2071
  for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
@@ -1941,6 +2089,42 @@ function renderTraversal(response) {
1941
2089
  });
1942
2090
  sections.push({ heading: "## Paths", items });
1943
2091
  }
2092
+ if (hasSteps) {
2093
+ const stepItems = rawSteps.map((raw) => obj(raw)).filter((step) => Boolean(step)).map((step) => {
2094
+ const depth = num(step.depth);
2095
+ const uid = str(step.node_uid) ?? str(step.uid) ?? str(step.edge_to_uid) ?? "?";
2096
+ const parent = str(step.parent_uid) ?? str(step.edge_from_uid);
2097
+ const label = str(step.label) || "(unlabeled)";
2098
+ const relation = edgeTypeName(step.edge_type);
2099
+ const role = str(step.traversal_role);
2100
+ const type = str(step.node_type) ?? str(obj(step.node_type)?.Custom);
2101
+ const cost = num(step.path_cost);
2102
+ const confidence = num(step.path_confidence);
2103
+ const prefix = [
2104
+ depth !== void 0 ? `depth ${depth}` : void 0,
2105
+ role,
2106
+ relation ? `\u2014${relation}\u2192` : void 0
2107
+ ].filter(Boolean).join(" ");
2108
+ const meta = [
2109
+ type,
2110
+ parent ? `parent ${parent}` : void 0,
2111
+ cost !== void 0 ? `cost ${cost}` : void 0,
2112
+ confidence !== void 0 ? `confidence ${confidence}` : void 0
2113
+ ].filter(Boolean).join(", ");
2114
+ return `- ${prefix ? `${prefix}: ` : ""}**${label}** [${uid}]${meta ? ` (${meta})` : ""}`;
2115
+ });
2116
+ if (stepItems.length > 0) {
2117
+ sections.push({ heading: "## Traversal steps", items: stepItems });
2118
+ } else {
2119
+ const start = str(body.start_uid) || "?";
2120
+ const end = str(body.end_uid);
2121
+ const mode2 = str(body.mode);
2122
+ sections.push({
2123
+ heading: mode2 === "path" && end ? `No path found from [${start}] to [${end}].` : `No traversal steps returned from [${start}].`,
2124
+ items: []
2125
+ });
2126
+ }
2127
+ }
1944
2128
  if (nodes.length > 0) {
1945
2129
  sections.push(...groupedNodeSections(nodes, state, "## Nodes\n\n"));
1946
2130
  const relationships = relationshipItems(
@@ -1952,7 +2136,13 @@ function renderTraversal(response) {
1952
2136
  sections.push({ heading: "## Relationships", items: relationships });
1953
2137
  }
1954
2138
  }
1955
- return assemble("# Graph traversal", sections, state);
2139
+ const mode = str(body.mode);
2140
+ return assemble(
2141
+ `# Graph traversal${mode ? ` (${mode})` : ""}`,
2142
+ sections,
2143
+ state,
2144
+ maxChars
2145
+ );
1956
2146
  }
1957
2147
  function neighborLine(value) {
1958
2148
  const node = obj(value);
@@ -1973,7 +2163,7 @@ function renderOntologyAnswer(response) {
1973
2163
  if (objects.length === 0 && cognitiveEmpty && provenance.length === 0) {
1974
2164
  return hasQuerySignature ? "No matching domain objects." : void 0;
1975
2165
  }
1976
- const state = { bounded: false };
2166
+ const state = { bounded: false, omitted: 0 };
1977
2167
  const sections = [];
1978
2168
  if (objects.length > 0) {
1979
2169
  sections.push(...groupedNodeSections(objects, state));
@@ -2039,7 +2229,7 @@ function renderObjectList(response, title) {
2039
2229
  if (items.length === 0) {
2040
2230
  return rawItems ? `# ${title} (0 results)` : void 0;
2041
2231
  }
2042
- const state = { bounded: false };
2232
+ const state = { bounded: false, omitted: 0 };
2043
2233
  const sections = groupedNodeSections(items, state);
2044
2234
  const total = num(payload.total_count);
2045
2235
  const truncations = Array.isArray(payload.truncation_reasons) ? payload.truncation_reasons.filter((reason) => typeof reason === "string") : [];
@@ -2055,7 +2245,7 @@ function renderJobs(response) {
2055
2245
  const jobs = Array.isArray(response) ? response : obj(response)?.jobs;
2056
2246
  if (!Array.isArray(jobs)) return void 0;
2057
2247
  if (jobs.length === 0) return "No ingestion jobs.";
2058
- const state = { bounded: false };
2248
+ const state = { bounded: false, omitted: 0 };
2059
2249
  const sorted = jobs.map((raw) => obj(raw)).filter((job) => Boolean(job)).sort((a, b) => (num(b.created_at) ?? 0) - (num(a.created_at) ?? 0));
2060
2250
  const shown = sorted.slice(0, JOBS_RENDER_MAX);
2061
2251
  if (shown.length < sorted.length) state.bounded = true;
@@ -2081,7 +2271,7 @@ function renderJobs(response) {
2081
2271
  function renderSignals(response) {
2082
2272
  const payload = obj(response);
2083
2273
  if (!payload) return void 0;
2084
- const state = { bounded: false };
2274
+ const state = { bounded: false, omitted: 0 };
2085
2275
  const sections = [];
2086
2276
  for (const [key, value] of Object.entries(payload)) {
2087
2277
  if (!Array.isArray(value) || value.length === 0) continue;
@@ -2106,7 +2296,581 @@ function renderSignals(response) {
2106
2296
  return assemble("# Synthesis signals", sections, state);
2107
2297
  }
2108
2298
 
2299
+ // src/memory-tool.ts
2300
+ var RESULT_SCHEMA_VERSION = "mindgraph.memory.result.v1";
2301
+ var DEFAULT_OUTPUT_CHARS = 8e3;
2302
+ var MIN_OUTPUT_CHARS = 512;
2303
+ var MAX_OUTPUT_CHARS = 24e3;
2304
+ var HIDDEN_CONTEXT_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
2305
+ var HIDDEN_CONTEXT_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
2306
+ var MEMORY_TOOL = {
2307
+ name: "mindgraph_memory",
2308
+ description: "Read the portable MindGraph memory surface. Use action='context' with mode='resume' or 'continuity' for authoritative task-first work state, and mode='topic' with a short keyword query for prior knowledge relevant to the user's request. Use action='status' to inspect integration capabilities and limitations. This tool never writes; durable capture and correction use the existing typed MindGraph write tools.",
2309
+ inputSchema: {
2310
+ type: "object",
2311
+ properties: {
2312
+ action: {
2313
+ type: "string",
2314
+ enum: ["status", "context"],
2315
+ description: "Read integration status or bounded memory context"
2316
+ },
2317
+ mode: {
2318
+ type: "string",
2319
+ enum: ["topic", "continuity", "resume"],
2320
+ description: "Context strategy. topic uses query-shaped retrieval; continuity/resume use authoritative task-first resume_work."
2321
+ },
2322
+ query: {
2323
+ type: "string",
2324
+ description: "Required for mode='topic': 1-3 discriminating BM25 keywords, not a full sentence"
2325
+ },
2326
+ project_uid: { type: "string", description: "Optional project scope" },
2327
+ task_uid: { type: "string", description: "Optional task scope for resume context" },
2328
+ session_uid: { type: "string", description: "Optional durable session scope" },
2329
+ scope_uids: {
2330
+ type: "array",
2331
+ items: { type: "string" },
2332
+ description: "Optional explicit graph scopes for continuity/resume"
2333
+ },
2334
+ node_types: {
2335
+ type: "array",
2336
+ items: { type: "string" },
2337
+ description: "Optional node-type filter for topic context"
2338
+ },
2339
+ layer: {
2340
+ type: "string",
2341
+ enum: ["reality", "epistemic", "intent", "action", "memory", "agent"],
2342
+ description: "Optional cognitive-layer filter for topic context"
2343
+ },
2344
+ limit: {
2345
+ type: "integer",
2346
+ minimum: 1,
2347
+ maximum: 50,
2348
+ default: 8,
2349
+ description: "Maximum direct graph items requested before bounded expansion"
2350
+ },
2351
+ include_chunks: {
2352
+ type: "boolean",
2353
+ default: false,
2354
+ description: "Include bounded source excerpts when topic summaries are insufficient"
2355
+ },
2356
+ include_documents: {
2357
+ type: "boolean",
2358
+ default: false,
2359
+ description: "Include bounded knowledge articles for topic context"
2360
+ },
2361
+ valid_at: {
2362
+ type: "string",
2363
+ description: "Optional ISO timestamp for time-aware topic retrieval"
2364
+ },
2365
+ graph_expansion_limit: {
2366
+ type: "integer",
2367
+ minimum: 0,
2368
+ maximum: 20,
2369
+ default: 3,
2370
+ description: "Bounded graph neighbors added after direct topic matches"
2371
+ },
2372
+ graph_max_depth: {
2373
+ type: "integer",
2374
+ minimum: 0,
2375
+ maximum: 5,
2376
+ default: 2,
2377
+ description: "Maximum graph-expansion depth for topic context"
2378
+ },
2379
+ max_output_chars: {
2380
+ type: "integer",
2381
+ minimum: MIN_OUTPUT_CHARS,
2382
+ maximum: MAX_OUTPUT_CHARS,
2383
+ default: DEFAULT_OUTPUT_CHARS,
2384
+ description: "Hard character budget for model-ready context; whole items are retained or omitted"
2385
+ },
2386
+ agent_id: { type: "string", description: "Agent identity" }
2387
+ },
2388
+ required: ["action"]
2389
+ },
2390
+ outputSchema: {
2391
+ type: "object",
2392
+ properties: {
2393
+ schema_version: { type: "string", const: RESULT_SCHEMA_VERSION },
2394
+ action: { type: "string", enum: ["status", "context"] },
2395
+ profile: { type: "string" },
2396
+ server: { type: "object" },
2397
+ integration: { type: "object" },
2398
+ capabilities: { type: "object" },
2399
+ degradation: { type: "array", items: { type: "object" } },
2400
+ context_id: { type: "string" },
2401
+ mode: { type: "string", enum: ["topic", "continuity", "resume"] },
2402
+ scope: { type: "object" },
2403
+ rendered_context: { type: "string" },
2404
+ rendered_hash: { type: "string" },
2405
+ items: { type: "array", items: { type: "object" } },
2406
+ retrieval_trace: { type: "object" },
2407
+ budget: { type: "object" },
2408
+ freshness: { type: "object" },
2409
+ warnings: { type: "array", items: { type: "object" } }
2410
+ },
2411
+ required: ["schema_version", "action"],
2412
+ oneOf: [
2413
+ {
2414
+ properties: { action: { const: "status" } },
2415
+ required: ["profile", "server", "integration", "scope", "capabilities", "degradation"]
2416
+ },
2417
+ {
2418
+ properties: { action: { const: "context" } },
2419
+ required: [
2420
+ "context_id",
2421
+ "mode",
2422
+ "scope",
2423
+ "rendered_context",
2424
+ "rendered_hash",
2425
+ "items",
2426
+ "retrieval_trace",
2427
+ "budget",
2428
+ "freshness",
2429
+ "warnings"
2430
+ ]
2431
+ }
2432
+ ]
2433
+ },
2434
+ annotations: {
2435
+ readOnlyHint: true,
2436
+ destructiveHint: false,
2437
+ idempotentHint: true,
2438
+ openWorldHint: false
2439
+ }
2440
+ };
2441
+ function obj2(value) {
2442
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2443
+ }
2444
+ function text(value) {
2445
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2446
+ }
2447
+ function stableJson(value) {
2448
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
2449
+ if (value && typeof value === "object") {
2450
+ return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
2451
+ }
2452
+ return JSON.stringify(value);
2453
+ }
2454
+ function sha256(value) {
2455
+ return (0, import_node_crypto4.createHash)("sha256").update(value).digest("hex");
2456
+ }
2457
+ function outputBudget(value) {
2458
+ if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OUTPUT_CHARS;
2459
+ return Math.min(MAX_OUTPUT_CHARS, Math.max(MIN_OUTPUT_CHARS, Math.floor(value)));
2460
+ }
2461
+ function defined(values) {
2462
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
2463
+ }
2464
+ function memoryStatus(config = {}) {
2465
+ return {
2466
+ schema_version: RESULT_SCHEMA_VERSION,
2467
+ action: "status",
2468
+ profile: "M0",
2469
+ server: defined({
2470
+ name: "mindgraph-mcp",
2471
+ version: config.serverVersion ?? "unknown",
2472
+ base_url: config.baseUrl
2473
+ }),
2474
+ integration: {
2475
+ adapter_state: "model_controlled",
2476
+ harness: config.harness ?? "generic",
2477
+ configured_tool_profile: config.profile ?? "general",
2478
+ automatic_capture: false,
2479
+ automatic_delivery: false,
2480
+ delivery_verification: "unsupported",
2481
+ curator_mode: "disabled"
2482
+ },
2483
+ scope: defined({ org_id: config.orgId, agent_id: config.agentId }),
2484
+ capabilities: {
2485
+ status: true,
2486
+ topic_context: true,
2487
+ authoritative_resume_context: true,
2488
+ bounded_rendering: true,
2489
+ structured_content: true,
2490
+ typed_explicit_writes: true,
2491
+ lifecycle_event_ingest: false,
2492
+ automatic_context_injection: false,
2493
+ curator_mutation: false
2494
+ },
2495
+ degradation: [
2496
+ {
2497
+ code: "automatic_capture_unavailable",
2498
+ message: "M0 does not observe host lifecycle events; explicit typed write tools remain available."
2499
+ },
2500
+ {
2501
+ code: "delivery_unverified",
2502
+ message: "A successful MCP read does not prove that the host injected or used the returned context."
2503
+ },
2504
+ {
2505
+ code: "curator_disabled",
2506
+ message: "Curator proposals and autonomous mutation are not enabled in M0."
2507
+ }
2508
+ ]
2509
+ };
2510
+ }
2511
+ function nodeType(node) {
2512
+ const raw = node.node_type;
2513
+ if (typeof raw === "string") return raw;
2514
+ return text(obj2(raw)?.Custom) ?? "Node";
2515
+ }
2516
+ function compactItem(raw, fallbackType) {
2517
+ const wrapper = obj2(raw);
2518
+ if (!wrapper) return void 0;
2519
+ const value = obj2(wrapper.node) ?? obj2(wrapper.object) ?? wrapper;
2520
+ const uid = text(value.uid) ?? text(value.chunk_uid);
2521
+ if (!uid) return void 0;
2522
+ return defined({
2523
+ uid,
2524
+ type: fallbackType ?? nodeType(value),
2525
+ label: text(value.label) ?? text(value.document_title),
2526
+ truth_status: text(value.truth_status) ?? text(obj2(value.props)?.truth_status),
2527
+ superseded: value.superseded === true ? true : void 0,
2528
+ currently_valid: value.currently_valid === false ? false : void 0,
2529
+ valid_at_time: value.valid_at_time === false ? false : void 0,
2530
+ retrieval_origin: text(value.retrieval_origin),
2531
+ retrieval_depth: typeof value.retrieval_depth === "number" ? value.retrieval_depth : void 0
2532
+ });
2533
+ }
2534
+ function topicCandidates(raw) {
2535
+ const payload = obj2(raw);
2536
+ if (!payload) return [];
2537
+ const graph = obj2(payload.graph);
2538
+ const items = [];
2539
+ for (const article of Array.isArray(payload.articles) ? payload.articles : []) {
2540
+ const item = compactItem(article, "Article");
2541
+ if (item) items.push(item);
2542
+ }
2543
+ const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
2544
+ const labels = /* @__PURE__ */ new Map();
2545
+ for (const node of rawNodes) {
2546
+ const item = compactItem(node);
2547
+ if (item) {
2548
+ if (HIDDEN_CONTEXT_NODE_TYPES.has(text(item.type) ?? "")) continue;
2549
+ items.push(item);
2550
+ const uid = text(item.uid);
2551
+ const label = text(item.label);
2552
+ if (uid && label) labels.set(uid, label);
2553
+ }
2554
+ }
2555
+ for (const rawEdge of Array.isArray(graph?.edges) ? graph.edges : []) {
2556
+ const edge = obj2(rawEdge);
2557
+ const fromUid = text(edge?.from_uid);
2558
+ const toUid = text(edge?.to_uid);
2559
+ const edgeType = text(edge?.edge_type) ?? text(obj2(edge?.edge_type)?.Custom);
2560
+ if (!fromUid || !toUid || !edgeType || HIDDEN_CONTEXT_EDGE_TYPES.has(edgeType) || !labels.has(fromUid) || !labels.has(toUid)) {
2561
+ continue;
2562
+ }
2563
+ items.push(
2564
+ defined({
2565
+ type: "Relationship",
2566
+ from_uid: fromUid,
2567
+ to_uid: toUid,
2568
+ edge_type: edgeType,
2569
+ from_label: labels.get(fromUid),
2570
+ to_label: labels.get(toUid)
2571
+ })
2572
+ );
2573
+ }
2574
+ for (const chunk of Array.isArray(payload.chunks) ? payload.chunks : []) {
2575
+ const item = compactItem(chunk, "SourceExcerpt");
2576
+ if (item) items.push(item);
2577
+ }
2578
+ return items;
2579
+ }
2580
+ function resumeCandidates(raw) {
2581
+ const payload = obj2(raw);
2582
+ if (!payload) return [];
2583
+ const items = [];
2584
+ for (const key of ["task", "goal", "project", "plan", "active_execution", "lease"]) {
2585
+ const item = compactItem(payload[key], key);
2586
+ if (item) items.push(item);
2587
+ }
2588
+ for (const key of ["next_steps", "recent_executions", "blockers", "code_targets"]) {
2589
+ for (const value of Array.isArray(payload[key]) ? payload[key] : []) {
2590
+ const item = compactItem(value, key.replace(/s$/, ""));
2591
+ if (item) items.push(item);
2592
+ }
2593
+ }
2594
+ const knowledge = obj2(payload.knowledge);
2595
+ for (const key of ["lessons", "risks"]) {
2596
+ for (const value of Array.isArray(knowledge?.[key]) ? knowledge[key] : []) {
2597
+ const item = compactItem(value, key.replace(/s$/, ""));
2598
+ if (item) items.push(item);
2599
+ }
2600
+ }
2601
+ return items;
2602
+ }
2603
+ function visibleItems(candidates, rendered2) {
2604
+ const seen = /* @__PURE__ */ new Set();
2605
+ return candidates.filter((item) => {
2606
+ const uid = text(item.uid);
2607
+ if (uid) {
2608
+ if (seen.has(uid) || !rendered2.includes(`[${uid}]`)) return false;
2609
+ seen.add(uid);
2610
+ return true;
2611
+ }
2612
+ if (item.type === "Relationship") {
2613
+ const from = text(item.from_label);
2614
+ const to = text(item.to_label);
2615
+ const edgeType = text(item.edge_type);
2616
+ if (!from || !to || !edgeType) return false;
2617
+ const key = `${text(item.from_uid)}:${edgeType}:${text(item.to_uid)}`;
2618
+ if (seen.has(key) || !rendered2.includes(`${from} \u2014${edgeType}\u2192 ${to}`)) return false;
2619
+ seen.add(key);
2620
+ return true;
2621
+ }
2622
+ return false;
2623
+ });
2624
+ }
2625
+ function latestTimestamp(value) {
2626
+ let latest;
2627
+ const visit = (current) => {
2628
+ if (Array.isArray(current)) {
2629
+ for (const item of current) visit(item);
2630
+ return;
2631
+ }
2632
+ const record = obj2(current);
2633
+ if (!record) return;
2634
+ for (const [key, item] of Object.entries(record)) {
2635
+ if (["updated_at", "created_at", "completed_at", "started_at", "observed_at"].includes(key) && typeof item === "string" && !Number.isNaN(Date.parse(item)) && (!latest || Date.parse(item) > Date.parse(latest))) {
2636
+ latest = item;
2637
+ }
2638
+ visit(item);
2639
+ }
2640
+ };
2641
+ visit(value);
2642
+ return latest;
2643
+ }
2644
+ function contextWarnings(raw, rendered2, available, returned) {
2645
+ const warnings = [
2646
+ {
2647
+ code: "delivery_unverified",
2648
+ message: "The MCP server returned this context but cannot verify that the host injected or used it."
2649
+ }
2650
+ ];
2651
+ const serialized = JSON.stringify(raw);
2652
+ if (available > returned || rendered2.includes("item(s) omitted")) {
2653
+ warnings.push({
2654
+ code: "context_bounded",
2655
+ message: `${Math.max(0, available - returned)} structured item(s) were omitted from the rendered context.`
2656
+ });
2657
+ }
2658
+ if (/"superseded":true/.test(serialized)) {
2659
+ warnings.push({ code: "superseded_items_present", message: "Some retrieved items are superseded." });
2660
+ }
2661
+ if (/"currently_valid":false|"valid_at_time":false/.test(serialized)) {
2662
+ warnings.push({
2663
+ code: "validity_warning",
2664
+ message: "Some retrieved items are outside their current or requested validity window."
2665
+ });
2666
+ }
2667
+ const payload = obj2(raw);
2668
+ if (payload?.truncated === true || obj2(payload?.truncation)) {
2669
+ const truncation = obj2(payload?.truncation);
2670
+ if (payload?.truncated === true || Object.values(truncation ?? {}).some(Boolean)) {
2671
+ warnings.push({ code: "source_truncated", message: "The authoritative source reported truncated fields." });
2672
+ }
2673
+ }
2674
+ if (text(payload?.warning)) {
2675
+ warnings.push({ code: "source_warning", message: text(payload?.warning) });
2676
+ }
2677
+ return warnings;
2678
+ }
2679
+ function error(message) {
2680
+ return {
2681
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
2682
+ isError: true
2683
+ };
2684
+ }
2685
+ async function handleMemoryTool(client2, args, config = {}) {
2686
+ const action = text(args.action);
2687
+ if (action === "status") {
2688
+ const status = memoryStatus({ ...config, agentId: text(args.agent_id) ?? config.agentId });
2689
+ return {
2690
+ content: [{ type: "text", text: JSON.stringify(status) }],
2691
+ structuredContent: status
2692
+ };
2693
+ }
2694
+ if (action !== "context") {
2695
+ return error("action must be 'status' or 'context'");
2696
+ }
2697
+ const mode = text(args.mode) ?? (text(args.query) ? "topic" : void 0);
2698
+ if (!mode || !["topic", "continuity", "resume"].includes(mode)) {
2699
+ return error("action='context' requires mode='topic', 'continuity', or 'resume'");
2700
+ }
2701
+ const budget = outputBudget(args.max_output_chars);
2702
+ const agentId = text(args.agent_id) ?? config.agentId;
2703
+ let raw;
2704
+ let rendered2;
2705
+ let strategy;
2706
+ let candidates;
2707
+ if (mode === "topic") {
2708
+ const query = text(args.query);
2709
+ if (!query) return error("mode='topic' requires a non-empty query of 1-3 discriminating keywords");
2710
+ const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.min(50, Math.max(1, Math.floor(args.limit))) : 8;
2711
+ const request = defined({
2712
+ query,
2713
+ project_uid: text(args.project_uid),
2714
+ node_limit: limit,
2715
+ node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
2716
+ layer: text(args.layer),
2717
+ chunk_limit: args.include_chunks === true ? limit : 0,
2718
+ article_limit: args.include_documents === true ? limit : 0,
2719
+ valid_at: text(args.valid_at),
2720
+ include_graph: true,
2721
+ graph_expansion_limit: typeof args.graph_expansion_limit === "number" ? args.graph_expansion_limit : 3,
2722
+ graph_max_depth: typeof args.graph_max_depth === "number" ? args.graph_max_depth : 2
2723
+ });
2724
+ raw = await client2.retrieveContext(request);
2725
+ rendered2 = renderContext(raw, budget) ?? "No results found for this search.";
2726
+ strategy = "retrieve_context";
2727
+ candidates = topicCandidates(raw);
2728
+ } else {
2729
+ const scopeUids = Array.from(
2730
+ /* @__PURE__ */ new Set([
2731
+ ...Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : [],
2732
+ ...text(args.project_uid) ? [text(args.project_uid)] : []
2733
+ ])
2734
+ );
2735
+ raw = await client2.plan(
2736
+ defined({
2737
+ action: "resume_work",
2738
+ task_uid: text(args.task_uid),
2739
+ session_uid: text(args.session_uid),
2740
+ scope_uids: scopeUids.length > 0 ? scopeUids : void 0,
2741
+ limit: 1,
2742
+ agent_id: agentId
2743
+ })
2744
+ );
2745
+ rendered2 = renderResumeContext(raw, budget) ?? "No eligible work was found in the requested scope.";
2746
+ strategy = "resume_work";
2747
+ candidates = resumeCandidates(raw);
2748
+ }
2749
+ const items = visibleItems(candidates, rendered2);
2750
+ const renderedHash = sha256(rendered2);
2751
+ const scope = defined({
2752
+ org_id: config.orgId,
2753
+ agent_id: agentId,
2754
+ harness: config.harness ?? "generic",
2755
+ project_uid: text(args.project_uid),
2756
+ task_uid: text(args.task_uid),
2757
+ session_uid: text(args.session_uid),
2758
+ scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0
2759
+ });
2760
+ const contextId = `ctx_${sha256(
2761
+ stableJson({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
2762
+ ).slice(0, 24)}`;
2763
+ const bounded = candidates.length > items.length || rendered2.includes("item(s) omitted");
2764
+ const result = {
2765
+ schema_version: RESULT_SCHEMA_VERSION,
2766
+ action: "context",
2767
+ context_id: contextId,
2768
+ mode,
2769
+ scope,
2770
+ rendered_context: rendered2,
2771
+ rendered_hash: renderedHash,
2772
+ items,
2773
+ retrieval_trace: defined({
2774
+ strategy,
2775
+ query: text(args.query),
2776
+ project_uid: text(args.project_uid),
2777
+ task_uid: text(args.task_uid),
2778
+ session_uid: text(args.session_uid),
2779
+ node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
2780
+ graph_expansion_limit: mode === "topic" ? args.graph_expansion_limit ?? 3 : void 0,
2781
+ graph_max_depth: mode === "topic" ? args.graph_max_depth ?? 2 : void 0,
2782
+ available_items: candidates.length,
2783
+ returned_items: items.length,
2784
+ omitted_items: Math.max(0, candidates.length - items.length)
2785
+ }),
2786
+ budget: {
2787
+ unit: "characters",
2788
+ requested_chars: budget,
2789
+ used_chars: rendered2.length,
2790
+ available_items: candidates.length,
2791
+ returned_items: items.length,
2792
+ omitted_items: Math.max(0, candidates.length - items.length),
2793
+ bounded
2794
+ },
2795
+ freshness: defined({
2796
+ valid_at: text(args.valid_at),
2797
+ latest_source_timestamp: latestTimestamp(raw)
2798
+ }),
2799
+ warnings: contextWarnings(raw, rendered2, candidates.length, items.length)
2800
+ };
2801
+ return {
2802
+ content: [{ type: "text", text: rendered2 }],
2803
+ structuredContent: result
2804
+ };
2805
+ }
2806
+
2109
2807
  // src/tools.ts
2808
+ var RESOLUTION_PROPERTIES = {
2809
+ idempotency_key: {
2810
+ type: "string",
2811
+ description: "Stable retry key; a mismatched replay is rejected"
2812
+ },
2813
+ reuse_uid: {
2814
+ type: "string",
2815
+ description: "Reuse this live same-scope node after a duplicate response"
2816
+ },
2817
+ supersedes_uid: {
2818
+ type: "string",
2819
+ description: "Create and mark this same-scope node as superseded"
2820
+ },
2821
+ contradicts_uid: {
2822
+ type: "string",
2823
+ description: "Create and mark this same-scope node as contradicted"
2824
+ },
2825
+ confirm_new: {
2826
+ type: "boolean",
2827
+ description: "Confirm that the new fact is genuinely distinct"
2828
+ },
2829
+ on_near_duplicate: {
2830
+ type: "string",
2831
+ enum: ["create"],
2832
+ description: "Alias for confirm_new on a near-duplicate response"
2833
+ }
2834
+ };
2835
+ function stableJson2(value) {
2836
+ if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
2837
+ if (value && typeof value === "object") {
2838
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson2(item)}`).join(",")}}`;
2839
+ }
2840
+ return JSON.stringify(value);
2841
+ }
2842
+ function defaultWriteIdempotencyKey(tool, args) {
2843
+ if (typeof args.idempotency_key === "string") return args.idempotency_key;
2844
+ const context = args.invocation_context;
2845
+ const sessionId = context?.harnessSessionId;
2846
+ if (typeof sessionId !== "string" || sessionId.length === 0) return void 0;
2847
+ const canonicalArgs = Object.fromEntries(
2848
+ Object.entries(args).filter(
2849
+ ([key]) => key !== "invocation_context" && key !== "idempotency_key"
2850
+ )
2851
+ );
2852
+ return (0, import_node_crypto5.createHash)("sha256").update(
2853
+ stableJson2({
2854
+ harness_session_id: sessionId,
2855
+ harness_turn_id: context?.harnessTurnId ?? null,
2856
+ tool,
2857
+ action: args.action ?? null,
2858
+ args: canonicalArgs
2859
+ })
2860
+ ).digest("hex");
2861
+ }
2862
+ function resolutionFields(args) {
2863
+ return Object.fromEntries(
2864
+ [
2865
+ "idempotency_key",
2866
+ "reuse_uid",
2867
+ "supersedes_uid",
2868
+ "contradicts_uid",
2869
+ "confirm_new",
2870
+ "on_near_duplicate"
2871
+ ].filter((key) => args[key] !== void 0).map((key) => [key, args[key]])
2872
+ );
2873
+ }
2110
2874
  var TOOLS = [
2111
2875
  {
2112
2876
  name: "mindgraph_capture",
@@ -2171,14 +2935,7 @@ var TOOLS = [
2171
2935
  type: "string",
2172
2936
  description: "Material Execution that produced the lesson"
2173
2937
  },
2174
- idempotency_key: {
2175
- type: "string",
2176
- description: "Stable retry key for an intentional capture"
2177
- },
2178
- supersedes_uid: {
2179
- type: "string",
2180
- description: "Earlier capture corrected or superseded by this one"
2181
- },
2938
+ ...RESOLUTION_PROPERTIES,
2182
2939
  summarizes_uids: {
2183
2940
  type: "array",
2184
2941
  items: { type: "string" },
@@ -2276,6 +3033,7 @@ var TOOLS = [
2276
3033
  inputSchema: {
2277
3034
  type: "object",
2278
3035
  properties: {
3036
+ ...RESOLUTION_PROPERTIES,
2279
3037
  action: {
2280
3038
  type: "string",
2281
3039
  enum: [
@@ -2370,6 +3128,7 @@ var TOOLS = [
2370
3128
  inputSchema: {
2371
3129
  type: "object",
2372
3130
  properties: {
3131
+ ...RESOLUTION_PROPERTIES,
2373
3132
  action: {
2374
3133
  type: "string",
2375
3134
  enum: [
@@ -2728,6 +3487,12 @@ var TOOLS = [
2728
3487
  enum: ["text", "json"],
2729
3488
  description: "Output format (default 'text'): a compact rendered context block \u2014 labels, uids, summaries, status tags, relationships. Pass 'json' for the raw server response (full props, source-chunk offsets, retrieval metadata)."
2730
3489
  },
3490
+ max_output_chars: {
3491
+ type: "integer",
3492
+ minimum: 512,
3493
+ maximum: 24e3,
3494
+ description: "Hard character budget for rendered text output (512-24000; default 24000). Normal per-field clipping still applies. Whole evidence items are then kept or omitted rather than cut to satisfy this budget; the final marker reports omissions. Ignored when format='json'."
3495
+ },
2731
3496
  query: {
2732
3497
  type: "string",
2733
3498
  description: "Search query \u2014 for 'text'/'context': 1-3 BM25 keywords, drop filler words. For 'semantic'/'hybrid': natural language is OK since it uses vector similarity."
@@ -3154,6 +3919,7 @@ var TOOLS = [
3154
3919
  required: ["action"]
3155
3920
  }
3156
3921
  },
3922
+ MEMORY_TOOL,
3157
3923
  SYNC_TOOL,
3158
3924
  CODE_TOOL
3159
3925
  ];
@@ -3174,7 +3940,7 @@ var INVOCATION_CONTEXT_SCHEMA = {
3174
3940
  }
3175
3941
  };
3176
3942
  for (const tool of TOOLS) {
3177
- if (tool.name === "mindgraph_retrieve") continue;
3943
+ if (tool.name === "mindgraph_retrieve" || tool.name === "mindgraph_memory") continue;
3178
3944
  const schema = tool.inputSchema;
3179
3945
  if (schema.properties && !schema.properties.invocation_context) {
3180
3946
  schema.properties.invocation_context = INVOCATION_CONTEXT_SCHEMA;
@@ -3193,8 +3959,8 @@ function ok3(data) {
3193
3959
  }
3194
3960
  function renderedResult(format, raw, render) {
3195
3961
  if (format === "json") return ok3(raw);
3196
- const text = render(raw);
3197
- return text !== void 0 ? { content: [{ type: "text", text }] } : ok3(raw);
3962
+ const text2 = render(raw);
3963
+ return text2 !== void 0 ? { content: [{ type: "text", text: text2 }] } : ok3(raw);
3198
3964
  }
3199
3965
  function err3(message) {
3200
3966
  return {
@@ -3202,8 +3968,22 @@ function err3(message) {
3202
3968
  isError: true
3203
3969
  };
3204
3970
  }
3205
- async function handleTool(client2, name, args) {
3971
+ async function handleTool(client2, name, args, memoryConfig = {}) {
3206
3972
  try {
3973
+ if (["mindgraph_capture", "mindgraph_reason", "mindgraph_commit"].includes(name) && args.action !== "get_open_decisions") {
3974
+ const key = defaultWriteIdempotencyKey(name, args);
3975
+ if (key) args = { ...args, idempotency_key: key };
3976
+ const dispositions = [
3977
+ args.reuse_uid,
3978
+ args.supersedes_uid,
3979
+ args.contradicts_uid,
3980
+ args.confirm_new === true ? true : void 0,
3981
+ args.on_near_duplicate
3982
+ ].filter((value) => value !== void 0);
3983
+ if (dispositions.length > 1) {
3984
+ return err3("resolution dispositions are mutually exclusive");
3985
+ }
3986
+ }
3207
3987
  let result;
3208
3988
  switch (name) {
3209
3989
  case "mindgraph_capture":
@@ -3221,6 +4001,9 @@ async function handleTool(client2, name, args) {
3221
4001
  case "mindgraph_retrieve":
3222
4002
  result = await handleRetrieve(client2, args);
3223
4003
  break;
4004
+ case "mindgraph_memory":
4005
+ result = await handleMemoryTool(client2, args, memoryConfig);
4006
+ break;
3224
4007
  case "mindgraph_series_query":
3225
4008
  result = await handleSeriesQuery(client2, args);
3226
4009
  break;
@@ -3302,7 +4085,11 @@ async function handleCapture(client2, args) {
3302
4085
  work_uid,
3303
4086
  execution_uid,
3304
4087
  idempotency_key,
4088
+ reuse_uid,
3305
4089
  supersedes_uid,
4090
+ contradicts_uid,
4091
+ confirm_new,
4092
+ on_near_duplicate,
3306
4093
  summarizes_uids,
3307
4094
  entity_type,
3308
4095
  source_uid,
@@ -3326,7 +4113,11 @@ async function handleCapture(client2, args) {
3326
4113
  work_uid,
3327
4114
  execution_uid,
3328
4115
  idempotency_key,
4116
+ reuse_uid,
3329
4117
  supersedes_uid,
4118
+ contradicts_uid,
4119
+ confirm_new,
4120
+ on_near_duplicate,
3330
4121
  summarizes_uids,
3331
4122
  props,
3332
4123
  agent_id
@@ -3372,27 +4163,32 @@ async function handleCapture(client2, args) {
3372
4163
  const journalProps = { content };
3373
4164
  if (mood) journalProps.mood = mood;
3374
4165
  if (tags) journalProps.tags = tags;
3375
- return ok3(await client2.journal(label, journalProps, { agent_id }));
4166
+ return ok3(await client2.journal(label, journalProps, {
4167
+ agent_id,
4168
+ idempotency_key,
4169
+ reuse_uid,
4170
+ supersedes_uid,
4171
+ contradicts_uid,
4172
+ confirm_new,
4173
+ on_near_duplicate
4174
+ }));
3376
4175
  }
3377
4176
  case "entity": {
3378
4177
  const type = entity_type || "concept";
3379
4178
  const entityProps = { ...props };
3380
4179
  if (summary) entityProps.description = summary;
3381
- switch (type) {
3382
- case "person":
3383
- return ok3(await client2.findOrCreatePerson(label, entityProps, agent_id));
3384
- case "organization":
3385
- return ok3(await client2.findOrCreateOrganization(label, entityProps, agent_id));
3386
- case "nation":
3387
- return ok3(await client2.findOrCreateNation(label, entityProps, agent_id));
3388
- case "event":
3389
- return ok3(await client2.findOrCreateEvent(label, entityProps, agent_id));
3390
- case "place":
3391
- return ok3(await client2.findOrCreatePlace(label, entityProps, agent_id));
3392
- case "concept":
3393
- default:
3394
- return ok3(await client2.findOrCreateConcept(label, entityProps, agent_id));
3395
- }
4180
+ return ok3(await client2.entity({
4181
+ action: "create",
4182
+ label,
4183
+ props: { ...entityProps, entity_type: type },
4184
+ agent_id,
4185
+ idempotency_key,
4186
+ reuse_uid,
4187
+ supersedes_uid,
4188
+ contradicts_uid,
4189
+ confirm_new,
4190
+ on_near_duplicate
4191
+ }));
3396
4192
  }
3397
4193
  case "observation":
3398
4194
  return ok3(
@@ -3403,7 +4199,13 @@ async function handleCapture(client2, args) {
3403
4199
  confidence,
3404
4200
  salience,
3405
4201
  props,
3406
- agent_id
4202
+ agent_id,
4203
+ idempotency_key,
4204
+ reuse_uid,
4205
+ supersedes_uid,
4206
+ contradicts_uid,
4207
+ confirm_new,
4208
+ on_near_duplicate
3407
4209
  })
3408
4210
  );
3409
4211
  case "source":
@@ -3415,7 +4217,13 @@ async function handleCapture(client2, args) {
3415
4217
  confidence,
3416
4218
  salience,
3417
4219
  props,
3418
- agent_id
4220
+ agent_id,
4221
+ idempotency_key,
4222
+ reuse_uid,
4223
+ supersedes_uid,
4224
+ contradicts_uid,
4225
+ confirm_new,
4226
+ on_near_duplicate
3419
4227
  })
3420
4228
  );
3421
4229
  case "snippet":
@@ -3428,26 +4236,38 @@ async function handleCapture(client2, args) {
3428
4236
  confidence,
3429
4237
  salience,
3430
4238
  props,
3431
- agent_id
4239
+ agent_id,
4240
+ idempotency_key,
4241
+ reuse_uid,
4242
+ supersedes_uid,
4243
+ contradicts_uid,
4244
+ confirm_new,
4245
+ on_near_duplicate
3432
4246
  })
3433
4247
  );
3434
4248
  case "concept":
3435
- return ok3(
3436
- await client2.structure({
3437
- action: "concept",
3438
- label,
3439
- summary,
3440
- confidence,
3441
- salience,
3442
- props,
3443
- agent_id
3444
- })
3445
- );
4249
+ return ok3(await client2.entity({
4250
+ action: "create",
4251
+ label,
4252
+ props: {
4253
+ ...props,
4254
+ entity_type: "concept",
4255
+ ...summary ? { description: summary } : {}
4256
+ },
4257
+ agent_id,
4258
+ idempotency_key,
4259
+ reuse_uid,
4260
+ supersedes_uid,
4261
+ contradicts_uid,
4262
+ confirm_new,
4263
+ on_near_duplicate
4264
+ }));
3446
4265
  default:
3447
4266
  return err3(`Unknown capture action: ${action}`);
3448
4267
  }
3449
4268
  }
3450
4269
  async function handleReason(client2, args) {
4270
+ const resolution = resolutionFields(args);
3451
4271
  const {
3452
4272
  action,
3453
4273
  label,
@@ -3480,7 +4300,8 @@ async function handleReason(client2, args) {
3480
4300
  ...warrant.content ? { props: { content: warrant.content } } : {}
3481
4301
  } : void 0,
3482
4302
  argument,
3483
- agent_id
4303
+ agent_id,
4304
+ ...resolution
3484
4305
  })
3485
4306
  );
3486
4307
  }
@@ -3493,11 +4314,13 @@ async function handleReason(client2, args) {
3493
4314
  confidence,
3494
4315
  salience,
3495
4316
  props,
3496
- agent_id
4317
+ agent_id,
4318
+ ...resolution
3497
4319
  })
3498
4320
  );
3499
4321
  }
3500
4322
  async function handleCommit(client2, args) {
4323
+ const resolution = resolutionFields(args);
3501
4324
  const {
3502
4325
  action,
3503
4326
  label,
@@ -3528,16 +4351,17 @@ async function handleCommit(client2, args) {
3528
4351
  confidence,
3529
4352
  salience,
3530
4353
  props,
3531
- agent_id
4354
+ agent_id,
4355
+ ...resolution
3532
4356
  })
3533
4357
  );
3534
4358
  case "open_decision":
3535
4359
  if (!label) return err3("label is required for open_decision");
3536
- return ok3(await client2.openDecision(label, { summary, props, agent_id }));
4360
+ return ok3(await client2.openDecision(label, { summary, props, agent_id, ...resolution }));
3537
4361
  case "add_option":
3538
4362
  if (!decision_uid) return err3("decision_uid is required for add_option");
3539
4363
  if (!label) return err3("label is required for add_option");
3540
- return ok3(await client2.addOption(decision_uid, label, { summary, props, agent_id }));
4364
+ return ok3(await client2.addOption(decision_uid, label, { summary, props, agent_id, ...resolution }));
3541
4365
  case "add_constraint":
3542
4366
  if (!decision_uid) return err3("decision_uid is required for add_constraint");
3543
4367
  return ok3(
@@ -3547,7 +4371,8 @@ async function handleCommit(client2, args) {
3547
4371
  decision_uid,
3548
4372
  summary,
3549
4373
  props,
3550
- agent_id
4374
+ agent_id,
4375
+ ...resolution
3551
4376
  })
3552
4377
  );
3553
4378
  case "resolve_decision":
@@ -3561,7 +4386,8 @@ async function handleCommit(client2, args) {
3561
4386
  as_of_date,
3562
4387
  session_id,
3563
4388
  retrieval_trace_id,
3564
- agent_id
4389
+ agent_id,
4390
+ ...resolution
3565
4391
  })
3566
4392
  );
3567
4393
  case "get_open_decisions":
@@ -3642,7 +4468,7 @@ async function handlePlan(client2, args) {
3642
4468
  output_snapshot,
3643
4469
  side_effects,
3644
4470
  outcome,
3645
- error,
4471
+ error: error2,
3646
4472
  next_action,
3647
4473
  checkpoint_summary,
3648
4474
  test_summary,
@@ -3705,7 +4531,7 @@ async function handlePlan(client2, args) {
3705
4531
  output_snapshot,
3706
4532
  side_effects,
3707
4533
  outcome,
3708
- error,
4534
+ error: error2,
3709
4535
  next_action,
3710
4536
  checkpoint_summary,
3711
4537
  test_summary,
@@ -3754,7 +4580,7 @@ async function handlePlan(client2, args) {
3754
4580
  output_snapshot,
3755
4581
  side_effects,
3756
4582
  outcome,
3757
- error,
4583
+ error: error2,
3758
4584
  props,
3759
4585
  agent_id
3760
4586
  })
@@ -3840,6 +4666,7 @@ async function handleRetrieve(client2, args) {
3840
4666
  const {
3841
4667
  action,
3842
4668
  format,
4669
+ max_output_chars,
3843
4670
  query,
3844
4671
  start_uid,
3845
4672
  end_uid,
@@ -3894,7 +4721,7 @@ async function handleRetrieve(client2, args) {
3894
4721
  graph_expansion_limit: graph_expansion_limit ?? 3,
3895
4722
  graph_max_depth: graph_max_depth ?? 2
3896
4723
  }),
3897
- renderContext
4724
+ (raw) => renderContext(raw, max_output_chars)
3898
4725
  );
3899
4726
  case "text":
3900
4727
  if (!query) return err3("query is required for text search");
@@ -3908,7 +4735,7 @@ async function handleRetrieve(client2, args) {
3908
4735
  explain,
3909
4736
  agent_id
3910
4737
  }),
3911
- (raw) => renderNodeList(raw, `Text search: ${query}`)
4738
+ (raw) => renderNodeList(raw, `Text search: ${query}`, void 0, max_output_chars)
3912
4739
  );
3913
4740
  case "semantic":
3914
4741
  if (!query) return err3("query is required for semantic search");
@@ -3922,7 +4749,7 @@ async function handleRetrieve(client2, args) {
3922
4749
  explain,
3923
4750
  agent_id
3924
4751
  }),
3925
- (raw) => renderNodeList(raw, `Semantic search: ${query}`)
4752
+ (raw) => renderNodeList(raw, `Semantic search: ${query}`, void 0, max_output_chars)
3926
4753
  );
3927
4754
  case "hybrid":
3928
4755
  if (!query) return err3("query is required for hybrid search");
@@ -3937,7 +4764,7 @@ async function handleRetrieve(client2, args) {
3937
4764
  agent_id
3938
4765
  }),
3939
4766
  // With explain, per-leg scoring must survive — raw JSON only.
3940
- explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`)
4767
+ explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`, void 0, max_output_chars)
3941
4768
  );
3942
4769
  // Structured queries. These go through the generic /retrieve action
3943
4770
  // dispatch rather than the SDK's no-argument convenience methods — the
@@ -3955,7 +4782,7 @@ async function handleRetrieve(client2, args) {
3955
4782
  offset,
3956
4783
  agent_id
3957
4784
  }),
3958
- (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit)
4785
+ (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit, max_output_chars)
3959
4786
  );
3960
4787
  case "stale_derivations":
3961
4788
  return rendered2(
@@ -3964,7 +4791,7 @@ async function handleRetrieve(client2, args) {
3964
4791
  limit,
3965
4792
  offset
3966
4793
  }),
3967
- (raw) => renderNodeList(raw, "stale derivations", limit)
4794
+ (raw) => renderNodeList(raw, "stale derivations", limit, max_output_chars)
3968
4795
  );
3969
4796
  case "preferences":
3970
4797
  return rendered2(
@@ -3974,7 +4801,7 @@ async function handleRetrieve(client2, args) {
3974
4801
  limit,
3975
4802
  agent_id
3976
4803
  }),
3977
- (raw) => renderNodeList(raw, "Preferences", limit)
4804
+ (raw) => renderNodeList(raw, "Preferences", limit, max_output_chars)
3978
4805
  );
3979
4806
  case "layer":
3980
4807
  if (!layer) return err3("layer is required for layer retrieval");
@@ -3986,7 +4813,7 @@ async function handleRetrieve(client2, args) {
3986
4813
  node_types,
3987
4814
  agent_id
3988
4815
  }),
3989
- (raw) => renderNodeList(raw, `Layer: ${layer}`, limit)
4816
+ (raw) => renderNodeList(raw, `Layer: ${layer}`, limit, max_output_chars)
3990
4817
  );
3991
4818
  case "recent":
3992
4819
  return rendered2(
@@ -4000,7 +4827,7 @@ async function handleRetrieve(client2, args) {
4000
4827
  created_before,
4001
4828
  agent_id
4002
4829
  }),
4003
- (raw) => renderNodeList(raw, "Recently ingested", limit)
4830
+ (raw) => renderNodeList(raw, "Recently ingested", limit, max_output_chars)
4004
4831
  );
4005
4832
  // Document inventory
4006
4833
  case "document_index":
@@ -4009,7 +4836,7 @@ async function handleRetrieve(client2, args) {
4009
4836
  node_type: "Document",
4010
4837
  limit: limit ?? 100
4011
4838
  }),
4012
- (raw) => renderNodeList(raw, "Ingested documents")
4839
+ (raw) => renderNodeList(raw, "Ingested documents", void 0, max_output_chars)
4013
4840
  );
4014
4841
  // Graph traversal
4015
4842
  case "chain":
@@ -4023,7 +4850,7 @@ async function handleRetrieve(client2, args) {
4023
4850
  include_provenance,
4024
4851
  max_nodes
4025
4852
  }),
4026
- renderTraversal
4853
+ (raw) => renderTraversal(raw, max_output_chars)
4027
4854
  );
4028
4855
  case "neighborhood":
4029
4856
  if (!start_uid) return err3("start_uid is required for neighborhood traversal");
@@ -4038,7 +4865,7 @@ async function handleRetrieve(client2, args) {
4038
4865
  include_provenance,
4039
4866
  max_nodes
4040
4867
  }),
4041
- renderTraversal
4868
+ (raw) => renderTraversal(raw, max_output_chars)
4042
4869
  );
4043
4870
  case "path":
4044
4871
  if (!start_uid) return err3("start_uid is required for path traversal");
@@ -4055,7 +4882,7 @@ async function handleRetrieve(client2, args) {
4055
4882
  include_provenance,
4056
4883
  max_nodes
4057
4884
  }),
4058
- renderTraversal
4885
+ (raw) => renderTraversal(raw, max_output_chars)
4059
4886
  );
4060
4887
  case "subgraph":
4061
4888
  if (!start_uid) return err3("start_uid is required for subgraph traversal");
@@ -4070,7 +4897,7 @@ async function handleRetrieve(client2, args) {
4070
4897
  include_provenance,
4071
4898
  max_nodes
4072
4899
  }),
4073
- renderTraversal
4900
+ (raw) => renderTraversal(raw, max_output_chars)
4074
4901
  );
4075
4902
  case "top_k_paths":
4076
4903
  if (!start_uid) return err3("start_uid is required for top_k_paths");
@@ -4086,7 +4913,7 @@ async function handleRetrieve(client2, args) {
4086
4913
  edge_types,
4087
4914
  exclude_edge_types
4088
4915
  }),
4089
- renderTraversal
4916
+ (raw) => renderTraversal(raw, max_output_chars)
4090
4917
  );
4091
4918
  default:
4092
4919
  return err3(`Unknown retrieve action: ${action}`);
@@ -4350,8 +5177,8 @@ var ok4 = (data) => ({
4350
5177
  });
4351
5178
  var rendered = (format, raw, render) => {
4352
5179
  if (format === "json") return ok4(raw);
4353
- const text = render(raw);
4354
- return text !== void 0 ? { content: [{ type: "text", text }] } : ok4(raw);
5180
+ const text2 = render(raw);
5181
+ return text2 !== void 0 ? { content: [{ type: "text", text: text2 }] } : ok4(raw);
4355
5182
  };
4356
5183
  var err4 = (message) => ({
4357
5184
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
@@ -4411,8 +5238,8 @@ async function getGeneratedTools(client2) {
4411
5238
  cache = { tools, byName, fetchedAt: Date.now() };
4412
5239
  retryNotBefore = 0;
4413
5240
  return { tools, byName };
4414
- } catch (error) {
4415
- if (error instanceof DuplicateGeneratedToolError) throw error;
5241
+ } catch (error2) {
5242
+ if (error2 instanceof DuplicateGeneratedToolError) throw error2;
4416
5243
  retryNotBefore = Date.now() + FAILURE_COOLDOWN_MS;
4417
5244
  return cache ? { tools: cache.tools, byName: cache.byName } : EMPTY;
4418
5245
  }
@@ -4569,6 +5396,7 @@ var ONTOLOGY_READ_ACTIONS = /* @__PURE__ */ new Set([
4569
5396
  "get_proposal"
4570
5397
  ]);
4571
5398
  function toolMutability(toolName, action) {
5399
+ if (toolName === "mindgraph_memory") return "read";
4572
5400
  if (toolName === "mindgraph_retrieve") return "read";
4573
5401
  if (toolName === "mindgraph_series_query") return "read";
4574
5402
  if (toolName === "mindgraph_plan") {
@@ -4689,8 +5517,8 @@ async function checkMcpGovernance(toolName, args, config) {
4689
5517
  response = candidate;
4690
5518
  body = candidateBody;
4691
5519
  break;
4692
- } catch (error) {
4693
- lastFailure = error instanceof Error ? error.message : String(error);
5520
+ } catch (error2) {
5521
+ lastFailure = error2 instanceof Error ? error2.message : String(error2);
4694
5522
  if (attempt < retryDelays.length) {
4695
5523
  await sleep(retryDelays[attempt]);
4696
5524
  continue;
@@ -4764,6 +5592,12 @@ async function checkMcpGovernance(toolName, args, config) {
4764
5592
 
4765
5593
  // src/resources.ts
4766
5594
  var RESOURCES = [
5595
+ {
5596
+ uri: "mindgraph://memory/status",
5597
+ name: "Memory Integration Status",
5598
+ description: "Portable memory profile, adapter state, supported capabilities, and explicit limitations",
5599
+ mimeType: "application/json"
5600
+ },
4767
5601
  {
4768
5602
  uri: "mindgraph://stats",
4769
5603
  name: "Graph Statistics",
@@ -4821,8 +5655,10 @@ function json(uri, data) {
4821
5655
  contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
4822
5656
  };
4823
5657
  }
4824
- async function readResource(client2, uri) {
5658
+ async function readResource(client2, uri, memoryConfig = {}) {
4825
5659
  switch (uri) {
5660
+ case "mindgraph://memory/status":
5661
+ return json(uri, memoryStatus(memoryConfig));
4826
5662
  case "mindgraph://stats":
4827
5663
  return json(uri, await client2.stats());
4828
5664
  case "mindgraph://goals":
@@ -4854,16 +5690,18 @@ async function readResource(client2, uri) {
4854
5690
  }
4855
5691
  throw new Error(`Unknown resource: ${uri}`);
4856
5692
  }
4857
- async function readGovernedResource(client2, uri, governanceConfig) {
5693
+ async function readGovernedResource(client2, uri, governanceConfig, memoryConfig = {}) {
5694
+ const toolName = uri === "mindgraph://memory/status" ? "mindgraph_memory" : RESOURCE_GOVERNANCE_TOOL;
5695
+ const toolArgs = uri === "mindgraph://memory/status" ? { action: "status" } : {};
4858
5696
  const governance = await checkMcpGovernance(
4859
- RESOURCE_GOVERNANCE_TOOL,
4860
- {},
5697
+ toolName,
5698
+ toolArgs,
4861
5699
  { ...governanceConfig, context: { resource_uri: uri } }
4862
5700
  );
4863
5701
  if (!governance.allowed) {
4864
5702
  throw new Error(governance.message);
4865
5703
  }
4866
- return readResource(client2, uri);
5704
+ return readResource(client2, uri, memoryConfig);
4867
5705
  }
4868
5706
 
4869
5707
  // src/index.ts
@@ -4873,7 +5711,7 @@ var import_node_path5 = __toESM(require("path"));
4873
5711
  var import_node_fs4 = __toESM(require("fs"));
4874
5712
  var import_node_os3 = __toESM(require("os"));
4875
5713
  var import_node_path4 = __toESM(require("path"));
4876
- var import_node_crypto4 = __toESM(require("crypto"));
5714
+ var import_node_crypto6 = __toESM(require("crypto"));
4877
5715
  var OWNER_MARKER = "--owner mindgraph";
4878
5716
  var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
4879
5717
  function command(harness) {
@@ -4886,7 +5724,7 @@ var RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
4886
5724
  function installHookRunner(sourceFile, homeDir, version) {
4887
5725
  const target = import_node_path4.default.join(homeDir || import_node_os3.default.homedir(), RUNNER_RELATIVE);
4888
5726
  import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
4889
- const temporary = `${target}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
5727
+ const temporary = `${target}.${process.pid}.${import_node_crypto6.default.randomUUID()}.tmp`;
4890
5728
  import_node_fs4.default.copyFileSync(sourceFile, temporary);
4891
5729
  import_node_fs4.default.renameSync(temporary, target);
4892
5730
  const versionPath = import_node_path4.default.join(homeDir || import_node_os3.default.homedir(), RUNNER_VERSION_RELATIVE);
@@ -5056,7 +5894,7 @@ function entryHasOwnedHook(value) {
5056
5894
  }
5057
5895
  function writeSettings(file, settings) {
5058
5896
  import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
5059
- const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
5897
+ const temporary = `${file}.${process.pid}.${import_node_crypto6.default.randomUUID()}.tmp`;
5060
5898
  import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
5061
5899
  `, {
5062
5900
  mode: 384
@@ -5109,56 +5947,29 @@ var client = new import_mindgraph2.MindGraph({
5109
5947
  });
5110
5948
  var INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph, plus any ontology tools generated by the active schema.
5111
5949
 
5112
- ## #1 RULE: Go straight to the user's topic
5113
-
5114
- When the user asks about something, your FIRST and ONLY action should be a SINGLE \`mindgraph_retrieve\` call with action "context" and keyword terms from their message. Nothing else first.
5115
-
5116
- **NEVER do any of these before addressing the user's topic:**
5117
- - Do NOT retrieve active_goals, open_questions, contradictions, or weak_claims
5118
- - Do NOT follow a "session lifecycle" or "warm-up" sequence
5119
- - Do NOT make multiple serial retrieve calls when one "context" call covers it
5120
-
5121
- There is no session to open. There is no context to pre-load. Just search for what the user asked about.
5950
+ ## Default memory workflow: orient \u2192 answer \u2192 preserve
5122
5951
 
5123
- **Example:** User says "Tell me about Indian philosophy and Taoism"
5124
- \u2713 CORRECT: \`mindgraph_retrieve({action: "context", query: "Indian philosophy Taoism"})\`
5125
- \u2717 WRONG: First retrieve active_goals, then open_questions, then search
5126
- \u2717 WRONG: \`mindgraph_retrieve({action: "context", query: "Tell me about Indian philosophy and Taoism"})\` \u2014 natural language won't match BM25
5952
+ **Orient only when prior durable state may matter.** Self-contained questions do not require a memory call or a warm-up sequence.
5127
5953
 
5128
- ## Two-phase retrieval protocol
5954
+ - Resuming work, asking what was in progress, or continuing a task: call \`mindgraph_memory\` once with action \`context\` and mode \`resume\` or \`continuity\`. This uses authoritative, task-first, lease-aware work state.
5955
+ - Asking about a topic that may depend on prior knowledge: call \`mindgraph_memory\` once with action \`context\`, mode \`topic\`, and 1\u20133 discriminating keywords.
5956
+ - Asking about integration capability or automatic behavior: call \`mindgraph_memory\` with action \`status\`.
5957
+ - Use the comprehensive read tools below only when the high-level result is insufficient or the user asks for a specific graph operation.
5129
5958
 
5130
- **Phase 1 \u2014 Orient (default, fast, lightweight):**
5131
- Use \`action: "context"\` with keywords. By default this returns only graph nodes \u2014 structured knowledge with labels, summaries, types, confidence scores, and edges. This is usually sufficient and keeps results compact.
5959
+ **Answer from the bounded result.** Treat superseded, refuted, or out-of-validity items as warnings, not current facts. Retrieval proves only that the server returned context; it does not prove that a host automatically injected or used it.
5132
5960
 
5133
- **Phase 2 \u2014 Fetch source text (only when needed):**
5134
- If you need the original source text behind a node (for verbatim quotes, citations, or when graph summaries lack detail), use \`action: "context"\` with \`include_chunks: true\`. This adds full document chunks (400-800 words each) to the response.
5961
+ **Preserve selectively after material durable change.** Use the existing typed write tool for a fact, correction, decision, work-state transition, or reusable lesson. Do not write ephemeral conversation, routine mechanics, unsupported guesses, or information already represented accurately. An explicit no-write is the correct outcome when nothing durable changed.
5135
5962
 
5136
- **Orientation shortcut:**
5137
- - \`action: "document_index"\` \u2014 list all ingested documents (titles, dates, UIDs) to see what's in the graph
5963
+ ## Topic retrieval details
5138
5964
 
5139
- ## Search strategy \u2014 keyword first, semantic as fallback
5965
+ Topic context is graph-only by default. Set \`include_chunks: true\` only for source text needed for quotes, citations, or missing detail. Query with a few proper nouns or technical terms rather than a full sentence. If keyword retrieval fails, use \`mindgraph_retrieve\` with \`semantic\` or \`hybrid\`; use \`document_index\` to inspect ingested sources.
5140
5966
 
5141
- The default search uses BM25 keyword matching. Query with 1\u20133 discriminating terms (proper nouns, technical terms). Drop filler words.
5967
+ ## Comprehensive read tools
5142
5968
 
5143
- **Keyword examples:**
5144
- - "What is Kissinger's view on NATO?" \u2192 query: \`"Kissinger NATO"\`
5145
- - "Tell me about async in Rust" \u2192 query: \`"Rust async runtime"\`
5146
- - "Implications of carbon tax?" \u2192 query: \`"carbon tax"\`
5147
-
5148
- If keyword search returns no results, escalate:
5149
- 1. Try synonyms (e.g. \`"global warming"\` instead of \`"climate change"\`)
5150
- 2. Use \`action: "semantic"\` \u2014 vector similarity, natural language OK (e.g. \`"ways to manage stress"\` finds "mental health" nodes)
5151
- 3. Use \`action: "hybrid"\` \u2014 combines BM25 + semantic for best coverage
5152
-
5153
- ## When to READ
5154
-
5155
- - User asks about topic X \u2192 \`mindgraph_retrieve\` action "context", query "X keywords"
5156
- - User asks "what documents are in my graph?" \u2192 action "document_index"
5157
- - User explicitly asks "what are my goals?" \u2192 action "active_goals"
5158
- - User explicitly asks about open questions \u2192 action "open_questions"
5159
- - User asks for advice or a recommendation ("suggest a hotel", "what should I read?") \u2192 action "preferences", query "topic keywords" \u2014 tailor the answer to what they like
5160
- - Explore around a known node \u2192 action "neighborhood" with start_uid
5161
- - Find connections between nodes \u2192 action "path" with start_uid + end_uid
5969
+ - Explicit graph inventory or state questions \u2192 the matching \`mindgraph_retrieve\` action such as \`document_index\`, \`active_goals\`, or \`open_questions\`
5970
+ - Advice or recommendations \u2192 \`mindgraph_retrieve\` action \`preferences\`, with topic keywords
5971
+ - Explore around a known node \u2192 \`mindgraph_retrieve\` action \`neighborhood\` with start_uid
5972
+ - Find connections between nodes \u2192 \`mindgraph_retrieve\` action \`path\` with start_uid + end_uid
5162
5973
 
5163
5974
  ## When to WRITE
5164
5975
 
@@ -5193,13 +6004,14 @@ if (PROFILE === "coding") {
5193
6004
 
5194
6005
  ## Coding profile
5195
6006
 
5196
- **Recall order (overrides the #1 rule above for work and code questions):**
5197
- - "What was I working on?" / current work state / resuming anything: call \`mindgraph_plan\` action \`resume_work\` FIRST. Work state is structural; never answer it from keyword search ("recent work" famously retrieves people named Work).
6007
+ **Coding recall order:**
6008
+ - "What was I working on?" / current work state / resuming anything: call \`mindgraph_memory\` action \`context\`, mode \`resume\` FIRST. It delegates to structural \`resume_work\`; never answer work state from keyword search ("recent work" famously retrieves people named Work).
5198
6009
  - Knowledge about specific code (a function, file, or route): \`mindgraph_code\` action \`recall\` \u2014 anchor-based, not keyword-based.
5199
6010
  - Broad knowledge searches: \`mindgraph_retrieve\` as usual, but when searching for work artifacts pass node_types filters (Task, Lesson, RiskAssessment, Claim) to keep personal-graph content out of engineering answers.
5200
6011
 
5201
6012
  - Use mindgraph_sync for explicit, one-way project memory-file imports. It discovers and hashes files, but you deliberately map durable assertions with record; it never writes graph state back to files.
5202
6013
  - Capture corrections, decisions with rationale, fixes that survived meaningful failed attempts, durable invariants/preferences, and blockers with evidence.
6014
+ - If a write returns \`near_duplicate\`, choose \`reuse_uid\` or \`supersedes_uid\`; use \`confirm_new\` only for genuinely distinct facts.
5203
6015
  - Do not capture routine mechanics, transient command output, facts cheaply derivable from code, unsupported speculation as high-confidence lessons, or completion without evidence.
5204
6016
  - Adapter context is owned by ${HARNESS}; omit invocation_context because hooks replace it.`;
5205
6017
  } else {
@@ -5209,7 +6021,7 @@ if (PROFILE === "coding") {
5209
6021
  }
5210
6022
  var RUNNER_REFRESH = (() => {
5211
6023
  try {
5212
- return refreshHookRunner("0.19.0", import_node_path5.default.join(__dirname, "cli.js"));
6024
+ return refreshHookRunner("0.20.0", import_node_path5.default.join(__dirname, "cli.js"));
5213
6025
  } catch {
5214
6026
  return void 0;
5215
6027
  }
@@ -5223,12 +6035,12 @@ if (RUNNER_REFRESH?.refreshed) {
5223
6035
  }
5224
6036
  })();
5225
6037
  console.error(
5226
- `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.19.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
6038
+ `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.20.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
5227
6039
  );
5228
6040
  }
5229
6041
  var HOOK_SKEW_NOTE = (() => {
5230
6042
  try {
5231
- return versionSkewNote("0.19.0");
6043
+ return versionSkewNote("0.20.0");
5232
6044
  } catch {
5233
6045
  return void 0;
5234
6046
  }
@@ -5242,7 +6054,7 @@ ${HOOK_SKEW_NOTE}`;
5242
6054
  var server = new import_server.Server(
5243
6055
  {
5244
6056
  name: "mindgraph",
5245
- version: "0.19.0"
6057
+ version: "0.20.0"
5246
6058
  },
5247
6059
  {
5248
6060
  capabilities: {
@@ -5266,6 +6078,14 @@ var GOVERNANCE_CONFIG = {
5266
6078
  return process.env.MINDGRAPH_GOVERNANCE === "off";
5267
6079
  }
5268
6080
  };
6081
+ var MEMORY_RUNTIME_CONFIG = {
6082
+ serverVersion: "0.20.0",
6083
+ baseUrl: BASE_URL,
6084
+ profile: PROFILE,
6085
+ harness: HARNESS,
6086
+ orgId: ORG_ID,
6087
+ agentId: AGENT_ID
6088
+ };
5269
6089
  server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
5270
6090
  const { name, arguments: args } = request.params;
5271
6091
  const rawArgs = { ...args || {} };
@@ -5285,7 +6105,7 @@ server.setRequestHandler(import_types.CallToolRequestSchema, async (request) =>
5285
6105
  if (desc) {
5286
6106
  return handleGeneratedTool(client, desc, rawArgs);
5287
6107
  }
5288
- return handleTool(client, name, toolArgs);
6108
+ return handleTool(client, name, toolArgs, MEMORY_RUNTIME_CONFIG);
5289
6109
  });
5290
6110
  var PROMPTS = [
5291
6111
  {
@@ -5450,16 +6270,21 @@ server.setRequestHandler(import_types.ListResourceTemplatesRequestSchema, async
5450
6270
  return { resourceTemplates: RESOURCE_TEMPLATES };
5451
6271
  });
5452
6272
  server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
5453
- return readGovernedResource(client, request.params.uri, GOVERNANCE_CONFIG);
6273
+ return readGovernedResource(
6274
+ client,
6275
+ request.params.uri,
6276
+ GOVERNANCE_CONFIG,
6277
+ MEMORY_RUNTIME_CONFIG
6278
+ );
5454
6279
  });
5455
6280
  async function main() {
5456
6281
  const transport = new import_stdio.StdioServerTransport();
5457
6282
  await server.connect(transport);
5458
6283
  console.error(
5459
- `MindGraph MCP server v${"0.19.0"} running on stdio (${BASE_URL})`
6284
+ `MindGraph MCP server v${"0.20.0"} running on stdio (${BASE_URL})`
5460
6285
  );
5461
6286
  }
5462
- main().catch((error) => {
5463
- console.error("Fatal error:", error);
6287
+ main().catch((error2) => {
6288
+ console.error("Fatal error:", error2);
5464
6289
  process.exit(1);
5465
6290
  });