mindgraph-mcp 0.19.0 → 0.21.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 +38 -3
  2. package/dist/cli.js +1385 -247
  3. package/dist/index.js +1142 -197
  4. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -27,25 +27,127 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  ));
28
28
 
29
29
  // node_modules/mindgraph/dist/index.mjs
30
+ function errorFields(body) {
31
+ if (!body || typeof body !== "object" || Array.isArray(body)) return {};
32
+ const fields = body;
33
+ return {
34
+ ...typeof fields.code === "string" ? { code: fields.code } : {},
35
+ ...typeof fields.retriable === "boolean" ? { retriable: fields.retriable } : {}
36
+ };
37
+ }
38
+ function canRetryRequest(method, path8, body) {
39
+ if (method === "GET" || method === "HEAD") return true;
40
+ if (method !== "POST") return false;
41
+ if (READ_POSTS.has(path8)) return true;
42
+ const fields = body && typeof body === "object" && !Array.isArray(body) ? body : {};
43
+ if (typeof fields.action !== "string") return false;
44
+ if (Object.prototype.hasOwnProperty.call(READ_ACTIONS, path8) && READ_ACTIONS[path8].includes(fields.action)) return true;
45
+ return path8 === "/agent/plan" && IDEMPOTENT_WORK.has(fields.action) && typeof fields.idempotency_key === "string" && fields.idempotency_key.trim().length > 0;
46
+ }
47
+ function permitsRetry(fields) {
48
+ return fields.retriable !== false && ![
49
+ "query_memory_budget_exceeded",
50
+ "query_timeout",
51
+ "query_cancelled",
52
+ "vector_index_rebuilding"
53
+ ].includes(fields.code ?? "");
54
+ }
55
+ function retryDelayMs(header, backoff, attempt) {
56
+ const hint = Number(header);
57
+ return Number.isFinite(hint) && hint > 0 ? Math.min(hint * 1e3, 1e4) : Math.min(backoff * 2 ** Math.min(attempt, 32), 1e4);
58
+ }
30
59
  function seg(value) {
31
60
  return encodeURIComponent(String(value));
32
61
  }
62
+ function pathSteps(response) {
63
+ const wrapped = response && typeof response === "object" && !Array.isArray(response) ? response : void 0;
64
+ const raw = wrapped?.steps ?? response;
65
+ if (!Array.isArray(raw)) return [];
66
+ return raw.flatMap((value) => {
67
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [];
68
+ const step = value;
69
+ const taggedName = (tagged) => {
70
+ if (typeof tagged === "string") return tagged;
71
+ if (!tagged || typeof tagged !== "object" || Array.isArray(tagged)) return null;
72
+ const custom = tagged.Custom;
73
+ return typeof custom === "string" ? custom : null;
74
+ };
75
+ return [{
76
+ ...step,
77
+ node_type: taggedName(step.node_type) ?? "Unknown",
78
+ edge_type: taggedName(step.edge_type)
79
+ }];
80
+ });
81
+ }
33
82
  function createRequestId() {
34
83
  if (typeof globalThis.crypto?.randomUUID === "function") {
35
84
  return globalThis.crypto.randomUUID();
36
85
  }
37
86
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
38
87
  }
