mindgraph 0.15.1 → 0.17.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.
package/dist/index.js CHANGED
@@ -25,16 +25,120 @@ __export(index_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
27
 
28
+ // src/retry.ts
29
+ var READ_POSTS = /* @__PURE__ */ new Set([
30
+ "/search",
31
+ "/retrieve/context",
32
+ "/nodes/batch",
33
+ "/edges/batch",
34
+ "/subgraph",
35
+ "/embeddings/search",
36
+ "/embeddings/search-text",
37
+ "/ontology/query",
38
+ "/ontology/query/structured",
39
+ "/ontology/objects/search"
40
+ ]);
41
+ var READ_ACTIONS = {
42
+ "/retrieve": [
43
+ "text",
44
+ "semantic",
45
+ "hybrid",
46
+ "active_goals",
47
+ "open_questions",
48
+ "preferences",
49
+ "weak_claims",
50
+ "pending_approvals",
51
+ "unresolved_contradictions",
52
+ "merge_candidates",
53
+ "stale_derivations",
54
+ "curation_counts",
55
+ "layer",
56
+ "recent"
57
+ ],
58
+ "/traverse": ["chain", "top_k_paths", "neighborhood", "path", "subgraph"],
59
+ "/reality/series": ["window", "aggregate", "latest", "list_for_entity", "batch_latest", "batch_aggregate"],
60
+ "/intent/deliberation": ["get_open"],
61
+ "/action/risk": ["get_assessments"],
62
+ "/action/skill": ["get", "list"],
63
+ "/memory/config": ["get_preferences", "get_policies"],
64
+ "/memory/sync": ["status"],
65
+ "/agent/plan": ["get_plan", "resume_work"],
66
+ "/agent/governance": ["get_pending", "check"],
67
+ "/agent/execution": ["get_executions"],
68
+ "/evolve": ["history", "snapshot"]
69
+ };
70
+ var IDEMPOTENT_WORK = /* @__PURE__ */ new Set([
71
+ "claim_task",
72
+ "heartbeat",
73
+ "start_iteration",
74
+ "checkpoint_iteration",
75
+ "block_task",
76
+ "complete_task",
77
+ "abandon_iteration"
78
+ ]);
79
+ function errorFields(body) {
80
+ if (!body || typeof body !== "object" || Array.isArray(body)) return {};
81
+ const fields = body;
82
+ return {
83
+ ...typeof fields.code === "string" ? { code: fields.code } : {},
84
+ ...typeof fields.retriable === "boolean" ? { retriable: fields.retriable } : {}
85
+ };
86
+ }
87
+ function canRetryRequest(method, path, body) {
88
+ if (method === "GET" || method === "HEAD") return true;
89
+ if (method !== "POST") return false;
90
+ if (READ_POSTS.has(path)) return true;
91
+ const fields = body && typeof body === "object" && !Array.isArray(body) ? body : {};
92
+ if (typeof fields.action !== "string") return false;
93
+ if (Object.prototype.hasOwnProperty.call(READ_ACTIONS, path) && READ_ACTIONS[path].includes(fields.action)) return true;
94
+ return path === "/agent/plan" && IDEMPOTENT_WORK.has(fields.action) && typeof fields.idempotency_key === "string" && fields.idempotency_key.trim().length > 0;
95
+ }
96
+ function permitsRetry(fields) {
97
+ return fields.retriable !== false && ![
98
+ "query_memory_budget_exceeded",
99
+ "query_timeout",
100
+ "query_cancelled",
101
+ "vector_index_rebuilding"
102
+ ].includes(fields.code ?? "");
103
+ }
104
+ function retryDelayMs(header, backoff, attempt) {
105
+ const hint = Number(header);
106
+ return Number.isFinite(hint) && hint > 0 ? Math.min(hint * 1e3, 1e4) : Math.min(backoff * 2 ** Math.min(attempt, 32), 1e4);
107
+ }
108
+
28
109
  // src/client.ts
29
110
  function seg(value) {
30
111
  return encodeURIComponent(String(value));
31
112
  }
113
+ function pathSteps(response) {
114
+ const wrapped = response && typeof response === "object" && !Array.isArray(response) ? response : void 0;
115
+ const raw = wrapped?.steps ?? response;
116
+ if (!Array.isArray(raw)) return [];
117
+ return raw.flatMap((value) => {
118
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [];
119
+ const step = value;
120
+ const taggedName = (tagged) => {
121
+ if (typeof tagged === "string") return tagged;
122
+ if (!tagged || typeof tagged !== "object" || Array.isArray(tagged)) return null;
123
+ const custom = tagged.Custom;
124
+ return typeof custom === "string" ? custom : null;
125
+ };
126
+ return [{
127
+ ...step,
128
+ node_type: taggedName(step.node_type) ?? "Unknown",
129
+ edge_type: taggedName(step.edge_type)
130
+ }];
131
+ });
132
+ }
32
133
  var MindGraphError = class extends Error {
33
134
  constructor(message, status, body) {
34
135
  super(message);
35
136
  this.status = status;
36
137
  this.body = body;
37
138
  this.name = "MindGraphError";
139
+ const fields = errorFields(body);
140
+ this.code = fields.code;
141
+ this.retriable = fields.retriable;
38
142
  }
39
143
  };
40
144
  var MindGraph = class {
@@ -52,6 +156,18 @@ var MindGraph = class {
52
156
  this.telemetrySurface = config.telemetrySurface;
53
157
  this.maxRetries = config.maxRetries ?? 3;
54
158
  this.retryBackoffMs = config.retryBackoffMs ?? 1e3;
159
+ if (!Number.isSafeInteger(this.maxRetries) || this.maxRetries < 0) {
160
+ throw new RangeError("maxRetries must be a non-negative safe integer");
161
+ }
162
+ if (!Number.isFinite(this.retryBackoffMs) || this.retryBackoffMs < 0) {
163
+ throw new RangeError("retryBackoffMs must be finite and non-negative");
164
+ }
165
+ if (config.timeoutMs !== void 0) {
166
+ if (!Number.isFinite(config.timeoutMs) || config.timeoutMs <= 0) {
167
+ throw new RangeError("timeoutMs must be a positive finite number of milliseconds");
168
+ }
169
+ this.timeoutMs = config.timeoutMs;
170
+ }
55
171
  }
56
172
  // ---- HTTP helpers ----
57
173
  async request(method, path, body) {
@@ -68,9 +184,14 @@ var MindGraph = class {
68
184
  if (body !== void 0) {
69
185
  init.body = JSON.stringify(body);
70
186
  }
187
+ const retrySafe = canRetryRequest(
188
+ method,
189
+ path,
190
+ typeof init.body === "string" ? JSON.parse(init.body) : void 0
191
+ );
71
192
  let lastError;
72
193
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
73
- const res = await fetch(url, init);
194
+ const res = await this.fetchWithDeadline(url, init, method, path);
74
195
  if (!res.ok) {
75
196
  const text2 = await res.text().catch(() => "");
76
197
  let parsed;
@@ -84,10 +205,9 @@ var MindGraph = class {
84
205
  res.status,
85
206
  parsed
86
207
  );
87
- if (res.status === 503 && attempt < this.maxRetries) {
208
+ if (res.status === 503 && retrySafe && permitsRetry(err) && attempt < this.maxRetries) {
88
209
  lastError = err;
89
- const retryAfterSecs = Number(res.headers.get("retry-after"));
90
- const delay = Number.isFinite(retryAfterSecs) && retryAfterSecs > 0 ? Math.min(retryAfterSecs * 1e3, 1e4) : this.retryBackoffMs * 2 ** attempt;
210
+ const delay = retryDelayMs(res.headers.get("retry-after"), this.retryBackoffMs, attempt);
91
211
  await new Promise((r) => setTimeout(r, delay));
92
212
  continue;
93
213
  }
@@ -99,6 +219,33 @@ var MindGraph = class {
99
219
  }
100
220
  throw lastError;
101
221
  }
222
+ /**
223
+ * One HTTP attempt. With `timeoutMs` set, the attempt is aborted at the
224
+ * deadline and surfaces as a non-retriable `MindGraphError` (`code:
225
+ * "timeout"`, `status: 0`). Without it the request is sent unchanged — no
226
+ * AbortSignal is attached, preserving the pre-0.16 behaviour.
227
+ */
228
+ async fetchWithDeadline(url, init, method, path) {
229
+ if (this.timeoutMs === void 0) {
230
+ return fetch(url, init);
231
+ }
232
+ const controller = new AbortController();
233
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
234
+ try {
235
+ return await fetch(url, { ...init, signal: controller.signal });
236
+ } catch (error) {
237
+ if (controller.signal.aborted) {
238
+ throw new MindGraphError(
239
+ `${method} ${path} timed out after ${this.timeoutMs}ms`,
240
+ 0,
241
+ { error: "timeout", code: "timeout", retriable: false }
242
+ );
243
+ }
244
+ throw error;
245
+ } finally {
246
+ clearTimeout(timer);
247
+ }
248
+ }
102
249
  get(path) {
103
250
  return this.request("GET", path);
104
251
  }
@@ -348,6 +495,40 @@ var MindGraph = class {
348
495
  async memorySync(req) {
349
496
  return this.post("/memory/sync", req);
350
497
  }
498
+ // ---- Memory (fast path) ----
499
+ /**
500
+ * Store a small piece of text as a memory, synchronously. The node is
501
+ * BM25- and vector-searchable when this resolves (`searchable` says which).
502
+ * Pass `custom_id` to make re-sends an upsert of the same node.
503
+ */
504
+ async remember(text, options = {}) {
505
+ return this.post("/memory/remember", { text, ...options });
506
+ }
507
+ /**
508
+ * Reversibly remove a memory by `uid` or `custom_id` (tombstone plus its
509
+ * connected edges). `dry_run: true` previews the affected edge uids first.
510
+ * Undo with `restore(uid)` and `evolve({ action: "restore_edge", uid })`.
511
+ */
512
+ async forget(target, options = {}) {
513
+ return this.post("/memory/forget", { ...target, ...options });
514
+ }
515
+ /**
516
+ * Operator guidance for what agents should remember into a Space; surfaced
517
+ * on every `remember()` response and on `retrieveContext()`. Empty clears it.
518
+ */
519
+ async setRememberInstructions(text, options = {}) {
520
+ return this.post("/memory/config", {
521
+ action: "set_remember_instructions",
522
+ text,
523
+ ...options
524
+ });
525
+ }
526
+ async getRememberInstructions(options = {}) {
527
+ return this.post("/memory/config", {
528
+ action: "get_remember_instructions",
529
+ ...options
530
+ });
531
+ }
351
532
  // ---- Agent Layer ----
352
533
  async plan(req) {
353
534
  return this.post("/agent/plan", req);
@@ -570,7 +751,7 @@ var MindGraph = class {
570
751
  start_uid: uid,
571
752
  max_depth: maxDepth
572
753
  });
573
- return r?.steps ?? r;
754
+ return pathSteps(r);
574
755
  }
575
756
  async neighborhood(uid, maxDepth = 1) {
576
757
  const r = await this.post("/traverse", {
@@ -578,7 +759,7 @@ var MindGraph = class {
578
759
  start_uid: uid,
579
760
  max_depth: maxDepth
580
761
  });
581
- return r?.steps ?? r;
762
+ return pathSteps(r);
582
763
  }
583
764
  async subgraph(uid, opts) {
584
765
  return this.post("/subgraph", { start_uids: [uid], ...opts });
package/dist/index.mjs CHANGED
@@ -1,13 +1,117 @@
1
+ // src/retry.ts
2
+ var READ_POSTS = /* @__PURE__ */ new Set([
3
+ "/search",
4
+ "/retrieve/context",
5
+ "/nodes/batch",
6
+ "/edges/batch",
7
+ "/subgraph",
8
+ "/embeddings/search",
9
+ "/embeddings/search-text",
10
+ "/ontology/query",
11
+ "/ontology/query/structured",
12
+ "/ontology/objects/search"
13
+ ]);
14
+ var READ_ACTIONS = {
15
+ "/retrieve": [
16
+ "text",
17
+ "semantic",
18
+ "hybrid",
19
+ "active_goals",
20
+ "open_questions",
21
+ "preferences",
22
+ "weak_claims",
23
+ "pending_approvals",
24
+ "unresolved_contradictions",
25
+ "merge_candidates",
26
+ "stale_derivations",
27
+ "curation_counts",
28
+ "layer",
29
+ "recent"
30
+ ],
31
+ "/traverse": ["chain", "top_k_paths", "neighborhood", "path", "subgraph"],
32
+ "/reality/series": ["window", "aggregate", "latest", "list_for_entity", "batch_latest", "batch_aggregate"],
33
+ "/intent/deliberation": ["get_open"],
34
+ "/action/risk": ["get_assessments"],
35
+ "/action/skill": ["get", "list"],
36
+ "/memory/config": ["get_preferences", "get_policies"],
37
+ "/memory/sync": ["status"],
38
+ "/agent/plan": ["get_plan", "resume_work"],
39
+ "/agent/governance": ["get_pending", "check"],
40
+ "/agent/execution": ["get_executions"],
41
+ "/evolve": ["history", "snapshot"]
42
+ };
43
+ var IDEMPOTENT_WORK = /* @__PURE__ */ new Set([
44
+ "claim_task",
45
+ "heartbeat",
46
+ "start_iteration",
47
+ "checkpoint_iteration",
48
+ "block_task",
49
+ "complete_task",
50
+ "abandon_iteration"
51
+ ]);
52
+ function errorFields(body) {
53
+ if (!body || typeof body !== "object" || Array.isArray(body)) return {};
54
+ const fields = body;
55
+ return {
56
+ ...typeof fields.code === "string" ? { code: fields.code } : {},
57
+ ...typeof fields.retriable === "boolean" ? { retriable: fields.retriable } : {}
58
+ };
59
+ }
60
+ function canRetryRequest(method, path, body) {
61
+ if (method === "GET" || method === "HEAD") return true;
62
+ if (method !== "POST") return false;
63
+ if (READ_POSTS.has(path)) return true;
64
+ const fields = body && typeof body === "object" && !Array.isArray(body) ? body : {};
65
+ if (typeof fields.action !== "string") return false;
66
+ if (Object.prototype.hasOwnProperty.call(READ_ACTIONS, path) && READ_ACTIONS[path].includes(fields.action)) return true;
67
+ return path === "/agent/plan" && IDEMPOTENT_WORK.has(fields.action) && typeof fields.idempotency_key === "string" && fields.idempotency_key.trim().length > 0;
68
+ }
69
+ function permitsRetry(fields) {
70
+ return fields.retriable !== false && ![
71
+ "query_memory_budget_exceeded",
72
+ "query_timeout",
73
+ "query_cancelled",
74
+ "vector_index_rebuilding"
75
+ ].includes(fields.code ?? "");
76
+ }
77
+ function retryDelayMs(header, backoff, attempt) {
78
+ const hint = Number(header);
79
+ return Number.isFinite(hint) && hint > 0 ? Math.min(hint * 1e3, 1e4) : Math.min(backoff * 2 ** Math.min(attempt, 32), 1e4);
80
+ }
81
+
1
82
  // src/client.ts
2
83
  function seg(value) {
3
84
  return encodeURIComponent(String(value));
4
85
  }
86
+ function pathSteps(response) {
87
+ const wrapped = response && typeof response === "object" && !Array.isArray(response) ? response : void 0;
88
+ const raw = wrapped?.steps ?? response;
89
+ if (!Array.isArray(raw)) return [];
90
+ return raw.flatMap((value) => {
91
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [];
92
+ const step = value;
93
+ const taggedName = (tagged) => {
94
+ if (typeof tagged === "string") return tagged;
95
+ if (!tagged || typeof tagged !== "object" || Array.isArray(tagged)) return null;
96
+ const custom = tagged.Custom;
97
+ return typeof custom === "string" ? custom : null;
98
+ };
99
+ return [{
100
+ ...step,
101
+ node_type: taggedName(step.node_type) ?? "Unknown",
102
+ edge_type: taggedName(step.edge_type)
103
+ }];
104
+ });
105
+ }
5
106
  var MindGraphError = class extends Error {
6
107
  constructor(message, status, body) {
7
108
  super(message);
8
109
  this.status = status;
9
110
  this.body = body;
10
111
  this.name = "MindGraphError";
112
+ const fields = errorFields(body);
113
+ this.code = fields.code;
114
+ this.retriable = fields.retriable;
11
115
  }
12
116
  };
13
117
  var MindGraph = class {
@@ -25,6 +129,18 @@ var MindGraph = class {
25
129
  this.telemetrySurface = config.telemetrySurface;
26
130
  this.maxRetries = config.maxRetries ?? 3;
27
131
  this.retryBackoffMs = config.retryBackoffMs ?? 1e3;
132
+ if (!Number.isSafeInteger(this.maxRetries) || this.maxRetries < 0) {
133
+ throw new RangeError("maxRetries must be a non-negative safe integer");
134
+ }
135
+ if (!Number.isFinite(this.retryBackoffMs) || this.retryBackoffMs < 0) {
136
+ throw new RangeError("retryBackoffMs must be finite and non-negative");
137
+ }
138
+ if (config.timeoutMs !== void 0) {
139
+ if (!Number.isFinite(config.timeoutMs) || config.timeoutMs <= 0) {
140
+ throw new RangeError("timeoutMs must be a positive finite number of milliseconds");
141
+ }
142
+ this.timeoutMs = config.timeoutMs;
143
+ }
28
144
  }
29
145
  // ---- HTTP helpers ----
30
146
  async request(method, path, body) {
@@ -41,9 +157,14 @@ var MindGraph = class {
41
157
  if (body !== void 0) {
42
158
  init.body = JSON.stringify(body);
43
159
  }
160
+ const retrySafe = canRetryRequest(
161
+ method,
162
+ path,
163
+ typeof init.body === "string" ? JSON.parse(init.body) : void 0
164
+ );
44
165
  let lastError;
45
166
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
46
- const res = await fetch(url, init);
167
+ const res = await this.fetchWithDeadline(url, init, method, path);
47
168
  if (!res.ok) {
48
169
  const text2 = await res.text().catch(() => "");
49
170
  let parsed;
@@ -57,10 +178,9 @@ var MindGraph = class {
57
178
  res.status,
58
179
  parsed
59
180
  );
60
- if (res.status === 503 && attempt < this.maxRetries) {
181
+ if (res.status === 503 && retrySafe && permitsRetry(err) && attempt < this.maxRetries) {
61
182
  lastError = err;
62
- const retryAfterSecs = Number(res.headers.get("retry-after"));
63
- const delay = Number.isFinite(retryAfterSecs) && retryAfterSecs > 0 ? Math.min(retryAfterSecs * 1e3, 1e4) : this.retryBackoffMs * 2 ** attempt;
183
+ const delay = retryDelayMs(res.headers.get("retry-after"), this.retryBackoffMs, attempt);
64
184
  await new Promise((r) => setTimeout(r, delay));
65
185
  continue;
66
186
  }
@@ -72,6 +192,33 @@ var MindGraph = class {
72
192
  }
73
193
  throw lastError;
74
194
  }
195
+ /**
196
+ * One HTTP attempt. With `timeoutMs` set, the attempt is aborted at the
197
+ * deadline and surfaces as a non-retriable `MindGraphError` (`code:
198
+ * "timeout"`, `status: 0`). Without it the request is sent unchanged — no
199
+ * AbortSignal is attached, preserving the pre-0.16 behaviour.
200
+ */
201
+ async fetchWithDeadline(url, init, method, path) {
202
+ if (this.timeoutMs === void 0) {
203
+ return fetch(url, init);
204
+ }
205
+ const controller = new AbortController();
206
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
207
+ try {
208
+ return await fetch(url, { ...init, signal: controller.signal });
209
+ } catch (error) {
210
+ if (controller.signal.aborted) {
211
+ throw new MindGraphError(
212
+ `${method} ${path} timed out after ${this.timeoutMs}ms`,
213
+ 0,
214
+ { error: "timeout", code: "timeout", retriable: false }
215
+ );
216
+ }
217
+ throw error;
218
+ } finally {
219
+ clearTimeout(timer);
220
+ }
221
+ }
75
222
  get(path) {
76
223
  return this.request("GET", path);
77
224
  }
@@ -321,6 +468,40 @@ var MindGraph = class {
321
468
  async memorySync(req) {
322
469
  return this.post("/memory/sync", req);
323
470
  }
471
+ // ---- Memory (fast path) ----
472
+ /**
473
+ * Store a small piece of text as a memory, synchronously. The node is
474
+ * BM25- and vector-searchable when this resolves (`searchable` says which).
475
+ * Pass `custom_id` to make re-sends an upsert of the same node.
476
+ */
477
+ async remember(text, options = {}) {
478
+ return this.post("/memory/remember", { text, ...options });
479
+ }
480
+ /**
481
+ * Reversibly remove a memory by `uid` or `custom_id` (tombstone plus its
482
+ * connected edges). `dry_run: true` previews the affected edge uids first.
483
+ * Undo with `restore(uid)` and `evolve({ action: "restore_edge", uid })`.
484
+ */
485
+ async forget(target, options = {}) {
486
+ return this.post("/memory/forget", { ...target, ...options });
487
+ }
488
+ /**
489
+ * Operator guidance for what agents should remember into a Space; surfaced
490
+ * on every `remember()` response and on `retrieveContext()`. Empty clears it.
491
+ */
492
+ async setRememberInstructions(text, options = {}) {
493
+ return this.post("/memory/config", {
494
+ action: "set_remember_instructions",
495
+ text,
496
+ ...options
497
+ });
498
+ }
499
+ async getRememberInstructions(options = {}) {
500
+ return this.post("/memory/config", {
501
+ action: "get_remember_instructions",
502
+ ...options
503
+ });
504
+ }
324
505
  // ---- Agent Layer ----
325
506
  async plan(req) {
326
507
  return this.post("/agent/plan", req);
@@ -543,7 +724,7 @@ var MindGraph = class {
543
724
  start_uid: uid,
544
725
  max_depth: maxDepth
545
726
  });
546
- return r?.steps ?? r;
727
+ return pathSteps(r);
547
728
  }
548
729
  async neighborhood(uid, maxDepth = 1) {
549
730
  const r = await this.post("/traverse", {
@@ -551,7 +732,7 @@ var MindGraph = class {
551
732
  start_uid: uid,
552
733
  max_depth: maxDepth
553
734
  });
554
- return r?.steps ?? r;
735
+ return pathSteps(r);
555
736
  }
556
737
  async subgraph(uid, opts) {
557
738
  return this.post("/subgraph", { start_uids: [uid], ...opts });
package/package.json CHANGED
@@ -1,20 +1,33 @@
1
1
  {
2
2
  "name": "mindgraph",
3
- "version": "0.15.1",
3
+ "version": "0.17.0",
4
4
  "description": "TypeScript client for the MindGraph Cloud API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
- "files": ["dist"],
8
+ "files": [
9
+ "dist"
10
+ ],
9
11
  "scripts": {
12
+ "prepare": "npm run build",
10
13
  "build": "tsup src/index.ts --format cjs,esm --dts",
11
14
  "lint": "tsc --noEmit",
12
15
  "test": "vitest run",
13
16
  "test:offline": "vitest run src/offline.test.ts",
14
17
  "test:e2e": "vitest run src/integration.test.ts"
15
18
  },
16
- "keywords": ["mindgraph", "knowledge-graph", "cognitive", "ai", "agent"],
19
+ "keywords": [
20
+ "mindgraph",
21
+ "knowledge-graph",
22
+ "cognitive",
23
+ "ai",
24
+ "agent"
25
+ ],
17
26
  "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/shuruheel/mindgraph-ts.git"
30
+ },
18
31
  "devDependencies": {
19
32
  "@types/node": "^25.4.0",
20
33
  "tsup": "^8.0.0",