39
- var MindGraphError, MindGraph;
88
+ var READ_POSTS, READ_ACTIONS, IDEMPOTENT_WORK, MindGraphError, MindGraph;
40
89
  var init_dist = __esm({
41
90
  "node_modules/mindgraph/dist/index.mjs"() {
42
91
  "use strict";
92
+ READ_POSTS = /* @__PURE__ */ new Set([
93
+ "/search",
94
+ "/retrieve/context",
95
+ "/nodes/batch",
96
+ "/edges/batch",
97
+ "/subgraph",
98
+ "/embeddings/search",
99
+ "/embeddings/search-text",
100
+ "/ontology/query",
101
+ "/ontology/query/structured",
102
+ "/ontology/objects/search"
103
+ ]);
104
+ READ_ACTIONS = {
105
+ "/retrieve": [
106
+ "text",
107
+ "semantic",
108
+ "hybrid",
109
+ "active_goals",
110
+ "open_questions",
111
+ "preferences",
112
+ "weak_claims",
113
+ "pending_approvals",
114
+ "unresolved_contradictions",
115
+ "merge_candidates",
116
+ "stale_derivations",
117
+ "curation_counts",
118
+ "layer",
119
+ "recent"
120
+ ],
121
+ "/traverse": ["chain", "top_k_paths", "neighborhood", "path", "subgraph"],
122
+ "/reality/series": ["window", "aggregate", "latest", "list_for_entity", "batch_latest", "batch_aggregate"],
123
+ "/intent/deliberation": ["get_open"],
124
+ "/action/risk": ["get_assessments"],
125
+ "/action/skill": ["get", "list"],
126
+ "/memory/config": ["get_preferences", "get_policies"],
127
+ "/memory/sync": ["status"],
128
+ "/agent/plan": ["get_plan", "resume_work"],
129
+ "/agent/governance": ["get_pending", "check"],
130
+ "/agent/execution": ["get_executions"],
131
+ "/evolve": ["history", "snapshot"]
132
+ };
133
+ IDEMPOTENT_WORK = /* @__PURE__ */ new Set([
134
+ "claim_task",
135
+ "heartbeat",
136
+ "start_iteration",
137
+ "checkpoint_iteration",
138
+ "block_task",
139
+ "complete_task",
140
+ "abandon_iteration"
141
+ ]);
43
142
  MindGraphError = class extends Error {
44
143
  constructor(message, status, body) {
45
144
  super(message);
46
145
  this.status = status;
47
146
  this.body = body;
48
147
  this.name = "MindGraphError";
148
+ const fields = errorFields(body);
149
+ this.code = fields.code;
150
+ this.retriable = fields.retriable;
49
151
  }
50
152
  };
51
153
  MindGraph = class {
@@ -63,6 +165,18 @@ var init_dist = __esm({
63
165
  this.telemetrySurface = config.telemetrySurface;
64
166
  this.maxRetries = config.maxRetries ?? 3;
65
167
  this.retryBackoffMs = config.retryBackoffMs ?? 1e3;
168
+ if (!Number.isSafeInteger(this.maxRetries) || this.maxRetries < 0) {
169
+ throw new RangeError("maxRetries must be a non-negative safe integer");
170
+ }
171
+ if (!Number.isFinite(this.retryBackoffMs) || this.retryBackoffMs < 0) {
172
+ throw new RangeError("retryBackoffMs must be finite and non-negative");
173
+ }
174
+ if (config.timeoutMs !== void 0) {
175
+ if (!Number.isFinite(config.timeoutMs) || config.timeoutMs <= 0) {
176
+ throw new RangeError("timeoutMs must be a positive finite number of milliseconds");
177
+ }
178
+ this.timeoutMs = config.timeoutMs;
179
+ }
66
180
  }
67
181
  // ---- HTTP helpers ----
68
182
  async request(method, path8, body) {
@@ -79,37 +193,68 @@ var init_dist = __esm({
79
193
  if (body !== void 0) {
80
194
  init.body = JSON.stringify(body);
81
195
  }
196
+ const retrySafe = canRetryRequest(
197
+ method,
198
+ path8,
199
+ typeof init.body === "string" ? JSON.parse(init.body) : void 0
200
+ );
82
201
  let lastError;
83
202
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
84
- const res = await fetch(url, init);
203
+ const res = await this.fetchWithDeadline(url, init, method, path8);
85
204
  if (!res.ok) {
86
- const text2 = await res.text().catch(() => "");
205
+ const text22 = await res.text().catch(() => "");
87
206
  let parsed;
88
207
  try {
89
- parsed = JSON.parse(text2);
208
+ parsed = JSON.parse(text22);
90
209
  } catch {
91
- parsed = text2;
210
+ parsed = text22;
92
211
  }
93
212
  const err5 = new MindGraphError(
94
213
  `${method} ${path8} failed: ${res.status}`,
95
214
  res.status,
96
215
  parsed
97
216
  );
98
- if (res.status === 503 && attempt < this.maxRetries) {
217
+ if (res.status === 503 && retrySafe && permitsRetry(err5) && attempt < this.maxRetries) {
99
218
  lastError = err5;
100
- const retryAfterSecs = Number(res.headers.get("retry-after"));
101
- const delay = Number.isFinite(retryAfterSecs) && retryAfterSecs > 0 ? Math.min(retryAfterSecs * 1e3, 1e4) : this.retryBackoffMs * 2 ** attempt;
219
+ const delay = retryDelayMs(res.headers.get("retry-after"), this.retryBackoffMs, attempt);
102
220
  await new Promise((r) => setTimeout(r, delay));
103
221
  continue;
104
222
  }
105
223
  throw err5;
106
224
  }
107
- const text = await res.text();
108
- if (!text) return void 0;
109
- return JSON.parse(text);
225
+ const text2 = await res.text();
226
+ if (!text2) return void 0;
227
+ return JSON.parse(text2);
110
228
  }
111
229
  throw lastError;
112
230
  }
231
+ /**
232
+ * One HTTP attempt. With `timeoutMs` set, the attempt is aborted at the
233
+ * deadline and surfaces as a non-retriable `MindGraphError` (`code:
234
+ * "timeout"`, `status: 0`). Without it the request is sent unchanged — no
235
+ * AbortSignal is attached, preserving the pre-0.16 behaviour.
236
+ */
237
+ async fetchWithDeadline(url, init, method, path8) {
238
+ if (this.timeoutMs === void 0) {
239
+ return fetch(url, init);
240
+ }
241
+ const controller = new AbortController();
242
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
243
+ try {
244
+ return await fetch(url, { ...init, signal: controller.signal });
245
+ } catch (error2) {
246
+ if (controller.signal.aborted) {
247
+ throw new MindGraphError(
248
+ `${method} ${path8} timed out after ${this.timeoutMs}ms`,
249
+ 0,
250
+ { error: "timeout", code: "timeout", retriable: false }
251
+ );
252
+ }
253
+ throw error2;
254
+ } finally {
255
+ clearTimeout(timer);
256
+ }
257
+ }
113
258
  get(path8) {
114
259
  return this.request("GET", path8);
115
260
  }
@@ -232,10 +377,10 @@ var init_dist = __esm({
232
377
  * Resolve text to an existing entity via alias matching.
233
378
  * Returns `{ uid: string | null }`.
234
379
  */
235
- async resolveEntity(text, agentId) {
380
+ async resolveEntity(text2, agentId) {
236
381
  return this.post("/reality/entity", {
237
382
  action: "resolve",
238
- text,
383
+ text: text2,
239
384
  agent_id: agentId
240
385
  });
241
386
  }
@@ -243,10 +388,10 @@ var init_dist = __esm({
243
388
  * Fuzzy-match text against existing entities.
244
389
  * Returns `{ matches: [{ uid, label, score }] }`.
245
390
  */
246
- async fuzzyResolveEntity(text, limit = 5, agentId) {
391
+ async fuzzyResolveEntity(text2, limit = 5, agentId) {
247
392
  return this.post("/reality/entity", {
248
393
  action: "fuzzy_resolve",
249
- text,
394
+ text: text2,
250
395
  limit,
251
396
  agent_id: agentId
252
397
  });
@@ -359,6 +504,40 @@ var init_dist = __esm({
359
504
  async memorySync(req) {
360
505
  return this.post("/memory/sync", req);
361
506
  }
507
+ // ---- Memory (fast path) ----
508
+ /**
509
+ * Store a small piece of text as a memory, synchronously. The node is
510
+ * BM25- and vector-searchable when this resolves (`searchable` says which).
511
+ * Pass `custom_id` to make re-sends an upsert of the same node.
512
+ */
513
+ async remember(text2, options = {}) {
514
+ return this.post("/memory/remember", { text: text2, ...options });
515
+ }
516
+ /**
517
+ * Reversibly remove a memory by `uid` or `custom_id` (tombstone plus its
518
+ * connected edges). `dry_run: true` previews the affected edge uids first.
519
+ * Undo with `restore(uid)` and `evolve({ action: "restore_edge", uid })`.
520
+ */
521
+ async forget(target, options = {}) {
522
+ return this.post("/memory/forget", { ...target, ...options });
523
+ }
524
+ /**
525
+ * Operator guidance for what agents should remember into a Space; surfaced
526
+ * on every `remember()` response and on `retrieveContext()`. Empty clears it.
527
+ */
528
+ async setRememberInstructions(text2, options = {}) {
529
+ return this.post("/memory/config", {
530
+ action: "set_remember_instructions",
531
+ text: text2,
532
+ ...options
533
+ });
534
+ }
535
+ async getRememberInstructions(options = {}) {
536
+ return this.post("/memory/config", {
537
+ action: "get_remember_instructions",
538
+ ...options
539
+ });
540
+ }
362
541
  // ---- Agent Layer ----
363
542
  async plan(req) {
364
543
  return this.post("/agent/plan", req);
@@ -545,8 +724,8 @@ var init_dist = __esm({
545
724
  async getAliases(uid) {
546
725
  return this.get(`/aliases/${seg(uid)}`);
547
726
  }
548
- async resolveAlias(text) {
549
- return this.get(`/resolve?text=${encodeURIComponent(text)}`);
727
+ async resolveAlias(text2) {
728
+ return this.get(`/resolve?text=${encodeURIComponent(text2)}`);
550
729
  }
551
730
  // ---- Export / Import ----
552
731
  async exportGraph() {
@@ -581,7 +760,7 @@ var init_dist = __esm({
581
760
  start_uid: uid,
582
761
  max_depth: maxDepth
583
762
  });
584
- return r?.steps ?? r;
763
+ return pathSteps(r);
585
764
  }
586
765
  async neighborhood(uid, maxDepth = 1) {
587
766
  const r = await this.post("/traverse", {
@@ -589,7 +768,7 @@ var init_dist = __esm({
589
768
  start_uid: uid,
590
769
  max_depth: maxDepth
591
770
  });
592
- return r?.steps ?? r;
771
+ return pathSteps(r);
593
772
  }
594
773
  async subgraph(uid, opts) {
595
774
  return this.post("/subgraph", { start_uids: [uid], ...opts });
@@ -960,11 +1139,19 @@ var init_dist = __esm({
960
1139
  /**
961
1140
  * Create a domain object by hand (auto-approved). Returns the created node
962
1141
  * uid + the audit proposal id. Throws 409 if an object of the same type +
963
- * canonical_name already exists, unless `allow_duplicate` is set.
1142
+ * canonical_name already exists, unless `allow_duplicate` is set. The
1143
+ * schema's identity key is always enforced.
964
1144
  */
965
1145
  async createDomainObject(req) {
966
1146
  return this.post("/v1/ontology/objects", req);
967
1147
  }
1148
+ /**
1149
+ * Schema-validated authored update with optimistic concurrency. Identity
1150
+ * fields are immutable; create and explicitly merge when identity changes.
1151
+ */
1152
+ async updateDomainObject(uid, req) {
1153
+ return this.patch(`/v1/ontology/objects/${seg(uid)}`, req);
1154
+ }
968
1155
  async linkDomainObjects(req) {
969
1156
  return this.post("/ontology/relation", { action: "create", ...req });
970
1157
  }
@@ -1163,29 +1350,29 @@ async function runProcess(executable, args, timeoutMs, env, cwd) {
1163
1350
  child.stderr.on("data", collect(stderr));
1164
1351
  child.on("error", (cause) => {
1165
1352
  clearTimeout(timer);
1166
- const error = new Error(cause.message);
1167
- error.kind = cause.code === "ENOENT" ? "absent" : "command_failed";
1168
- rejectPromise(error);
1353
+ const error2 = new Error(cause.message);
1354
+ error2.kind = cause.code === "ENOENT" ? "absent" : "command_failed";
1355
+ rejectPromise(error2);
1169
1356
  });
1170
1357
  child.on("close", (code) => {
1171
1358
  clearTimeout(timer);
1172
1359
  if (timedOut) {
1173
- const error = new Error(`command timed out after ${timeoutMs}ms`);
1174
- error.kind = "timeout";
1175
- rejectPromise(error);
1360
+ const error2 = new Error(`command timed out after ${timeoutMs}ms`);
1361
+ error2.kind = "timeout";
1362
+ rejectPromise(error2);
1176
1363
  return;
1177
1364
  }
1178
1365
  if (outputBytes > MAX_OUTPUT_BYTES) {
1179
- const error = new Error("codegraph output exceeded 5 MiB");
1180
- error.kind = "command_failed";
1181
- rejectPromise(error);
1366
+ const error2 = new Error("codegraph output exceeded 5 MiB");
1367
+ error2.kind = "command_failed";
1368
+ rejectPromise(error2);
1182
1369
  return;
1183
1370
  }
1184
1371
  if (code !== 0) {
1185
- const error = new Error(Buffer.concat(stderr).toString("utf8").trim());
1186
- error.kind = "command_failed";
1187
- error.exitCode = code;
1188
- rejectPromise(error);
1372
+ const error2 = new Error(Buffer.concat(stderr).toString("utf8").trim());
1373
+ error2.kind = "command_failed";
1374
+ error2.exitCode = code;
1375
+ rejectPromise(error2);
1189
1376
  return;
1190
1377
  }
1191
1378
  resolvePromise(Buffer.concat(stdout).toString("utf8"));
@@ -1213,9 +1400,7 @@ function codeRefIdentityKey(ref) {
1213
1400
  qualified_name: ref.qualifiedName,
1214
1401
  disambiguator: {
1215
1402
  signature: ref.signature ?? "",
1216
- path: ref.path,
1217
- start_line: ref.startLine,
1218
- end_line: ref.endLine
1403
+ path: ref.path
1219
1404
  }
1220
1405
  };
1221
1406
  }
@@ -1789,6 +1974,22 @@ function conflictState(e) {
1789
1974
  }
1790
1975
  return lifted;
1791
1976
  }
1977
+ function toolError(e, fallbackCode) {
1978
+ const body = e instanceof MindGraphError && e.body && typeof e.body === "object" && !Array.isArray(e.body) ? e.body : {};
1979
+ const code = typeof body.code === "string" ? body.code : fallbackCode;
1980
+ const payload = {
1981
+ error: errorDetail(e),
1982
+ ...conflictState(e),
1983
+ ...e instanceof MindGraphError ? { status: e.status } : {},
1984
+ ...code !== void 0 ? { code } : {},
1985
+ ...typeof body.retriable === "boolean" ? { retriable: body.retriable } : {}
1986
+ };
1987
+ return {
1988
+ content: [{ type: "text", text: JSON.stringify(payload) }],
1989
+ structuredContent: payload,
1990
+ isError: true
1991
+ };
1992
+ }
1792
1993
  var CONFLICT_STATE_KEYS;
1793
1994
  var init_error_detail = __esm({
1794
1995
  "src/error-detail.ts"() {
@@ -1996,7 +2197,7 @@ async function handleCodeTool(client2, args, adapter = new CodegraphAdapter({ cw
1996
2197
  if (cause instanceof UnknownRepositoryError) {
1997
2198
  return err("unknown_repository", cause.message);
1998
2199
  }
1999
- return err("code_tool_failed", errorDetail(cause));
2200
+ return toolError(cause, "code_tool_failed");
2000
2201
  }
2001
2202
  }
2002
2203
  async function attachCodeRefsToToolResult(client2, result, args, adapter) {
@@ -2104,7 +2305,7 @@ async function anchorCodeRef(client2, ref, repositoryUid, spaceUid, agentId) {
2104
2305
  identity_space_uid: spaceUid,
2105
2306
  agent_id: agentId
2106
2307
  });
2107
- if (response.uid) {
2308
+ if (response.uid && response.created !== false && response.status !== "existing") {
2108
2309
  await client2.entity({
2109
2310
  action: "relate",
2110
2311
  source_uid: response.uid,
@@ -2739,8 +2940,8 @@ async function handleSyncTool(client2, args) {
2739
2940
  }
2740
2941
  }
2741
2942
  return ok2(await remote.memorySync(request));
2742
- } catch (error) {
2743
- return err2("sync_failed", errorDetail(error));
2943
+ } catch (error2) {
2944
+ return toolError(error2, "sync_failed");
2744
2945
  }
2745
2946
  }
2746
2947
  async function scanRepository(remote, repository, requested, provider, agentId) {
@@ -2874,12 +3075,22 @@ function str(value) {
2874
3075
  function num(value) {
2875
3076
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2876
3077
  }
3078
+ function edgeTypeName(value) {
3079
+ return str(value) ?? str(obj(value)?.Custom);
3080
+ }
3081
+ function renderBudget(requested) {
3082
+ if (requested === void 0 || !Number.isFinite(requested)) return RENDER_CHAR_BUDGET;
3083
+ return Math.min(
3084
+ RENDER_CHAR_BUDGET,
3085
+ Math.max(MIN_RENDER_CHAR_BUDGET, Math.floor(requested))
3086
+ );
3087
+ }
2877
3088
  function clip(value, max, state) {
2878
- const text = str(value);
2879
- if (!text) return void 0;
2880
- if (text.length <= max) return text;
3089
+ const text2 = str(value);
3090
+ if (!text2) return void 0;
3091
+ if (text2.length <= max) return text2;
2881
3092
  state.bounded = true;
2882
- return `${text.slice(0, max - 1)}\u2026`;
3093
+ return `${text2.slice(0, max - 1)}\u2026`;
2883
3094
  }
2884
3095
  function isNodeShaped(value) {
2885
3096
  const node = obj(value);
@@ -3001,7 +3212,7 @@ function relationshipItems(edges, labelByUid, state, options) {
3001
3212
  for (const raw of edges) {
3002
3213
  const edge = obj(raw);
3003
3214
  if (!edge) continue;
3004
- const type = str(edge.edge_type) || "?";
3215
+ const type = edgeTypeName(edge.edge_type) || "?";
3005
3216
  const from = str(edge.from_uid);
3006
3217
  const to = str(edge.to_uid);
3007
3218
  if (CURATION_EDGE_TYPES.has(type)) continue;
@@ -3014,25 +3225,29 @@ function relationshipItems(edges, labelByUid, state, options) {
3014
3225
  if (lines.length > RELATIONSHIP_MAX) {
3015
3226
  state.bounded = true;
3016
3227
  const extra = lines.length - RELATIONSHIP_MAX;
3228
+ state.omitted += extra;
3017
3229
  return [...lines.slice(0, RELATIONSHIP_MAX), `- (+${extra} more relationships)`];
3018
3230
  }
3019
3231
  return lines;
3020
3232
  }
3021
- function assemble(header, sections, state) {
3233
+ function assemble(header, sections, state, maxChars) {
3234
+ const budget = renderBudget(maxChars);
3235
+ const packingBudget = Math.max(header.length, budget - BOUNDED_MARKER_RESERVE);
3022
3236
  const lines = [header];
3023
3237
  let used = header.length;
3024
3238
  for (const section of sections) {
3025
3239
  if (section.items.length === 0 && !section.heading) continue;
3026
3240
  const headingCost = section.heading.length + 2;
3027
- if (used + headingCost > RENDER_CHAR_BUDGET) {
3241
+ if (used + headingCost > packingBudget) {
3028
3242
  state.bounded = true;
3243
+ state.omitted += Math.max(1, section.items.length);
3029
3244
  continue;
3030
3245
  }
3031
3246
  let sectionUsed = 0;
3032
3247
  const kept = [];
3033
3248
  let dropped = 0;
3034
3249
  for (const item of section.items) {
3035
- if (used + headingCost + sectionUsed + item.length + 1 > RENDER_CHAR_BUDGET) {
3250
+ if (used + headingCost + sectionUsed + item.length + 1 > packingBudget) {
3036
3251
  dropped += 1;
3037
3252
  continue;
3038
3253
  }
@@ -3041,13 +3256,17 @@ function assemble(header, sections, state) {
3041
3256
  }
3042
3257
  if (kept.length === 0 && section.items.length > 0) {
3043
3258
  state.bounded = true;
3259
+ state.omitted += dropped;
3044
3260
  continue;
3045
3261
  }
3046
3262
  if (dropped > 0) {
3047
3263
  state.bounded = true;
3264
+ state.omitted += dropped;
3048
3265
  const marker = `(+${dropped} more not shown)`;
3049
- kept.push(marker);
3050
- sectionUsed += marker.length + 1;
3266
+ if (used + headingCost + sectionUsed + marker.length + 1 <= packingBudget) {
3267
+ kept.push(marker);
3268
+ sectionUsed += marker.length + 1;
3269
+ }
3051
3270
  }
3052
3271
  lines.push(
3053
3272
  section.items.length === 0 ? section.heading : section.heading ? `${section.heading}
@@ -3057,10 +3276,10 @@ ${kept.join("\n")}` : kept.join("\n")
3057
3276
  }
3058
3277
  if (state.bounded) {
3059
3278
  lines.push(
3060
- '\u2026[rendered context bounded \u2014 fetch full node content by uid, or pass format: "json" for the raw response]'
3279
+ `\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]`
3061
3280
  );
3062
3281
  }
3063
- return lines.join("\n\n");
3282
+ return lines.join("\n\n").slice(0, budget);
3064
3283
  }
3065
3284
  function policySections(payload, state) {
3066
3285
  const policies = Array.isArray(payload.applicable_policies) ? payload.applicable_policies : [];
@@ -3081,7 +3300,7 @@ function hiddenSections(payload) {
3081
3300
  }
3082
3301
  ];
3083
3302
  }
3084
- function renderContext(response) {
3303
+ function renderContext(response, maxChars) {
3085
3304
  const payload = obj(response);
3086
3305
  if (!payload) return void 0;
3087
3306
  const graph = obj(payload.graph);
@@ -3091,7 +3310,7 @@ function renderContext(response) {
3091
3310
  const infraOnly = shaped.length - nodes.length;
3092
3311
  const articles = Array.isArray(payload.articles) ? payload.articles : [];
3093
3312
  const chunks = Array.isArray(payload.chunks) ? payload.chunks : [];
3094
- const state = { bounded: false };
3313
+ const state = { bounded: false, omitted: 0 };
3095
3314
  const sections = [];
3096
3315
  if (articles.length > 0) {
3097
3316
  sections.push({
@@ -3134,11 +3353,129 @@ ${clip(article.content, ARTICLE_MAX, state) || ""}`
3134
3353
  return "No results found for this search.";
3135
3354
  }
3136
3355
  if (nodes.length === 0 && articles.length === 0 && chunks.length === 0) {
3137
- return assemble("# Retrieved context\n\nNo graph results matched this search.", sections, state);
3356
+ return assemble(
3357
+ "# Retrieved context\n\nNo graph results matched this search.",
3358
+ sections,
3359
+ state,
3360
+ maxChars
3361
+ );
3362
+ }
3363
+ return assemble("# Retrieved context", sections, state, maxChars);
3364
+ }
3365
+ function resumeEntry(raw, state, fallbackType) {
3366
+ const wrapper = obj(raw);
3367
+ if (!wrapper) return void 0;
3368
+ const value = obj(wrapper.node) ?? wrapper;
3369
+ if (isNodeShaped(value)) return nodeLines(value, state);
3370
+ const uid = str(value.uid);
3371
+ const props = obj(value.props);
3372
+ 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;
3373
+ const status = str(value.status) ?? str(props?.status);
3374
+ 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);
3375
+ const details = [
3376
+ status ? `status ${status}` : void 0,
3377
+ str(value.path) ? `path ${str(value.path)}` : void 0,
3378
+ str(value.symbol) ? `symbol ${str(value.symbol)}` : void 0
3379
+ ].filter(Boolean);
3380
+ const head = `- **${label}**${uid ? ` [${uid}]` : ""}${details.length > 0 ? ` (${details.join(", ")})` : ""}`;
3381
+ return summary && summary !== label ? `${head}: ${summary}` : head;
3382
+ }
3383
+ function resumeSection(heading, values, state, fallbackType) {
3384
+ const items = values.map((value) => resumeEntry(value, state, fallbackType)).filter((value) => Boolean(value));
3385
+ return items.length > 0 ? { heading, items } : void 0;
3386
+ }
3387
+ function renderResumeContext(response, maxChars) {
3388
+ const payload = obj(response);
3389
+ if (!payload) return void 0;
3390
+ const state = { bounded: false, omitted: 0 };
3391
+ const status = str(payload.status);
3392
+ if (!payload.task && status === "no_eligible_work") {
3393
+ const scopedOut = Array.isArray(payload.scoped_out_tasks) ? payload.scoped_out_tasks.length : 0;
3394
+ const note = scopedOut > 0 ? ` ${scopedOut} task(s) exist outside the requested scope.` : "";
3395
+ return assemble(
3396
+ "# Resume context",
3397
+ [{ heading: `No eligible work was found in the requested scope.${note}`, items: [] }],
3398
+ state,
3399
+ maxChars
3400
+ );
3401
+ }
3402
+ const sections = [];
3403
+ const push = (section) => {
3404
+ if (section) sections.push(section);
3405
+ };
3406
+ push(resumeSection("## Current task", [payload.task], state, "Task"));
3407
+ push(
3408
+ resumeSection(
3409
+ "## Goal and project",
3410
+ [payload.goal, payload.project].filter(Boolean),
3411
+ state,
3412
+ "Scope"
3413
+ )
3414
+ );
3415
+ push(resumeSection("## Plan", [payload.plan], state, "Plan"));
3416
+ push(
3417
+ resumeSection(
3418
+ "## Next steps",
3419
+ Array.isArray(payload.next_steps) ? payload.next_steps : [],
3420
+ state,
3421
+ "Next step"
3422
+ )
3423
+ );
3424
+ push(
3425
+ resumeSection(
3426
+ "## Blockers",
3427
+ Array.isArray(payload.blockers) ? payload.blockers : [],
3428
+ state,
3429
+ "Blocker"
3430
+ )
3431
+ );
3432
+ const knowledge = obj(payload.knowledge);
3433
+ push(
3434
+ resumeSection(
3435
+ "## Relevant lessons and risks",
3436
+ [
3437
+ ...Array.isArray(knowledge?.lessons) ? knowledge.lessons : [],
3438
+ ...Array.isArray(knowledge?.risks) ? knowledge.risks : []
3439
+ ],
3440
+ state,
3441
+ "Knowledge"
3442
+ )
3443
+ );
3444
+ push(
3445
+ resumeSection(
3446
+ "## Active execution",
3447
+ [payload.active_execution].filter(Boolean),
3448
+ state,
3449
+ "Execution"
3450
+ )
3451
+ );
3452
+ push(
3453
+ resumeSection(
3454
+ "## Recent executions",
3455
+ Array.isArray(payload.recent_executions) ? payload.recent_executions : [],
3456
+ state,
3457
+ "Execution"
3458
+ )
3459
+ );
3460
+ push(
3461
+ resumeSection(
3462
+ "## Code targets",
3463
+ Array.isArray(payload.code_targets) ? payload.code_targets : [],
3464
+ state,
3465
+ "Code target"
3466
+ )
3467
+ );
3468
+ push(resumeSection("## Lease", [payload.lease].filter(Boolean), state, "Lease"));
3469
+ const warning = clip(payload.warning, SUMMARY_MAX, state);
3470
+ if (warning) sections.push({ heading: "## Warning", items: [`- ${warning}`] });
3471
+ const selectionReason = clip(payload.selection_reason, SUMMARY_MAX, state);
3472
+ if (selectionReason) {
3473
+ sections.push({ heading: "## Selection", items: [`- ${selectionReason}`] });
3138
3474
  }
3139
- return assemble("# Retrieved context", sections, state);
3475
+ if (sections.length === 0) return void 0;
3476
+ return assemble("# Resume context", sections, state, maxChars);
3140
3477
  }
3141
- function renderNodeList(response, title, limit) {
3478
+ function renderNodeList(response, title, limit, maxChars) {
3142
3479
  let candidates = [];
3143
3480
  if (Array.isArray(response)) {
3144
3481
  candidates = response;
@@ -3156,8 +3493,9 @@ function renderNodeList(response, title, limit) {
3156
3493
  let nodes = candidates.map(unwrapSearchResult).filter((node) => Boolean(node));
3157
3494
  if (nodes.length === 0) return void 0;
3158
3495
  const total = nodes.length;
3159
- const state = { bounded: false };
3496
+ const state = { bounded: false, omitted: 0 };
3160
3497
  if (limit !== void 0 && limit > 0 && nodes.length > limit) {
3498
+ state.omitted += nodes.length - limit;
3161
3499
  nodes = nodes.slice(0, limit);
3162
3500
  state.bounded = true;
3163
3501
  }
@@ -3166,18 +3504,23 @@ function renderNodeList(response, title, limit) {
3166
3504
  return assemble(
3167
3505
  `# ${title} (${shown === total ? total : `${shown} of ${total}`})`,
3168
3506
  sections,
3169
- state
3507
+ state,
3508
+ maxChars
3170
3509
  );
3171
3510
  }
3172
- function renderTraversal(response) {
3511
+ function renderTraversal(response, maxChars) {
3512
+ const directSteps = Array.isArray(response) ? response : void 0;
3173
3513
  const payload = obj(response);
3174
- if (!payload) return void 0;
3175
- const container = obj(payload.graph) ?? payload;
3514
+ if (!payload && !directSteps) return void 0;
3515
+ const body = payload ?? {};
3516
+ const container = obj(body.graph) ?? body;
3176
3517
  const rawNodes = Array.isArray(container.nodes) ? container.nodes : [];
3177
3518
  const nodes = rawNodes.filter(isNodeShaped);
3178
- const paths = Array.isArray(payload.paths) ? payload.paths : [];
3179
- if (nodes.length === 0 && paths.length === 0) return void 0;
3180
- const state = { bounded: false };
3519
+ const paths = Array.isArray(body.paths) ? body.paths : [];
3520
+ const hasSteps = directSteps !== void 0 || Object.hasOwn(body, "steps");
3521
+ const rawSteps = directSteps ?? (Array.isArray(body.steps) ? body.steps : []);
3522
+ if (nodes.length === 0 && paths.length === 0 && !hasSteps) return void 0;
3523
+ const state = { bounded: false, omitted: 0 };
3181
3524
  const sections = [];
3182
3525
  const labelByUid = /* @__PURE__ */ new Map();
3183
3526
  for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
@@ -3201,6 +3544,42 @@ function renderTraversal(response) {
3201
3544
  });
3202
3545
  sections.push({ heading: "## Paths", items });
3203
3546
  }
3547
+ if (hasSteps) {
3548
+ const stepItems = rawSteps.map((raw) => obj(raw)).filter((step) => Boolean(step)).map((step) => {
3549
+ const depth = num(step.depth);
3550
+ const uid = str(step.node_uid) ?? str(step.uid) ?? str(step.edge_to_uid) ?? "?";
3551
+ const parent = str(step.parent_uid) ?? str(step.edge_from_uid);
3552
+ const label = str(step.label) || "(unlabeled)";
3553
+ const relation = edgeTypeName(step.edge_type);
3554
+ const role = str(step.traversal_role);
3555
+ const type = str(step.node_type) ?? str(obj(step.node_type)?.Custom);
3556
+ const cost = num(step.path_cost);
3557
+ const confidence = num(step.path_confidence);
3558
+ const prefix = [
3559
+ depth !== void 0 ? `depth ${depth}` : void 0,
3560
+ role,
3561
+ relation ? `\u2014${relation}\u2192` : void 0
3562
+ ].filter(Boolean).join(" ");
3563
+ const meta = [
3564
+ type,
3565
+ parent ? `parent ${parent}` : void 0,
3566
+ cost !== void 0 ? `cost ${cost}` : void 0,
3567
+ confidence !== void 0 ? `confidence ${confidence}` : void 0
3568
+ ].filter(Boolean).join(", ");
3569
+ return `- ${prefix ? `${prefix}: ` : ""}**${label}** [${uid}]${meta ? ` (${meta})` : ""}`;
3570
+ });
3571
+ if (stepItems.length > 0) {
3572
+ sections.push({ heading: "## Traversal steps", items: stepItems });
3573
+ } else {
3574
+ const start = str(body.start_uid) || "?";
3575
+ const end = str(body.end_uid);
3576
+ const mode2 = str(body.mode);
3577
+ sections.push({
3578
+ heading: mode2 === "path" && end ? `No path found from [${start}] to [${end}].` : `No traversal steps returned from [${start}].`,
3579
+ items: []
3580
+ });
3581
+ }
3582
+ }
3204
3583
  if (nodes.length > 0) {
3205
3584
  sections.push(...groupedNodeSections(nodes, state, "## Nodes\n\n"));
3206
3585
  const relationships = relationshipItems(
@@ -3212,7 +3591,13 @@ function renderTraversal(response) {
3212
3591
  sections.push({ heading: "## Relationships", items: relationships });
3213
3592
  }
3214
3593
  }
3215
- return assemble("# Graph traversal", sections, state);
3594
+ const mode = str(body.mode);
3595
+ return assemble(
3596
+ `# Graph traversal${mode ? ` (${mode})` : ""}`,
3597
+ sections,
3598
+ state,
3599
+ maxChars
3600
+ );
3216
3601
  }
3217
3602
  function neighborLine(value) {
3218
3603
  const node = obj(value);
@@ -3233,7 +3618,7 @@ function renderOntologyAnswer(response) {
3233
3618
  if (objects.length === 0 && cognitiveEmpty && provenance.length === 0) {
3234
3619
  return hasQuerySignature ? "No matching domain objects." : void 0;
3235
3620
  }
3236
- const state = { bounded: false };
3621
+ const state = { bounded: false, omitted: 0 };
3237
3622
  const sections = [];
3238
3623
  if (objects.length > 0) {
3239
3624
  sections.push(...groupedNodeSections(objects, state));
@@ -3299,7 +3684,7 @@ function renderObjectList(response, title) {
3299
3684
  if (items.length === 0) {
3300
3685
  return rawItems ? `# ${title} (0 results)` : void 0;
3301
3686
  }
3302
- const state = { bounded: false };
3687
+ const state = { bounded: false, omitted: 0 };
3303
3688
  const sections = groupedNodeSections(items, state);
3304
3689
  const total = num(payload.total_count);
3305
3690
  const truncations = Array.isArray(payload.truncation_reasons) ? payload.truncation_reasons.filter((reason) => typeof reason === "string") : [];
@@ -3314,7 +3699,7 @@ function renderJobs(response) {
3314
3699
  const jobs = Array.isArray(response) ? response : obj(response)?.jobs;
3315
3700
  if (!Array.isArray(jobs)) return void 0;
3316
3701
  if (jobs.length === 0) return "No ingestion jobs.";
3317
- const state = { bounded: false };
3702
+ const state = { bounded: false, omitted: 0 };
3318
3703
  const sorted = jobs.map((raw) => obj(raw)).filter((job) => Boolean(job)).sort((a, b) => (num(b.created_at) ?? 0) - (num(a.created_at) ?? 0));
3319
3704
  const shown = sorted.slice(0, JOBS_RENDER_MAX);
3320
3705
  if (shown.length < sorted.length) state.bounded = true;
@@ -3340,7 +3725,7 @@ function renderJobs(response) {
3340
3725
  function renderSignals(response) {
3341
3726
  const payload = obj(response);
3342
3727
  if (!payload) return void 0;
3343
- const state = { bounded: false };
3728
+ const state = { bounded: false, omitted: 0 };
3344
3729
  const sections = [];
3345
3730
  for (const [key, value] of Object.entries(payload)) {
3346
3731
  if (!Array.isArray(value) || value.length === 0) continue;
@@ -3364,7 +3749,7 @@ function renderSignals(response) {
3364
3749
  if (sections.length === 0) return void 0;
3365
3750
  return assemble("# Synthesis signals", sections, state);
3366
3751
  }
3367
- var LABEL_MAX, SUMMARY_MAX, QUOTE_MAX, ARTICLE_MAX, CHUNK_MAX, RELATIONSHIP_MAX, RENDER_CHAR_BUDGET, CURATION_EDGE_TYPES, INFRA_NODE_TYPES, JOBS_RENDER_MAX;
3752
+ var LABEL_MAX, SUMMARY_MAX, QUOTE_MAX, ARTICLE_MAX, CHUNK_MAX, RELATIONSHIP_MAX, RENDER_CHAR_BUDGET, MIN_RENDER_CHAR_BUDGET, BOUNDED_MARKER_RESERVE, CURATION_EDGE_TYPES, INFRA_NODE_TYPES, JOBS_RENDER_MAX;
3368
3753
  var init_render = __esm({
3369
3754
  "src/render.ts"() {
3370
3755
  "use strict";
@@ -3375,13 +3760,570 @@ var init_render = __esm({
3375
3760
  CHUNK_MAX = 6e3;
3376
3761
  RELATIONSHIP_MAX = 40;
3377
3762
  RENDER_CHAR_BUDGET = 24e3;
3763
+ MIN_RENDER_CHAR_BUDGET = 512;
3764
+ BOUNDED_MARKER_RESERVE = 220;
3378
3765
  CURATION_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
3379
3766
  INFRA_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
3380
3767
  JOBS_RENDER_MAX = 20;
3381
3768
  }
3382
3769
  });
3383
3770
 
3771
+ // src/memory-tool.ts
3772
+ function obj2(value) {
3773
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3774
+ }
3775
+ function text(value) {
3776
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3777
+ }
3778
+ function stableJson(value) {
3779
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
3780
+ if (value && typeof value === "object") {
3781
+ return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
3782
+ }
3783
+ return JSON.stringify(value);
3784
+ }
3785
+ function sha2562(value) {
3786
+ return (0, import_node_crypto7.createHash)("sha256").update(value).digest("hex");
3787
+ }
3788
+ function outputBudget(value) {
3789
+ if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OUTPUT_CHARS;
3790
+ return Math.min(MAX_OUTPUT_CHARS, Math.max(MIN_OUTPUT_CHARS, Math.floor(value)));
3791
+ }
3792
+ function defined(values) {
3793
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
3794
+ }
3795
+ function memoryStatus(config = {}) {
3796
+ return {
3797
+ schema_version: RESULT_SCHEMA_VERSION,
3798
+ action: "status",
3799
+ profile: "M0",
3800
+ server: defined({
3801
+ name: "mindgraph-mcp",
3802
+ version: config.serverVersion ?? "unknown",
3803
+ base_url: config.baseUrl
3804
+ }),
3805
+ integration: {
3806
+ adapter_state: "model_controlled",
3807
+ harness: config.harness ?? "generic",
3808
+ configured_tool_profile: config.profile ?? "general",
3809
+ automatic_capture: false,
3810
+ automatic_delivery: false,
3811
+ delivery_verification: "unsupported",
3812
+ curator_mode: "disabled"
3813
+ },
3814
+ scope: defined({ org_id: config.orgId, agent_id: config.agentId }),
3815
+ capabilities: {
3816
+ status: true,
3817
+ topic_context: true,
3818
+ authoritative_resume_context: true,
3819
+ bounded_rendering: true,
3820
+ structured_content: true,
3821
+ typed_explicit_writes: true,
3822
+ lifecycle_event_ingest: false,
3823
+ automatic_context_injection: false,
3824
+ curator_mutation: false
3825
+ },
3826
+ degradation: [
3827
+ {
3828
+ code: "automatic_capture_unavailable",
3829
+ message: "M0 does not observe host lifecycle events; explicit typed write tools remain available."
3830
+ },
3831
+ {
3832
+ code: "delivery_unverified",
3833
+ message: "A successful MCP read does not prove that the host injected or used the returned context."
3834
+ },
3835
+ {
3836
+ code: "curator_disabled",
3837
+ message: "Curator proposals and autonomous mutation are not enabled in M0."
3838
+ }
3839
+ ]
3840
+ };
3841
+ }
3842
+ function nodeType(node) {
3843
+ const raw = node.node_type;
3844
+ if (typeof raw === "string") return raw;
3845
+ return text(obj2(raw)?.Custom) ?? "Node";
3846
+ }
3847
+ function compactItem(raw, fallbackType) {
3848
+ const wrapper = obj2(raw);
3849
+ if (!wrapper) return void 0;
3850
+ const value = obj2(wrapper.node) ?? obj2(wrapper.object) ?? wrapper;
3851
+ const uid = text(value.uid) ?? text(value.chunk_uid);
3852
+ if (!uid) return void 0;
3853
+ return defined({
3854
+ uid,
3855
+ type: fallbackType ?? nodeType(value),
3856
+ label: text(value.label) ?? text(value.document_title),
3857
+ truth_status: text(value.truth_status) ?? text(obj2(value.props)?.truth_status),
3858
+ superseded: value.superseded === true ? true : void 0,
3859
+ currently_valid: value.currently_valid === false ? false : void 0,
3860
+ valid_at_time: value.valid_at_time === false ? false : void 0,
3861
+ retrieval_origin: text(value.retrieval_origin),
3862
+ retrieval_depth: typeof value.retrieval_depth === "number" ? value.retrieval_depth : void 0
3863
+ });
3864
+ }
3865
+ function topicCandidates(raw) {
3866
+ const payload = obj2(raw);
3867
+ if (!payload) return [];
3868
+ const graph = obj2(payload.graph);
3869
+ const items = [];
3870
+ for (const article of Array.isArray(payload.articles) ? payload.articles : []) {
3871
+ const item = compactItem(article, "Article");
3872
+ if (item) items.push(item);
3873
+ }
3874
+ const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
3875
+ const labels = /* @__PURE__ */ new Map();
3876
+ for (const node of rawNodes) {
3877
+ const item = compactItem(node);
3878
+ if (item) {
3879
+ if (HIDDEN_CONTEXT_NODE_TYPES.has(text(item.type) ?? "")) continue;
3880
+ items.push(item);
3881
+ const uid = text(item.uid);
3882
+ const label = text(item.label);
3883
+ if (uid && label) labels.set(uid, label);
3884
+ }
3885
+ }
3886
+ for (const rawEdge of Array.isArray(graph?.edges) ? graph.edges : []) {
3887
+ const edge = obj2(rawEdge);
3888
+ const fromUid = text(edge?.from_uid);
3889
+ const toUid = text(edge?.to_uid);
3890
+ const edgeType = text(edge?.edge_type) ?? text(obj2(edge?.edge_type)?.Custom);
3891
+ if (!fromUid || !toUid || !edgeType || HIDDEN_CONTEXT_EDGE_TYPES.has(edgeType) || !labels.has(fromUid) || !labels.has(toUid)) {
3892
+ continue;
3893
+ }
3894
+ items.push(
3895
+ defined({
3896
+ type: "Relationship",
3897
+ from_uid: fromUid,
3898
+ to_uid: toUid,
3899
+ edge_type: edgeType,
3900
+ from_label: labels.get(fromUid),
3901
+ to_label: labels.get(toUid)
3902
+ })
3903
+ );
3904
+ }
3905
+ for (const chunk of Array.isArray(payload.chunks) ? payload.chunks : []) {
3906
+ const item = compactItem(chunk, "SourceExcerpt");
3907
+ if (item) items.push(item);
3908
+ }
3909
+ return items;
3910
+ }
3911
+ function resumeCandidates(raw) {
3912
+ const payload = obj2(raw);
3913
+ if (!payload) return [];
3914
+ const items = [];
3915
+ for (const key of ["task", "goal", "project", "plan", "active_execution", "lease"]) {
3916
+ const item = compactItem(payload[key], key);
3917
+ if (item) items.push(item);
3918
+ }
3919
+ for (const key of ["next_steps", "recent_executions", "blockers", "code_targets"]) {
3920
+ for (const value of Array.isArray(payload[key]) ? payload[key] : []) {
3921
+ const item = compactItem(value, key.replace(/s$/, ""));
3922
+ if (item) items.push(item);
3923
+ }
3924
+ }
3925
+ const knowledge = obj2(payload.knowledge);
3926
+ for (const key of ["lessons", "risks"]) {
3927
+ for (const value of Array.isArray(knowledge?.[key]) ? knowledge[key] : []) {
3928
+ const item = compactItem(value, key.replace(/s$/, ""));
3929
+ if (item) items.push(item);
3930
+ }
3931
+ }
3932
+ return items;
3933
+ }
3934
+ function visibleItems(candidates, rendered2) {
3935
+ const seen = /* @__PURE__ */ new Set();
3936
+ return candidates.filter((item) => {
3937
+ const uid = text(item.uid);
3938
+ if (uid) {
3939
+ if (seen.has(uid) || !rendered2.includes(`[${uid}]`)) return false;
3940
+ seen.add(uid);
3941
+ return true;
3942
+ }
3943
+ if (item.type === "Relationship") {
3944
+ const from = text(item.from_label);
3945
+ const to = text(item.to_label);
3946
+ const edgeType = text(item.edge_type);
3947
+ if (!from || !to || !edgeType) return false;
3948
+ const key = `${text(item.from_uid)}:${edgeType}:${text(item.to_uid)}`;
3949
+ if (seen.has(key) || !rendered2.includes(`${from} \u2014${edgeType}\u2192 ${to}`)) return false;
3950
+ seen.add(key);
3951
+ return true;
3952
+ }
3953
+ return false;
3954
+ });
3955
+ }
3956
+ function latestTimestamp(value) {
3957
+ let latest;
3958
+ const visit = (current) => {
3959
+ if (Array.isArray(current)) {
3960
+ for (const item of current) visit(item);
3961
+ return;
3962
+ }
3963
+ const record = obj2(current);
3964
+ if (!record) return;
3965
+ for (const [key, item] of Object.entries(record)) {
3966
+ 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))) {
3967
+ latest = item;
3968
+ }
3969
+ visit(item);
3970
+ }
3971
+ };
3972
+ visit(value);
3973
+ return latest;
3974
+ }
3975
+ function contextWarnings(raw, rendered2, available, returned) {
3976
+ const warnings = [
3977
+ {
3978
+ code: "delivery_unverified",
3979
+ message: "The MCP server returned this context but cannot verify that the host injected or used it."
3980
+ }
3981
+ ];
3982
+ const serialized = JSON.stringify(raw);
3983
+ if (available > returned || rendered2.includes("item(s) omitted")) {
3984
+ warnings.push({
3985
+ code: "context_bounded",
3986
+ message: `${Math.max(0, available - returned)} structured item(s) were omitted from the rendered context.`
3987
+ });
3988
+ }
3989
+ if (/"superseded":true/.test(serialized)) {
3990
+ warnings.push({ code: "superseded_items_present", message: "Some retrieved items are superseded." });
3991
+ }
3992
+ if (/"currently_valid":false|"valid_at_time":false/.test(serialized)) {
3993
+ warnings.push({
3994
+ code: "validity_warning",
3995
+ message: "Some retrieved items are outside their current or requested validity window."
3996
+ });
3997
+ }
3998
+ const payload = obj2(raw);
3999
+ if (payload?.truncated === true || obj2(payload?.truncation)) {
4000
+ const truncation = obj2(payload?.truncation);
4001
+ if (payload?.truncated === true || Object.values(truncation ?? {}).some(Boolean)) {
4002
+ warnings.push({ code: "source_truncated", message: "The authoritative source reported truncated fields." });
4003
+ }
4004
+ }
4005
+ if (text(payload?.warning)) {
4006
+ warnings.push({ code: "source_warning", message: text(payload?.warning) });
4007
+ }
4008
+ return warnings;
4009
+ }
4010
+ function error(message) {
4011
+ return {
4012
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
4013
+ isError: true
4014
+ };
4015
+ }
4016
+ async function handleMemoryTool(client2, args, config = {}) {
4017
+ const action = text(args.action);
4018
+ if (action === "status") {
4019
+ const status = memoryStatus({ ...config, agentId: text(args.agent_id) ?? config.agentId });
4020
+ return {
4021
+ content: [{ type: "text", text: JSON.stringify(status) }],
4022
+ structuredContent: status
4023
+ };
4024
+ }
4025
+ if (action !== "context") {
4026
+ return error("action must be 'status' or 'context'");
4027
+ }
4028
+ const mode = text(args.mode) ?? (text(args.query) ? "topic" : void 0);
4029
+ if (!mode || !["topic", "continuity", "resume"].includes(mode)) {
4030
+ return error("action='context' requires mode='topic', 'continuity', or 'resume'");
4031
+ }
4032
+ const budget = outputBudget(args.max_output_chars);
4033
+ const agentId = text(args.agent_id) ?? config.agentId;
4034
+ let raw;
4035
+ let rendered2;
4036
+ let strategy;
4037
+ let candidates;
4038
+ if (mode === "topic") {
4039
+ const query = text(args.query);
4040
+ if (!query) return error("mode='topic' requires a non-empty query of 1-3 discriminating keywords");
4041
+ const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.min(50, Math.max(1, Math.floor(args.limit))) : 8;
4042
+ const request = defined({
4043
+ query,
4044
+ project_uid: text(args.project_uid),
4045
+ node_limit: limit,
4046
+ node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
4047
+ layer: text(args.layer),
4048
+ chunk_limit: args.include_chunks === true ? limit : 0,
4049
+ article_limit: args.include_documents === true ? limit : 0,
4050
+ valid_at: text(args.valid_at),
4051
+ include_graph: true,
4052
+ graph_expansion_limit: typeof args.graph_expansion_limit === "number" ? args.graph_expansion_limit : 3,
4053
+ graph_max_depth: typeof args.graph_max_depth === "number" ? args.graph_max_depth : 2
4054
+ });
4055
+ raw = await client2.retrieveContext(request);
4056
+ rendered2 = renderContext(raw, budget) ?? "No results found for this search.";
4057
+ strategy = "retrieve_context";
4058
+ candidates = topicCandidates(raw);
4059
+ } else {
4060
+ const scopeUids = Array.from(
4061
+ /* @__PURE__ */ new Set([
4062
+ ...Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : [],
4063
+ ...text(args.project_uid) ? [text(args.project_uid)] : []
4064
+ ])
4065
+ );
4066
+ raw = await client2.plan(
4067
+ defined({
4068
+ action: "resume_work",
4069
+ task_uid: text(args.task_uid),
4070
+ session_uid: text(args.session_uid),
4071
+ scope_uids: scopeUids.length > 0 ? scopeUids : void 0,
4072
+ limit: 1,
4073
+ agent_id: agentId
4074
+ })
4075
+ );
4076
+ rendered2 = renderResumeContext(raw, budget) ?? "No eligible work was found in the requested scope.";
4077
+ strategy = "resume_work";
4078
+ candidates = resumeCandidates(raw);
4079
+ }
4080
+ const items = visibleItems(candidates, rendered2);
4081
+ const renderedHash = sha2562(rendered2);
4082
+ const scope = defined({
4083
+ org_id: config.orgId,
4084
+ agent_id: agentId,
4085
+ harness: config.harness ?? "generic",
4086
+ project_uid: text(args.project_uid),
4087
+ task_uid: text(args.task_uid),
4088
+ session_uid: text(args.session_uid),
4089
+ scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0
4090
+ });
4091
+ const contextId = `ctx_${sha2562(
4092
+ stableJson({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
4093
+ ).slice(0, 24)}`;
4094
+ const bounded = candidates.length > items.length || rendered2.includes("item(s) omitted");
4095
+ const result = {
4096
+ schema_version: RESULT_SCHEMA_VERSION,
4097
+ action: "context",
4098
+ context_id: contextId,
4099
+ mode,
4100
+ scope,
4101
+ rendered_context: rendered2,
4102
+ rendered_hash: renderedHash,
4103
+ items,
4104
+ retrieval_trace: defined({
4105
+ strategy,
4106
+ query: text(args.query),
4107
+ project_uid: text(args.project_uid),
4108
+ task_uid: text(args.task_uid),
4109
+ session_uid: text(args.session_uid),
4110
+ node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
4111
+ graph_expansion_limit: mode === "topic" ? args.graph_expansion_limit ?? 3 : void 0,
4112
+ graph_max_depth: mode === "topic" ? args.graph_max_depth ?? 2 : void 0,
4113
+ available_items: candidates.length,
4114
+ returned_items: items.length,
4115
+ omitted_items: Math.max(0, candidates.length - items.length)
4116
+ }),
4117
+ budget: {
4118
+ unit: "characters",
4119
+ requested_chars: budget,
4120
+ used_chars: rendered2.length,
4121
+ available_items: candidates.length,
4122
+ returned_items: items.length,
4123
+ omitted_items: Math.max(0, candidates.length - items.length),
4124
+ bounded
4125
+ },
4126
+ freshness: defined({
4127
+ valid_at: text(args.valid_at),
4128
+ latest_source_timestamp: latestTimestamp(raw)
4129
+ }),
4130
+ warnings: contextWarnings(raw, rendered2, candidates.length, items.length)
4131
+ };
4132
+ return {
4133
+ content: [{ type: "text", text: rendered2 }],
4134
+ structuredContent: result
4135
+ };
4136
+ }
4137
+ var import_node_crypto7, RESULT_SCHEMA_VERSION, DEFAULT_OUTPUT_CHARS, MIN_OUTPUT_CHARS, MAX_OUTPUT_CHARS, HIDDEN_CONTEXT_NODE_TYPES, HIDDEN_CONTEXT_EDGE_TYPES, MEMORY_TOOL;
4138
+ var init_memory_tool = __esm({
4139
+ "src/memory-tool.ts"() {
4140
+ "use strict";
4141
+ import_node_crypto7 = require("crypto");
4142
+ init_render();
4143
+ RESULT_SCHEMA_VERSION = "mindgraph.memory.result.v1";
4144
+ DEFAULT_OUTPUT_CHARS = 8e3;
4145
+ MIN_OUTPUT_CHARS = 512;
4146
+ MAX_OUTPUT_CHARS = 24e3;
4147
+ HIDDEN_CONTEXT_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
4148
+ HIDDEN_CONTEXT_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
4149
+ MEMORY_TOOL = {
4150
+ name: "mindgraph_memory",
4151
+ 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.",
4152
+ inputSchema: {
4153
+ type: "object",
4154
+ properties: {
4155
+ action: {
4156
+ type: "string",
4157
+ enum: ["status", "context"],
4158
+ description: "Read integration status or bounded memory context"
4159
+ },
4160
+ mode: {
4161
+ type: "string",
4162
+ enum: ["topic", "continuity", "resume"],
4163
+ description: "Context strategy. topic uses query-shaped retrieval; continuity/resume use authoritative task-first resume_work."
4164
+ },
4165
+ query: {
4166
+ type: "string",
4167
+ description: "Required for mode='topic': 1-3 discriminating BM25 keywords, not a full sentence"
4168
+ },
4169
+ project_uid: { type: "string", description: "Optional project scope" },
4170
+ task_uid: { type: "string", description: "Optional task scope for resume context" },
4171
+ session_uid: { type: "string", description: "Optional durable session scope" },
4172
+ scope_uids: {
4173
+ type: "array",
4174
+ items: { type: "string" },
4175
+ description: "Optional explicit graph scopes for continuity/resume"
4176
+ },
4177
+ node_types: {
4178
+ type: "array",
4179
+ items: { type: "string" },
4180
+ description: "Optional node-type filter for topic context"
4181
+ },
4182
+ layer: {
4183
+ type: "string",
4184
+ enum: ["reality", "epistemic", "intent", "action", "memory", "agent"],
4185
+ description: "Optional cognitive-layer filter for topic context"
4186
+ },
4187
+ limit: {
4188
+ type: "integer",
4189
+ minimum: 1,
4190
+ maximum: 50,
4191
+ default: 8,
4192
+ description: "Maximum direct graph items requested before bounded expansion"
4193
+ },
4194
+ include_chunks: {
4195
+ type: "boolean",
4196
+ default: false,
4197
+ description: "Include bounded source excerpts when topic summaries are insufficient"
4198
+ },
4199
+ include_documents: {
4200
+ type: "boolean",
4201
+ default: false,
4202
+ description: "Include bounded knowledge articles for topic context"
4203
+ },
4204
+ valid_at: {
4205
+ type: "string",
4206
+ description: "Optional ISO timestamp for time-aware topic retrieval"
4207
+ },
4208
+ graph_expansion_limit: {
4209
+ type: "integer",
4210
+ minimum: 0,
4211
+ maximum: 20,
4212
+ default: 3,
4213
+ description: "Bounded graph neighbors added after direct topic matches"
4214
+ },
4215
+ graph_max_depth: {
4216
+ type: "integer",
4217
+ minimum: 0,
4218
+ maximum: 5,
4219
+ default: 2,
4220
+ description: "Maximum graph-expansion depth for topic context"
4221
+ },
4222
+ max_output_chars: {
4223
+ type: "integer",
4224
+ minimum: MIN_OUTPUT_CHARS,
4225
+ maximum: MAX_OUTPUT_CHARS,
4226
+ default: DEFAULT_OUTPUT_CHARS,
4227
+ description: "Hard character budget for model-ready context; whole items are retained or omitted"
4228
+ },
4229
+ agent_id: { type: "string", description: "Agent identity" }
4230
+ },
4231
+ required: ["action"]
4232
+ },
4233
+ outputSchema: {
4234
+ type: "object",
4235
+ properties: {
4236
+ schema_version: { type: "string", const: RESULT_SCHEMA_VERSION },
4237
+ action: { type: "string", enum: ["status", "context"] },
4238
+ profile: { type: "string" },
4239
+ server: { type: "object" },
4240
+ integration: { type: "object" },
4241
+ capabilities: { type: "object" },
4242
+ degradation: { type: "array", items: { type: "object" } },
4243
+ context_id: { type: "string" },
4244
+ mode: { type: "string", enum: ["topic", "continuity", "resume"] },
4245
+ scope: { type: "object" },
4246
+ rendered_context: { type: "string" },
4247
+ rendered_hash: { type: "string" },
4248
+ items: { type: "array", items: { type: "object" } },
4249
+ retrieval_trace: { type: "object" },
4250
+ budget: { type: "object" },
4251
+ freshness: { type: "object" },
4252
+ warnings: { type: "array", items: { type: "object" } }
4253
+ },
4254
+ required: ["schema_version", "action"],
4255
+ oneOf: [
4256
+ {
4257
+ properties: { action: { const: "status" } },
4258
+ required: ["profile", "server", "integration", "scope", "capabilities", "degradation"]
4259
+ },
4260
+ {
4261
+ properties: { action: { const: "context" } },
4262
+ required: [
4263
+ "context_id",
4264
+ "mode",
4265
+ "scope",
4266
+ "rendered_context",
4267
+ "rendered_hash",
4268
+ "items",
4269
+ "retrieval_trace",
4270
+ "budget",
4271
+ "freshness",
4272
+ "warnings"
4273
+ ]
4274
+ }
4275
+ ]
4276
+ },
4277
+ annotations: {
4278
+ readOnlyHint: true,
4279
+ destructiveHint: false,
4280
+ idempotentHint: true,
4281
+ openWorldHint: false
4282
+ }
4283
+ };
4284
+ }
4285
+ });
4286
+
3384
4287
  // src/tools.ts
4288
+ function stableJson2(value) {
4289
+ if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
4290
+ if (value && typeof value === "object") {
4291
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson2(item)}`).join(",")}}`;
4292
+ }
4293
+ return JSON.stringify(value);
4294
+ }
4295
+ function defaultWriteIdempotencyKey(tool, args) {
4296
+ if (typeof args.idempotency_key === "string") return args.idempotency_key;
4297
+ const context = args.invocation_context;
4298
+ const sessionId = context?.harnessSessionId;
4299
+ if (typeof sessionId !== "string" || sessionId.length === 0) return void 0;
4300
+ const canonicalArgs = Object.fromEntries(
4301
+ Object.entries(args).filter(
4302
+ ([key]) => key !== "invocation_context" && key !== "idempotency_key"
4303
+ )
4304
+ );
4305
+ return (0, import_node_crypto8.createHash)("sha256").update(
4306
+ stableJson2({
4307
+ harness_session_id: sessionId,
4308
+ harness_turn_id: context?.harnessTurnId ?? null,
4309
+ tool,
4310
+ action: args.action ?? null,
4311
+ args: canonicalArgs
4312
+ })
4313
+ ).digest("hex");
4314
+ }
4315
+ function resolutionFields(args) {
4316
+ return Object.fromEntries(
4317
+ [
4318
+ "idempotency_key",
4319
+ "reuse_uid",
4320
+ "supersedes_uid",
4321
+ "contradicts_uid",
4322
+ "confirm_new",
4323
+ "on_near_duplicate"
4324
+ ].filter((key) => args[key] !== void 0).map((key) => [key, args[key]])
4325
+ );
4326
+ }
3385
4327
  function toolsForProfile(profile = process.env.MINDGRAPH_PROFILE || "general") {
3386
4328
  if (profile === "coding") return TOOLS;
3387
4329
  return TOOLS.filter(
@@ -3395,8 +4337,8 @@ function ok3(data) {
3395
4337
  }
3396
4338
  function renderedResult(format, raw, render) {
3397
4339
  if (format === "json") return ok3(raw);
3398
- const text = render(raw);
3399
- return text !== void 0 ? { content: [{ type: "text", text }] } : ok3(raw);
4340
+ const text2 = render(raw);
4341
+ return text2 !== void 0 ? { content: [{ type: "text", text: text2 }] } : ok3(raw);
3400
4342
  }
3401
4343
  function err3(message) {
3402
4344
  return {
@@ -3404,8 +4346,22 @@ function err3(message) {
3404
4346
  isError: true
3405
4347
  };
3406
4348
  }
3407
- async function handleTool(client2, name, args) {
4349
+ async function handleTool(client2, name, args, memoryConfig = {}) {
3408
4350
  try {
4351
+ if (["mindgraph_capture", "mindgraph_reason", "mindgraph_commit"].includes(name) && args.action !== "get_open_decisions") {
4352
+ const key = defaultWriteIdempotencyKey(name, args);
4353
+ if (key) args = { ...args, idempotency_key: key };
4354
+ const dispositions = [
4355
+ args.reuse_uid,
4356
+ args.supersedes_uid,
4357
+ args.contradicts_uid,
4358
+ args.confirm_new === true ? true : void 0,
4359
+ args.on_near_duplicate
4360
+ ].filter((value) => value !== void 0);
4361
+ if (dispositions.length > 1) {
4362
+ return err3("resolution dispositions are mutually exclusive");
4363
+ }
4364
+ }
3409
4365
  let result;
3410
4366
  switch (name) {
3411
4367
  case "mindgraph_capture":
@@ -3423,6 +4379,12 @@ async function handleTool(client2, name, args) {
3423
4379
  case "mindgraph_retrieve":
3424
4380
  result = await handleRetrieve(client2, args);
3425
4381
  break;
4382
+ case "mindgraph_memory":
4383
+ result = await handleMemoryTool(client2, args, memoryConfig);
4384
+ break;
4385
+ case "mindgraph_remember":
4386
+ result = await handleRemember(client2, args);
4387
+ break;
3426
4388
  case "mindgraph_series_query":
3427
4389
  result = await handleSeriesQuery(client2, args);
3428
4390
  break;
@@ -3455,19 +4417,7 @@ async function handleTool(client2, name, args) {
3455
4417
  ].includes(action);
3456
4418
  return attach ? await attachCodeRefsToToolResult(client2, result, args) : result;
3457
4419
  } catch (e) {
3458
- const lifted = conflictState(e);
3459
- if (Object.keys(lifted).length > 0) {
3460
- return {
3461
- content: [
3462
- {
3463
- type: "text",
3464
- text: JSON.stringify({ error: errorDetail(e), ...lifted })
3465
- }
3466
- ],
3467
- isError: true
3468
- };
3469
- }
3470
- return err3(errorDetail(e));
4420
+ return toolError(e);
3471
4421
  }
3472
4422
  }
3473
4423
  async function handleSeriesQuery(client2, args) {
@@ -3491,6 +4441,56 @@ async function handleSeriesQuery(client2, args) {
3491
4441
  await client2.series(args)
3492
4442
  );
3493
4443
  }
4444
+ async function handleRemember(client2, args) {
4445
+ const {
4446
+ action,
4447
+ text: text2,
4448
+ custom_id,
4449
+ label,
4450
+ uid,
4451
+ dry_run,
4452
+ cascade,
4453
+ reason,
4454
+ space_uid,
4455
+ on_near_duplicate,
4456
+ props,
4457
+ agent_id,
4458
+ // Adapter-owned; consumed locally, never sent to the API.
4459
+ invocation_context: _invocationContext,
4460
+ ...rest
4461
+ } = args;
4462
+ void _invocationContext;
4463
+ void rest;
4464
+ switch (action) {
4465
+ case "remember": {
4466
+ if (typeof text2 !== "string" || !text2.trim()) {
4467
+ return err3("remember requires non-empty text");
4468
+ }
4469
+ const options = {};
4470
+ if (typeof custom_id === "string" && custom_id.trim()) options.custom_id = custom_id.trim();
4471
+ if (typeof label === "string" && label.trim()) options.label = label;
4472
+ if (typeof space_uid === "string" && space_uid.trim()) options.space_uid = space_uid;
4473
+ if (on_near_duplicate === "reuse" || on_near_duplicate === "create") {
4474
+ options.on_near_duplicate = on_near_duplicate;
4475
+ }
4476
+ if (props && typeof props === "object") options.props = props;
4477
+ if (typeof agent_id === "string") options.agent_id = agent_id;
4478
+ return ok3(await client2.remember(text2, options));
4479
+ }
4480
+ case "forget": {
4481
+ const target = typeof uid === "string" && uid.trim() ? { uid: uid.trim() } : typeof custom_id === "string" && custom_id.trim() ? { custom_id: custom_id.trim() } : null;
4482
+ if (!target) return err3("forget requires uid or custom_id");
4483
+ const options = {};
4484
+ if (dry_run === true) options.dry_run = true;
4485
+ if (typeof cascade === "boolean") options.cascade = cascade;
4486
+ if (typeof reason === "string" && reason.trim()) options.reason = reason;
4487
+ if (typeof agent_id === "string") options.agent_id = agent_id;
4488
+ return ok3(await client2.forget(target, options));
4489
+ }
4490
+ default:
4491
+ return err3(`Unknown remember action: ${String(action)}`);
4492
+ }
4493
+ }
3494
4494
  async function handleCapture(client2, args) {
3495
4495
  const {
3496
4496
  action,
@@ -3504,7 +4504,11 @@ async function handleCapture(client2, args) {
3504
4504
  work_uid,
3505
4505
  execution_uid,
3506
4506
  idempotency_key,
4507
+ reuse_uid,
3507
4508
  supersedes_uid,
4509
+ contradicts_uid,
4510
+ confirm_new,
4511
+ on_near_duplicate,
3508
4512
  summarizes_uids,
3509
4513
  entity_type,
3510
4514
  source_uid,
@@ -3528,7 +4532,11 @@ async function handleCapture(client2, args) {
3528
4532
  work_uid,
3529
4533
  execution_uid,
3530
4534
  idempotency_key,
4535
+ reuse_uid,
3531
4536
  supersedes_uid,
4537
+ contradicts_uid,
4538
+ confirm_new,
4539
+ on_near_duplicate,
3532
4540
  summarizes_uids,
3533
4541
  props,
3534
4542
  agent_id
@@ -3574,27 +4582,32 @@ async function handleCapture(client2, args) {
3574
4582
  const journalProps = { content };
3575
4583
  if (mood) journalProps.mood = mood;
3576
4584
  if (tags) journalProps.tags = tags;
3577
- return ok3(await client2.journal(label, journalProps, { agent_id }));
4585
+ return ok3(await client2.journal(label, journalProps, {
4586
+ agent_id,
4587
+ idempotency_key,
4588
+ reuse_uid,
4589
+ supersedes_uid,
4590
+ contradicts_uid,
4591
+ confirm_new,
4592
+ on_near_duplicate
4593
+ }));
3578
4594
  }
3579
4595
  case "entity": {
3580
4596
  const type = entity_type || "concept";
3581
4597
  const entityProps = { ...props };
3582
4598
  if (summary) entityProps.description = summary;
3583
- switch (type) {
3584
- case "person":
3585
- return ok3(await client2.findOrCreatePerson(label, entityProps, agent_id));
3586
- case "organization":
3587
- return ok3(await client2.findOrCreateOrganization(label, entityProps, agent_id));
3588
- case "nation":
3589
- return ok3(await client2.findOrCreateNation(label, entityProps, agent_id));
3590
- case "event":
3591
- return ok3(await client2.findOrCreateEvent(label, entityProps, agent_id));
3592
- case "place":
3593
- return ok3(await client2.findOrCreatePlace(label, entityProps, agent_id));
3594
- case "concept":
3595
- default:
3596
- return ok3(await client2.findOrCreateConcept(label, entityProps, agent_id));
3597
- }
4599
+ return ok3(await client2.entity({
4600
+ action: "create",
4601
+ label,
4602
+ props: { ...entityProps, entity_type: type },
4603
+ agent_id,
4604
+ idempotency_key,
4605
+ reuse_uid,
4606
+ supersedes_uid,
4607
+ contradicts_uid,
4608
+ confirm_new,
4609
+ on_near_duplicate
4610
+ }));
3598
4611
  }
3599
4612
  case "observation":
3600
4613
  return ok3(
@@ -3605,7 +4618,13 @@ async function handleCapture(client2, args) {
3605
4618
  confidence,
3606
4619
  salience,
3607
4620
  props,
3608
- agent_id
4621
+ agent_id,
4622
+ idempotency_key,
4623
+ reuse_uid,
4624
+ supersedes_uid,
4625
+ contradicts_uid,
4626
+ confirm_new,
4627
+ on_near_duplicate
3609
4628
  })
3610
4629
  );
3611
4630
  case "source":
@@ -3617,7 +4636,13 @@ async function handleCapture(client2, args) {
3617
4636
  confidence,
3618
4637
  salience,
3619
4638
  props,
3620
- agent_id
4639
+ agent_id,
4640
+ idempotency_key,
4641
+ reuse_uid,
4642
+ supersedes_uid,
4643
+ contradicts_uid,
4644
+ confirm_new,
4645
+ on_near_duplicate
3621
4646
  })
3622
4647
  );
3623
4648
  case "snippet":
@@ -3630,26 +4655,38 @@ async function handleCapture(client2, args) {
3630
4655
  confidence,
3631
4656
  salience,
3632
4657
  props,
3633
- agent_id
4658
+ agent_id,
4659
+ idempotency_key,
4660
+ reuse_uid,
4661
+ supersedes_uid,
4662
+ contradicts_uid,
4663
+ confirm_new,
4664
+ on_near_duplicate
3634
4665
  })
3635
4666
  );
3636
4667
  case "concept":
3637
- return ok3(
3638
- await client2.structure({
3639
- action: "concept",
3640
- label,
3641
- summary,
3642
- confidence,
3643
- salience,
3644
- props,
3645
- agent_id
3646
- })
3647
- );
4668
+ return ok3(await client2.entity({
4669
+ action: "create",
4670
+ label,
4671
+ props: {
4672
+ ...props,
4673
+ entity_type: "concept",
4674
+ ...summary ? { description: summary } : {}
4675
+ },
4676
+ agent_id,
4677
+ idempotency_key,
4678
+ reuse_uid,
4679
+ supersedes_uid,
4680
+ contradicts_uid,
4681
+ confirm_new,
4682
+ on_near_duplicate
4683
+ }));
3648
4684
  default:
3649
4685
  return err3(`Unknown capture action: ${action}`);
3650
4686
  }
3651
4687
  }
3652
4688
  async function handleReason(client2, args) {
4689
+ const resolution = resolutionFields(args);
3653
4690
  const {
3654
4691
  action,
3655
4692
  label,
@@ -3682,7 +4719,8 @@ async function handleReason(client2, args) {
3682
4719
  ...warrant.content ? { props: { content: warrant.content } } : {}
3683
4720
  } : void 0,
3684
4721
  argument,
3685
- agent_id
4722
+ agent_id,
4723
+ ...resolution
3686
4724
  })
3687
4725
  );
3688
4726
  }
@@ -3695,11 +4733,13 @@ async function handleReason(client2, args) {
3695
4733
  confidence,
3696
4734
  salience,
3697
4735
  props,
3698
- agent_id
4736
+ agent_id,
4737
+ ...resolution
3699
4738
  })
3700
4739
  );
3701
4740
  }
3702
4741
  async function handleCommit(client2, args) {
4742
+ const resolution = resolutionFields(args);
3703
4743
  const {
3704
4744
  action,
3705
4745
  label,
@@ -3730,16 +4770,17 @@ async function handleCommit(client2, args) {
3730
4770
  confidence,
3731
4771
  salience,
3732
4772
  props,
3733
- agent_id
4773
+ agent_id,
4774
+ ...resolution
3734
4775
  })
3735
4776
  );
3736
4777
  case "open_decision":
3737
4778
  if (!label) return err3("label is required for open_decision");
3738
- return ok3(await client2.openDecision(label, { summary, props, agent_id }));
4779
+ return ok3(await client2.openDecision(label, { summary, props, agent_id, ...resolution }));
3739
4780
  case "add_option":
3740
4781
  if (!decision_uid) return err3("decision_uid is required for add_option");
3741
4782
  if (!label) return err3("label is required for add_option");
3742
- return ok3(await client2.addOption(decision_uid, label, { summary, props, agent_id }));
4783
+ return ok3(await client2.addOption(decision_uid, label, { summary, props, agent_id, ...resolution }));
3743
4784
  case "add_constraint":
3744
4785
  if (!decision_uid) return err3("decision_uid is required for add_constraint");
3745
4786
  return ok3(
@@ -3749,7 +4790,8 @@ async function handleCommit(client2, args) {
3749
4790
  decision_uid,
3750
4791
  summary,
3751
4792
  props,
3752
- agent_id
4793
+ agent_id,
4794
+ ...resolution
3753
4795
  })
3754
4796
  );
3755
4797
  case "resolve_decision":
@@ -3763,7 +4805,8 @@ async function handleCommit(client2, args) {
3763
4805
  as_of_date,
3764
4806
  session_id,
3765
4807
  retrieval_trace_id,
3766
- agent_id
4808
+ agent_id,
4809
+ ...resolution
3767
4810
  })
3768
4811
  );
3769
4812
  case "get_open_decisions":
@@ -3844,7 +4887,7 @@ async function handlePlan(client2, args) {
3844
4887
  output_snapshot,
3845
4888
  side_effects,
3846
4889
  outcome,
3847
- error,
4890
+ error: error2,
3848
4891
  next_action,
3849
4892
  checkpoint_summary,
3850
4893
  test_summary,
@@ -3907,7 +4950,7 @@ async function handlePlan(client2, args) {
3907
4950
  output_snapshot,
3908
4951
  side_effects,
3909
4952
  outcome,
3910
- error,
4953
+ error: error2,
3911
4954
  next_action,
3912
4955
  checkpoint_summary,
3913
4956
  test_summary,
@@ -3956,7 +4999,7 @@ async function handlePlan(client2, args) {
3956
4999
  output_snapshot,
3957
5000
  side_effects,
3958
5001
  outcome,
3959
- error,
5002
+ error: error2,
3960
5003
  props,
3961
5004
  agent_id
3962
5005
  })
@@ -4042,6 +5085,7 @@ async function handleRetrieve(client2, args) {
4042
5085
  const {
4043
5086
  action,
4044
5087
  format,
5088
+ max_output_chars,
4045
5089
  query,
4046
5090
  start_uid,
4047
5091
  end_uid,
@@ -4096,7 +5140,7 @@ async function handleRetrieve(client2, args) {
4096
5140
  graph_expansion_limit: graph_expansion_limit ?? 3,
4097
5141
  graph_max_depth: graph_max_depth ?? 2
4098
5142
  }),
4099
- renderContext
5143
+ (raw) => renderContext(raw, max_output_chars)
4100
5144
  );
4101
5145
  case "text":
4102
5146
  if (!query) return err3("query is required for text search");
@@ -4110,7 +5154,7 @@ async function handleRetrieve(client2, args) {
4110
5154
  explain,
4111
5155
  agent_id
4112
5156
  }),
4113
- (raw) => renderNodeList(raw, `Text search: ${query}`)
5157
+ (raw) => renderNodeList(raw, `Text search: ${query}`, void 0, max_output_chars)
4114
5158
  );
4115
5159
  case "semantic":
4116
5160
  if (!query) return err3("query is required for semantic search");
@@ -4124,7 +5168,7 @@ async function handleRetrieve(client2, args) {
4124
5168
  explain,
4125
5169
  agent_id
4126
5170
  }),
4127
- (raw) => renderNodeList(raw, `Semantic search: ${query}`)
5171
+ (raw) => renderNodeList(raw, `Semantic search: ${query}`, void 0, max_output_chars)
4128
5172
  );
4129
5173
  case "hybrid":
4130
5174
  if (!query) return err3("query is required for hybrid search");
@@ -4139,7 +5183,7 @@ async function handleRetrieve(client2, args) {
4139
5183
  agent_id
4140
5184
  }),
4141
5185
  // With explain, per-leg scoring must survive — raw JSON only.
4142
- explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`)
5186
+ explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`, void 0, max_output_chars)
4143
5187
  );
4144
5188
  // Structured queries. These go through the generic /retrieve action
4145
5189
  // dispatch rather than the SDK's no-argument convenience methods — the
@@ -4157,7 +5201,7 @@ async function handleRetrieve(client2, args) {
4157
5201
  offset,
4158
5202
  agent_id
4159
5203
  }),
4160
- (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit)
5204
+ (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit, max_output_chars)
4161
5205
  );
4162
5206
  case "stale_derivations":
4163
5207
  return rendered2(
@@ -4166,7 +5210,7 @@ async function handleRetrieve(client2, args) {
4166
5210
  limit,
4167
5211
  offset
4168
5212
  }),
4169
- (raw) => renderNodeList(raw, "stale derivations", limit)
5213
+ (raw) => renderNodeList(raw, "stale derivations", limit, max_output_chars)
4170
5214
  );
4171
5215
  case "preferences":
4172
5216
  return rendered2(
@@ -4176,7 +5220,7 @@ async function handleRetrieve(client2, args) {
4176
5220
  limit,
4177
5221
  agent_id
4178
5222
  }),
4179
- (raw) => renderNodeList(raw, "Preferences", limit)
5223
+ (raw) => renderNodeList(raw, "Preferences", limit, max_output_chars)
4180
5224
  );
4181
5225
  case "layer":
4182
5226
  if (!layer) return err3("layer is required for layer retrieval");
@@ -4188,7 +5232,7 @@ async function handleRetrieve(client2, args) {
4188
5232
  node_types,
4189
5233
  agent_id
4190
5234
  }),
4191
- (raw) => renderNodeList(raw, `Layer: ${layer}`, limit)
5235
+ (raw) => renderNodeList(raw, `Layer: ${layer}`, limit, max_output_chars)
4192
5236
  );
4193
5237
  case "recent":
4194
5238
  return rendered2(
@@ -4202,7 +5246,7 @@ async function handleRetrieve(client2, args) {
4202
5246
  created_before,
4203
5247
  agent_id
4204
5248
  }),
4205
- (raw) => renderNodeList(raw, "Recently ingested", limit)
5249
+ (raw) => renderNodeList(raw, "Recently ingested", limit, max_output_chars)
4206
5250
  );
4207
5251
  // Document inventory
4208
5252
  case "document_index":
@@ -4211,7 +5255,7 @@ async function handleRetrieve(client2, args) {
4211
5255
  node_type: "Document",
4212
5256
  limit: limit ?? 100
4213
5257
  }),
4214
- (raw) => renderNodeList(raw, "Ingested documents")
5258
+ (raw) => renderNodeList(raw, "Ingested documents", void 0, max_output_chars)
4215
5259
  );
4216
5260
  // Graph traversal
4217
5261
  case "chain":
@@ -4225,7 +5269,7 @@ async function handleRetrieve(client2, args) {
4225
5269
  include_provenance,
4226
5270
  max_nodes
4227
5271
  }),
4228
- renderTraversal
5272
+ (raw) => renderTraversal(raw, max_output_chars)
4229
5273
  );
4230
5274
  case "neighborhood":
4231
5275
  if (!start_uid) return err3("start_uid is required for neighborhood traversal");
@@ -4240,7 +5284,7 @@ async function handleRetrieve(client2, args) {
4240
5284
  include_provenance,
4241
5285
  max_nodes
4242
5286
  }),
4243
- renderTraversal
5287
+ (raw) => renderTraversal(raw, max_output_chars)
4244
5288
  );
4245
5289
  case "path":
4246
5290
  if (!start_uid) return err3("start_uid is required for path traversal");
@@ -4257,7 +5301,7 @@ async function handleRetrieve(client2, args) {
4257
5301
  include_provenance,
4258
5302
  max_nodes
4259
5303
  }),
4260
- renderTraversal
5304
+ (raw) => renderTraversal(raw, max_output_chars)
4261
5305
  );
4262
5306
  case "subgraph":
4263
5307
  if (!start_uid) return err3("start_uid is required for subgraph traversal");
@@ -4272,7 +5316,7 @@ async function handleRetrieve(client2, args) {
4272
5316
  include_provenance,
4273
5317
  max_nodes
4274
5318
  }),
4275
- renderTraversal
5319
+ (raw) => renderTraversal(raw, max_output_chars)
4276
5320
  );
4277
5321
  case "top_k_paths":
4278
5322
  if (!start_uid) return err3("start_uid is required for top_k_paths");
@@ -4288,7 +5332,7 @@ async function handleRetrieve(client2, args) {
4288
5332
  edge_types,
4289
5333
  exclude_edge_types
4290
5334
  }),
4291
- renderTraversal
5335
+ (raw) => renderTraversal(raw, max_output_chars)
4292
5336
  );
4293
5337
  default:
4294
5338
  return err3(`Unknown retrieve action: ${action}`);
@@ -4544,15 +5588,44 @@ async function handleOntology(client2, args) {
4544
5588
  return err3(`Unknown ontology action: ${action}`);
4545
5589
  }
4546
5590
  }
4547
- var TOOLS, INVOCATION_CONTEXT_SCHEMA;
5591
+ var import_node_crypto8, RESOLUTION_PROPERTIES, TOOLS, INVOCATION_CONTEXT_SCHEMA;
4548
5592
  var init_tools = __esm({
4549
5593
  "src/tools.ts"() {
4550
5594
  "use strict";
5595
+ import_node_crypto8 = require("crypto");
4551
5596
  init_error_detail();
4552
5597
  init_code_tool();
4553
5598
  init_codegraph();
4554
5599
  init_sync_tool();
5600
+ init_memory_tool();
4555
5601
  init_render();
5602
+ RESOLUTION_PROPERTIES = {
5603
+ idempotency_key: {
5604
+ type: "string",
5605
+ description: "Stable retry key; a mismatched replay is rejected"
5606
+ },
5607
+ reuse_uid: {
5608
+ type: "string",
5609
+ description: "Reuse this live same-scope node after a duplicate response"
5610
+ },
5611
+ supersedes_uid: {
5612
+ type: "string",
5613
+ description: "Create and mark this same-scope node as superseded"
5614
+ },
5615
+ contradicts_uid: {
5616
+ type: "string",
5617
+ description: "Create and mark this same-scope node as contradicted"
5618
+ },
5619
+ confirm_new: {
5620
+ type: "boolean",
5621
+ description: "Confirm that the new fact is genuinely distinct"
5622
+ },
5623
+ on_near_duplicate: {
5624
+ type: "string",
5625
+ enum: ["create"],
5626
+ description: "Alias for confirm_new on a near-duplicate response"
5627
+ }
5628
+ };
4556
5629
  TOOLS = [
4557
5630
  {
4558
5631
  name: "mindgraph_capture",
@@ -4617,14 +5690,7 @@ var init_tools = __esm({
4617
5690
  type: "string",
4618
5691
  description: "Material Execution that produced the lesson"
4619
5692
  },
4620
- idempotency_key: {
4621
- type: "string",
4622
- description: "Stable retry key for an intentional capture"
4623
- },
4624
- supersedes_uid: {
4625
- type: "string",
4626
- description: "Earlier capture corrected or superseded by this one"
4627
- },
5693
+ ...RESOLUTION_PROPERTIES,
4628
5694
  summarizes_uids: {
4629
5695
  type: "array",
4630
5696
  items: { type: "string" },
@@ -4722,6 +5788,7 @@ var init_tools = __esm({
4722
5788
  inputSchema: {
4723
5789
  type: "object",
4724
5790
  properties: {
5791
+ ...RESOLUTION_PROPERTIES,
4725
5792
  action: {
4726
5793
  type: "string",
4727
5794
  enum: [
@@ -4816,6 +5883,7 @@ var init_tools = __esm({
4816
5883
  inputSchema: {
4817
5884
  type: "object",
4818
5885
  properties: {
5886
+ ...RESOLUTION_PROPERTIES,
4819
5887
  action: {
4820
5888
  type: "string",
4821
5889
  enum: [
@@ -5174,6 +6242,12 @@ var init_tools = __esm({
5174
6242
  enum: ["text", "json"],
5175
6243
  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)."
5176
6244
  },
6245
+ max_output_chars: {
6246
+ type: "integer",
6247
+ minimum: 512,
6248
+ maximum: 24e3,
6249
+ 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'."
6250
+ },
5177
6251
  query: {
5178
6252
  type: "string",
5179
6253
  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."
@@ -5600,6 +6674,67 @@ var init_tools = __esm({
5600
6674
  required: ["action"]
5601
6675
  }
5602
6676
  },
6677
+ {
6678
+ name: "mindgraph_remember",
6679
+ description: "Small-text memory fast path. 'remember' stores one fact, preference, decision or note synchronously; it is BM25- and vector-searchable the moment the call returns (the response says which). Pass a stable custom_id (e.g. 'pref:theme', 'deploy:target') so re-sending the same key UPDATES the same memory instead of creating a duplicate \u2014 that is the idempotency contract; without custom_id an exact duplicate is reused. 'forget' reversibly removes a memory by uid or custom_id (tombstone plus connected edges): call it with dry_run=true first to see the edge uids it would remove, then without. Undo with mindgraph_capture-independent server actions /evolve restore and restore_edge. Use mindgraph_capture for typed entities, sources and skills, and mindgraph_ingest for documents longer than a few paragraphs.",
6680
+ inputSchema: {
6681
+ type: "object",
6682
+ properties: {
6683
+ action: {
6684
+ type: "string",
6685
+ enum: ["remember", "forget"],
6686
+ description: "remember = store/upsert one small text; forget = reversible removal"
6687
+ },
6688
+ text: {
6689
+ type: "string",
6690
+ description: "remember: the memory text (\u2264 8 KiB). Use mindgraph_ingest for longer content."
6691
+ },
6692
+ custom_id: {
6693
+ type: "string",
6694
+ description: "Caller-owned key. remember: re-sending it edits the same memory (version bump, history kept). forget: the memory to remove."
6695
+ },
6696
+ label: {
6697
+ type: "string",
6698
+ description: "remember: optional short title; defaults to the first line of text"
6699
+ },
6700
+ uid: {
6701
+ type: "string",
6702
+ description: "forget: node uid to remove (alternative to custom_id)"
6703
+ },
6704
+ dry_run: {
6705
+ type: "boolean",
6706
+ description: "forget: preview the affected edge uids without changing anything"
6707
+ },
6708
+ cascade: {
6709
+ type: "boolean",
6710
+ description: "forget: also tombstone connected edges (default true)"
6711
+ },
6712
+ reason: {
6713
+ type: "string",
6714
+ description: "forget: why (recorded on the tombstone)"
6715
+ },
6716
+ space_uid: {
6717
+ type: "string",
6718
+ description: "remember: explicit target Space (default: the org space)"
6719
+ },
6720
+ on_near_duplicate: {
6721
+ type: "string",
6722
+ enum: ["reuse", "create"],
6723
+ description: "remember without custom_id: on an exact duplicate, reuse the existing memory (default) or create anyway"
6724
+ },
6725
+ props: {
6726
+ type: "object",
6727
+ description: "remember: extra Observation props"
6728
+ },
6729
+ agent_id: {
6730
+ type: "string",
6731
+ description: "Agent identifier (default: configured agent)"
6732
+ }
6733
+ },
6734
+ required: ["action"]
6735
+ }
6736
+ },
6737
+ MEMORY_TOOL,
5603
6738
  SYNC_TOOL,
5604
6739
  CODE_TOOL
5605
6740
  ];
@@ -5620,7 +6755,7 @@ var init_tools = __esm({
5620
6755
  }
5621
6756
  };
5622
6757
  for (const tool of TOOLS) {
5623
- if (tool.name === "mindgraph_retrieve") continue;
6758
+ if (tool.name === "mindgraph_retrieve" || tool.name === "mindgraph_memory") continue;
5624
6759
  const schema = tool.inputSchema;
5625
6760
  if (schema.properties && !schema.properties.invocation_context) {
5626
6761
  schema.properties.invocation_context = INVOCATION_CONTEXT_SCHEMA;
@@ -5674,8 +6809,8 @@ async function getGeneratedTools(client2) {
5674
6809
  cache = { tools, byName, fetchedAt: Date.now() };
5675
6810
  retryNotBefore = 0;
5676
6811
  return { tools, byName };
5677
- } catch (error) {
5678
- if (error instanceof DuplicateGeneratedToolError) throw error;
6812
+ } catch (error2) {
6813
+ if (error2 instanceof DuplicateGeneratedToolError) throw error2;
5679
6814
  retryNotBefore = Date.now() + FAILURE_COOLDOWN_MS;
5680
6815
  return cache ? { tools: cache.tools, byName: cache.byName } : EMPTY;
5681
6816
  }
@@ -5774,13 +6909,14 @@ async function handleGeneratedTool(client2, desc, args) {
5774
6909
  return err4(`unknown generated tool mapping: ${desc.maps_to}`);
5775
6910
  }
5776
6911
  } catch (e) {
5777
- return err4(e instanceof Error ? e.message : String(e));
6912
+ return toolError(e);
5778
6913
  }
5779
6914
  }
5780
6915
  var ok4, rendered, err4, CACHE_TTL_MS, FAILURE_COOLDOWN_MS, cache, retryNotBefore, EMPTY, DuplicateGeneratedToolError, STRUCTURED_QUERY_PASSTHROUGH;
5781
6916
  var init_generated_tools = __esm({
5782
6917
  "src/generated-tools.ts"() {
5783
6918
  "use strict";
6919
+ init_error_detail();
5784
6920
  init_render();
5785
6921
  ok4 = (data) => ({
5786
6922
  // Compact on purpose — pretty-printing doubled the token cost.
@@ -5788,8 +6924,8 @@ var init_generated_tools = __esm({
5788
6924
  });
5789
6925
  rendered = (format, raw, render) => {
5790
6926
  if (format === "json") return ok4(raw);
5791
- const text = render(raw);
5792
- return text !== void 0 ? { content: [{ type: "text", text }] } : ok4(raw);
6927
+ const text2 = render(raw);
6928
+ return text2 !== void 0 ? { content: [{ type: "text", text: text2 }] } : ok4(raw);
5793
6929
  };
5794
6930
  err4 = (message) => ({
5795
6931
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
@@ -5838,7 +6974,9 @@ function unsupportedResponse(status, body) {
5838
6974
  return lower.includes("unknown governance action") || lower.includes("unknown action") || lower.includes("invalid_governance_action");
5839
6975
  }
5840
6976
  function toolMutability(toolName, action) {
6977
+ if (toolName === "mindgraph_memory") return "read";
5841
6978
  if (toolName === "mindgraph_retrieve") return "read";
6979
+ if (toolName === "mindgraph_remember") return "write";
5842
6980
  if (toolName === "mindgraph_series_query") return "read";
5843
6981
  if (toolName === "mindgraph_plan") {
5844
6982
  return action && PLAN_READ_ACTIONS.has(action) ? "read" : "write";
@@ -5921,8 +7059,8 @@ async function checkMcpGovernance(toolName, args, config) {
5921
7059
  response = candidate;
5922
7060
  body = candidateBody;
5923
7061
  break;
5924
- } catch (error) {
5925
- lastFailure = error instanceof Error ? error.message : String(error);
7062
+ } catch (error2) {
7063
+ lastFailure = error2 instanceof Error ? error2.message : String(error2);
5926
7064
  if (attempt < retryDelays.length) {
5927
7065
  await sleep(retryDelays[attempt]);
5928
7066
  continue;
@@ -6067,8 +7205,10 @@ function json(uri, data) {
6067
7205
  contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
6068
7206
  };
6069
7207
  }
6070
- async function readResource(client2, uri) {
7208
+ async function readResource(client2, uri, memoryConfig = {}) {
6071
7209
  switch (uri) {
7210
+ case "mindgraph://memory/status":
7211
+ return json(uri, memoryStatus(memoryConfig));
6072
7212
  case "mindgraph://stats":
6073
7213
  return json(uri, await client2.stats());
6074
7214
  case "mindgraph://goals":
@@ -6100,23 +7240,32 @@ async function readResource(client2, uri) {
6100
7240
  }
6101
7241
  throw new Error(`Unknown resource: ${uri}`);
6102
7242
  }
6103
- async function readGovernedResource(client2, uri, governanceConfig) {
7243
+ async function readGovernedResource(client2, uri, governanceConfig, memoryConfig = {}) {
7244
+ const toolName = uri === "mindgraph://memory/status" ? "mindgraph_memory" : RESOURCE_GOVERNANCE_TOOL;
7245
+ const toolArgs = uri === "mindgraph://memory/status" ? { action: "status" } : {};
6104
7246
  const governance = await checkMcpGovernance(
6105
- RESOURCE_GOVERNANCE_TOOL,
6106
- {},
7247
+ toolName,
7248
+ toolArgs,
6107
7249
  { ...governanceConfig, context: { resource_uri: uri } }
6108
7250
  );
6109
7251
  if (!governance.allowed) {
6110
7252
  throw new Error(governance.message);
6111
7253
  }
6112
- return readResource(client2, uri);
7254
+ return readResource(client2, uri, memoryConfig);
6113
7255
  }
6114
7256
  var RESOURCES, RESOURCE_TEMPLATES, RESOURCE_GOVERNANCE_TOOL;
6115
7257
  var init_resources = __esm({
6116
7258
  "src/resources.ts"() {
6117
7259
  "use strict";
6118
7260
  init_governance();
7261
+ init_memory_tool();
6119
7262
  RESOURCES = [
7263
+ {
7264
+ uri: "mindgraph://memory/status",
7265
+ name: "Memory Integration Status",
7266
+ description: "Portable memory profile, adapter state, supported capabilities, and explicit limitations",
7267
+ mimeType: "application/json"
7268
+ },
6120
7269
  {
6121
7270
  uri: "mindgraph://stats",
6122
7271
  name: "Graph Statistics",
@@ -6178,10 +7327,10 @@ async function main() {
6178
7327
  const transport = new import_stdio.StdioServerTransport();
6179
7328
  await server.connect(transport);
6180
7329
  console.error(
6181
- `MindGraph MCP server v${"0.19.0"} running on stdio (${BASE_URL})`
7330
+ `MindGraph MCP server v${"0.21.0"} running on stdio (${BASE_URL})`
6182
7331
  );
6183
7332
  }
6184
- var import_server, import_stdio, import_types, import_node_path6, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, RUNNER_REFRESH, HOOK_SKEW_NOTE, server, GOVERNANCE_CONFIG, PROMPTS;
7333
+ var import_server, import_stdio, import_types, import_node_path6, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, RUNNER_REFRESH, HOOK_SKEW_NOTE, server, GOVERNANCE_CONFIG, MEMORY_RUNTIME_CONFIG, PROMPTS;
6185
7334
  var init_src = __esm({
6186
7335
  "src/index.ts"() {
6187
7336
  "use strict";
@@ -6219,56 +7368,31 @@ var init_src = __esm({
6219
7368
  });
6220
7369
  INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph, plus any ontology tools generated by the active schema.
6221
7370
 
6222
- ## #1 RULE: Go straight to the user's topic
6223
-
6224
- 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.
6225
-
6226
- **NEVER do any of these before addressing the user's topic:**
6227
- - Do NOT retrieve active_goals, open_questions, contradictions, or weak_claims
6228
- - Do NOT follow a "session lifecycle" or "warm-up" sequence
6229
- - Do NOT make multiple serial retrieve calls when one "context" call covers it
6230
-
6231
- There is no session to open. There is no context to pre-load. Just search for what the user asked about.
6232
-
6233
- **Example:** User says "Tell me about Indian philosophy and Taoism"
6234
- \u2713 CORRECT: \`mindgraph_retrieve({action: "context", query: "Indian philosophy Taoism"})\`
6235
- \u2717 WRONG: First retrieve active_goals, then open_questions, then search
6236
- \u2717 WRONG: \`mindgraph_retrieve({action: "context", query: "Tell me about Indian philosophy and Taoism"})\` \u2014 natural language won't match BM25
7371
+ ## Default memory workflow: orient \u2192 answer \u2192 preserve
6237
7372
 
6238
- ## Two-phase retrieval protocol
7373
+ **Orient only when prior durable state may matter.** Self-contained questions do not require a memory call or a warm-up sequence.
6239
7374
 
6240
- **Phase 1 \u2014 Orient (default, fast, lightweight):**
6241
- 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.
7375
+ - 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.
7376
+ - Asking about a topic that may depend on prior knowledge: call \`mindgraph_memory\` once with action \`context\`, mode \`topic\`, and 1\u20133 discriminating keywords.
7377
+ - Asking about integration capability or automatic behavior: call \`mindgraph_memory\` with action \`status\`.
7378
+ - Use the comprehensive read tools below only when the high-level result is insufficient or the user asks for a specific graph operation.
6242
7379
 
6243
- **Phase 2 \u2014 Fetch source text (only when needed):**
6244
- 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.
7380
+ **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.
6245
7381
 
6246
- **Orientation shortcut:**
6247
- - \`action: "document_index"\` \u2014 list all ingested documents (titles, dates, UIDs) to see what's in the graph
7382
+ **Respect failed tool results.** An isError result is a failed operation, not evidence that the graph has no matches. Preserve its code and retry guidance. If retriable is false, do not automatically repeat the unchanged call or silently substitute another retrieval mode. Explain the failure; a narrower query or a later user-requested retry is a separate decision.
6248
7383
 
6249
- ## Search strategy \u2014 keyword first, semantic as fallback
7384
+ **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.
6250
7385
 
6251
- The default search uses BM25 keyword matching. Query with 1\u20133 discriminating terms (proper nouns, technical terms). Drop filler words.
7386
+ ## Topic retrieval details
6252
7387
 
6253
- **Keyword examples:**
6254
- - "What is Kissinger's view on NATO?" \u2192 query: \`"Kissinger NATO"\`
6255
- - "Tell me about async in Rust" \u2192 query: \`"Rust async runtime"\`
6256
- - "Implications of carbon tax?" \u2192 query: \`"carbon tax"\`
7388
+ 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 succeeds but returns no matches, use \`mindgraph_retrieve\` with \`semantic\` or \`hybrid\`; use \`document_index\` to inspect ingested sources.
6257
7389
 
6258
- If keyword search returns no results, escalate:
6259
- 1. Try synonyms (e.g. \`"global warming"\` instead of \`"climate change"\`)
6260
- 2. Use \`action: "semantic"\` \u2014 vector similarity, natural language OK (e.g. \`"ways to manage stress"\` finds "mental health" nodes)
6261
- 3. Use \`action: "hybrid"\` \u2014 combines BM25 + semantic for best coverage
7390
+ ## Comprehensive read tools
6262
7391
 
6263
- ## When to READ
6264
-
6265
- - User asks about topic X \u2192 \`mindgraph_retrieve\` action "context", query "X keywords"
6266
- - User asks "what documents are in my graph?" \u2192 action "document_index"
6267
- - User explicitly asks "what are my goals?" \u2192 action "active_goals"
6268
- - User explicitly asks about open questions \u2192 action "open_questions"
6269
- - 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
6270
- - Explore around a known node \u2192 action "neighborhood" with start_uid
6271
- - Find connections between nodes \u2192 action "path" with start_uid + end_uid
7392
+ - Explicit graph inventory or state questions \u2192 the matching \`mindgraph_retrieve\` action such as \`document_index\`, \`active_goals\`, or \`open_questions\`
7393
+ - Advice or recommendations \u2192 \`mindgraph_retrieve\` action \`preferences\`, with topic keywords
7394
+ - Explore around a known node \u2192 \`mindgraph_retrieve\` action \`neighborhood\` with start_uid
7395
+ - Find connections between nodes \u2192 \`mindgraph_retrieve\` action \`path\` with start_uid + end_uid
6272
7396
 
6273
7397
  ## When to WRITE
6274
7398
 
@@ -6303,13 +7427,14 @@ Capture knowledge when the user shares something worth remembering:
6303
7427
 
6304
7428
  ## Coding profile
6305
7429
 
6306
- **Recall order (overrides the #1 rule above for work and code questions):**
6307
- - "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).
7430
+ **Coding recall order:**
7431
+ - "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).
6308
7432
  - Knowledge about specific code (a function, file, or route): \`mindgraph_code\` action \`recall\` \u2014 anchor-based, not keyword-based.
6309
7433
  - 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.
6310
7434
 
6311
7435
  - 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.
6312
7436
  - Capture corrections, decisions with rationale, fixes that survived meaningful failed attempts, durable invariants/preferences, and blockers with evidence.
7437
+ - If a write returns \`near_duplicate\`, choose \`reuse_uid\` or \`supersedes_uid\`; use \`confirm_new\` only for genuinely distinct facts.
6313
7438
  - Do not capture routine mechanics, transient command output, facts cheaply derivable from code, unsupported speculation as high-confidence lessons, or completion without evidence.
6314
7439
  - Adapter context is owned by ${HARNESS}; omit invocation_context because hooks replace it.`;
6315
7440
  } else {
@@ -6319,7 +7444,7 @@ Capture knowledge when the user shares something worth remembering:
6319
7444
  }
6320
7445
  RUNNER_REFRESH = (() => {
6321
7446
  try {
6322
- return refreshHookRunner("0.19.0", import_node_path6.default.join(__dirname, "cli.js"));
7447
+ return refreshHookRunner("0.21.0", import_node_path6.default.join(__dirname, "cli.js"));
6323
7448
  } catch {
6324
7449
  return void 0;
6325
7450
  }
@@ -6333,12 +7458,12 @@ Capture knowledge when the user shares something worth remembering:
6333
7458
  }
6334
7459
  })();
6335
7460
  console.error(
6336
- `[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(", ")}` : "")
7461
+ `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.21.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
6337
7462
  );
6338
7463
  }
6339
7464
  HOOK_SKEW_NOTE = (() => {
6340
7465
  try {
6341
- return versionSkewNote("0.19.0");
7466
+ return versionSkewNote("0.21.0");
6342
7467
  } catch {
6343
7468
  return void 0;
6344
7469
  }
@@ -6352,7 +7477,7 @@ ${HOOK_SKEW_NOTE}`;
6352
7477
  server = new import_server.Server(
6353
7478
  {
6354
7479
  name: "mindgraph",
6355
- version: "0.19.0"
7480
+ version: "0.21.0"
6356
7481
  },
6357
7482
  {
6358
7483
  capabilities: {
@@ -6376,6 +7501,14 @@ ${HOOK_SKEW_NOTE}`;
6376
7501
  return process.env.MINDGRAPH_GOVERNANCE === "off";
6377
7502
  }
6378
7503
  };
7504
+ MEMORY_RUNTIME_CONFIG = {
7505
+ serverVersion: "0.21.0",
7506
+ baseUrl: BASE_URL,
7507
+ profile: PROFILE,
7508
+ harness: HARNESS,
7509
+ orgId: ORG_ID,
7510
+ agentId: AGENT_ID
7511
+ };
6379
7512
  server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
6380
7513
  const { name, arguments: args } = request.params;
6381
7514
  const rawArgs = { ...args || {} };
@@ -6395,7 +7528,7 @@ ${HOOK_SKEW_NOTE}`;
6395
7528
  if (desc) {
6396
7529
  return handleGeneratedTool(client, desc, rawArgs);
6397
7530
  }
6398
- return handleTool(client, name, toolArgs);
7531
+ return handleTool(client, name, toolArgs, MEMORY_RUNTIME_CONFIG);
6399
7532
  });
6400
7533
  PROMPTS = [
6401
7534
  {
@@ -6560,10 +7693,15 @@ ${conversation}`
6560
7693
  return { resourceTemplates: RESOURCE_TEMPLATES };
6561
7694
  });
6562
7695
  server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
6563
- return readGovernedResource(client, request.params.uri, GOVERNANCE_CONFIG);
7696
+ return readGovernedResource(
7697
+ client,
7698
+ request.params.uri,
7699
+ GOVERNANCE_CONFIG,
7700
+ MEMORY_RUNTIME_CONFIG
7701
+ );
6564
7702
  });
6565
- main().catch((error) => {
6566
- console.error("Fatal error:", error);
7703
+ main().catch((error2) => {
7704
+ console.error("Fatal error:", error2);
6567
7705
  process.exit(1);
6568
7706
  });
6569
7707
  }
@@ -6761,12 +7899,12 @@ function scalar(raw) {
6761
7899
  }
6762
7900
  return trimmed;
6763
7901
  }
6764
- function parseSkillMd(text) {
6765
- if (!text.startsWith("---\n")) return null;
6766
- const end = text.indexOf("\n---\n", 4);
7902
+ function parseSkillMd(text2) {
7903
+ if (!text2.startsWith("---\n")) return null;
7904
+ const end = text2.indexOf("\n---\n", 4);
6767
7905
  if (end < 0) return null;
6768
- const frontmatter = text.slice(4, end + 1);
6769
- const body = text.slice(end + 5).replace(/^\n/, "").replace(/\n$/, "");
7906
+ const frontmatter = text2.slice(4, end + 1);
7907
+ const body = text2.slice(end + 5).replace(/^\n/, "").replace(/\n$/, "");
6770
7908
  const entries = [];
6771
7909
  for (const line of frontmatter.split("\n")) {
6772
7910
  const top = /^([A-Za-z0-9_-]+):(.*)$/.exec(line);
@@ -6812,8 +7950,8 @@ ${line}`;
6812
7950
  return result;
6813
7951
  }
6814
7952
  var STATE_FILE = ".mindgraph-skills-state.json";
6815
- function sha256(text) {
6816
- return (0, import_node_crypto2.createHash)("sha256").update(text).digest("hex");
7953
+ function sha256(text2) {
7954
+ return (0, import_node_crypto2.createHash)("sha256").update(text2).digest("hex");
6817
7955
  }
6818
7956
  function loadState(dir) {
6819
7957
  try {
@@ -6959,13 +8097,13 @@ async function skillsPush(api, skillPath) {
6959
8097
  if (isSymlink(file)) {
6960
8098
  return { outcome: "error", message: `${file} is a symlink; refusing` };
6961
8099
  }
6962
- let text;
8100
+ let text2;
6963
8101
  try {
6964
- text = fs2.readFileSync(file, "utf8");
8102
+ text2 = fs2.readFileSync(file, "utf8");
6965
8103
  } catch {
6966
8104
  return { outcome: "error", message: `could not read ${file}` };
6967
8105
  }
6968
- const parsed = parseSkillMd(text);
8106
+ const parsed = parseSkillMd(text2);
6969
8107
  if (!parsed) {
6970
8108
  return {
6971
8109
  outcome: "error",
@@ -7083,8 +8221,8 @@ function withLedger(sessionId, options, update) {
7083
8221
  try {
7084
8222
  lockFd = import_node_fs3.default.openSync(lock, "wx", 384);
7085
8223
  break;
7086
- } catch (error) {
7087
- if (error.code !== "EEXIST") throw error;
8224
+ } catch (error2) {
8225
+ if (error2.code !== "EEXIST") throw error2;
7088
8226
  try {
7089
8227
  if (Date.now() - import_node_fs3.default.statSync(lock).mtimeMs > 3e3) import_node_fs3.default.unlinkSync(lock);
7090
8228
  } catch {
@@ -7189,11 +8327,11 @@ var BRIEF_CHAR_BUDGET = 9e3;
7189
8327
  function renderBrief(brief, meta) {
7190
8328
  let bounded = false;
7191
8329
  const clipTracked = (value, max) => {
7192
- const text = string(value);
7193
- if (!text) return void 0;
7194
- if (text.length <= max) return text;
8330
+ const text2 = string(value);
8331
+ if (!text2) return void 0;
8332
+ if (text2.length <= max) return text2;
7195
8333
  bounded = true;
7196
- return `${text.slice(0, max - 1)}\u2026`;
8334
+ return `${text2.slice(0, max - 1)}\u2026`;
7197
8335
  };
7198
8336
  const nodeLine = (value, summaryMax) => {
7199
8337
  const node = object(value);
@@ -7352,9 +8490,9 @@ async function repositoryScopeUids(input, client2, options, agentId) {
7352
8490
  `${repository.repoId}: ${string(payload?.status) || "unresolved"}`
7353
8491
  );
7354
8492
  return void 0;
7355
- } catch (error) {
8493
+ } catch (error2) {
7356
8494
  failures.push(
7357
- `${repository.repoId}: ${error?.message || "resolve failed"}`
8495
+ `${repository.repoId}: ${error2?.message || "resolve failed"}`
7358
8496
  );
7359
8497
  return void 0;
7360
8498
  }
@@ -7378,12 +8516,12 @@ function hasMindgraphServer(servers) {
7378
8516
  for (const [name, entry] of Object.entries(servers)) {
7379
8517
  if (name === "mindgraph") return true;
7380
8518
  const cfg = entry && typeof entry === "object" ? entry : void 0;
7381
- const text = [
8519
+ const text2 = [
7382
8520
  String(cfg?.command ?? ""),
7383
8521
  Array.isArray(cfg?.args) ? cfg.args.join(" ") : "",
7384
8522
  String(cfg?.url ?? "")
7385
8523
  ].join(" ");
7386
- if (text.includes("mindgraph-mcp")) return true;
8524
+ if (text2.includes("mindgraph-mcp")) return true;
7387
8525
  }
7388
8526
  return false;
7389
8527
  }
@@ -7613,7 +8751,7 @@ async function postTool(input, client2, options) {
7613
8751
  ledger.mutatingOperations += 1;
7614
8752
  }
7615
8753
  if (!payload?.error) {
7616
- if (name.includes("mindgraph_capture") && ["lesson", "journal", "skill"].includes(action || "") || name.includes("mindgraph_commit") && action === "resolve_decision" || name.includes("mindgraph_plan") && action === "assess_risk") {
8754
+ if (name.includes("mindgraph_capture") && (["lesson", "journal", "skill"].includes(action || "") || ["observation", "entity"].includes(action || "") && (Array.isArray(args.code_refs) || typeof args.supersedes_uid === "string")) || name.includes("mindgraph_commit") && action === "resolve_decision" || name.includes("mindgraph_plan") && action === "assess_risk") {
7617
8755
  ledger.memoryWritten = true;
7618
8756
  }
7619
8757
  }
@@ -7894,7 +9032,7 @@ async function runCodexHook(input, client2, options = {}) {
7894
9032
 
7895
9033
  // src/cli.ts
7896
9034
  init_hook_installer();
7897
- function recordHookHealth(harness, error) {
9035
+ function recordHookHealth(harness, error2) {
7898
9036
  try {
7899
9037
  const dir = process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime");
7900
9038
  fs6.mkdirSync(dir, { recursive: true, mode: 448 });
@@ -7908,7 +9046,7 @@ function recordHookHealth(harness, error) {
7908
9046
  file,
7909
9047
  `${JSON.stringify({
7910
9048
  harness,
7911
- lastError: error.slice(0, 500),
9049
+ lastError: error2.slice(0, 500),
7912
9050
  lastFailureAt: (/* @__PURE__ */ new Date()).toISOString(),
7913
9051
  consecutiveFailures: failures + 1
7914
9052
  })}
@@ -8131,7 +9269,7 @@ function withHooksFlag() {
8131
9269
  }
8132
9270
  function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
8133
9271
  if (withHooks) {
8134
- const runner = installHookRunner(__filename, void 0, "0.19.0");
9272
+ const runner = installHookRunner(__filename, void 0, "0.21.0");
8135
9273
  console.log(`Installed pinned hook runner at ${runner}`);
8136
9274
  const result = installClaudeHooks("user");
8137
9275
  console.log(
@@ -8446,7 +9584,7 @@ ${total} skill(s) synced.`);
8446
9584
  if (hookHarness !== "claude-code" && hookHarness !== "codex") {
8447
9585
  throw new Error("--harness must be claude-code or codex");
8448
9586
  }
8449
- const runner = installHookRunner(__filename, void 0, "0.19.0");
9587
+ const runner = installHookRunner(__filename, void 0, "0.21.0");
8450
9588
  console.log(`Installed pinned hook runner at ${runner}`);
8451
9589
  const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir);
8452
9590
  console.log(
@@ -8527,13 +9665,13 @@ ${total} skill(s) synced.`);
8527
9665
  process.stdout.write(`${JSON.stringify(output)}
8528
9666
  `);
8529
9667
  clearHookHealth(hookHarness);
8530
- } catch (error) {
9668
+ } catch (error2) {
8531
9669
  recordHookHealth(
8532
9670
  hookHarness,
8533
- error?.message || "hook failed"
9671
+ error2?.message || "hook failed"
8534
9672
  );
8535
9673
  console.error(
8536
- `mindgraph-hook: failing open (${error?.message || "unknown error"})`
9674
+ `mindgraph-hook: failing open (${error2?.message || "unknown error"})`
8537
9675
  );
8538
9676
  process.stdout.write("{}\n");
8539
9677
  }
@@ -8552,7 +9690,7 @@ ${total} skill(s) synced.`);
8552
9690
  process.exit(1);
8553
9691
  }
8554
9692
  }
8555
- main2().catch((error) => {
8556
- console.error("Error:", error.message || error);
9693
+ main2().catch((error2) => {
9694
+ console.error("Error:", error2.message || error2);
8557
9695
  process.exit(1);
8558
9696
  });