mindgraph-mcp 0.13.4 → 0.14.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/LICENSE +1 -1
- package/README.md +71 -3
- package/dist/cli.js +3207 -494
- package/dist/index.js +2353 -359
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -26,13 +26,1422 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
mod
|
|
27
27
|
));
|
|
28
28
|
|
|
29
|
+
// src/codegraph.ts
|
|
30
|
+
function normalizePath(path6) {
|
|
31
|
+
return path6.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
32
|
+
}
|
|
33
|
+
function normalizeSignature(signature) {
|
|
34
|
+
return signature.replace(/\s+/g, " ").trim();
|
|
35
|
+
}
|
|
36
|
+
function languageFromPath(path6) {
|
|
37
|
+
const extension = (0, import_node_path2.extname)(path6).toLowerCase();
|
|
38
|
+
return {
|
|
39
|
+
".c": "c",
|
|
40
|
+
".cc": "cpp",
|
|
41
|
+
".cpp": "cpp",
|
|
42
|
+
".go": "go",
|
|
43
|
+
".java": "java",
|
|
44
|
+
".js": "javascript",
|
|
45
|
+
".jsx": "javascript",
|
|
46
|
+
".kt": "kotlin",
|
|
47
|
+
".py": "python",
|
|
48
|
+
".rb": "ruby",
|
|
49
|
+
".rs": "rust",
|
|
50
|
+
".swift": "swift",
|
|
51
|
+
".ts": "typescript",
|
|
52
|
+
".tsx": "typescript"
|
|
53
|
+
}[extension] ?? "unknown";
|
|
54
|
+
}
|
|
55
|
+
function asNode(hit) {
|
|
56
|
+
return hit.node ?? hit;
|
|
57
|
+
}
|
|
58
|
+
function toCandidate(node) {
|
|
59
|
+
if (!node.filePath || !node.kind || !(node.qualifiedName || node.name)) return null;
|
|
60
|
+
return {
|
|
61
|
+
path: normalizePath(node.filePath),
|
|
62
|
+
language: node.language ?? languageFromPath(node.filePath),
|
|
63
|
+
kind: node.kind,
|
|
64
|
+
qualifiedName: node.qualifiedName ?? node.name ?? "",
|
|
65
|
+
signature: node.signature ?? null,
|
|
66
|
+
startLine: node.startLine ?? 1,
|
|
67
|
+
endLine: node.endLine ?? node.startLine ?? 1
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function normalizeRemote(remote) {
|
|
71
|
+
const trimmed = remote.trim();
|
|
72
|
+
if (!trimmed) return null;
|
|
73
|
+
const scp = /^(?:[^@]+@)?([^:]+):(.+)$/.exec(trimmed);
|
|
74
|
+
if (scp && !trimmed.includes("://")) {
|
|
75
|
+
return `${scp[1].toLowerCase()}/${scp[2].replace(/\.git$/, "")}`;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const url = new URL(trimmed);
|
|
79
|
+
const pathname = url.pathname.replace(/\/$/, "").replace(/\.git$/, "");
|
|
80
|
+
return `${url.host.toLowerCase()}${pathname}`;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function pathContains(root, candidate) {
|
|
86
|
+
const child = (0, import_node_path2.relative)(root, candidate);
|
|
87
|
+
return child === "" || child !== ".." && !child.startsWith(`..${import_node_path2.sep}`) && !(0, import_node_path2.isAbsolute)(child);
|
|
88
|
+
}
|
|
89
|
+
function nearestExistingDirectory(candidate) {
|
|
90
|
+
let current = (0, import_node_path2.resolve)(candidate);
|
|
91
|
+
for (; ; ) {
|
|
92
|
+
if ((0, import_node_fs2.existsSync)(current)) {
|
|
93
|
+
try {
|
|
94
|
+
return (0, import_node_fs2.statSync)(current).isDirectory() ? current : (0, import_node_path2.dirname)(current);
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const parent = (0, import_node_path2.dirname)(current);
|
|
99
|
+
if (parent === current) return current;
|
|
100
|
+
current = parent;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function findWorkspaceFile(start, explicit) {
|
|
104
|
+
if (explicit) return (0, import_node_path2.resolve)(start, explicit);
|
|
105
|
+
let current = (0, import_node_path2.resolve)(start);
|
|
106
|
+
for (; ; ) {
|
|
107
|
+
const candidate = (0, import_node_path2.join)(current, ".mindgraph", "workspace.json");
|
|
108
|
+
if ((0, import_node_fs2.existsSync)(candidate)) return candidate;
|
|
109
|
+
const parent = (0, import_node_path2.dirname)(current);
|
|
110
|
+
if (parent === current) return void 0;
|
|
111
|
+
current = parent;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function runProcess(executable, args, timeoutMs, env, cwd) {
|
|
115
|
+
return await new Promise((resolvePromise, rejectPromise) => {
|
|
116
|
+
const child = (0, import_node_child_process.spawn)(executable, args, {
|
|
117
|
+
cwd,
|
|
118
|
+
env,
|
|
119
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
120
|
+
windowsHide: true
|
|
121
|
+
});
|
|
122
|
+
const stdout = [];
|
|
123
|
+
const stderr = [];
|
|
124
|
+
let outputBytes = 0;
|
|
125
|
+
let timedOut = false;
|
|
126
|
+
const timer = setTimeout(() => {
|
|
127
|
+
timedOut = true;
|
|
128
|
+
child.kill("SIGTERM");
|
|
129
|
+
setTimeout(() => child.kill("SIGKILL"), 250).unref();
|
|
130
|
+
}, timeoutMs);
|
|
131
|
+
timer.unref();
|
|
132
|
+
const collect = (target) => (chunk) => {
|
|
133
|
+
outputBytes += chunk.length;
|
|
134
|
+
if (outputBytes > MAX_OUTPUT_BYTES) {
|
|
135
|
+
child.kill("SIGTERM");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
target.push(chunk);
|
|
139
|
+
};
|
|
140
|
+
child.stdout.on("data", collect(stdout));
|
|
141
|
+
child.stderr.on("data", collect(stderr));
|
|
142
|
+
child.on("error", (cause) => {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
const error = new Error(cause.message);
|
|
145
|
+
error.kind = cause.code === "ENOENT" ? "absent" : "command_failed";
|
|
146
|
+
rejectPromise(error);
|
|
147
|
+
});
|
|
148
|
+
child.on("close", (code) => {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
if (timedOut) {
|
|
151
|
+
const error = new Error(`command timed out after ${timeoutMs}ms`);
|
|
152
|
+
error.kind = "timeout";
|
|
153
|
+
rejectPromise(error);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (outputBytes > MAX_OUTPUT_BYTES) {
|
|
157
|
+
const error = new Error("codegraph output exceeded 5 MiB");
|
|
158
|
+
error.kind = "command_failed";
|
|
159
|
+
rejectPromise(error);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (code !== 0) {
|
|
163
|
+
const error = new Error(Buffer.concat(stderr).toString("utf8").trim());
|
|
164
|
+
error.kind = "command_failed";
|
|
165
|
+
error.exitCode = code;
|
|
166
|
+
rejectPromise(error);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
resolvePromise(Buffer.concat(stdout).toString("utf8"));
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function repositoryIdentityKey(repository) {
|
|
174
|
+
return { v: 1, kind: "repository", repo_id: repository.repoId };
|
|
175
|
+
}
|
|
176
|
+
function codeRefIdentityKey(ref) {
|
|
177
|
+
if (ref.kind === "file") {
|
|
178
|
+
return {
|
|
179
|
+
v: 1,
|
|
180
|
+
kind: "file",
|
|
181
|
+
repo_id: ref.repoId,
|
|
182
|
+
path: ref.path
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
v: 1,
|
|
187
|
+
kind: "symbol",
|
|
188
|
+
repo_id: ref.repoId,
|
|
189
|
+
language: ref.language,
|
|
190
|
+
symbol_kind: ref.kind,
|
|
191
|
+
qualified_name: ref.qualifiedName,
|
|
192
|
+
disambiguator: {
|
|
193
|
+
signature: ref.signature ?? "",
|
|
194
|
+
path: ref.path,
|
|
195
|
+
start_line: ref.startLine,
|
|
196
|
+
end_line: ref.endLine
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
|
|
201
|
+
var init_codegraph = __esm({
|
|
202
|
+
"src/codegraph.ts"() {
|
|
203
|
+
"use strict";
|
|
204
|
+
import_node_child_process = require("child_process");
|
|
205
|
+
import_node_fs2 = require("fs");
|
|
206
|
+
import_promises = require("fs/promises");
|
|
207
|
+
import_node_os2 = require("os");
|
|
208
|
+
import_node_path2 = require("path");
|
|
209
|
+
import_node_crypto = require("crypto");
|
|
210
|
+
DEFAULT_TIMEOUT_MS = 5e3;
|
|
211
|
+
DEFAULT_STATUS_TTL_MS = 6e4;
|
|
212
|
+
MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
213
|
+
CodegraphAdapter = class {
|
|
214
|
+
timeoutMs;
|
|
215
|
+
statusTtlMs;
|
|
216
|
+
env;
|
|
217
|
+
cwd;
|
|
218
|
+
workspaceFile;
|
|
219
|
+
now;
|
|
220
|
+
identityCache = /* @__PURE__ */ new Map();
|
|
221
|
+
statusCache = /* @__PURE__ */ new Map();
|
|
222
|
+
constructor(options = {}) {
|
|
223
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
224
|
+
this.statusTtlMs = options.statusTtlMs ?? DEFAULT_STATUS_TTL_MS;
|
|
225
|
+
this.env = options.env ?? process.env;
|
|
226
|
+
this.cwd = options.cwd ?? process.cwd();
|
|
227
|
+
this.workspaceFile = findWorkspaceFile(
|
|
228
|
+
this.cwd,
|
|
229
|
+
options.workspaceFile ?? this.env.MINDGRAPH_WORKSPACE_FILE
|
|
230
|
+
);
|
|
231
|
+
this.now = options.now ?? Date.now;
|
|
232
|
+
}
|
|
233
|
+
async resolveRepository(repo, fallbackSpaceUid) {
|
|
234
|
+
const configured = await this.resolveRepositories(fallbackSpaceUid);
|
|
235
|
+
const selected = repo && configured.find((candidate) => candidate.repoId === repo) || repo && configured.find(
|
|
236
|
+
(candidate) => candidate.repoRoot === repo || (0, import_node_path2.basename)(candidate.repoRoot) === repo
|
|
237
|
+
) || (!repo ? [...configured].sort((a, b) => b.repoRoot.length - a.repoRoot.length).find((candidate) => pathContains(candidate.repoRoot, this.cwd)) : void 0) || (!repo && configured.length === 1 ? configured[0] : void 0);
|
|
238
|
+
if (selected) {
|
|
239
|
+
return !repo && this.env.MINDGRAPH_REPO_ID ? this.resolveRepositoryIdentity(
|
|
240
|
+
selected.repoRoot,
|
|
241
|
+
this.env.MINDGRAPH_REPO_ID,
|
|
242
|
+
selected.spaceUid
|
|
243
|
+
) : selected;
|
|
244
|
+
}
|
|
245
|
+
const requestedRoot = repo || this.env.MINDGRAPH_REPO_ROOT || this.cwd;
|
|
246
|
+
const requestedPath = await (0, import_promises.realpath)((0, import_node_path2.resolve)(this.cwd, requestedRoot)).catch(
|
|
247
|
+
() => (0, import_node_path2.resolve)(this.cwd, requestedRoot)
|
|
248
|
+
);
|
|
249
|
+
const repoRoot = await this.gitRoot(requestedPath) ?? requestedPath;
|
|
250
|
+
return this.resolveRepositoryIdentity(
|
|
251
|
+
repoRoot,
|
|
252
|
+
this.env.MINDGRAPH_REPO_ID,
|
|
253
|
+
this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
async resolveRepositories(fallbackSpaceUid) {
|
|
257
|
+
return Promise.all(
|
|
258
|
+
this.configuredRepositories().map(
|
|
259
|
+
(repository) => this.resolveRepositoryIdentity(
|
|
260
|
+
repository.repoRoot,
|
|
261
|
+
repository.repoId,
|
|
262
|
+
repository.spaceUid || this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
async resolveRepositoryIdentity(root, explicitRepoId, spaceUid) {
|
|
268
|
+
const repoRoot = await (0, import_promises.realpath)(root).catch(() => (0, import_node_path2.resolve)(root));
|
|
269
|
+
const cacheKey = `${repoRoot}\0${explicitRepoId ?? ""}\0${spaceUid ?? ""}`;
|
|
270
|
+
const cached = this.identityCache.get(cacheKey);
|
|
271
|
+
if (cached) return cached;
|
|
272
|
+
const pending = this.loadRepositoryIdentity(repoRoot, explicitRepoId, spaceUid);
|
|
273
|
+
this.identityCache.set(cacheKey, pending);
|
|
274
|
+
return pending;
|
|
275
|
+
}
|
|
276
|
+
async loadRepositoryIdentity(repoRoot, explicitRepoId, spaceUid) {
|
|
277
|
+
const remote = await runProcess(
|
|
278
|
+
"git",
|
|
279
|
+
["-C", repoRoot, "config", "--get", "remote.origin.url"],
|
|
280
|
+
Math.min(this.timeoutMs, 2e3),
|
|
281
|
+
this.env
|
|
282
|
+
).catch(() => "");
|
|
283
|
+
const repoId = explicitRepoId || normalizeRemote(remote) || await this.persistedRepositoryId(repoRoot);
|
|
284
|
+
return {
|
|
285
|
+
repoId,
|
|
286
|
+
repoRoot,
|
|
287
|
+
spaceUid
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
async persistedRepositoryId(repoRoot) {
|
|
291
|
+
const rawCommonDir = await runProcess(
|
|
292
|
+
"git",
|
|
293
|
+
["-C", repoRoot, "rev-parse", "--git-common-dir"],
|
|
294
|
+
Math.min(this.timeoutMs, 2e3),
|
|
295
|
+
this.env
|
|
296
|
+
).catch(() => "");
|
|
297
|
+
if (!rawCommonDir.trim()) {
|
|
298
|
+
return `local:${(0, import_node_path2.basename)(repoRoot)}`;
|
|
299
|
+
}
|
|
300
|
+
const commonDir = (0, import_node_path2.resolve)(repoRoot, rawCommonDir.trim());
|
|
301
|
+
const identityDir = (0, import_node_path2.join)(commonDir, "mindgraph");
|
|
302
|
+
const identityFile = (0, import_node_path2.join)(identityDir, "repository-id");
|
|
303
|
+
try {
|
|
304
|
+
const existing = (0, import_node_fs2.readFileSync)(identityFile, "utf8").trim();
|
|
305
|
+
if (existing) return existing;
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
(0, import_node_fs2.mkdirSync)(identityDir, { recursive: true, mode: 448 });
|
|
309
|
+
const generated = (0, import_node_crypto.randomUUID)();
|
|
310
|
+
try {
|
|
311
|
+
(0, import_node_fs2.writeFileSync)(identityFile, `${generated}
|
|
312
|
+
`, {
|
|
313
|
+
encoding: "utf8",
|
|
314
|
+
flag: "wx",
|
|
315
|
+
mode: 384
|
|
316
|
+
});
|
|
317
|
+
return generated;
|
|
318
|
+
} catch (cause) {
|
|
319
|
+
if (cause.code !== "EEXIST") throw cause;
|
|
320
|
+
const winner = (0, import_node_fs2.readFileSync)(identityFile, "utf8").trim();
|
|
321
|
+
if (!winner) throw new Error(`empty repository identity file: ${identityFile}`);
|
|
322
|
+
return winner;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async gitRoot(candidate) {
|
|
326
|
+
const start = nearestExistingDirectory(candidate);
|
|
327
|
+
const raw = await runProcess(
|
|
328
|
+
"git",
|
|
329
|
+
["-C", start, "rev-parse", "--show-toplevel"],
|
|
330
|
+
Math.min(this.timeoutMs, 2e3),
|
|
331
|
+
this.env
|
|
332
|
+
).catch(() => "");
|
|
333
|
+
return raw.trim() ? (0, import_node_path2.resolve)(start, raw.trim()) : void 0;
|
|
334
|
+
}
|
|
335
|
+
async availability(repository) {
|
|
336
|
+
const cached = this.statusCache.get(repository.repoRoot);
|
|
337
|
+
if (cached && cached.expiresAt > this.now()) return cached.availability;
|
|
338
|
+
try {
|
|
339
|
+
const status = await this.runJson([
|
|
340
|
+
"status",
|
|
341
|
+
repository.repoRoot,
|
|
342
|
+
"-j"
|
|
343
|
+
]);
|
|
344
|
+
const caveats = [];
|
|
345
|
+
let availability;
|
|
346
|
+
if (status.worktreeMismatch != null) {
|
|
347
|
+
availability = {
|
|
348
|
+
available: false,
|
|
349
|
+
reason: "wrong_index",
|
|
350
|
+
stale: false,
|
|
351
|
+
caveats: ["the index belongs to a different worktree"],
|
|
352
|
+
lastIndexed: status.lastIndexed
|
|
353
|
+
};
|
|
354
|
+
} else if (!status.initialized || status.index?.state !== "complete") {
|
|
355
|
+
availability = {
|
|
356
|
+
available: false,
|
|
357
|
+
reason: "index_incomplete",
|
|
358
|
+
stale: false,
|
|
359
|
+
caveats: [
|
|
360
|
+
!status.initialized ? "codegraph is not initialized for this repository" : `codegraph index state is ${status.index?.state ?? "unknown"}`
|
|
361
|
+
],
|
|
362
|
+
lastIndexed: status.lastIndexed
|
|
363
|
+
};
|
|
364
|
+
} else {
|
|
365
|
+
const pending = (status.pendingChanges?.added ?? 0) + (status.pendingChanges?.modified ?? 0) + (status.pendingChanges?.removed ?? 0);
|
|
366
|
+
if (pending > 0) caveats.push(`${pending} worktree changes are not indexed`);
|
|
367
|
+
if ((status.index?.pendingRefs ?? 0) > 0) {
|
|
368
|
+
caveats.push(`${status.index?.pendingRefs} references remain pending`);
|
|
369
|
+
}
|
|
370
|
+
if (status.index?.reindexRecommended) caveats.push("codegraph recommends reindexing");
|
|
371
|
+
availability = {
|
|
372
|
+
available: true,
|
|
373
|
+
stale: caveats.length > 0,
|
|
374
|
+
caveats,
|
|
375
|
+
lastIndexed: status.lastIndexed
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
this.statusCache.set(repository.repoRoot, {
|
|
379
|
+
status,
|
|
380
|
+
availability,
|
|
381
|
+
expiresAt: this.now() + this.statusTtlMs
|
|
382
|
+
});
|
|
383
|
+
return availability;
|
|
384
|
+
} catch (cause) {
|
|
385
|
+
const failure = cause;
|
|
386
|
+
return {
|
|
387
|
+
available: false,
|
|
388
|
+
reason: failure.kind ?? "command_failed",
|
|
389
|
+
stale: false,
|
|
390
|
+
caveats: [failure.message || "codegraph status failed"]
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
async resolveRefs(refs, options = {}) {
|
|
395
|
+
const normalized = refs.map(
|
|
396
|
+
(ref) => typeof ref === "string" ? { symbol: ref } : ref
|
|
397
|
+
);
|
|
398
|
+
const results = await Promise.all(
|
|
399
|
+
normalized.map(async (ref) => {
|
|
400
|
+
const repository2 = await this.repositoryForRef(
|
|
401
|
+
ref,
|
|
402
|
+
options.repo,
|
|
403
|
+
options.fallbackSpaceUid
|
|
404
|
+
);
|
|
405
|
+
const availability2 = await this.availability(repository2);
|
|
406
|
+
if (!availability2.available) {
|
|
407
|
+
return {
|
|
408
|
+
ref,
|
|
409
|
+
repository: repository2,
|
|
410
|
+
availability: availability2,
|
|
411
|
+
candidates: [],
|
|
412
|
+
error: availability2.reason
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
...await this.resolveOne(ref, repository2),
|
|
417
|
+
repository: repository2,
|
|
418
|
+
availability: availability2
|
|
419
|
+
};
|
|
420
|
+
})
|
|
421
|
+
);
|
|
422
|
+
const repositories = [
|
|
423
|
+
...new Map(
|
|
424
|
+
results.flatMap(
|
|
425
|
+
(result) => result.repository ? [[result.repository.repoRoot, result.repository]] : []
|
|
426
|
+
)
|
|
427
|
+
).values()
|
|
428
|
+
];
|
|
429
|
+
const repository = repositories[0] ?? await this.resolveRepository(options.repo, options.fallbackSpaceUid);
|
|
430
|
+
if (repositories.length === 0) repositories.push(repository);
|
|
431
|
+
const availabilities = results.flatMap(
|
|
432
|
+
(result) => result.availability ? [result.availability] : []
|
|
433
|
+
);
|
|
434
|
+
const availability = availabilities.length === 0 ? await this.availability(repository) : {
|
|
435
|
+
available: availabilities.every((item) => item.available),
|
|
436
|
+
reason: availabilities.find((item) => !item.available)?.reason,
|
|
437
|
+
stale: availabilities.some((item) => item.stale),
|
|
438
|
+
caveats: [
|
|
439
|
+
...new Set(availabilities.flatMap((item) => item.caveats))
|
|
440
|
+
]
|
|
441
|
+
};
|
|
442
|
+
return { repository, repositories, availability, results };
|
|
443
|
+
}
|
|
444
|
+
async repositoryForRef(ref, defaultRepo, fallbackSpaceUid) {
|
|
445
|
+
if (ref.repo || defaultRepo) {
|
|
446
|
+
return this.resolveRepository(ref.repo || defaultRepo, fallbackSpaceUid);
|
|
447
|
+
}
|
|
448
|
+
if (ref.path) {
|
|
449
|
+
const requested = (0, import_node_path2.isAbsolute)(ref.path) ? (0, import_node_path2.resolve)(ref.path) : (0, import_node_path2.resolve)(this.cwd, ref.path);
|
|
450
|
+
const absolute = await (0, import_promises.realpath)(requested).catch(() => requested);
|
|
451
|
+
const configured = await this.resolveRepositories(fallbackSpaceUid);
|
|
452
|
+
const defining = [...configured].sort((a, b) => b.repoRoot.length - a.repoRoot.length).find((candidate) => pathContains(candidate.repoRoot, absolute));
|
|
453
|
+
if (defining) {
|
|
454
|
+
return this.env.MINDGRAPH_REPO_ID && pathContains(defining.repoRoot, this.cwd) ? this.resolveRepositoryIdentity(
|
|
455
|
+
defining.repoRoot,
|
|
456
|
+
this.env.MINDGRAPH_REPO_ID,
|
|
457
|
+
defining.spaceUid
|
|
458
|
+
) : defining;
|
|
459
|
+
}
|
|
460
|
+
const gitRoot = await this.gitRoot(absolute);
|
|
461
|
+
if (gitRoot) {
|
|
462
|
+
return this.resolveRepositoryIdentity(
|
|
463
|
+
gitRoot,
|
|
464
|
+
this.env.MINDGRAPH_REPO_ID,
|
|
465
|
+
this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return this.resolveRepository(void 0, fallbackSpaceUid);
|
|
470
|
+
}
|
|
471
|
+
async expand(resolved, limit = 20) {
|
|
472
|
+
const repository = {
|
|
473
|
+
repoId: resolved.repoId,
|
|
474
|
+
repoRoot: resolved.repoRoot
|
|
475
|
+
};
|
|
476
|
+
const availability = await this.availability(repository);
|
|
477
|
+
if (!availability.available) {
|
|
478
|
+
return { availability, callers: [], callees: [], truncated: false };
|
|
479
|
+
}
|
|
480
|
+
const cap = Math.min(Math.max(limit, 1), 20);
|
|
481
|
+
const [callers, callees] = await Promise.all([
|
|
482
|
+
this.related("callers", resolved, cap + 1),
|
|
483
|
+
this.related("callees", resolved, cap + 1)
|
|
484
|
+
]);
|
|
485
|
+
return {
|
|
486
|
+
availability,
|
|
487
|
+
callers: callers.slice(0, cap),
|
|
488
|
+
callees: callees.slice(0, cap),
|
|
489
|
+
truncated: callers.length > cap || callees.length > cap
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
async affected(resolved, limit = 50) {
|
|
493
|
+
const repository = {
|
|
494
|
+
repoId: resolved.repoId,
|
|
495
|
+
repoRoot: resolved.repoRoot
|
|
496
|
+
};
|
|
497
|
+
const availability = await this.availability(repository);
|
|
498
|
+
if (!availability.available) {
|
|
499
|
+
return { availability, affected: [], truncated: false };
|
|
500
|
+
}
|
|
501
|
+
const cap = Math.min(Math.max(limit, 1), 50);
|
|
502
|
+
const raw = await this.runJson([
|
|
503
|
+
"impact",
|
|
504
|
+
"-p",
|
|
505
|
+
resolved.repoRoot,
|
|
506
|
+
"-j",
|
|
507
|
+
"-d",
|
|
508
|
+
"2",
|
|
509
|
+
resolved.qualifiedName
|
|
510
|
+
]);
|
|
511
|
+
const refs = (raw.affected ?? []).map((node) => ({
|
|
512
|
+
path: node.filePath,
|
|
513
|
+
line: node.startLine,
|
|
514
|
+
symbol: node.qualifiedName ?? node.name,
|
|
515
|
+
kind: node.kind
|
|
516
|
+
}));
|
|
517
|
+
const resolvedRefs = await this.resolveRelatedRefs(refs.slice(0, cap + 1), repository);
|
|
518
|
+
return {
|
|
519
|
+
availability,
|
|
520
|
+
affected: resolvedRefs.slice(0, cap),
|
|
521
|
+
truncated: refs.length > cap
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
configuredRepositories() {
|
|
525
|
+
const configured = [];
|
|
526
|
+
if (this.workspaceFile && (0, import_node_fs2.existsSync)(this.workspaceFile)) {
|
|
527
|
+
try {
|
|
528
|
+
const parsed = JSON.parse(
|
|
529
|
+
(0, import_node_fs2.readFileSync)(this.workspaceFile, "utf8")
|
|
530
|
+
);
|
|
531
|
+
if (parsed.v === 1 && Array.isArray(parsed.repositories)) {
|
|
532
|
+
const configDirectory = (0, import_node_path2.dirname)(this.workspaceFile);
|
|
533
|
+
const base = (0, import_node_path2.basename)(configDirectory) === ".mindgraph" ? (0, import_node_path2.dirname)(configDirectory) : configDirectory;
|
|
534
|
+
for (const repository of parsed.repositories) {
|
|
535
|
+
if (!repository || typeof repository.root !== "string") continue;
|
|
536
|
+
configured.push({
|
|
537
|
+
repoId: typeof repository.repo_id === "string" ? repository.repo_id : void 0,
|
|
538
|
+
repoRoot: (0, import_node_path2.resolve)(base, repository.root),
|
|
539
|
+
spaceUid: typeof repository.space_uid === "string" ? repository.space_uid : void 0
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} catch {
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const raw = this.env.MINDGRAPH_CODE_REPOS;
|
|
547
|
+
if (raw) {
|
|
548
|
+
try {
|
|
549
|
+
const parsed = JSON.parse(raw);
|
|
550
|
+
for (const [repoId, config] of Object.entries(parsed)) {
|
|
551
|
+
configured.push({
|
|
552
|
+
repoId,
|
|
553
|
+
repoRoot: (0, import_node_path2.resolve)(
|
|
554
|
+
this.cwd,
|
|
555
|
+
typeof config === "string" ? config : config.root
|
|
556
|
+
),
|
|
557
|
+
spaceUid: typeof config === "string" ? void 0 : config.space_uid
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
} catch {
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return [
|
|
564
|
+
...new Map(
|
|
565
|
+
configured.map((repository) => [repository.repoRoot, repository])
|
|
566
|
+
).values()
|
|
567
|
+
];
|
|
568
|
+
}
|
|
569
|
+
async resolveOne(ref, repository) {
|
|
570
|
+
const search = ref.symbol || ref.path;
|
|
571
|
+
if (!search || ref.line !== void 0 && (!Number.isInteger(ref.line) || ref.line < 1)) {
|
|
572
|
+
return { ref, candidates: [], error: "invalid_ref" };
|
|
573
|
+
}
|
|
574
|
+
try {
|
|
575
|
+
const args = ["query", "-p", repository.repoRoot, "-j", "-l", "50"];
|
|
576
|
+
if (ref.kind) args.push("-k", ref.kind);
|
|
577
|
+
args.push(search);
|
|
578
|
+
const hits = await this.runJson(args);
|
|
579
|
+
let candidates = hits.map(asNode).map(toCandidate).filter((item) => item !== null);
|
|
580
|
+
if (ref.path) {
|
|
581
|
+
const absolutePath = (0, import_node_path2.isAbsolute)(ref.path) ? await (0, import_promises.realpath)(ref.path).catch(() => ref.path) : void 0;
|
|
582
|
+
const wanted = normalizePath(
|
|
583
|
+
absolutePath ? (0, import_node_path2.relative)(repository.repoRoot, absolutePath) : ref.path
|
|
584
|
+
);
|
|
585
|
+
candidates = candidates.filter((candidate) => candidate.path === wanted);
|
|
586
|
+
}
|
|
587
|
+
if (ref.language) {
|
|
588
|
+
candidates = candidates.filter(
|
|
589
|
+
(candidate) => candidate.language.toLowerCase() === ref.language?.toLowerCase()
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
if (ref.kind) {
|
|
593
|
+
candidates = candidates.filter(
|
|
594
|
+
(candidate) => candidate.kind.toLowerCase() === ref.kind?.toLowerCase()
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
if (ref.symbol) {
|
|
598
|
+
const exact = candidates.filter((candidate) => {
|
|
599
|
+
const tail = candidate.qualifiedName.split(/::|\.|#/).at(-1);
|
|
600
|
+
return candidate.qualifiedName === ref.symbol || tail === ref.symbol;
|
|
601
|
+
});
|
|
602
|
+
if (exact.length > 0) candidates = exact;
|
|
603
|
+
}
|
|
604
|
+
if (ref.signature) {
|
|
605
|
+
const signature = normalizeSignature(ref.signature);
|
|
606
|
+
candidates = candidates.filter(
|
|
607
|
+
(candidate) => candidate.signature !== null && normalizeSignature(candidate.signature) === signature
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
if (ref.line !== void 0) {
|
|
611
|
+
candidates = candidates.filter(
|
|
612
|
+
(candidate) => candidate.startLine <= ref.line && candidate.endLine >= ref.line
|
|
613
|
+
);
|
|
614
|
+
if (candidates.length > 1) {
|
|
615
|
+
const narrowest = Math.min(
|
|
616
|
+
...candidates.map((candidate) => candidate.endLine - candidate.startLine)
|
|
617
|
+
);
|
|
618
|
+
const narrowed = candidates.filter(
|
|
619
|
+
(candidate) => candidate.endLine - candidate.startLine === narrowest
|
|
620
|
+
);
|
|
621
|
+
if (narrowed.length === 1) candidates = narrowed;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (candidates.length !== 1) {
|
|
625
|
+
return {
|
|
626
|
+
ref,
|
|
627
|
+
candidates,
|
|
628
|
+
error: candidates.length === 0 ? "not_found" : "ambiguous"
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
ref,
|
|
633
|
+
candidates,
|
|
634
|
+
resolved: {
|
|
635
|
+
repoId: repository.repoId,
|
|
636
|
+
repoRoot: repository.repoRoot,
|
|
637
|
+
...candidates[0]
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
} catch (cause) {
|
|
641
|
+
const failure = cause;
|
|
642
|
+
return {
|
|
643
|
+
ref,
|
|
644
|
+
candidates: [],
|
|
645
|
+
error: failure.kind ?? "command_failed"
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
async related(action, resolved, limit) {
|
|
650
|
+
const raw = await this.runJson([
|
|
651
|
+
action,
|
|
652
|
+
"-p",
|
|
653
|
+
resolved.repoRoot,
|
|
654
|
+
"-j",
|
|
655
|
+
"-l",
|
|
656
|
+
String(limit),
|
|
657
|
+
resolved.qualifiedName
|
|
658
|
+
]);
|
|
659
|
+
const refs = (raw[action] ?? []).map((node) => ({
|
|
660
|
+
path: node.filePath,
|
|
661
|
+
line: node.startLine,
|
|
662
|
+
symbol: node.qualifiedName ?? node.name,
|
|
663
|
+
kind: node.kind
|
|
664
|
+
}));
|
|
665
|
+
return this.resolveRelatedRefs(refs, {
|
|
666
|
+
repoId: resolved.repoId,
|
|
667
|
+
repoRoot: resolved.repoRoot
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
async resolveRelatedRefs(refs, repository) {
|
|
671
|
+
const results = await Promise.all(refs.map((ref) => this.resolveOne(ref, repository)));
|
|
672
|
+
const seen = /* @__PURE__ */ new Set();
|
|
673
|
+
return results.flatMap((result) => result.resolved ? [result.resolved] : []).filter((result) => {
|
|
674
|
+
const key = `${result.path}:${result.qualifiedName}:${result.startLine}`;
|
|
675
|
+
if (seen.has(key)) return false;
|
|
676
|
+
seen.add(key);
|
|
677
|
+
return true;
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
async runJson(args) {
|
|
681
|
+
const candidates = [
|
|
682
|
+
this.env.MINDGRAPH_CODEGRAPH_BIN,
|
|
683
|
+
"codegraph",
|
|
684
|
+
(0, import_node_path2.resolve)((0, import_node_os2.homedir)(), ".local", "bin", "codegraph")
|
|
685
|
+
].filter((candidate, index, all) => {
|
|
686
|
+
return Boolean(candidate) && all.indexOf(candidate) === index;
|
|
687
|
+
});
|
|
688
|
+
let lastFailure;
|
|
689
|
+
for (const executable of candidates) {
|
|
690
|
+
try {
|
|
691
|
+
const stdout = await runProcess(
|
|
692
|
+
executable,
|
|
693
|
+
args,
|
|
694
|
+
this.timeoutMs,
|
|
695
|
+
this.env,
|
|
696
|
+
this.cwd
|
|
697
|
+
);
|
|
698
|
+
return JSON.parse(stdout);
|
|
699
|
+
} catch (cause) {
|
|
700
|
+
const failure = cause;
|
|
701
|
+
lastFailure = failure;
|
|
702
|
+
if (failure.kind !== "absent") throw failure;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
throw lastFailure ?? Object.assign(new Error("codegraph executable not found"), {
|
|
706
|
+
kind: "absent"
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
// src/code-tool.ts
|
|
714
|
+
async function handleCodeTool(client2, args, adapter = new CodegraphAdapter()) {
|
|
715
|
+
const codeClient = client2;
|
|
716
|
+
const action = args.action;
|
|
717
|
+
const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
|
|
718
|
+
const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
|
|
719
|
+
const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
|
|
720
|
+
const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : `space:agent:${agentId}`;
|
|
721
|
+
const options = {
|
|
722
|
+
repo: typeof args.repo === "string" ? args.repo : void 0,
|
|
723
|
+
fallbackSpaceUid
|
|
724
|
+
};
|
|
725
|
+
const limit = typeof args.limit === "number" ? args.limit : void 0;
|
|
726
|
+
try {
|
|
727
|
+
switch (action) {
|
|
728
|
+
case "anchor": {
|
|
729
|
+
if (refs.length === 0) return err("missing_code_refs", "code_refs is required for anchor");
|
|
730
|
+
const resolution = await adapter.resolveRefs(refs, options);
|
|
731
|
+
const resolved = resolution.results.flatMap(
|
|
732
|
+
(result) => result.resolved && result.repository ? [{ ref: result.resolved, repository: result.repository }] : []
|
|
733
|
+
);
|
|
734
|
+
const unresolved = resolution.results.filter((result) => !result.resolved);
|
|
735
|
+
if (resolved.length === 0) {
|
|
736
|
+
return ok({ ...resolution, anchors: [], wrote: false });
|
|
737
|
+
}
|
|
738
|
+
const missingSpace = resolved.find(
|
|
739
|
+
({ repository }) => typeof args.repo_space_uid !== "string" && !repository.spaceUid
|
|
740
|
+
);
|
|
741
|
+
if (missingSpace) {
|
|
742
|
+
return err(
|
|
743
|
+
"missing_repository_space",
|
|
744
|
+
`repo_space_uid, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID is required for ${missingSpace.repository.repoId}`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
const repositoryAnchors = /* @__PURE__ */ new Map();
|
|
748
|
+
for (const { repository } of resolved) {
|
|
749
|
+
if (repositoryAnchors.has(repository.repoRoot)) continue;
|
|
750
|
+
const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
|
|
751
|
+
const anchored = await anchorRepository(
|
|
752
|
+
codeClient,
|
|
753
|
+
repository,
|
|
754
|
+
spaceUid,
|
|
755
|
+
agentId
|
|
756
|
+
);
|
|
757
|
+
repositoryAnchors.set(repository.repoRoot, {
|
|
758
|
+
...anchored,
|
|
759
|
+
repository
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
const anchors = [];
|
|
763
|
+
for (const { ref, repository } of resolved) {
|
|
764
|
+
const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
|
|
765
|
+
if (!repositoryAnchor.uid) continue;
|
|
766
|
+
const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
|
|
767
|
+
const anchor = await anchorCodeRef(
|
|
768
|
+
codeClient,
|
|
769
|
+
ref,
|
|
770
|
+
repositoryAnchor.uid,
|
|
771
|
+
spaceUid,
|
|
772
|
+
agentId
|
|
773
|
+
);
|
|
774
|
+
anchors.push(anchor);
|
|
775
|
+
}
|
|
776
|
+
return ok({
|
|
777
|
+
availability: resolution.availability,
|
|
778
|
+
repository: repositoryAnchors.values().next().value,
|
|
779
|
+
repositories: [...repositoryAnchors.values()],
|
|
780
|
+
anchors,
|
|
781
|
+
unresolved,
|
|
782
|
+
wrote: anchors.some((anchor) => anchor.created)
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
case "recall": {
|
|
786
|
+
const materialized = new Set(anchorUids);
|
|
787
|
+
let resolution;
|
|
788
|
+
if (refs.length > 0) {
|
|
789
|
+
resolution = await adapter.resolveRefs(refs, options);
|
|
790
|
+
for (const result of resolution.results) {
|
|
791
|
+
if (!result.resolved) continue;
|
|
792
|
+
const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
|
|
793
|
+
if (uid) materialized.add(uid);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (materialized.size === 0) {
|
|
797
|
+
return ok({
|
|
798
|
+
availability: resolution?.availability,
|
|
799
|
+
anchors: [],
|
|
800
|
+
context: [],
|
|
801
|
+
unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
const context = await Promise.all(
|
|
805
|
+
[...materialized].map(async (uid) => ({
|
|
806
|
+
anchor_uid: uid,
|
|
807
|
+
graph: await codeClient.traverse({
|
|
808
|
+
action: "neighborhood",
|
|
809
|
+
start_uid: uid,
|
|
810
|
+
depth: 2,
|
|
811
|
+
max_nodes: Math.min(limit ?? 20, 20)
|
|
812
|
+
})
|
|
813
|
+
}))
|
|
814
|
+
);
|
|
815
|
+
return ok({
|
|
816
|
+
availability: resolution?.availability,
|
|
817
|
+
anchors: [...materialized],
|
|
818
|
+
context,
|
|
819
|
+
unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
case "expand": {
|
|
823
|
+
const target = await resolveSingleTarget(
|
|
824
|
+
codeClient,
|
|
825
|
+
adapter,
|
|
826
|
+
refs,
|
|
827
|
+
anchorUids,
|
|
828
|
+
options
|
|
829
|
+
);
|
|
830
|
+
if ("error" in target) return target.error;
|
|
831
|
+
const expanded = await adapter.expand(target.ref, limit);
|
|
832
|
+
const joined = await joinMaterialized(
|
|
833
|
+
codeClient,
|
|
834
|
+
[...expanded.callers, ...expanded.callees],
|
|
835
|
+
agentId,
|
|
836
|
+
Math.min(limit ?? 20, 20)
|
|
837
|
+
);
|
|
838
|
+
return ok({ anchor: target.ref, ...expanded, materialized: joined });
|
|
839
|
+
}
|
|
840
|
+
case "affected": {
|
|
841
|
+
const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
|
|
842
|
+
let graphContext;
|
|
843
|
+
const graphRefs = [];
|
|
844
|
+
if (targetUid) {
|
|
845
|
+
graphContext = await codeClient.traverse({
|
|
846
|
+
action: "neighborhood",
|
|
847
|
+
start_uid: targetUid,
|
|
848
|
+
depth: 2,
|
|
849
|
+
max_nodes: 50
|
|
850
|
+
});
|
|
851
|
+
graphRefs.push(
|
|
852
|
+
...await codeRefsFromGraphContext(codeClient, adapter, graphContext)
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
if (refs.length > 0 || anchorUids.length > 0) {
|
|
856
|
+
const target = await resolveSingleTarget(
|
|
857
|
+
codeClient,
|
|
858
|
+
adapter,
|
|
859
|
+
refs,
|
|
860
|
+
anchorUids,
|
|
861
|
+
options
|
|
862
|
+
);
|
|
863
|
+
if ("error" in target) return target.error;
|
|
864
|
+
graphRefs.unshift(target.ref);
|
|
865
|
+
}
|
|
866
|
+
const unique = uniqueRefs(graphRefs);
|
|
867
|
+
if (unique.length === 0) {
|
|
868
|
+
return ok({
|
|
869
|
+
target_uid: targetUid,
|
|
870
|
+
graph_context: graphContext,
|
|
871
|
+
affected: [],
|
|
872
|
+
degradation: "no materialized code anchors were attached"
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
const impacts = await Promise.all(
|
|
876
|
+
unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
|
|
877
|
+
);
|
|
878
|
+
const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
|
|
879
|
+
const joined = await joinMaterialized(
|
|
880
|
+
codeClient,
|
|
881
|
+
structural,
|
|
882
|
+
agentId,
|
|
883
|
+
Math.min(limit ?? 50, 50)
|
|
884
|
+
);
|
|
885
|
+
return ok({
|
|
886
|
+
target_uid: targetUid,
|
|
887
|
+
graph_context: graphContext,
|
|
888
|
+
impacts,
|
|
889
|
+
materialized: joined
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
default:
|
|
893
|
+
return err("unknown_action", `unknown mindgraph_code action: ${action}`);
|
|
894
|
+
}
|
|
895
|
+
} catch (cause) {
|
|
896
|
+
return err(
|
|
897
|
+
"code_tool_failed",
|
|
898
|
+
cause instanceof Error ? cause.message : String(cause)
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
async function attachCodeRefsToToolResult(client2, result, args, adapter) {
|
|
903
|
+
if (result.isError || !Array.isArray(args.code_refs) || args.code_refs.length === 0) {
|
|
904
|
+
return result;
|
|
905
|
+
}
|
|
906
|
+
const original = JSON.parse(result.content[0]?.text ?? "null");
|
|
907
|
+
const targetUids = primaryUids(original);
|
|
908
|
+
if (targetUids.length === 0) {
|
|
909
|
+
return ok({
|
|
910
|
+
result: original,
|
|
911
|
+
code_anchors: {
|
|
912
|
+
wrote: false,
|
|
913
|
+
caveat: "the write response exposed no primary UID to attach"
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
const anchorResult = await handleCodeTool(
|
|
918
|
+
client2,
|
|
919
|
+
{
|
|
920
|
+
action: "anchor",
|
|
921
|
+
code_refs: args.code_refs,
|
|
922
|
+
repo: args.repo,
|
|
923
|
+
repo_space_uid: args.repo_space_uid,
|
|
924
|
+
agent_id: args.agent_id
|
|
925
|
+
},
|
|
926
|
+
adapter
|
|
927
|
+
);
|
|
928
|
+
const anchors = JSON.parse(anchorResult.content[0]?.text ?? "null");
|
|
929
|
+
if (!anchorResult.isError) {
|
|
930
|
+
const codeClient = client2;
|
|
931
|
+
const anchorUids = (anchors.anchors ?? []).flatMap(
|
|
932
|
+
(anchor) => anchor.uid ? [anchor.uid] : []
|
|
933
|
+
);
|
|
934
|
+
for (const targetUid of targetUids) {
|
|
935
|
+
for (const anchorUid of anchorUids) {
|
|
936
|
+
await codeClient.entity({
|
|
937
|
+
action: "relate",
|
|
938
|
+
source_uid: targetUid,
|
|
939
|
+
target_uid: anchorUid,
|
|
940
|
+
edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
|
|
941
|
+
agent_id: args.agent_id
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return ok({ result: original, code_anchors: anchors });
|
|
947
|
+
}
|
|
948
|
+
async function anchorRepository(client2, repository, spaceUid, agentId) {
|
|
949
|
+
return client2.entity({
|
|
950
|
+
action: "create",
|
|
951
|
+
label: repository.repoId,
|
|
952
|
+
props: {
|
|
953
|
+
entity_type: "repository",
|
|
954
|
+
canonical_name: repository.repoId,
|
|
955
|
+
identifiers: { repo_id: repository.repoId }
|
|
956
|
+
},
|
|
957
|
+
identity: {
|
|
958
|
+
namespace: "external.code",
|
|
959
|
+
key_version: 1,
|
|
960
|
+
key: repositoryIdentityKey(repository)
|
|
961
|
+
},
|
|
962
|
+
identity_space_uid: spaceUid,
|
|
963
|
+
agent_id: agentId
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
async function anchorCodeRef(client2, ref, repositoryUid, spaceUid, agentId) {
|
|
967
|
+
const response = await client2.entity({
|
|
968
|
+
action: "create",
|
|
969
|
+
label: ref.qualifiedName,
|
|
970
|
+
props: {
|
|
971
|
+
entity_type: ref.kind === "file" ? "code_file" : "code_symbol",
|
|
972
|
+
canonical_name: ref.qualifiedName,
|
|
973
|
+
identifiers: {
|
|
974
|
+
repo_id: ref.repoId,
|
|
975
|
+
path: ref.path,
|
|
976
|
+
qualified_name: ref.qualifiedName
|
|
977
|
+
},
|
|
978
|
+
attributes: { code_ref: storedCodeRef(ref) }
|
|
979
|
+
},
|
|
980
|
+
identity: {
|
|
981
|
+
namespace: "external.code",
|
|
982
|
+
key_version: 1,
|
|
983
|
+
key: codeRefIdentityKey(ref)
|
|
984
|
+
},
|
|
985
|
+
identity_space_uid: spaceUid,
|
|
986
|
+
agent_id: agentId
|
|
987
|
+
});
|
|
988
|
+
if (response.uid) {
|
|
989
|
+
await client2.entity({
|
|
990
|
+
action: "relate",
|
|
991
|
+
source_uid: response.uid,
|
|
992
|
+
target_uid: repositoryUid,
|
|
993
|
+
edge_type: "PART_OF",
|
|
994
|
+
agent_id: agentId
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
return { ...response, ref };
|
|
998
|
+
}
|
|
999
|
+
async function resolveMaterializedUid(client2, ref, agentId) {
|
|
1000
|
+
const response = await client2.entity({
|
|
1001
|
+
action: "resolve_identity",
|
|
1002
|
+
identity: {
|
|
1003
|
+
namespace: "external.code",
|
|
1004
|
+
key_version: 1,
|
|
1005
|
+
key: codeRefIdentityKey(ref)
|
|
1006
|
+
},
|
|
1007
|
+
agent_id: agentId
|
|
1008
|
+
});
|
|
1009
|
+
return response.status === "existing" ? response.uid : void 0;
|
|
1010
|
+
}
|
|
1011
|
+
async function codeRefFromAnchor(client2, adapter, uid) {
|
|
1012
|
+
const node = await client2.getNode(uid);
|
|
1013
|
+
const attributes = node.props?.attributes;
|
|
1014
|
+
if (!attributes || typeof attributes !== "object") return void 0;
|
|
1015
|
+
const codeRef = attributes.code_ref;
|
|
1016
|
+
if (!isStoredCodeRef(codeRef)) return void 0;
|
|
1017
|
+
const repository = await adapter.resolveRepository(codeRef.repoId);
|
|
1018
|
+
return { ...codeRef, repoRoot: repository.repoRoot };
|
|
1019
|
+
}
|
|
1020
|
+
async function resolveSingleTarget(client2, adapter, refs, anchorUids, options) {
|
|
1021
|
+
if (refs.length > 1 || anchorUids.length > 1) {
|
|
1022
|
+
return { error: err("ambiguous_target", "expand/affected accepts exactly one code target") };
|
|
1023
|
+
}
|
|
1024
|
+
if (anchorUids.length === 1) {
|
|
1025
|
+
const ref = await codeRefFromAnchor(client2, adapter, anchorUids[0]);
|
|
1026
|
+
return ref ? { ref } : { error: err("invalid_anchor", "anchor UID does not contain a typed code_ref") };
|
|
1027
|
+
}
|
|
1028
|
+
if (refs.length !== 1) {
|
|
1029
|
+
return { error: err("missing_code_ref", "one code_ref or anchor_uid is required") };
|
|
1030
|
+
}
|
|
1031
|
+
const resolution = await adapter.resolveRefs(refs, options);
|
|
1032
|
+
const result = resolution.results[0];
|
|
1033
|
+
if (!result.resolved) {
|
|
1034
|
+
return {
|
|
1035
|
+
error: ok({
|
|
1036
|
+
availability: resolution.availability,
|
|
1037
|
+
result,
|
|
1038
|
+
wrote: false
|
|
1039
|
+
})
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
return { ref: result.resolved };
|
|
1043
|
+
}
|
|
1044
|
+
async function joinMaterialized(client2, refs, agentId, limit) {
|
|
1045
|
+
const joined = [];
|
|
1046
|
+
for (const ref of uniqueRefs(refs).slice(0, limit)) {
|
|
1047
|
+
const uid = await resolveMaterializedUid(client2, ref, agentId);
|
|
1048
|
+
if (!uid) continue;
|
|
1049
|
+
joined.push({
|
|
1050
|
+
ref,
|
|
1051
|
+
anchor_uid: uid,
|
|
1052
|
+
graph: await client2.traverse({
|
|
1053
|
+
action: "neighborhood",
|
|
1054
|
+
start_uid: uid,
|
|
1055
|
+
depth: 2,
|
|
1056
|
+
max_nodes: 20
|
|
1057
|
+
})
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
return joined;
|
|
1061
|
+
}
|
|
1062
|
+
async function codeRefsFromGraphContext(client2, adapter, context) {
|
|
1063
|
+
const uids = /* @__PURE__ */ new Set();
|
|
1064
|
+
collectUids(context, uids);
|
|
1065
|
+
if (uids.size === 0) return [];
|
|
1066
|
+
const nodes = client2.getNodesBatch ? await client2.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client2.getNode(uid).catch(() => void 0)));
|
|
1067
|
+
const stored = nodes.flatMap((node) => {
|
|
1068
|
+
if (!node) return [];
|
|
1069
|
+
const attributes = node.props?.attributes;
|
|
1070
|
+
if (!attributes || typeof attributes !== "object") return [];
|
|
1071
|
+
const codeRef = attributes.code_ref;
|
|
1072
|
+
return isStoredCodeRef(codeRef) ? [codeRef] : [];
|
|
1073
|
+
});
|
|
1074
|
+
return Promise.all(
|
|
1075
|
+
stored.map(async (ref) => {
|
|
1076
|
+
const repository = await adapter.resolveRepository(ref.repoId);
|
|
1077
|
+
return { ...ref, repoRoot: repository.repoRoot };
|
|
1078
|
+
})
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
function collectUids(value, uids) {
|
|
1082
|
+
if (Array.isArray(value)) {
|
|
1083
|
+
value.forEach((item) => collectUids(item, uids));
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
if (!value || typeof value !== "object") return;
|
|
1087
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1088
|
+
if (typeof child === "string" && ["uid", "node_uid", "neighbor_uid", "from_uid", "to_uid"].includes(key)) {
|
|
1089
|
+
uids.add(child);
|
|
1090
|
+
} else {
|
|
1091
|
+
collectUids(child, uids);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
function isStoredCodeRef(value) {
|
|
1096
|
+
if (!value || typeof value !== "object") return false;
|
|
1097
|
+
const ref = value;
|
|
1098
|
+
return typeof ref.repoId === "string" && typeof ref.path === "string" && typeof ref.language === "string" && typeof ref.kind === "string" && typeof ref.qualifiedName === "string" && (typeof ref.signature === "string" || ref.signature === null) && typeof ref.startLine === "number" && typeof ref.endLine === "number";
|
|
1099
|
+
}
|
|
1100
|
+
function storedCodeRef(ref) {
|
|
1101
|
+
const { repoRoot: _localOnly, ...stored } = ref;
|
|
1102
|
+
return stored;
|
|
1103
|
+
}
|
|
1104
|
+
function uniqueRefs(refs) {
|
|
1105
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1106
|
+
return refs.filter((ref) => {
|
|
1107
|
+
const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
|
|
1108
|
+
if (seen.has(key)) return false;
|
|
1109
|
+
seen.add(key);
|
|
1110
|
+
return true;
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
function primaryUids(value) {
|
|
1114
|
+
if (!value || typeof value !== "object") return [];
|
|
1115
|
+
const object2 = value;
|
|
1116
|
+
const keys = [
|
|
1117
|
+
"uid",
|
|
1118
|
+
"claim_uid",
|
|
1119
|
+
"goal_uid",
|
|
1120
|
+
"project_uid",
|
|
1121
|
+
"milestone_uid",
|
|
1122
|
+
"decision_uid",
|
|
1123
|
+
"task_uid",
|
|
1124
|
+
"plan_uid",
|
|
1125
|
+
"step_uid",
|
|
1126
|
+
"execution_uid",
|
|
1127
|
+
"lesson_uid"
|
|
1128
|
+
];
|
|
1129
|
+
return [
|
|
1130
|
+
...new Set(
|
|
1131
|
+
keys.flatMap((key) => typeof object2[key] === "string" ? [object2[key]] : [])
|
|
1132
|
+
)
|
|
1133
|
+
];
|
|
1134
|
+
}
|
|
1135
|
+
var ok, err, CODE_TOOL;
|
|
1136
|
+
var init_code_tool = __esm({
|
|
1137
|
+
"src/code-tool.ts"() {
|
|
1138
|
+
"use strict";
|
|
1139
|
+
init_codegraph();
|
|
1140
|
+
ok = (data) => ({
|
|
1141
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1142
|
+
});
|
|
1143
|
+
err = (code, message, details) => ({
|
|
1144
|
+
content: [
|
|
1145
|
+
{
|
|
1146
|
+
type: "text",
|
|
1147
|
+
text: JSON.stringify({ error: message, code, details }, null, 2)
|
|
1148
|
+
}
|
|
1149
|
+
],
|
|
1150
|
+
isError: true
|
|
1151
|
+
});
|
|
1152
|
+
CODE_TOOL = {
|
|
1153
|
+
name: "mindgraph_code",
|
|
1154
|
+
description: "Resolve typed code references through the local codegraph index and join them to persistent MindGraph memory/work. anchor creates exact repository/code anchors; recall reads attached graph context; expand reads callers/callees; affected reads impact for a code ref or Task/Execution. Ambiguous refs return candidates without writing.",
|
|
1155
|
+
inputSchema: {
|
|
1156
|
+
type: "object",
|
|
1157
|
+
properties: {
|
|
1158
|
+
action: {
|
|
1159
|
+
type: "string",
|
|
1160
|
+
enum: ["anchor", "recall", "expand", "affected"]
|
|
1161
|
+
},
|
|
1162
|
+
code_refs: {
|
|
1163
|
+
type: "array",
|
|
1164
|
+
maxItems: 50,
|
|
1165
|
+
items: {
|
|
1166
|
+
oneOf: [
|
|
1167
|
+
{ type: "string" },
|
|
1168
|
+
{
|
|
1169
|
+
type: "object",
|
|
1170
|
+
properties: {
|
|
1171
|
+
repo: { type: "string" },
|
|
1172
|
+
path: { type: "string" },
|
|
1173
|
+
line: { type: "integer", minimum: 1 },
|
|
1174
|
+
symbol: { type: "string" },
|
|
1175
|
+
kind: { type: "string" },
|
|
1176
|
+
language: { type: "string" },
|
|
1177
|
+
signature: { type: "string" }
|
|
1178
|
+
},
|
|
1179
|
+
additionalProperties: false
|
|
1180
|
+
}
|
|
1181
|
+
]
|
|
1182
|
+
}
|
|
1183
|
+
},
|
|
1184
|
+
anchor_uids: {
|
|
1185
|
+
type: "array",
|
|
1186
|
+
maxItems: 50,
|
|
1187
|
+
items: { type: "string" },
|
|
1188
|
+
description: "Already-materialized code anchor UIDs (codegraph is not required for recall)"
|
|
1189
|
+
},
|
|
1190
|
+
repo: {
|
|
1191
|
+
type: "string",
|
|
1192
|
+
description: "Configured repository id or repository root"
|
|
1193
|
+
},
|
|
1194
|
+
repo_space_uid: {
|
|
1195
|
+
type: "string",
|
|
1196
|
+
description: "Writable shared Project/repository Space for new anchors"
|
|
1197
|
+
},
|
|
1198
|
+
task_uid: { type: "string" },
|
|
1199
|
+
execution_uid: { type: "string" },
|
|
1200
|
+
limit: { type: "integer", minimum: 1, maximum: 50 },
|
|
1201
|
+
agent_id: { type: "string" }
|
|
1202
|
+
},
|
|
1203
|
+
required: ["action"]
|
|
1204
|
+
},
|
|
1205
|
+
annotations: {
|
|
1206
|
+
readOnlyHint: false,
|
|
1207
|
+
destructiveHint: false,
|
|
1208
|
+
idempotentHint: true,
|
|
1209
|
+
openWorldHint: true
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
// src/sync-tool.ts
|
|
1216
|
+
function safePath(root, candidate) {
|
|
1217
|
+
const absolute = import_node_path5.default.resolve(root, candidate);
|
|
1218
|
+
const relative2 = import_node_path5.default.relative(root, absolute);
|
|
1219
|
+
if (relative2.startsWith("..") || import_node_path5.default.isAbsolute(relative2)) {
|
|
1220
|
+
throw new Error(`path escapes repository root: ${candidate}`);
|
|
1221
|
+
}
|
|
1222
|
+
return absolute;
|
|
1223
|
+
}
|
|
1224
|
+
function markdownFiles(target) {
|
|
1225
|
+
if (!import_node_fs5.default.existsSync(target)) return [];
|
|
1226
|
+
const stat = import_node_fs5.default.statSync(target);
|
|
1227
|
+
if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
|
|
1228
|
+
if (!stat.isDirectory()) return [];
|
|
1229
|
+
return import_node_fs5.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path5.default.join(target, entry.name)));
|
|
1230
|
+
}
|
|
1231
|
+
function fileDescriptor(root, absolute) {
|
|
1232
|
+
const content = import_node_fs5.default.readFileSync(absolute, "utf8");
|
|
1233
|
+
return {
|
|
1234
|
+
logical_path: import_node_path5.default.relative(root, absolute).split(import_node_path5.default.sep).join("/"),
|
|
1235
|
+
content_hash: import_node_crypto4.default.createHash("sha256").update(content).digest("hex"),
|
|
1236
|
+
bytes: Buffer.byteLength(content),
|
|
1237
|
+
content
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
async function handleSyncTool(client2, args) {
|
|
1241
|
+
try {
|
|
1242
|
+
const action = String(args.action || "");
|
|
1243
|
+
const context = args.invocation_context || {};
|
|
1244
|
+
const adapter = new CodegraphAdapter({
|
|
1245
|
+
cwd: context.cwd || process.cwd()
|
|
1246
|
+
});
|
|
1247
|
+
const requestedRepo = typeof args.repo === "string" ? args.repo : typeof args.repo_id === "string" ? args.repo_id : context.repoId;
|
|
1248
|
+
const repository = await adapter.resolveRepository(requestedRepo);
|
|
1249
|
+
const root = repository.repoRoot;
|
|
1250
|
+
const repoId = repository.repoId;
|
|
1251
|
+
const provider = String(args.provider || "claude_project");
|
|
1252
|
+
const remote = client2;
|
|
1253
|
+
if (action === "scan") {
|
|
1254
|
+
const repositories = args.workspace === true ? await adapter.resolveRepositories() : [repository];
|
|
1255
|
+
const requested = Array.isArray(args.paths) && args.paths.length > 0 ? args.paths.map(String) : [".claude/agent-memory", "memory"];
|
|
1256
|
+
const scans = [];
|
|
1257
|
+
for (const selected of repositories) {
|
|
1258
|
+
scans.push(
|
|
1259
|
+
await scanRepository(
|
|
1260
|
+
remote,
|
|
1261
|
+
selected,
|
|
1262
|
+
requested,
|
|
1263
|
+
provider,
|
|
1264
|
+
args.agent_id
|
|
1265
|
+
)
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
if (args.workspace === true) {
|
|
1269
|
+
return ok2({
|
|
1270
|
+
provider,
|
|
1271
|
+
workspace: true,
|
|
1272
|
+
repositories: scans,
|
|
1273
|
+
one_way: "files_to_graph"
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
return ok2(scans[0]);
|
|
1277
|
+
}
|
|
1278
|
+
const request = {
|
|
1279
|
+
...args,
|
|
1280
|
+
provider,
|
|
1281
|
+
repo_id: repoId
|
|
1282
|
+
};
|
|
1283
|
+
delete request.invocation_context;
|
|
1284
|
+
delete request.paths;
|
|
1285
|
+
delete request.repo;
|
|
1286
|
+
delete request.workspace;
|
|
1287
|
+
if (action === "begin" && !request.content) {
|
|
1288
|
+
if (!request.logical_path) {
|
|
1289
|
+
return err2("missing_field", "logical_path is required for begin");
|
|
1290
|
+
}
|
|
1291
|
+
const file = fileDescriptor(
|
|
1292
|
+
root,
|
|
1293
|
+
safePath(root, String(request.logical_path))
|
|
1294
|
+
);
|
|
1295
|
+
request.content = file.content;
|
|
1296
|
+
request.content_hash ||= file.content_hash;
|
|
1297
|
+
}
|
|
1298
|
+
if (action === "status" && request.logical_path && request.file_present !== false) {
|
|
1299
|
+
const absolute = safePath(root, String(request.logical_path));
|
|
1300
|
+
request.file_present = import_node_fs5.default.existsSync(absolute);
|
|
1301
|
+
if (request.file_present && !request.content_hash) {
|
|
1302
|
+
request.content_hash = fileDescriptor(root, absolute).content_hash;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
return ok2(await remote.memorySync(request));
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
return err2(
|
|
1308
|
+
"sync_failed",
|
|
1309
|
+
error instanceof Error ? error.message : String(error)
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
async function scanRepository(remote, repository, requested, provider, agentId) {
|
|
1314
|
+
const files = [
|
|
1315
|
+
...new Set(
|
|
1316
|
+
requested.flatMap(
|
|
1317
|
+
(candidate) => markdownFiles(safePath(repository.repoRoot, candidate))
|
|
1318
|
+
)
|
|
1319
|
+
)
|
|
1320
|
+
].sort();
|
|
1321
|
+
const results = [];
|
|
1322
|
+
for (const absolute of files) {
|
|
1323
|
+
const file = fileDescriptor(repository.repoRoot, absolute);
|
|
1324
|
+
const status = await remote.memorySync({
|
|
1325
|
+
action: "scan",
|
|
1326
|
+
provider,
|
|
1327
|
+
repo_id: repository.repoId,
|
|
1328
|
+
logical_path: file.logical_path,
|
|
1329
|
+
content_hash: file.content_hash,
|
|
1330
|
+
file_present: true,
|
|
1331
|
+
agent_id: agentId
|
|
1332
|
+
});
|
|
1333
|
+
results.push({
|
|
1334
|
+
logical_path: file.logical_path,
|
|
1335
|
+
content_hash: file.content_hash,
|
|
1336
|
+
bytes: file.bytes,
|
|
1337
|
+
status
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
return {
|
|
1341
|
+
repo_id: repository.repoId,
|
|
1342
|
+
root: ".",
|
|
1343
|
+
files: results
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
var import_node_crypto4, import_node_fs5, import_node_path5, ok2, err2, SYNC_TOOL;
|
|
1347
|
+
var init_sync_tool = __esm({
|
|
1348
|
+
"src/sync-tool.ts"() {
|
|
1349
|
+
"use strict";
|
|
1350
|
+
import_node_crypto4 = __toESM(require("crypto"));
|
|
1351
|
+
import_node_fs5 = __toESM(require("fs"));
|
|
1352
|
+
import_node_path5 = __toESM(require("path"));
|
|
1353
|
+
init_codegraph();
|
|
1354
|
+
ok2 = (data) => ({
|
|
1355
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1356
|
+
});
|
|
1357
|
+
err2 = (code, message) => ({
|
|
1358
|
+
content: [{ type: "text", text: JSON.stringify({ code, error: message }) }],
|
|
1359
|
+
isError: true
|
|
1360
|
+
});
|
|
1361
|
+
SYNC_TOOL = {
|
|
1362
|
+
name: "mindgraph_sync",
|
|
1363
|
+
description: "Transactional one-way project memory-file sync. scan/status are reads; begin/record/finalize/abandon are writes. Discovery and hashing are automatic; semantic assertions are deliberately supplied through record. This never writes graph state back to files.",
|
|
1364
|
+
annotations: {
|
|
1365
|
+
readOnlyHint: false,
|
|
1366
|
+
destructiveHint: false,
|
|
1367
|
+
idempotentHint: true,
|
|
1368
|
+
openWorldHint: true
|
|
1369
|
+
},
|
|
1370
|
+
inputSchema: {
|
|
1371
|
+
type: "object",
|
|
1372
|
+
properties: {
|
|
1373
|
+
action: {
|
|
1374
|
+
type: "string",
|
|
1375
|
+
enum: ["scan", "begin", "record", "finalize", "status", "abandon"]
|
|
1376
|
+
},
|
|
1377
|
+
paths: { type: "array", items: { type: "string" } },
|
|
1378
|
+
workspace: {
|
|
1379
|
+
type: "boolean",
|
|
1380
|
+
description: "For scan only: fan out across repositories explicitly listed in .mindgraph/workspace.json"
|
|
1381
|
+
},
|
|
1382
|
+
repo: {
|
|
1383
|
+
type: "string",
|
|
1384
|
+
description: "Configured repository id, name, or root"
|
|
1385
|
+
},
|
|
1386
|
+
logical_path: { type: "string" },
|
|
1387
|
+
content: { type: "string" },
|
|
1388
|
+
content_hash: { type: "string" },
|
|
1389
|
+
source_uid: { type: "string" },
|
|
1390
|
+
execution_uid: { type: "string" },
|
|
1391
|
+
assertion_fingerprint: { type: "string" },
|
|
1392
|
+
planned_fingerprints: { type: "array", items: { type: "string" } },
|
|
1393
|
+
current_fingerprints: { type: "array", items: { type: "string" } },
|
|
1394
|
+
source_selector: { type: "object" },
|
|
1395
|
+
capture_type: {
|
|
1396
|
+
type: "string",
|
|
1397
|
+
enum: ["lesson", "journal", "claim", "decision", "risk"]
|
|
1398
|
+
},
|
|
1399
|
+
label: { type: "string" },
|
|
1400
|
+
summary: { type: "string" },
|
|
1401
|
+
props: { type: "object" },
|
|
1402
|
+
expected_execution_version: { type: "integer", minimum: 1 },
|
|
1403
|
+
baseline_version: { type: "integer", minimum: 1 },
|
|
1404
|
+
repo_space_uid: { type: "string" },
|
|
1405
|
+
repo_id: { type: "string" },
|
|
1406
|
+
provider: { type: "string", enum: ["claude_project", "explicit"] },
|
|
1407
|
+
file_present: { type: "boolean" },
|
|
1408
|
+
agent_id: { type: "string" },
|
|
1409
|
+
invocation_context: {
|
|
1410
|
+
type: "object",
|
|
1411
|
+
description: "Adapter-owned; hooks replace forged or absent values.",
|
|
1412
|
+
properties: {
|
|
1413
|
+
harness: { type: "string" },
|
|
1414
|
+
harnessSessionId: { type: "string" },
|
|
1415
|
+
harnessTurnId: { type: "string" },
|
|
1416
|
+
cwd: { type: "string" },
|
|
1417
|
+
repoId: { type: "string" },
|
|
1418
|
+
branch: { type: "string" },
|
|
1419
|
+
commit: { type: "string" },
|
|
1420
|
+
worktreeState: { type: "string" },
|
|
1421
|
+
model: { type: "string" },
|
|
1422
|
+
injectedBy: { type: "string" }
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
required: ["action"]
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
|
|
29
1432
|
// src/tools.ts
|
|
30
|
-
function
|
|
1433
|
+
function toolsForProfile(profile = process.env.MINDGRAPH_PROFILE || "general") {
|
|
1434
|
+
if (profile === "coding") return TOOLS;
|
|
1435
|
+
return TOOLS.filter(
|
|
1436
|
+
(tool) => tool.name !== "mindgraph_code" && tool.name !== "mindgraph_sync"
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
function ok3(data) {
|
|
31
1440
|
return {
|
|
32
1441
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
33
1442
|
};
|
|
34
1443
|
}
|
|
35
|
-
function
|
|
1444
|
+
function err3(message) {
|
|
36
1445
|
return {
|
|
37
1446
|
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
38
1447
|
isError: true
|
|
@@ -40,29 +1449,58 @@ function err(message) {
|
|
|
40
1449
|
}
|
|
41
1450
|
async function handleTool(client2, name, args) {
|
|
42
1451
|
try {
|
|
1452
|
+
let result;
|
|
43
1453
|
switch (name) {
|
|
44
1454
|
case "mindgraph_capture":
|
|
45
|
-
|
|
1455
|
+
result = await handleCapture(client2, args);
|
|
1456
|
+
break;
|
|
46
1457
|
case "mindgraph_reason":
|
|
47
|
-
|
|
1458
|
+
result = await handleReason(client2, args);
|
|
1459
|
+
break;
|
|
48
1460
|
case "mindgraph_commit":
|
|
49
|
-
|
|
1461
|
+
result = await handleCommit(client2, args);
|
|
1462
|
+
break;
|
|
50
1463
|
case "mindgraph_plan":
|
|
51
|
-
|
|
1464
|
+
result = await handlePlan(client2, args);
|
|
1465
|
+
break;
|
|
52
1466
|
case "mindgraph_retrieve":
|
|
53
|
-
|
|
1467
|
+
result = await handleRetrieve(client2, args);
|
|
1468
|
+
break;
|
|
54
1469
|
case "mindgraph_ingest":
|
|
55
|
-
|
|
1470
|
+
result = await handleIngest(client2, args);
|
|
1471
|
+
break;
|
|
56
1472
|
case "mindgraph_synthesize":
|
|
57
|
-
|
|
1473
|
+
result = await handleSynthesize(client2, args);
|
|
1474
|
+
break;
|
|
58
1475
|
case "mindgraph_ontology":
|
|
59
|
-
|
|
1476
|
+
result = await handleOntology(client2, args);
|
|
1477
|
+
break;
|
|
1478
|
+
case "mindgraph_code":
|
|
1479
|
+
result = await handleCodeTool(client2, args);
|
|
1480
|
+
break;
|
|
1481
|
+
case "mindgraph_sync":
|
|
1482
|
+
result = await handleSyncTool(client2, args);
|
|
1483
|
+
break;
|
|
60
1484
|
default:
|
|
61
|
-
|
|
1485
|
+
result = err3(`Unknown tool: ${name}`);
|
|
62
1486
|
}
|
|
1487
|
+
const action = typeof args.action === "string" ? args.action : "";
|
|
1488
|
+
const attach = name === "mindgraph_capture" || name === "mindgraph_commit" && action !== "get_open_decisions" || name === "mindgraph_plan" && ![
|
|
1489
|
+
"get_plan",
|
|
1490
|
+
"get_executions",
|
|
1491
|
+
"get_pending",
|
|
1492
|
+
"get_assessments",
|
|
1493
|
+
"resume_work",
|
|
1494
|
+
"heartbeat"
|
|
1495
|
+
].includes(action);
|
|
1496
|
+
return attach ? await attachCodeRefsToToolResult(client2, result, args) : result;
|
|
63
1497
|
} catch (e) {
|
|
1498
|
+
if (e instanceof import_mindgraph.MindGraphError && e.body !== void 0) {
|
|
1499
|
+
const detail = typeof e.body === "string" ? e.body : JSON.stringify(e.body);
|
|
1500
|
+
return err3(`${e.message} \u2014 ${detail}`);
|
|
1501
|
+
}
|
|
64
1502
|
const message = e instanceof Error ? e.message : String(e);
|
|
65
|
-
return
|
|
1503
|
+
return err3(message);
|
|
66
1504
|
}
|
|
67
1505
|
}
|
|
68
1506
|
async function handleCapture(client2, args) {
|
|
@@ -71,6 +1509,12 @@ async function handleCapture(client2, args) {
|
|
|
71
1509
|
label,
|
|
72
1510
|
summary,
|
|
73
1511
|
content,
|
|
1512
|
+
session_uid,
|
|
1513
|
+
work_uid,
|
|
1514
|
+
execution_uid,
|
|
1515
|
+
idempotency_key,
|
|
1516
|
+
supersedes_uid,
|
|
1517
|
+
summarizes_uids,
|
|
74
1518
|
entity_type,
|
|
75
1519
|
source_uid,
|
|
76
1520
|
mood,
|
|
@@ -81,12 +1525,30 @@ async function handleCapture(client2, args) {
|
|
|
81
1525
|
agent_id
|
|
82
1526
|
} = args;
|
|
83
1527
|
switch (action) {
|
|
1528
|
+
case "lesson":
|
|
1529
|
+
return ok3(
|
|
1530
|
+
await client2.distill({
|
|
1531
|
+
label,
|
|
1532
|
+
output_type: "lesson",
|
|
1533
|
+
summary,
|
|
1534
|
+
confidence,
|
|
1535
|
+
salience,
|
|
1536
|
+
session_uid,
|
|
1537
|
+
work_uid,
|
|
1538
|
+
execution_uid,
|
|
1539
|
+
idempotency_key,
|
|
1540
|
+
supersedes_uid,
|
|
1541
|
+
summarizes_uids,
|
|
1542
|
+
props,
|
|
1543
|
+
agent_id
|
|
1544
|
+
})
|
|
1545
|
+
);
|
|
84
1546
|
case "journal": {
|
|
85
|
-
if (!content) return
|
|
1547
|
+
if (!content) return err3("content is required for action=journal");
|
|
86
1548
|
const journalProps = { content };
|
|
87
1549
|
if (mood) journalProps.mood = mood;
|
|
88
1550
|
if (tags) journalProps.tags = tags;
|
|
89
|
-
return
|
|
1551
|
+
return ok3(await client2.journal(label, journalProps, { agent_id }));
|
|
90
1552
|
}
|
|
91
1553
|
case "entity": {
|
|
92
1554
|
const type = entity_type || "concept";
|
|
@@ -94,22 +1556,22 @@ async function handleCapture(client2, args) {
|
|
|
94
1556
|
if (summary) entityProps.description = summary;
|
|
95
1557
|
switch (type) {
|
|
96
1558
|
case "person":
|
|
97
|
-
return
|
|
1559
|
+
return ok3(await client2.findOrCreatePerson(label, entityProps, agent_id));
|
|
98
1560
|
case "organization":
|
|
99
|
-
return
|
|
1561
|
+
return ok3(await client2.findOrCreateOrganization(label, entityProps, agent_id));
|
|
100
1562
|
case "nation":
|
|
101
|
-
return
|
|
1563
|
+
return ok3(await client2.findOrCreateNation(label, entityProps, agent_id));
|
|
102
1564
|
case "event":
|
|
103
|
-
return
|
|
1565
|
+
return ok3(await client2.findOrCreateEvent(label, entityProps, agent_id));
|
|
104
1566
|
case "place":
|
|
105
|
-
return
|
|
1567
|
+
return ok3(await client2.findOrCreatePlace(label, entityProps, agent_id));
|
|
106
1568
|
case "concept":
|
|
107
1569
|
default:
|
|
108
|
-
return
|
|
1570
|
+
return ok3(await client2.findOrCreateConcept(label, entityProps, agent_id));
|
|
109
1571
|
}
|
|
110
1572
|
}
|
|
111
1573
|
case "observation":
|
|
112
|
-
return
|
|
1574
|
+
return ok3(
|
|
113
1575
|
await client2.capture({
|
|
114
1576
|
action: "observation",
|
|
115
1577
|
label,
|
|
@@ -121,7 +1583,7 @@ async function handleCapture(client2, args) {
|
|
|
121
1583
|
})
|
|
122
1584
|
);
|
|
123
1585
|
case "source":
|
|
124
|
-
return
|
|
1586
|
+
return ok3(
|
|
125
1587
|
await client2.capture({
|
|
126
1588
|
action: "source",
|
|
127
1589
|
label,
|
|
@@ -133,7 +1595,7 @@ async function handleCapture(client2, args) {
|
|
|
133
1595
|
})
|
|
134
1596
|
);
|
|
135
1597
|
case "snippet":
|
|
136
|
-
return
|
|
1598
|
+
return ok3(
|
|
137
1599
|
await client2.capture({
|
|
138
1600
|
action: "snippet",
|
|
139
1601
|
label,
|
|
@@ -146,7 +1608,7 @@ async function handleCapture(client2, args) {
|
|
|
146
1608
|
})
|
|
147
1609
|
);
|
|
148
1610
|
case "concept":
|
|
149
|
-
return
|
|
1611
|
+
return ok3(
|
|
150
1612
|
await client2.structure({
|
|
151
1613
|
action: "concept",
|
|
152
1614
|
label,
|
|
@@ -158,7 +1620,7 @@ async function handleCapture(client2, args) {
|
|
|
158
1620
|
})
|
|
159
1621
|
);
|
|
160
1622
|
default:
|
|
161
|
-
return
|
|
1623
|
+
return err3(`Unknown capture action: ${action}`);
|
|
162
1624
|
}
|
|
163
1625
|
}
|
|
164
1626
|
async function handleReason(client2, args) {
|
|
@@ -177,8 +1639,8 @@ async function handleReason(client2, args) {
|
|
|
177
1639
|
} = args;
|
|
178
1640
|
if (action === "claim") {
|
|
179
1641
|
const claimObj = claim || (label ? { label, confidence } : null);
|
|
180
|
-
if (!claimObj) return
|
|
181
|
-
return
|
|
1642
|
+
if (!claimObj) return err3("claim object or label is required for action=claim");
|
|
1643
|
+
return ok3(
|
|
182
1644
|
await client2.argue({
|
|
183
1645
|
claim: claimObj,
|
|
184
1646
|
evidence,
|
|
@@ -188,8 +1650,8 @@ async function handleReason(client2, args) {
|
|
|
188
1650
|
})
|
|
189
1651
|
);
|
|
190
1652
|
}
|
|
191
|
-
if (!label) return
|
|
192
|
-
return
|
|
1653
|
+
if (!label) return err3(`label is required for action=${action}`);
|
|
1654
|
+
return ok3(
|
|
193
1655
|
await client2.inquire({
|
|
194
1656
|
action,
|
|
195
1657
|
label,
|
|
@@ -222,8 +1684,8 @@ async function handleCommit(client2, args) {
|
|
|
222
1684
|
case "goal":
|
|
223
1685
|
case "project":
|
|
224
1686
|
case "milestone":
|
|
225
|
-
if (!label) return
|
|
226
|
-
return
|
|
1687
|
+
if (!label) return err3(`label is required for action=${action}`);
|
|
1688
|
+
return ok3(
|
|
227
1689
|
await client2.commit({
|
|
228
1690
|
action,
|
|
229
1691
|
label,
|
|
@@ -236,15 +1698,15 @@ async function handleCommit(client2, args) {
|
|
|
236
1698
|
})
|
|
237
1699
|
);
|
|
238
1700
|
case "open_decision":
|
|
239
|
-
if (!label) return
|
|
240
|
-
return
|
|
1701
|
+
if (!label) return err3("label is required for open_decision");
|
|
1702
|
+
return ok3(await client2.openDecision(label, { summary, props, agent_id }));
|
|
241
1703
|
case "add_option":
|
|
242
|
-
if (!decision_uid) return
|
|
243
|
-
if (!label) return
|
|
244
|
-
return
|
|
1704
|
+
if (!decision_uid) return err3("decision_uid is required for add_option");
|
|
1705
|
+
if (!label) return err3("label is required for add_option");
|
|
1706
|
+
return ok3(await client2.addOption(decision_uid, label, { summary, props, agent_id }));
|
|
245
1707
|
case "add_constraint":
|
|
246
|
-
if (!decision_uid) return
|
|
247
|
-
return
|
|
1708
|
+
if (!decision_uid) return err3("decision_uid is required for add_constraint");
|
|
1709
|
+
return ok3(
|
|
248
1710
|
await client2.deliberate({
|
|
249
1711
|
action: "add_constraint",
|
|
250
1712
|
label,
|
|
@@ -255,9 +1717,9 @@ async function handleCommit(client2, args) {
|
|
|
255
1717
|
})
|
|
256
1718
|
);
|
|
257
1719
|
case "resolve_decision":
|
|
258
|
-
if (!decision_uid) return
|
|
259
|
-
if (!chosen_option_uid) return
|
|
260
|
-
return
|
|
1720
|
+
if (!decision_uid) return err3("decision_uid is required for resolve_decision");
|
|
1721
|
+
if (!chosen_option_uid) return err3("chosen_option_uid is required for resolve_decision");
|
|
1722
|
+
return ok3(
|
|
261
1723
|
await client2.resolveDecision(decision_uid, chosen_option_uid, {
|
|
262
1724
|
summary,
|
|
263
1725
|
props,
|
|
@@ -269,9 +1731,9 @@ async function handleCommit(client2, args) {
|
|
|
269
1731
|
})
|
|
270
1732
|
);
|
|
271
1733
|
case "get_open_decisions":
|
|
272
|
-
return
|
|
1734
|
+
return ok3(await client2.getOpenDecisions());
|
|
273
1735
|
default:
|
|
274
|
-
return
|
|
1736
|
+
return err3(`Unknown commit action: ${action}`);
|
|
275
1737
|
}
|
|
276
1738
|
}
|
|
277
1739
|
async function handlePlan(client2, args) {
|
|
@@ -281,8 +1743,41 @@ async function handlePlan(client2, args) {
|
|
|
281
1743
|
summary,
|
|
282
1744
|
plan_uid,
|
|
283
1745
|
task_uid,
|
|
1746
|
+
step_uid,
|
|
1747
|
+
session_uid,
|
|
1748
|
+
goal_uid,
|
|
284
1749
|
target_uid,
|
|
1750
|
+
execution_uid,
|
|
1751
|
+
executor_uid,
|
|
1752
|
+
affordance_uid,
|
|
1753
|
+
produces_node_uid,
|
|
1754
|
+
filter_plan_uid,
|
|
1755
|
+
depends_on_uids,
|
|
1756
|
+
related_uids,
|
|
285
1757
|
status,
|
|
1758
|
+
task_status,
|
|
1759
|
+
step_status,
|
|
1760
|
+
execution_status,
|
|
1761
|
+
expected_version,
|
|
1762
|
+
lease_epoch,
|
|
1763
|
+
lease_ttl_secs,
|
|
1764
|
+
idempotency_key,
|
|
1765
|
+
allow_takeover,
|
|
1766
|
+
release_lease,
|
|
1767
|
+
override_reason,
|
|
1768
|
+
scope_uids,
|
|
1769
|
+
limit,
|
|
1770
|
+
confidence,
|
|
1771
|
+
salience,
|
|
1772
|
+
input_snapshot,
|
|
1773
|
+
output_snapshot,
|
|
1774
|
+
side_effects,
|
|
1775
|
+
outcome,
|
|
1776
|
+
error,
|
|
1777
|
+
next_action,
|
|
1778
|
+
checkpoint_summary,
|
|
1779
|
+
test_summary,
|
|
1780
|
+
produces_node_uids,
|
|
286
1781
|
props,
|
|
287
1782
|
agent_id
|
|
288
1783
|
} = args;
|
|
@@ -293,25 +1788,120 @@ async function handlePlan(client2, args) {
|
|
|
293
1788
|
case "add_step":
|
|
294
1789
|
case "update_status":
|
|
295
1790
|
case "get_plan":
|
|
296
|
-
|
|
1791
|
+
case "resume_work":
|
|
1792
|
+
case "claim_task":
|
|
1793
|
+
case "heartbeat":
|
|
1794
|
+
case "start_iteration":
|
|
1795
|
+
case "checkpoint_iteration":
|
|
1796
|
+
case "block_task":
|
|
1797
|
+
case "complete_task":
|
|
1798
|
+
case "abandon_iteration":
|
|
1799
|
+
return ok3(
|
|
297
1800
|
await client2.plan({
|
|
298
1801
|
action,
|
|
299
1802
|
label,
|
|
300
1803
|
summary,
|
|
1804
|
+
confidence,
|
|
1805
|
+
salience,
|
|
1806
|
+
goal_uid,
|
|
301
1807
|
plan_uid,
|
|
302
1808
|
task_uid,
|
|
1809
|
+
step_uid,
|
|
1810
|
+
session_uid,
|
|
1811
|
+
target_uid,
|
|
1812
|
+
depends_on_uids,
|
|
1813
|
+
related_uids,
|
|
303
1814
|
status,
|
|
1815
|
+
task_status,
|
|
1816
|
+
step_status,
|
|
1817
|
+
execution_status,
|
|
1818
|
+
execution_uid,
|
|
1819
|
+
expected_version,
|
|
1820
|
+
lease_epoch,
|
|
1821
|
+
lease_ttl_secs,
|
|
1822
|
+
idempotency_key,
|
|
1823
|
+
allow_takeover,
|
|
1824
|
+
release_lease,
|
|
1825
|
+
override_reason,
|
|
1826
|
+
scope_uids,
|
|
1827
|
+
limit,
|
|
1828
|
+
input_snapshot,
|
|
1829
|
+
output_snapshot,
|
|
1830
|
+
side_effects,
|
|
1831
|
+
outcome,
|
|
1832
|
+
error,
|
|
1833
|
+
next_action,
|
|
1834
|
+
checkpoint_summary,
|
|
1835
|
+
test_summary,
|
|
1836
|
+
produces_node_uids,
|
|
304
1837
|
props,
|
|
305
1838
|
agent_id
|
|
306
1839
|
})
|
|
307
1840
|
);
|
|
308
1841
|
// Execution tracking
|
|
309
1842
|
case "start_execution":
|
|
310
|
-
return
|
|
1843
|
+
return ok3(
|
|
1844
|
+
await client2.execution({
|
|
1845
|
+
action: "start",
|
|
1846
|
+
label,
|
|
1847
|
+
summary,
|
|
1848
|
+
confidence,
|
|
1849
|
+
salience,
|
|
1850
|
+
plan_uid,
|
|
1851
|
+
executor_uid,
|
|
1852
|
+
affordance_uid,
|
|
1853
|
+
related_uids,
|
|
1854
|
+
input_snapshot,
|
|
1855
|
+
props,
|
|
1856
|
+
agent_id
|
|
1857
|
+
})
|
|
1858
|
+
);
|
|
311
1859
|
case "complete_execution":
|
|
312
|
-
return
|
|
1860
|
+
return ok3(
|
|
1861
|
+
await client2.execution({
|
|
1862
|
+
action: "complete",
|
|
1863
|
+
execution_uid,
|
|
1864
|
+
produces_node_uid,
|
|
1865
|
+
output_snapshot,
|
|
1866
|
+
side_effects,
|
|
1867
|
+
outcome,
|
|
1868
|
+
props,
|
|
1869
|
+
agent_id
|
|
1870
|
+
})
|
|
1871
|
+
);
|
|
313
1872
|
case "fail_execution":
|
|
314
|
-
return
|
|
1873
|
+
return ok3(
|
|
1874
|
+
await client2.execution({
|
|
1875
|
+
action: "fail",
|
|
1876
|
+
execution_uid,
|
|
1877
|
+
output_snapshot,
|
|
1878
|
+
side_effects,
|
|
1879
|
+
outcome,
|
|
1880
|
+
error,
|
|
1881
|
+
props,
|
|
1882
|
+
agent_id
|
|
1883
|
+
})
|
|
1884
|
+
);
|
|
1885
|
+
case "register_agent":
|
|
1886
|
+
return ok3(
|
|
1887
|
+
await client2.execution({
|
|
1888
|
+
action: "register_agent",
|
|
1889
|
+
label,
|
|
1890
|
+
summary,
|
|
1891
|
+
confidence,
|
|
1892
|
+
salience,
|
|
1893
|
+
props,
|
|
1894
|
+
agent_id
|
|
1895
|
+
})
|
|
1896
|
+
);
|
|
1897
|
+
case "get_executions":
|
|
1898
|
+
return ok3(
|
|
1899
|
+
await client2.execution({
|
|
1900
|
+
action: "get_executions",
|
|
1901
|
+
filter_plan_uid,
|
|
1902
|
+
agent_id
|
|
1903
|
+
})
|
|
1904
|
+
);
|
|
315
1905
|
// Governance — read-only from MCP by design (C33). The write actions were
|
|
316
1906
|
// removed rather than repaired: `resolve_approval` here also sent
|
|
317
1907
|
// `task_uid` where the server requires `approval_uid`, and never sent
|
|
@@ -319,12 +1909,12 @@ async function handlePlan(client2, args) {
|
|
|
319
1909
|
// DENIED every approval an agent tried to resolve. Wiring that correctly
|
|
320
1910
|
// would have meant building a working self-approval path — the hole itself.
|
|
321
1911
|
case "get_pending":
|
|
322
|
-
return
|
|
1912
|
+
return ok3(await client2.governance({ action: "get_pending", agent_id }));
|
|
323
1913
|
// Procedures (merged from mindgraph_action)
|
|
324
1914
|
case "create_flow":
|
|
325
1915
|
case "add_affordance":
|
|
326
1916
|
case "add_control":
|
|
327
|
-
return
|
|
1917
|
+
return ok3(
|
|
328
1918
|
await client2.procedure({
|
|
329
1919
|
action,
|
|
330
1920
|
label,
|
|
@@ -335,7 +1925,7 @@ async function handlePlan(client2, args) {
|
|
|
335
1925
|
})
|
|
336
1926
|
);
|
|
337
1927
|
case "add_procedure_step":
|
|
338
|
-
return
|
|
1928
|
+
return ok3(
|
|
339
1929
|
await client2.procedure({
|
|
340
1930
|
action: "add_step",
|
|
341
1931
|
label,
|
|
@@ -347,7 +1937,7 @@ async function handlePlan(client2, args) {
|
|
|
347
1937
|
);
|
|
348
1938
|
// Risk assessment (merged from mindgraph_action)
|
|
349
1939
|
case "assess_risk":
|
|
350
|
-
return
|
|
1940
|
+
return ok3(
|
|
351
1941
|
await client2.risk({
|
|
352
1942
|
action: "assess",
|
|
353
1943
|
label,
|
|
@@ -358,7 +1948,7 @@ async function handlePlan(client2, args) {
|
|
|
358
1948
|
})
|
|
359
1949
|
);
|
|
360
1950
|
case "get_assessments":
|
|
361
|
-
return
|
|
1951
|
+
return ok3(
|
|
362
1952
|
await client2.risk({
|
|
363
1953
|
action: "get_assessments",
|
|
364
1954
|
target_uid,
|
|
@@ -366,7 +1956,7 @@ async function handlePlan(client2, args) {
|
|
|
366
1956
|
})
|
|
367
1957
|
);
|
|
368
1958
|
default:
|
|
369
|
-
return
|
|
1959
|
+
return err3(`Unknown plan action: ${action}`);
|
|
370
1960
|
}
|
|
371
1961
|
}
|
|
372
1962
|
async function handleRetrieve(client2, args) {
|
|
@@ -395,8 +1985,8 @@ async function handleRetrieve(client2, args) {
|
|
|
395
1985
|
} = args;
|
|
396
1986
|
switch (action) {
|
|
397
1987
|
case "context":
|
|
398
|
-
if (!query) return
|
|
399
|
-
return
|
|
1988
|
+
if (!query) return err3("query is required for context retrieval");
|
|
1989
|
+
return ok3(
|
|
400
1990
|
await client2.retrieveContext({
|
|
401
1991
|
query,
|
|
402
1992
|
node_limit: limit,
|
|
@@ -409,8 +1999,8 @@ async function handleRetrieve(client2, args) {
|
|
|
409
1999
|
})
|
|
410
2000
|
);
|
|
411
2001
|
case "text":
|
|
412
|
-
if (!query) return
|
|
413
|
-
return
|
|
2002
|
+
if (!query) return err3("query is required for text search");
|
|
2003
|
+
return ok3(
|
|
414
2004
|
await client2.retrieve({
|
|
415
2005
|
action: "text",
|
|
416
2006
|
query,
|
|
@@ -421,8 +2011,8 @@ async function handleRetrieve(client2, args) {
|
|
|
421
2011
|
})
|
|
422
2012
|
);
|
|
423
2013
|
case "semantic":
|
|
424
|
-
if (!query) return
|
|
425
|
-
return
|
|
2014
|
+
if (!query) return err3("query is required for semantic search");
|
|
2015
|
+
return ok3(
|
|
426
2016
|
await client2.retrieve({
|
|
427
2017
|
action: "semantic",
|
|
428
2018
|
query,
|
|
@@ -433,8 +2023,8 @@ async function handleRetrieve(client2, args) {
|
|
|
433
2023
|
})
|
|
434
2024
|
);
|
|
435
2025
|
case "hybrid":
|
|
436
|
-
if (!query) return
|
|
437
|
-
return
|
|
2026
|
+
if (!query) return err3("query is required for hybrid search");
|
|
2027
|
+
return ok3(
|
|
438
2028
|
await client2.retrieve({
|
|
439
2029
|
action: "hybrid",
|
|
440
2030
|
query,
|
|
@@ -446,17 +2036,17 @@ async function handleRetrieve(client2, args) {
|
|
|
446
2036
|
);
|
|
447
2037
|
// Structured queries
|
|
448
2038
|
case "active_goals":
|
|
449
|
-
return
|
|
2039
|
+
return ok3(await client2.getGoals());
|
|
450
2040
|
case "open_questions":
|
|
451
|
-
return
|
|
2041
|
+
return ok3(await client2.getOpenQuestions());
|
|
452
2042
|
case "weak_claims":
|
|
453
|
-
return
|
|
2043
|
+
return ok3(await client2.getWeakClaims());
|
|
454
2044
|
case "pending_approvals":
|
|
455
|
-
return
|
|
2045
|
+
return ok3(await client2.getPendingApprovals());
|
|
456
2046
|
case "unresolved_contradictions":
|
|
457
|
-
return
|
|
2047
|
+
return ok3(await client2.getContradictions());
|
|
458
2048
|
case "stale_derivations":
|
|
459
|
-
return
|
|
2049
|
+
return ok3(
|
|
460
2050
|
await client2.retrieve({
|
|
461
2051
|
action: "stale_derivations",
|
|
462
2052
|
limit,
|
|
@@ -464,7 +2054,7 @@ async function handleRetrieve(client2, args) {
|
|
|
464
2054
|
})
|
|
465
2055
|
);
|
|
466
2056
|
case "preferences":
|
|
467
|
-
return
|
|
2057
|
+
return ok3(
|
|
468
2058
|
await client2.retrieve({
|
|
469
2059
|
action: "preferences",
|
|
470
2060
|
...query ? { query, k: limit } : {},
|
|
@@ -473,8 +2063,8 @@ async function handleRetrieve(client2, args) {
|
|
|
473
2063
|
})
|
|
474
2064
|
);
|
|
475
2065
|
case "layer":
|
|
476
|
-
if (!layer) return
|
|
477
|
-
return
|
|
2066
|
+
if (!layer) return err3("layer is required for layer retrieval");
|
|
2067
|
+
return ok3(
|
|
478
2068
|
await client2.retrieve({
|
|
479
2069
|
action: "layer",
|
|
480
2070
|
layer,
|
|
@@ -483,7 +2073,7 @@ async function handleRetrieve(client2, args) {
|
|
|
483
2073
|
})
|
|
484
2074
|
);
|
|
485
2075
|
case "recent":
|
|
486
|
-
return
|
|
2076
|
+
return ok3(
|
|
487
2077
|
await client2.retrieve({
|
|
488
2078
|
action: "recent",
|
|
489
2079
|
limit,
|
|
@@ -495,7 +2085,7 @@ async function handleRetrieve(client2, args) {
|
|
|
495
2085
|
);
|
|
496
2086
|
// Document inventory
|
|
497
2087
|
case "document_index":
|
|
498
|
-
return
|
|
2088
|
+
return ok3(
|
|
499
2089
|
await client2.getNodes({
|
|
500
2090
|
node_type: "Document",
|
|
501
2091
|
limit: limit ?? 100
|
|
@@ -503,8 +2093,8 @@ async function handleRetrieve(client2, args) {
|
|
|
503
2093
|
);
|
|
504
2094
|
// Graph traversal
|
|
505
2095
|
case "chain":
|
|
506
|
-
if (!start_uid) return
|
|
507
|
-
return
|
|
2096
|
+
if (!start_uid) return err3("start_uid is required for chain traversal");
|
|
2097
|
+
return ok3(
|
|
508
2098
|
await client2.traverse({
|
|
509
2099
|
action: "chain",
|
|
510
2100
|
start_uid,
|
|
@@ -515,8 +2105,8 @@ async function handleRetrieve(client2, args) {
|
|
|
515
2105
|
})
|
|
516
2106
|
);
|
|
517
2107
|
case "neighborhood":
|
|
518
|
-
if (!start_uid) return
|
|
519
|
-
return
|
|
2108
|
+
if (!start_uid) return err3("start_uid is required for neighborhood traversal");
|
|
2109
|
+
return ok3(
|
|
520
2110
|
await client2.traverse({
|
|
521
2111
|
action: "neighborhood",
|
|
522
2112
|
start_uid,
|
|
@@ -529,9 +2119,9 @@ async function handleRetrieve(client2, args) {
|
|
|
529
2119
|
})
|
|
530
2120
|
);
|
|
531
2121
|
case "path":
|
|
532
|
-
if (!start_uid) return
|
|
533
|
-
if (!end_uid) return
|
|
534
|
-
return
|
|
2122
|
+
if (!start_uid) return err3("start_uid is required for path traversal");
|
|
2123
|
+
if (!end_uid) return err3("end_uid is required for path traversal");
|
|
2124
|
+
return ok3(
|
|
535
2125
|
await client2.traverse({
|
|
536
2126
|
action: "path",
|
|
537
2127
|
start_uid,
|
|
@@ -545,8 +2135,8 @@ async function handleRetrieve(client2, args) {
|
|
|
545
2135
|
})
|
|
546
2136
|
);
|
|
547
2137
|
case "subgraph":
|
|
548
|
-
if (!start_uid) return
|
|
549
|
-
return
|
|
2138
|
+
if (!start_uid) return err3("start_uid is required for subgraph traversal");
|
|
2139
|
+
return ok3(
|
|
550
2140
|
await client2.traverse({
|
|
551
2141
|
action: "subgraph",
|
|
552
2142
|
start_uid,
|
|
@@ -559,7 +2149,7 @@ async function handleRetrieve(client2, args) {
|
|
|
559
2149
|
})
|
|
560
2150
|
);
|
|
561
2151
|
default:
|
|
562
|
-
return
|
|
2152
|
+
return err3(`Unknown retrieve action: ${action}`);
|
|
563
2153
|
}
|
|
564
2154
|
}
|
|
565
2155
|
async function handleIngest(client2, args) {
|
|
@@ -588,7 +2178,7 @@ async function handleIngest(client2, args) {
|
|
|
588
2178
|
const targetsOntology = Array.isArray(layers) && layers.includes("ontology");
|
|
589
2179
|
const cognitiveLayers = targetsOntology ? layers.filter((layer) => layer !== "ontology") : layers;
|
|
590
2180
|
if (targetsOntology && !ontology_schema_id) {
|
|
591
|
-
return
|
|
2181
|
+
return err3(
|
|
592
2182
|
"layers included 'ontology' but no ontology_schema_id was supplied. Typed Layer-7 extraction is driven by ontology_schema_id, not by the layers list \u2014 pass the schema id, or drop 'ontology' from layers."
|
|
593
2183
|
);
|
|
594
2184
|
}
|
|
@@ -596,8 +2186,8 @@ async function handleIngest(client2, args) {
|
|
|
596
2186
|
const ontologyOnly = targetsOntology && forwardedLayers === void 0;
|
|
597
2187
|
switch (action) {
|
|
598
2188
|
case "chunk":
|
|
599
|
-
if (!content) return
|
|
600
|
-
return
|
|
2189
|
+
if (!content) return err3("content is required for chunk ingestion");
|
|
2190
|
+
return ok3(
|
|
601
2191
|
await client2.ingestChunk({
|
|
602
2192
|
content,
|
|
603
2193
|
label: title,
|
|
@@ -611,8 +2201,8 @@ async function handleIngest(client2, args) {
|
|
|
611
2201
|
})
|
|
612
2202
|
);
|
|
613
2203
|
case "document":
|
|
614
|
-
if (!content) return
|
|
615
|
-
return
|
|
2204
|
+
if (!content) return err3("content is required for document ingestion");
|
|
2205
|
+
return ok3(
|
|
616
2206
|
await client2.ingestDocument({
|
|
617
2207
|
content,
|
|
618
2208
|
title,
|
|
@@ -631,8 +2221,8 @@ async function handleIngest(client2, args) {
|
|
|
631
2221
|
})
|
|
632
2222
|
);
|
|
633
2223
|
case "session":
|
|
634
|
-
if (!content) return
|
|
635
|
-
return
|
|
2224
|
+
if (!content) return err3("content is required for session ingestion");
|
|
2225
|
+
return ok3(
|
|
636
2226
|
await client2.ingestSession({
|
|
637
2227
|
content,
|
|
638
2228
|
title,
|
|
@@ -649,11 +2239,11 @@ async function handleIngest(client2, args) {
|
|
|
649
2239
|
);
|
|
650
2240
|
case "job_status":
|
|
651
2241
|
if (job_id) {
|
|
652
|
-
return
|
|
2242
|
+
return ok3(await client2.getJob(job_id));
|
|
653
2243
|
}
|
|
654
|
-
return
|
|
2244
|
+
return ok3(await client2.listJobs());
|
|
655
2245
|
default:
|
|
656
|
-
return
|
|
2246
|
+
return err3(`Unknown ingest action: ${action}`);
|
|
657
2247
|
}
|
|
658
2248
|
}
|
|
659
2249
|
async function handleSynthesize(client2, args) {
|
|
@@ -661,19 +2251,19 @@ async function handleSynthesize(client2, args) {
|
|
|
661
2251
|
switch (action) {
|
|
662
2252
|
case "signals":
|
|
663
2253
|
if (!project_uid)
|
|
664
|
-
return
|
|
665
|
-
return
|
|
2254
|
+
return err3("project_uid is required for action='signals'");
|
|
2255
|
+
return ok3(
|
|
666
2256
|
await client2.signals(project_uid, { signals, target_types })
|
|
667
2257
|
);
|
|
668
2258
|
case "run":
|
|
669
2259
|
if (!project_uid)
|
|
670
|
-
return
|
|
671
|
-
return
|
|
2260
|
+
return err3("project_uid is required for action='run'");
|
|
2261
|
+
return ok3(await client2.runSynthesis(project_uid));
|
|
672
2262
|
case "job_status":
|
|
673
|
-
if (!job_id) return
|
|
674
|
-
return
|
|
2263
|
+
if (!job_id) return err3("job_id is required for action='job_status'");
|
|
2264
|
+
return ok3(await client2.getJob(job_id));
|
|
675
2265
|
default:
|
|
676
|
-
return
|
|
2266
|
+
return err3(`Unknown synthesize action: ${action}`);
|
|
677
2267
|
}
|
|
678
2268
|
}
|
|
679
2269
|
async function handleOntology(client2, args) {
|
|
@@ -701,18 +2291,18 @@ async function handleOntology(client2, args) {
|
|
|
701
2291
|
} = args;
|
|
702
2292
|
switch (action) {
|
|
703
2293
|
case "schemas":
|
|
704
|
-
return
|
|
2294
|
+
return ok3(await client2.listOntologySchemas());
|
|
705
2295
|
case "schema":
|
|
706
|
-
if (!schema_id) return
|
|
707
|
-
return
|
|
2296
|
+
if (!schema_id) return err3("schema_id is required for action='schema'");
|
|
2297
|
+
return ok3(await client2.getOntologySchema(schema_id));
|
|
708
2298
|
case "query": {
|
|
709
|
-
if (!query) return
|
|
2299
|
+
if (!query) return err3("query is required for action='query'");
|
|
710
2300
|
if (!schema_id) {
|
|
711
|
-
return
|
|
2301
|
+
return err3(
|
|
712
2302
|
"schema_id is required for action='query' (use action='schemas' to find the active schema)"
|
|
713
2303
|
);
|
|
714
2304
|
}
|
|
715
|
-
return
|
|
2305
|
+
return ok3(
|
|
716
2306
|
await client2.queryOntology({
|
|
717
2307
|
query,
|
|
718
2308
|
schema_id,
|
|
@@ -725,8 +2315,8 @@ async function handleOntology(client2, args) {
|
|
|
725
2315
|
);
|
|
726
2316
|
}
|
|
727
2317
|
case "search":
|
|
728
|
-
if (!query) return
|
|
729
|
-
return
|
|
2318
|
+
if (!query) return err3("query is required for action='search'");
|
|
2319
|
+
return ok3(
|
|
730
2320
|
await client2.searchDomainObjects(query, {
|
|
731
2321
|
schema_id,
|
|
732
2322
|
object_types,
|
|
@@ -734,18 +2324,18 @@ async function handleOntology(client2, args) {
|
|
|
734
2324
|
})
|
|
735
2325
|
);
|
|
736
2326
|
case "objects":
|
|
737
|
-
if (!schema_id) return
|
|
738
|
-
return
|
|
2327
|
+
if (!schema_id) return err3("schema_id is required for action='objects'");
|
|
2328
|
+
return ok3(
|
|
739
2329
|
await client2.listDomainObjects({ schema_id, object_type, limit })
|
|
740
2330
|
);
|
|
741
2331
|
case "object":
|
|
742
|
-
if (!uid) return
|
|
743
|
-
return
|
|
2332
|
+
if (!uid) return err3("uid is required for action='object'");
|
|
2333
|
+
return ok3(await client2.getDomainObject(uid));
|
|
744
2334
|
case "object_context":
|
|
745
|
-
if (!uid) return
|
|
746
|
-
return
|
|
2335
|
+
if (!uid) return err3("uid is required for action='object_context'");
|
|
2336
|
+
return ok3(await client2.getDomainObjectContext(uid, depth));
|
|
747
2337
|
case "proposals":
|
|
748
|
-
return
|
|
2338
|
+
return ok3(
|
|
749
2339
|
await client2.listOntologyProposals({
|
|
750
2340
|
status: status ?? "pending",
|
|
751
2341
|
schema_id,
|
|
@@ -754,21 +2344,21 @@ async function handleOntology(client2, args) {
|
|
|
754
2344
|
})
|
|
755
2345
|
);
|
|
756
2346
|
case "proposal":
|
|
757
|
-
if (!proposal_id) return
|
|
758
|
-
return
|
|
2347
|
+
if (!proposal_id) return err3("proposal_id is required for action='proposal'");
|
|
2348
|
+
return ok3(await client2.getOntologyProposal(proposal_id));
|
|
759
2349
|
case "approve":
|
|
760
|
-
if (!proposal_id) return
|
|
761
|
-
return
|
|
2350
|
+
if (!proposal_id) return err3("proposal_id is required for action='approve'");
|
|
2351
|
+
return ok3(await client2.approveOntologyProposal(proposal_id, { feedback }));
|
|
762
2352
|
case "reject":
|
|
763
|
-
if (!proposal_id) return
|
|
764
|
-
return
|
|
2353
|
+
if (!proposal_id) return err3("proposal_id is required for action='reject'");
|
|
2354
|
+
return ok3(await client2.rejectOntologyProposal(proposal_id, reason));
|
|
765
2355
|
case "link": {
|
|
766
2356
|
if (!from_uid || !to_uid || !relation_type) {
|
|
767
|
-
return
|
|
2357
|
+
return err3(
|
|
768
2358
|
"from_uid, to_uid, and relation_type are required for action='link'"
|
|
769
2359
|
);
|
|
770
2360
|
}
|
|
771
|
-
return
|
|
2361
|
+
return ok3(
|
|
772
2362
|
await client2.linkDomainObjects({
|
|
773
2363
|
from_uid,
|
|
774
2364
|
to_uid,
|
|
@@ -779,11 +2369,11 @@ async function handleOntology(client2, args) {
|
|
|
779
2369
|
);
|
|
780
2370
|
}
|
|
781
2371
|
case "extract": {
|
|
782
|
-
if (!schema_id) return
|
|
2372
|
+
if (!schema_id) return err3("schema_id is required for action='extract'");
|
|
783
2373
|
if (!source_uids?.length) {
|
|
784
|
-
return
|
|
2374
|
+
return err3("source_uids is required for action='extract'");
|
|
785
2375
|
}
|
|
786
|
-
return
|
|
2376
|
+
return ok3(
|
|
787
2377
|
await client2.extractOntology({
|
|
788
2378
|
ontology_schema_id: schema_id,
|
|
789
2379
|
source_uids
|
|
@@ -791,23 +2381,34 @@ async function handleOntology(client2, args) {
|
|
|
791
2381
|
);
|
|
792
2382
|
}
|
|
793
2383
|
default:
|
|
794
|
-
return
|
|
2384
|
+
return err3(`Unknown ontology action: ${action}`);
|
|
795
2385
|
}
|
|
796
2386
|
}
|
|
797
|
-
var TOOLS;
|
|
2387
|
+
var import_mindgraph, TOOLS, INVOCATION_CONTEXT_SCHEMA;
|
|
798
2388
|
var init_tools = __esm({
|
|
799
2389
|
"src/tools.ts"() {
|
|
800
2390
|
"use strict";
|
|
2391
|
+
import_mindgraph = require("mindgraph");
|
|
2392
|
+
init_code_tool();
|
|
2393
|
+
init_sync_tool();
|
|
801
2394
|
TOOLS = [
|
|
802
2395
|
{
|
|
803
2396
|
name: "mindgraph_capture",
|
|
804
|
-
description: "Capture knowledge into the graph. Use 'entity' for people/orgs/places/events/nations/concepts (auto-deduplicates \u2014 safe to call even if already exists), 'observation' for factual statements, 'source' for documents/URLs, 'snippet' for quotes from a source, 'concept' for abstract ideas. Use 'journal' for quick personal notes
|
|
2397
|
+
description: "Capture knowledge into the graph. Use 'entity' for people/orgs/places/events/nations/concepts (auto-deduplicates \u2014 safe to call even if already exists), 'observation' for factual statements, 'source' for documents/URLs, 'snippet' for quotes from a source, and 'concept' for abstract ideas. Use 'journal' for quick personal notes and 'lesson' for a durable learning attributable to a session or source nodes. Prefer 'entity'/'observation' for objective facts, 'journal' for informal notes, and 'lesson' only for reusable knowledge.",
|
|
805
2398
|
inputSchema: {
|
|
806
2399
|
type: "object",
|
|
807
2400
|
properties: {
|
|
808
2401
|
action: {
|
|
809
2402
|
type: "string",
|
|
810
|
-
enum: [
|
|
2403
|
+
enum: [
|
|
2404
|
+
"entity",
|
|
2405
|
+
"observation",
|
|
2406
|
+
"source",
|
|
2407
|
+
"snippet",
|
|
2408
|
+
"concept",
|
|
2409
|
+
"journal",
|
|
2410
|
+
"lesson"
|
|
2411
|
+
],
|
|
811
2412
|
description: "Type of knowledge to capture"
|
|
812
2413
|
},
|
|
813
2414
|
label: {
|
|
@@ -822,6 +2423,31 @@ var init_tools = __esm({
|
|
|
822
2423
|
type: "string",
|
|
823
2424
|
description: "Journal content/body text (for action=journal)"
|
|
824
2425
|
},
|
|
2426
|
+
session_uid: {
|
|
2427
|
+
type: "string",
|
|
2428
|
+
description: "Session UID that produced a lesson (for action=lesson)"
|
|
2429
|
+
},
|
|
2430
|
+
work_uid: {
|
|
2431
|
+
type: "string",
|
|
2432
|
+
description: "Durable Task/work UID this capture is relevant to"
|
|
2433
|
+
},
|
|
2434
|
+
execution_uid: {
|
|
2435
|
+
type: "string",
|
|
2436
|
+
description: "Material Execution that produced the lesson"
|
|
2437
|
+
},
|
|
2438
|
+
idempotency_key: {
|
|
2439
|
+
type: "string",
|
|
2440
|
+
description: "Stable retry key for an intentional capture"
|
|
2441
|
+
},
|
|
2442
|
+
supersedes_uid: {
|
|
2443
|
+
type: "string",
|
|
2444
|
+
description: "Earlier capture corrected or superseded by this one"
|
|
2445
|
+
},
|
|
2446
|
+
summarizes_uids: {
|
|
2447
|
+
type: "array",
|
|
2448
|
+
items: { type: "string" },
|
|
2449
|
+
description: "Source node UIDs the lesson was learned from (for action=lesson)"
|
|
2450
|
+
},
|
|
825
2451
|
entity_type: {
|
|
826
2452
|
type: "string",
|
|
827
2453
|
enum: ["person", "organization", "place", "event", "nation", "concept"],
|
|
@@ -856,6 +2482,32 @@ var init_tools = __esm({
|
|
|
856
2482
|
type: "object",
|
|
857
2483
|
description: "Type-specific properties (e.g. birth_date for person, coordinates for place)"
|
|
858
2484
|
},
|
|
2485
|
+
code_refs: {
|
|
2486
|
+
type: "array",
|
|
2487
|
+
items: {
|
|
2488
|
+
oneOf: [
|
|
2489
|
+
{ type: "string" },
|
|
2490
|
+
{
|
|
2491
|
+
type: "object",
|
|
2492
|
+
properties: {
|
|
2493
|
+
repo: { type: "string" },
|
|
2494
|
+
path: { type: "string" },
|
|
2495
|
+
line: { type: "integer", minimum: 1 },
|
|
2496
|
+
symbol: { type: "string" },
|
|
2497
|
+
kind: { type: "string" },
|
|
2498
|
+
language: { type: "string" },
|
|
2499
|
+
signature: { type: "string" }
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
]
|
|
2503
|
+
},
|
|
2504
|
+
description: "Typed code targets to anchor and attach to the captured node"
|
|
2505
|
+
},
|
|
2506
|
+
repo: { type: "string", description: "Configured repository id or root" },
|
|
2507
|
+
repo_space_uid: {
|
|
2508
|
+
type: "string",
|
|
2509
|
+
description: "Writable shared repository/Project Space for new code anchors"
|
|
2510
|
+
},
|
|
859
2511
|
agent_id: {
|
|
860
2512
|
type: "string",
|
|
861
2513
|
description: "Agent identity"
|
|
@@ -1003,17 +2655,250 @@ var init_tools = __esm({
|
|
|
1003
2655
|
items: { type: "string" },
|
|
1004
2656
|
description: "Decision-time context UIDs to link with dated INFORMS edges"
|
|
1005
2657
|
},
|
|
1006
|
-
as_of_date: {
|
|
2658
|
+
as_of_date: {
|
|
2659
|
+
type: "string",
|
|
2660
|
+
description: "ISO 8601 date when the decision context was available"
|
|
2661
|
+
},
|
|
2662
|
+
session_id: {
|
|
2663
|
+
type: "string",
|
|
2664
|
+
description: "Session whose retrieval context informed the decision"
|
|
2665
|
+
},
|
|
2666
|
+
retrieval_trace_id: {
|
|
2667
|
+
type: "string",
|
|
2668
|
+
description: "Identifier of the decision-time retrieval trace"
|
|
2669
|
+
},
|
|
2670
|
+
confidence: {
|
|
2671
|
+
type: "number",
|
|
2672
|
+
minimum: 0,
|
|
2673
|
+
maximum: 1
|
|
2674
|
+
},
|
|
2675
|
+
salience: {
|
|
2676
|
+
type: "number",
|
|
2677
|
+
minimum: 0,
|
|
2678
|
+
maximum: 1
|
|
2679
|
+
},
|
|
2680
|
+
props: {
|
|
2681
|
+
type: "object",
|
|
2682
|
+
description: "Type-specific properties (e.g. status, priority, deadline, horizon for goals)"
|
|
2683
|
+
},
|
|
2684
|
+
code_refs: {
|
|
2685
|
+
type: "array",
|
|
2686
|
+
items: { oneOf: [{ type: "string" }, { type: "object" }] },
|
|
2687
|
+
description: "Typed code targets to anchor and attach to the created commitment"
|
|
2688
|
+
},
|
|
2689
|
+
repo: { type: "string", description: "Configured repository id or root" },
|
|
2690
|
+
repo_space_uid: {
|
|
2691
|
+
type: "string",
|
|
2692
|
+
description: "Writable shared repository/Project Space for new code anchors"
|
|
2693
|
+
},
|
|
2694
|
+
agent_id: {
|
|
2695
|
+
type: "string",
|
|
2696
|
+
description: "Agent identity"
|
|
2697
|
+
}
|
|
2698
|
+
},
|
|
2699
|
+
required: ["action"]
|
|
2700
|
+
}
|
|
2701
|
+
},
|
|
2702
|
+
{
|
|
2703
|
+
name: "mindgraph_plan",
|
|
2704
|
+
description: "Authoritative durable engineering work and execution evidence. Use create_task/create_plan/add_step for work that must survive sessions; resume_work for the deterministic bounded brief; claim_task + heartbeat for the fenced lease; and start_iteration/checkpoint_iteration/block_task/complete_task/abandon_iteration for idempotent material attempts. Every composite write needs task version, Session, lease epoch, and an idempotency key. Use the harness-native todo list only for local coordination; never maintain the same authoritative Task in both systems.",
|
|
2705
|
+
inputSchema: {
|
|
2706
|
+
type: "object",
|
|
2707
|
+
properties: {
|
|
2708
|
+
action: {
|
|
2709
|
+
type: "string",
|
|
2710
|
+
enum: [
|
|
2711
|
+
"create_task",
|
|
2712
|
+
"create_plan",
|
|
2713
|
+
"add_step",
|
|
2714
|
+
"update_status",
|
|
2715
|
+
"get_plan",
|
|
2716
|
+
"resume_work",
|
|
2717
|
+
"claim_task",
|
|
2718
|
+
"heartbeat",
|
|
2719
|
+
"start_iteration",
|
|
2720
|
+
"checkpoint_iteration",
|
|
2721
|
+
"block_task",
|
|
2722
|
+
"complete_task",
|
|
2723
|
+
"abandon_iteration",
|
|
2724
|
+
"start_execution",
|
|
2725
|
+
"complete_execution",
|
|
2726
|
+
"fail_execution",
|
|
2727
|
+
"register_agent",
|
|
2728
|
+
"get_executions",
|
|
2729
|
+
// C33: governance WRITES are deliberately not exposed here.
|
|
2730
|
+
// `create_policy` let a model author the rules it is judged by;
|
|
2731
|
+
// `request_approval` + `resolve_approval` let it raise and then
|
|
2732
|
+
// clear its own gate, which defeats the human-in-the-loop control
|
|
2733
|
+
// entirely. Authoring and resolving happen in the dashboard, by a
|
|
2734
|
+
// person. `get_pending` stays — reading the queue is safe and is
|
|
2735
|
+
// what an agent needs in order to wait correctly.
|
|
2736
|
+
"get_pending",
|
|
2737
|
+
"create_flow",
|
|
2738
|
+
"add_procedure_step",
|
|
2739
|
+
"add_affordance",
|
|
2740
|
+
"add_control",
|
|
2741
|
+
"assess_risk",
|
|
2742
|
+
"get_assessments"
|
|
2743
|
+
],
|
|
2744
|
+
description: "Planning, procedure, or governance action"
|
|
2745
|
+
},
|
|
2746
|
+
label: {
|
|
2747
|
+
type: "string",
|
|
2748
|
+
description: "Task, plan, step, flow, policy, or risk label"
|
|
2749
|
+
},
|
|
2750
|
+
summary: {
|
|
2751
|
+
type: "string",
|
|
2752
|
+
description: "Description"
|
|
2753
|
+
},
|
|
2754
|
+
plan_uid: {
|
|
2755
|
+
type: "string",
|
|
2756
|
+
description: "Plan UID (for create_plan execution link, add_step, get_plan)"
|
|
2757
|
+
},
|
|
2758
|
+
task_uid: {
|
|
2759
|
+
type: "string",
|
|
2760
|
+
description: "Task UID (for create_plan and graph-managed work actions)"
|
|
2761
|
+
},
|
|
2762
|
+
step_uid: {
|
|
2763
|
+
type: "string",
|
|
2764
|
+
description: "PlanStep UID for an iteration/checkpoint"
|
|
2765
|
+
},
|
|
2766
|
+
session_uid: {
|
|
2767
|
+
type: "string",
|
|
2768
|
+
description: "Durable Session UID that owns the task lease"
|
|
2769
|
+
},
|
|
2770
|
+
goal_uid: {
|
|
2771
|
+
type: "string",
|
|
2772
|
+
description: "Goal UID targeted by a task or plan"
|
|
2773
|
+
},
|
|
2774
|
+
target_uid: {
|
|
2775
|
+
type: "string",
|
|
2776
|
+
description: "Task/plan/step UID for update_status, or parent/target node for procedure and risk actions"
|
|
2777
|
+
},
|
|
2778
|
+
execution_uid: {
|
|
2779
|
+
type: "string",
|
|
2780
|
+
description: "Execution UID (required for complete_execution/fail_execution)"
|
|
2781
|
+
},
|
|
2782
|
+
executor_uid: {
|
|
2783
|
+
type: "string",
|
|
2784
|
+
description: "Agent node UID that executed the work"
|
|
2785
|
+
},
|
|
2786
|
+
affordance_uid: {
|
|
2787
|
+
type: "string",
|
|
2788
|
+
description: "Affordance invoked by start_execution"
|
|
2789
|
+
},
|
|
2790
|
+
produces_node_uid: {
|
|
2791
|
+
type: "string",
|
|
2792
|
+
description: "Node produced by a completed execution"
|
|
2793
|
+
},
|
|
2794
|
+
filter_plan_uid: {
|
|
2795
|
+
type: "string",
|
|
2796
|
+
description: "Restrict get_executions to one plan"
|
|
2797
|
+
},
|
|
2798
|
+
depends_on_uids: {
|
|
2799
|
+
type: "array",
|
|
2800
|
+
items: { type: "string" },
|
|
2801
|
+
description: "Plan-step UIDs that an added step depends on"
|
|
2802
|
+
},
|
|
2803
|
+
related_uids: {
|
|
2804
|
+
type: "array",
|
|
2805
|
+
items: { type: "string" },
|
|
2806
|
+
description: "Target node UIDs to link to created work or an execution"
|
|
2807
|
+
},
|
|
2808
|
+
status: {
|
|
2809
|
+
type: "string",
|
|
2810
|
+
description: "New status value (for update_status)"
|
|
2811
|
+
},
|
|
2812
|
+
task_status: {
|
|
2813
|
+
type: "string",
|
|
2814
|
+
description: "Task status requested by a checkpoint or abandon action"
|
|
2815
|
+
},
|
|
2816
|
+
step_status: {
|
|
2817
|
+
type: "string",
|
|
2818
|
+
description: "PlanStep status requested by a checkpoint"
|
|
2819
|
+
},
|
|
2820
|
+
execution_status: {
|
|
2821
|
+
type: "string",
|
|
2822
|
+
enum: ["completed", "failed", "abandoned"],
|
|
2823
|
+
description: "Terminal Execution status for checkpoint_iteration"
|
|
2824
|
+
},
|
|
2825
|
+
expected_version: {
|
|
2826
|
+
type: "number",
|
|
2827
|
+
description: "Current Task node version used as the optimistic fence"
|
|
2828
|
+
},
|
|
2829
|
+
lease_epoch: {
|
|
2830
|
+
type: "number",
|
|
2831
|
+
description: "Current monotonic task-lease epoch"
|
|
2832
|
+
},
|
|
2833
|
+
lease_ttl_secs: {
|
|
2834
|
+
type: "number",
|
|
2835
|
+
minimum: 1,
|
|
2836
|
+
description: "Requested server-time lease lifetime (default 300s)"
|
|
2837
|
+
},
|
|
2838
|
+
idempotency_key: {
|
|
2839
|
+
type: "string",
|
|
2840
|
+
description: "Caller-stable key required for every composite work mutation"
|
|
2841
|
+
},
|
|
2842
|
+
allow_takeover: {
|
|
2843
|
+
type: "boolean",
|
|
2844
|
+
description: "Governed claim override for a live lease owned by another agent"
|
|
2845
|
+
},
|
|
2846
|
+
release_lease: {
|
|
2847
|
+
type: "boolean",
|
|
2848
|
+
description: "Explicitly release or retain the lease after block/abandon/checkpoint"
|
|
2849
|
+
},
|
|
2850
|
+
override_reason: {
|
|
2851
|
+
type: "string",
|
|
2852
|
+
description: "Explicit completion reason when no terminal Execution evidence exists"
|
|
2853
|
+
},
|
|
2854
|
+
scope_uids: {
|
|
2855
|
+
type: "array",
|
|
2856
|
+
items: { type: "string" },
|
|
2857
|
+
description: "Ordinary target UIDs that bound deterministic resume selection"
|
|
2858
|
+
},
|
|
2859
|
+
limit: {
|
|
2860
|
+
type: "number",
|
|
2861
|
+
minimum: 1,
|
|
2862
|
+
maximum: 1,
|
|
2863
|
+
description: "Resume returns one authoritative Task"
|
|
2864
|
+
},
|
|
2865
|
+
input_snapshot: {
|
|
2866
|
+
type: "object",
|
|
2867
|
+
description: "Structured inputs captured when an execution starts"
|
|
2868
|
+
},
|
|
2869
|
+
output_snapshot: {
|
|
2870
|
+
type: "object",
|
|
2871
|
+
description: "Structured outputs captured when an execution completes or fails"
|
|
2872
|
+
},
|
|
2873
|
+
outcome: {
|
|
2874
|
+
type: "string",
|
|
2875
|
+
description: "Execution outcome"
|
|
2876
|
+
},
|
|
2877
|
+
error: {
|
|
2878
|
+
type: "string",
|
|
2879
|
+
description: "Execution error detail (for fail_execution)"
|
|
2880
|
+
},
|
|
2881
|
+
side_effects: {
|
|
2882
|
+
type: "array",
|
|
2883
|
+
items: { type: "string" },
|
|
2884
|
+
description: "Observable side effects of the execution"
|
|
2885
|
+
},
|
|
2886
|
+
next_action: {
|
|
1007
2887
|
type: "string",
|
|
1008
|
-
description: "
|
|
2888
|
+
description: "Recommended next action retained on terminal Execution evidence"
|
|
1009
2889
|
},
|
|
1010
|
-
|
|
2890
|
+
checkpoint_summary: {
|
|
1011
2891
|
type: "string",
|
|
1012
|
-
description: "
|
|
2892
|
+
description: "Bounded material-attempt checkpoint summary"
|
|
1013
2893
|
},
|
|
1014
|
-
|
|
2894
|
+
test_summary: {
|
|
1015
2895
|
type: "string",
|
|
1016
|
-
description: "
|
|
2896
|
+
description: "Test commands/results retained on the Execution"
|
|
2897
|
+
},
|
|
2898
|
+
produces_node_uids: {
|
|
2899
|
+
type: "array",
|
|
2900
|
+
items: { type: "string" },
|
|
2901
|
+
description: "Nodes produced by checkpoint_iteration"
|
|
1017
2902
|
},
|
|
1018
2903
|
confidence: {
|
|
1019
2904
|
type: "number",
|
|
@@ -1027,77 +2912,17 @@ var init_tools = __esm({
|
|
|
1027
2912
|
},
|
|
1028
2913
|
props: {
|
|
1029
2914
|
type: "object",
|
|
1030
|
-
description: "
|
|
1031
|
-
},
|
|
1032
|
-
agent_id: {
|
|
1033
|
-
type: "string",
|
|
1034
|
-
description: "Agent identity"
|
|
1035
|
-
}
|
|
1036
|
-
},
|
|
1037
|
-
required: ["action"]
|
|
1038
|
-
}
|
|
1039
|
-
},
|
|
1040
|
-
{
|
|
1041
|
-
name: "mindgraph_plan",
|
|
1042
|
-
description: "Agent-level task management, governance, and procedural knowledge. Use 'create_plan'/'create_task'/'add_step' for task breakdowns, 'update_status' for progress, 'get_plan' to review. Use 'create_flow'/'add_procedure_step'/'add_affordance'/'add_control' for workflows and procedures. Use 'assess_risk'/'get_assessments' for risk analysis. Use 'get_pending' to read the governance approval queue \u2014 approvals are granted by a person in the MindGraph dashboard, never from here, so wait for one rather than trying to clear it. Prefer mindgraph_commit for user goals/projects; use this for agent-managed execution.",
|
|
1043
|
-
inputSchema: {
|
|
1044
|
-
type: "object",
|
|
1045
|
-
properties: {
|
|
1046
|
-
action: {
|
|
1047
|
-
type: "string",
|
|
1048
|
-
enum: [
|
|
1049
|
-
"create_task",
|
|
1050
|
-
"create_plan",
|
|
1051
|
-
"add_step",
|
|
1052
|
-
"update_status",
|
|
1053
|
-
"get_plan",
|
|
1054
|
-
"start_execution",
|
|
1055
|
-
"complete_execution",
|
|
1056
|
-
"fail_execution",
|
|
1057
|
-
// C33: governance WRITES are deliberately not exposed here.
|
|
1058
|
-
// `create_policy` let a model author the rules it is judged by;
|
|
1059
|
-
// `request_approval` + `resolve_approval` let it raise and then
|
|
1060
|
-
// clear its own gate, which defeats the human-in-the-loop control
|
|
1061
|
-
// entirely. Authoring and resolving happen in the dashboard, by a
|
|
1062
|
-
// person. `get_pending` stays — reading the queue is safe and is
|
|
1063
|
-
// what an agent needs in order to wait correctly.
|
|
1064
|
-
"get_pending",
|
|
1065
|
-
"create_flow",
|
|
1066
|
-
"add_procedure_step",
|
|
1067
|
-
"add_affordance",
|
|
1068
|
-
"add_control",
|
|
1069
|
-
"assess_risk",
|
|
1070
|
-
"get_assessments"
|
|
1071
|
-
],
|
|
1072
|
-
description: "Planning, procedure, or governance action"
|
|
1073
|
-
},
|
|
1074
|
-
label: {
|
|
1075
|
-
type: "string",
|
|
1076
|
-
description: "Task, plan, step, flow, policy, or risk label"
|
|
1077
|
-
},
|
|
1078
|
-
summary: {
|
|
1079
|
-
type: "string",
|
|
1080
|
-
description: "Description"
|
|
1081
|
-
},
|
|
1082
|
-
plan_uid: {
|
|
1083
|
-
type: "string",
|
|
1084
|
-
description: "Plan UID (for add_step, get_plan)"
|
|
1085
|
-
},
|
|
1086
|
-
task_uid: {
|
|
1087
|
-
type: "string",
|
|
1088
|
-
description: "Task/step UID (for update_status, execution actions)"
|
|
2915
|
+
description: "Additional properties (e.g. likelihood, impact, mitigation for risks)"
|
|
1089
2916
|
},
|
|
1090
|
-
|
|
1091
|
-
type: "
|
|
1092
|
-
|
|
2917
|
+
code_refs: {
|
|
2918
|
+
type: "array",
|
|
2919
|
+
items: { oneOf: [{ type: "string" }, { type: "object" }] },
|
|
2920
|
+
description: "Typed code targets to anchor and attach to created work or execution"
|
|
1093
2921
|
},
|
|
1094
|
-
|
|
2922
|
+
repo: { type: "string", description: "Configured repository id or root" },
|
|
2923
|
+
repo_space_uid: {
|
|
1095
2924
|
type: "string",
|
|
1096
|
-
description: "
|
|
1097
|
-
},
|
|
1098
|
-
props: {
|
|
1099
|
-
type: "object",
|
|
1100
|
-
description: "Additional properties (e.g. likelihood, impact, mitigation for risks)"
|
|
2925
|
+
description: "Writable shared repository/Project Space for new code anchors"
|
|
1101
2926
|
},
|
|
1102
2927
|
agent_id: {
|
|
1103
2928
|
type: "string",
|
|
@@ -1105,6 +2930,12 @@ var init_tools = __esm({
|
|
|
1105
2930
|
}
|
|
1106
2931
|
},
|
|
1107
2932
|
required: ["action"]
|
|
2933
|
+
},
|
|
2934
|
+
annotations: {
|
|
2935
|
+
readOnlyHint: false,
|
|
2936
|
+
destructiveHint: false,
|
|
2937
|
+
idempotentHint: false,
|
|
2938
|
+
openWorldHint: true
|
|
1108
2939
|
}
|
|
1109
2940
|
},
|
|
1110
2941
|
{
|
|
@@ -1479,16 +3310,45 @@ var init_tools = __esm({
|
|
|
1479
3310
|
},
|
|
1480
3311
|
required: ["action"]
|
|
1481
3312
|
}
|
|
1482
|
-
}
|
|
3313
|
+
},
|
|
3314
|
+
SYNC_TOOL,
|
|
3315
|
+
CODE_TOOL
|
|
1483
3316
|
];
|
|
3317
|
+
INVOCATION_CONTEXT_SCHEMA = {
|
|
3318
|
+
type: "object",
|
|
3319
|
+
description: "Adapter-owned invocation context. Models should omit it; compatible hooks replace forged or absent values. cwd is consumed locally and never sent to the API.",
|
|
3320
|
+
properties: {
|
|
3321
|
+
harness: { type: "string", enum: ["claude-code", "codex", "generic"] },
|
|
3322
|
+
harnessSessionId: { type: "string" },
|
|
3323
|
+
harnessTurnId: { type: "string" },
|
|
3324
|
+
cwd: { type: "string" },
|
|
3325
|
+
repoId: { type: "string" },
|
|
3326
|
+
branch: { type: "string" },
|
|
3327
|
+
commit: { type: "string" },
|
|
3328
|
+
worktreeState: { type: "string", enum: ["clean", "dirty"] },
|
|
3329
|
+
model: { type: "string" },
|
|
3330
|
+
injectedBy: { type: "string", enum: ["hook", "mcp-process", "caller"] }
|
|
3331
|
+
}
|
|
3332
|
+
};
|
|
3333
|
+
for (const tool of TOOLS) {
|
|
3334
|
+
if (tool.name === "mindgraph_retrieve") continue;
|
|
3335
|
+
const schema = tool.inputSchema;
|
|
3336
|
+
if (schema.properties && !schema.properties.invocation_context) {
|
|
3337
|
+
schema.properties.invocation_context = INVOCATION_CONTEXT_SCHEMA;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
1484
3340
|
}
|
|
1485
3341
|
});
|
|
1486
3342
|
|
|
1487
3343
|
// src/generated-tools.ts
|
|
1488
3344
|
async function getGeneratedTools(client2) {
|
|
1489
|
-
|
|
3345
|
+
const now = Date.now();
|
|
3346
|
+
if (cache && now - cache.fetchedAt < CACHE_TTL_MS) {
|
|
1490
3347
|
return { tools: cache.tools, byName: cache.byName };
|
|
1491
3348
|
}
|
|
3349
|
+
if (now < retryNotBefore) {
|
|
3350
|
+
return cache ? { tools: cache.tools, byName: cache.byName } : EMPTY;
|
|
3351
|
+
}
|
|
1492
3352
|
try {
|
|
1493
3353
|
const c = client2;
|
|
1494
3354
|
if (typeof c.listOntologyTools !== "function") {
|
|
@@ -1512,11 +3372,12 @@ async function getGeneratedTools(client2) {
|
|
|
1512
3372
|
});
|
|
1513
3373
|
}
|
|
1514
3374
|
cache = { tools, byName, fetchedAt: Date.now() };
|
|
3375
|
+
retryNotBefore = 0;
|
|
1515
3376
|
return { tools, byName };
|
|
1516
3377
|
} catch (error) {
|
|
1517
3378
|
if (error instanceof DuplicateGeneratedToolError) throw error;
|
|
1518
|
-
|
|
1519
|
-
return { tools: cache.tools, byName: cache.byName };
|
|
3379
|
+
retryNotBefore = Date.now() + FAILURE_COOLDOWN_MS;
|
|
3380
|
+
return cache ? { tools: cache.tools, byName: cache.byName } : EMPTY;
|
|
1520
3381
|
}
|
|
1521
3382
|
}
|
|
1522
3383
|
async function handleGeneratedTool(client2, desc, args) {
|
|
@@ -1525,10 +3386,10 @@ async function handleGeneratedTool(client2, desc, args) {
|
|
|
1525
3386
|
switch (desc.maps_to) {
|
|
1526
3387
|
case "search": {
|
|
1527
3388
|
const query = args.query;
|
|
1528
|
-
if (!query) return
|
|
3389
|
+
if (!query) return err4(`query is required for ${desc.name}`);
|
|
1529
3390
|
const limit = typeof args.limit === "number" ? args.limit : void 0;
|
|
1530
3391
|
const filters = Array.isArray(args.filters) ? args.filters : void 0;
|
|
1531
|
-
return
|
|
3392
|
+
return ok4(
|
|
1532
3393
|
await ontologyClient.searchDomainObjects(query, {
|
|
1533
3394
|
schema_id: desc.schema_id,
|
|
1534
3395
|
object_types: [desc.object_type],
|
|
@@ -1539,33 +3400,33 @@ async function handleGeneratedTool(client2, desc, args) {
|
|
|
1539
3400
|
}
|
|
1540
3401
|
case "object": {
|
|
1541
3402
|
const uid = args.uid;
|
|
1542
|
-
if (!uid) return
|
|
1543
|
-
return
|
|
3403
|
+
if (!uid) return err4(`uid is required for ${desc.name}`);
|
|
3404
|
+
return ok4(await ontologyClient.getDomainObject(uid, {
|
|
1544
3405
|
schema_id: desc.schema_id,
|
|
1545
3406
|
object_type: desc.object_type
|
|
1546
3407
|
}));
|
|
1547
3408
|
}
|
|
1548
3409
|
case "object_context": {
|
|
1549
3410
|
const uid = args.uid;
|
|
1550
|
-
if (!uid) return
|
|
3411
|
+
if (!uid) return err4(`uid is required for ${desc.name}`);
|
|
1551
3412
|
const depth = typeof args.depth === "number" ? args.depth : void 0;
|
|
1552
|
-
return
|
|
3413
|
+
return ok4(await ontologyClient.getDomainObjectContext(uid, depth, {
|
|
1553
3414
|
schema_id: desc.schema_id,
|
|
1554
3415
|
object_type: desc.object_type
|
|
1555
3416
|
}));
|
|
1556
3417
|
}
|
|
1557
3418
|
case "related": {
|
|
1558
3419
|
const uid = args.uid;
|
|
1559
|
-
if (!uid) return
|
|
3420
|
+
if (!uid) return err4(`uid is required for ${desc.name}`);
|
|
1560
3421
|
if (!desc.relation || !desc.entry_role || !desc.far_type) {
|
|
1561
|
-
return
|
|
3422
|
+
return err4(`invalid related descriptor: ${desc.name}`);
|
|
1562
3423
|
}
|
|
1563
3424
|
if (!ontologyClient.queryRelatedDomainObjects) {
|
|
1564
|
-
return
|
|
3425
|
+
return err4(
|
|
1565
3426
|
"related ontology tools require a graph-aware version of the mindgraph SDK"
|
|
1566
3427
|
);
|
|
1567
3428
|
}
|
|
1568
|
-
return
|
|
3429
|
+
return ok4(await ontologyClient.queryRelatedDomainObjects({
|
|
1569
3430
|
schema_id: desc.schema_id,
|
|
1570
3431
|
entry_type: desc.object_type,
|
|
1571
3432
|
uid,
|
|
@@ -1580,44 +3441,67 @@ async function handleGeneratedTool(client2, desc, args) {
|
|
|
1580
3441
|
}
|
|
1581
3442
|
case "structured_query": {
|
|
1582
3443
|
const select = args.select;
|
|
1583
|
-
if (!select) return
|
|
3444
|
+
if (!select) return err4(`select is required for ${desc.name}`);
|
|
1584
3445
|
if (!ontologyClient.queryDomainStructured) {
|
|
1585
|
-
return
|
|
3446
|
+
return err4(
|
|
1586
3447
|
"structured ontology tools require a graph-aware version of the mindgraph SDK"
|
|
1587
3448
|
);
|
|
1588
3449
|
}
|
|
1589
|
-
|
|
1590
|
-
...args,
|
|
3450
|
+
const request = {
|
|
1591
3451
|
schema_id: desc.schema_id,
|
|
1592
3452
|
select
|
|
1593
|
-
}
|
|
3453
|
+
};
|
|
3454
|
+
for (const field of STRUCTURED_QUERY_PASSTHROUGH) {
|
|
3455
|
+
if (args[field] !== void 0) request[field] = args[field];
|
|
3456
|
+
}
|
|
3457
|
+
return ok4(await ontologyClient.queryDomainStructured(request));
|
|
1594
3458
|
}
|
|
1595
3459
|
default:
|
|
1596
|
-
return
|
|
3460
|
+
return err4(`unknown generated tool mapping: ${desc.maps_to}`);
|
|
1597
3461
|
}
|
|
1598
3462
|
} catch (e) {
|
|
1599
|
-
return
|
|
3463
|
+
return err4(e instanceof Error ? e.message : String(e));
|
|
1600
3464
|
}
|
|
1601
3465
|
}
|
|
1602
|
-
var
|
|
3466
|
+
var ok4, err4, CACHE_TTL_MS, FAILURE_COOLDOWN_MS, cache, retryNotBefore, EMPTY, DuplicateGeneratedToolError, STRUCTURED_QUERY_PASSTHROUGH;
|
|
1603
3467
|
var init_generated_tools = __esm({
|
|
1604
3468
|
"src/generated-tools.ts"() {
|
|
1605
3469
|
"use strict";
|
|
1606
|
-
|
|
3470
|
+
ok4 = (data) => ({
|
|
1607
3471
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1608
3472
|
});
|
|
1609
|
-
|
|
3473
|
+
err4 = (message) => ({
|
|
1610
3474
|
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
1611
3475
|
isError: true
|
|
1612
3476
|
});
|
|
1613
3477
|
CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3478
|
+
FAILURE_COOLDOWN_MS = 15 * 1e3;
|
|
1614
3479
|
cache = null;
|
|
3480
|
+
retryNotBefore = 0;
|
|
3481
|
+
EMPTY = {
|
|
3482
|
+
tools: [],
|
|
3483
|
+
byName: /* @__PURE__ */ new Map()
|
|
3484
|
+
};
|
|
1615
3485
|
DuplicateGeneratedToolError = class extends Error {
|
|
1616
3486
|
};
|
|
3487
|
+
STRUCTURED_QUERY_PASSTHROUGH = [
|
|
3488
|
+
"anchor",
|
|
3489
|
+
"path",
|
|
3490
|
+
"where",
|
|
3491
|
+
"include",
|
|
3492
|
+
"page",
|
|
3493
|
+
"aggregate"
|
|
3494
|
+
];
|
|
1617
3495
|
}
|
|
1618
3496
|
});
|
|
1619
3497
|
|
|
1620
3498
|
// src/governance.ts
|
|
3499
|
+
function isTransientStatus(status) {
|
|
3500
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
3501
|
+
}
|
|
3502
|
+
function sleep(ms) {
|
|
3503
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
3504
|
+
}
|
|
1621
3505
|
function headers(apiKey, orgId) {
|
|
1622
3506
|
const value = {
|
|
1623
3507
|
authorization: `Bearer ${apiKey}`,
|
|
@@ -1632,42 +3516,128 @@ function unsupportedResponse(status, body) {
|
|
|
1632
3516
|
const lower = body.toLowerCase();
|
|
1633
3517
|
return lower.includes("unknown governance action") || lower.includes("unknown action") || lower.includes("invalid_governance_action");
|
|
1634
3518
|
}
|
|
3519
|
+
function toolMutability(toolName, action) {
|
|
3520
|
+
if (toolName === "mindgraph_retrieve") return "read";
|
|
3521
|
+
if (toolName === "mindgraph_plan") {
|
|
3522
|
+
return action && PLAN_READ_ACTIONS.has(action) ? "read" : "write";
|
|
3523
|
+
}
|
|
3524
|
+
if (toolName === "mindgraph_ingest") {
|
|
3525
|
+
return action === "job_status" ? "read" : "write";
|
|
3526
|
+
}
|
|
3527
|
+
if (toolName === "mindgraph_synthesize") {
|
|
3528
|
+
return action === "signals" ? "read" : "write";
|
|
3529
|
+
}
|
|
3530
|
+
if (toolName === "mindgraph_code") {
|
|
3531
|
+
return action === "anchor" ? "write" : "read";
|
|
3532
|
+
}
|
|
3533
|
+
if (toolName === "mindgraph_sync") {
|
|
3534
|
+
return action === "scan" || action === "status" ? "read" : "write";
|
|
3535
|
+
}
|
|
3536
|
+
if (toolName === "mindgraph_ontology") {
|
|
3537
|
+
return action && ONTOLOGY_READ_ACTIONS.has(action) ? "read" : "write";
|
|
3538
|
+
}
|
|
3539
|
+
if (/_(search|get|context|related|query)$/.test(toolName) && toolName !== "mindgraph_capture") {
|
|
3540
|
+
return "read";
|
|
3541
|
+
}
|
|
3542
|
+
return "write";
|
|
3543
|
+
}
|
|
3544
|
+
function governanceTarget(toolName, args) {
|
|
3545
|
+
const action = typeof args.action === "string" ? args.action : void 0;
|
|
3546
|
+
const targetUids = /* @__PURE__ */ new Set();
|
|
3547
|
+
for (const field of UID_FIELDS) {
|
|
3548
|
+
const value = args[field];
|
|
3549
|
+
if (typeof value === "string" && value.length > 0) targetUids.add(value);
|
|
3550
|
+
}
|
|
3551
|
+
for (const field of UID_ARRAY_FIELDS) {
|
|
3552
|
+
const value = args[field];
|
|
3553
|
+
if (!Array.isArray(value)) continue;
|
|
3554
|
+
for (const uid of value) {
|
|
3555
|
+
if (typeof uid === "string" && uid.length > 0) targetUids.add(uid);
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
return {
|
|
3559
|
+
tool_name: toolName,
|
|
3560
|
+
...action ? { action } : {},
|
|
3561
|
+
mutability: toolMutability(toolName, action),
|
|
3562
|
+
target_uids: [...targetUids]
|
|
3563
|
+
};
|
|
3564
|
+
}
|
|
1635
3565
|
async function checkMcpGovernance(toolName, args, config) {
|
|
1636
|
-
if (config.disabled
|
|
3566
|
+
if (config.disabled) return { allowed: true };
|
|
3567
|
+
if (capability === "unsupported") {
|
|
3568
|
+
if (Date.now() < unsupportedUntil) return { allowed: true };
|
|
3569
|
+
capability = "unknown";
|
|
3570
|
+
}
|
|
1637
3571
|
const fetchImpl = config.fetchImpl ?? fetch;
|
|
3572
|
+
const retryDelays = config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
|
|
1638
3573
|
let response;
|
|
1639
|
-
let body;
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
3574
|
+
let body = "";
|
|
3575
|
+
let lastFailure = "";
|
|
3576
|
+
for (let attempt = 0; attempt <= retryDelays.length; attempt++) {
|
|
3577
|
+
try {
|
|
3578
|
+
const candidate = await fetchImpl(
|
|
3579
|
+
`${config.baseUrl.replace(/\/$/, "")}/agent/governance`,
|
|
3580
|
+
{
|
|
3581
|
+
method: "POST",
|
|
3582
|
+
headers: headers(config.apiKey, config.orgId),
|
|
3583
|
+
body: JSON.stringify({
|
|
3584
|
+
action: "check",
|
|
3585
|
+
act: "tool_invoke",
|
|
3586
|
+
agent_id: String(args.agent_id || config.agentId),
|
|
3587
|
+
target: governanceTarget(toolName, args),
|
|
3588
|
+
context: { adapter: "mindgraph-mcp", ...config.context },
|
|
3589
|
+
tier: "checkpoint"
|
|
3590
|
+
})
|
|
3591
|
+
}
|
|
3592
|
+
);
|
|
3593
|
+
const candidateBody = await candidate.text();
|
|
3594
|
+
if (isTransientStatus(candidate.status) && attempt < retryDelays.length) {
|
|
3595
|
+
lastFailure = `HTTP ${candidate.status}`;
|
|
3596
|
+
await sleep(retryDelays[attempt]);
|
|
3597
|
+
continue;
|
|
3598
|
+
}
|
|
3599
|
+
response = candidate;
|
|
3600
|
+
body = candidateBody;
|
|
3601
|
+
break;
|
|
3602
|
+
} catch (error) {
|
|
3603
|
+
lastFailure = error instanceof Error ? error.message : String(error);
|
|
3604
|
+
if (attempt < retryDelays.length) {
|
|
3605
|
+
await sleep(retryDelays[attempt]);
|
|
3606
|
+
continue;
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
if (!response) {
|
|
1655
3611
|
return {
|
|
1656
3612
|
allowed: false,
|
|
1657
|
-
message: `MindGraph governance could not establish permission
|
|
3613
|
+
message: `MindGraph governance could not establish permission after ${retryDelays.length + 1} attempts (${lastFailure}). Set MINDGRAPH_GOVERNANCE=off to run this adapter ungoverned.`
|
|
1658
3614
|
};
|
|
1659
3615
|
}
|
|
1660
3616
|
if (!response.ok) {
|
|
1661
3617
|
if (capability === "unknown" && unsupportedResponse(response.status, body)) {
|
|
1662
3618
|
capability = "unsupported";
|
|
3619
|
+
unsupportedUntil = Date.now() + UNSUPPORTED_LEASE_MS;
|
|
3620
|
+
if (!unsupportedLogged) {
|
|
3621
|
+
unsupportedLogged = true;
|
|
3622
|
+
console.error(
|
|
3623
|
+
`[mindgraph-mcp] This MindGraph server does not implement the governance checkpoint (HTTP ${response.status}); tool calls proceed ungoverned. Re-probing every ${Math.round(UNSUPPORTED_LEASE_MS / 6e4)} minutes.`
|
|
3624
|
+
);
|
|
3625
|
+
}
|
|
1663
3626
|
return { allowed: true };
|
|
1664
3627
|
}
|
|
3628
|
+
if (response.status === 401 || response.status === 403) {
|
|
3629
|
+
return {
|
|
3630
|
+
allowed: false,
|
|
3631
|
+
message: `This credential is not permitted to evaluate MindGraph governance (HTTP ${response.status}). Use a user-scoped API key, or set MINDGRAPH_GOVERNANCE=off to run this adapter ungoverned.`
|
|
3632
|
+
};
|
|
3633
|
+
}
|
|
1665
3634
|
return {
|
|
1666
3635
|
allowed: false,
|
|
1667
3636
|
message: `MindGraph governance check failed (${response.status})`
|
|
1668
3637
|
};
|
|
1669
3638
|
}
|
|
1670
3639
|
capability = "supported";
|
|
3640
|
+
unsupportedLogged = false;
|
|
1671
3641
|
let decision;
|
|
1672
3642
|
try {
|
|
1673
3643
|
decision = JSON.parse(body);
|
|
@@ -1701,12 +3671,180 @@ async function checkMcpGovernance(toolName, args, config) {
|
|
|
1701
3671
|
message: verdict === "conditional" ? `Approval is required${policy ? ` by policy ${policy}` : ""}${approvalUid ? ` (approval ${approvalUid})` : ""}` : `Denied by MindGraph governance${policy ? ` policy ${policy}` : ""}`
|
|
1702
3672
|
};
|
|
1703
3673
|
}
|
|
1704
|
-
var capability, KNOWN_DUTIES;
|
|
3674
|
+
var capability, UNSUPPORTED_LEASE_MS, unsupportedUntil, unsupportedLogged, DEFAULT_RETRY_DELAYS_MS, KNOWN_DUTIES, PLAN_READ_ACTIONS, ONTOLOGY_READ_ACTIONS, UID_FIELDS, UID_ARRAY_FIELDS;
|
|
1705
3675
|
var init_governance = __esm({
|
|
1706
3676
|
"src/governance.ts"() {
|
|
1707
3677
|
"use strict";
|
|
1708
3678
|
capability = "unknown";
|
|
3679
|
+
UNSUPPORTED_LEASE_MS = 5 * 60 * 1e3;
|
|
3680
|
+
unsupportedUntil = 0;
|
|
3681
|
+
unsupportedLogged = false;
|
|
3682
|
+
DEFAULT_RETRY_DELAYS_MS = [150, 600];
|
|
1709
3683
|
KNOWN_DUTIES = /* @__PURE__ */ new Set(["log", "require_approval"]);
|
|
3684
|
+
PLAN_READ_ACTIONS = /* @__PURE__ */ new Set([
|
|
3685
|
+
"get_plan",
|
|
3686
|
+
"get_executions",
|
|
3687
|
+
"get_pending",
|
|
3688
|
+
"get_assessments",
|
|
3689
|
+
"resume_work"
|
|
3690
|
+
]);
|
|
3691
|
+
ONTOLOGY_READ_ACTIONS = /* @__PURE__ */ new Set([
|
|
3692
|
+
"list_schemas",
|
|
3693
|
+
"get_schema",
|
|
3694
|
+
"query",
|
|
3695
|
+
"search_objects",
|
|
3696
|
+
"list_objects",
|
|
3697
|
+
"get_object",
|
|
3698
|
+
"get_context",
|
|
3699
|
+
"list_proposals",
|
|
3700
|
+
"get_proposal"
|
|
3701
|
+
]);
|
|
3702
|
+
UID_FIELDS = [
|
|
3703
|
+
"uid",
|
|
3704
|
+
"source_uid",
|
|
3705
|
+
"session_uid",
|
|
3706
|
+
"work_uid",
|
|
3707
|
+
"goal_uid",
|
|
3708
|
+
"project_uid",
|
|
3709
|
+
"task_uid",
|
|
3710
|
+
"plan_uid",
|
|
3711
|
+
"step_uid",
|
|
3712
|
+
"target_uid",
|
|
3713
|
+
"execution_uid",
|
|
3714
|
+
"executor_uid",
|
|
3715
|
+
"affordance_uid",
|
|
3716
|
+
"produces_node_uid",
|
|
3717
|
+
"filter_plan_uid",
|
|
3718
|
+
"parent_uid",
|
|
3719
|
+
"decision_uid",
|
|
3720
|
+
"chosen_option_uid",
|
|
3721
|
+
"governed_uid",
|
|
3722
|
+
"approval_uid",
|
|
3723
|
+
"requires_plan_uid",
|
|
3724
|
+
"start_uid",
|
|
3725
|
+
"end_uid"
|
|
3726
|
+
];
|
|
3727
|
+
UID_ARRAY_FIELDS = [
|
|
3728
|
+
"related_uids",
|
|
3729
|
+
"depends_on_uids",
|
|
3730
|
+
"summarizes_uids",
|
|
3731
|
+
"informs_uid",
|
|
3732
|
+
"target_uids",
|
|
3733
|
+
"anchor_uids",
|
|
3734
|
+
"scope_uids",
|
|
3735
|
+
"produces_node_uids"
|
|
3736
|
+
];
|
|
3737
|
+
}
|
|
3738
|
+
});
|
|
3739
|
+
|
|
3740
|
+
// src/resources.ts
|
|
3741
|
+
function json(uri, data) {
|
|
3742
|
+
return {
|
|
3743
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data, null, 2) }]
|
|
3744
|
+
};
|
|
3745
|
+
}
|
|
3746
|
+
async function readResource(client2, uri) {
|
|
3747
|
+
switch (uri) {
|
|
3748
|
+
case "mindgraph://stats":
|
|
3749
|
+
return json(uri, await client2.stats());
|
|
3750
|
+
case "mindgraph://goals":
|
|
3751
|
+
return json(uri, await client2.getGoals());
|
|
3752
|
+
case "mindgraph://questions":
|
|
3753
|
+
return json(uri, await client2.getOpenQuestions());
|
|
3754
|
+
case "mindgraph://contradictions":
|
|
3755
|
+
return json(uri, await client2.getContradictions());
|
|
3756
|
+
case "mindgraph://decisions":
|
|
3757
|
+
return json(uri, await client2.getOpenDecisions());
|
|
3758
|
+
}
|
|
3759
|
+
const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
|
|
3760
|
+
if (nodeMatch) {
|
|
3761
|
+
const uid = decodeURIComponent(nodeMatch[1]);
|
|
3762
|
+
const node = await client2.getNode(uid);
|
|
3763
|
+
const edges = await client2.getEdges({ from_uid: uid });
|
|
3764
|
+
const inEdges = await client2.getEdges({ to_uid: uid });
|
|
3765
|
+
return json(uri, { node, outgoing_edges: edges, incoming_edges: inEdges });
|
|
3766
|
+
}
|
|
3767
|
+
const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
|
|
3768
|
+
if (searchMatch) {
|
|
3769
|
+
const query = decodeURIComponent(searchMatch[1]);
|
|
3770
|
+
return json(uri, await client2.search(query, { limit: 20 }));
|
|
3771
|
+
}
|
|
3772
|
+
const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
|
|
3773
|
+
if (layerMatch) {
|
|
3774
|
+
const layer = decodeURIComponent(layerMatch[1]);
|
|
3775
|
+
return json(uri, await client2.getNodes({ layer, limit: 50 }));
|
|
3776
|
+
}
|
|
3777
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
3778
|
+
}
|
|
3779
|
+
async function readGovernedResource(client2, uri, governanceConfig) {
|
|
3780
|
+
const governance = await checkMcpGovernance(
|
|
3781
|
+
RESOURCE_GOVERNANCE_TOOL,
|
|
3782
|
+
{},
|
|
3783
|
+
{ ...governanceConfig, context: { resource_uri: uri } }
|
|
3784
|
+
);
|
|
3785
|
+
if (!governance.allowed) {
|
|
3786
|
+
throw new Error(governance.message);
|
|
3787
|
+
}
|
|
3788
|
+
return readResource(client2, uri);
|
|
3789
|
+
}
|
|
3790
|
+
var RESOURCES, RESOURCE_TEMPLATES, RESOURCE_GOVERNANCE_TOOL;
|
|
3791
|
+
var init_resources = __esm({
|
|
3792
|
+
"src/resources.ts"() {
|
|
3793
|
+
"use strict";
|
|
3794
|
+
init_governance();
|
|
3795
|
+
RESOURCES = [
|
|
3796
|
+
{
|
|
3797
|
+
uri: "mindgraph://stats",
|
|
3798
|
+
name: "Graph Statistics",
|
|
3799
|
+
description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
|
|
3800
|
+
mimeType: "application/json"
|
|
3801
|
+
},
|
|
3802
|
+
{
|
|
3803
|
+
uri: "mindgraph://goals",
|
|
3804
|
+
name: "Active Goals",
|
|
3805
|
+
description: "All active goals in the knowledge graph",
|
|
3806
|
+
mimeType: "application/json"
|
|
3807
|
+
},
|
|
3808
|
+
{
|
|
3809
|
+
uri: "mindgraph://questions",
|
|
3810
|
+
name: "Open Questions",
|
|
3811
|
+
description: "All open questions in the knowledge graph",
|
|
3812
|
+
mimeType: "application/json"
|
|
3813
|
+
},
|
|
3814
|
+
{
|
|
3815
|
+
uri: "mindgraph://contradictions",
|
|
3816
|
+
name: "Contradictions",
|
|
3817
|
+
description: "Contradictory claims in the knowledge graph",
|
|
3818
|
+
mimeType: "application/json"
|
|
3819
|
+
},
|
|
3820
|
+
{
|
|
3821
|
+
uri: "mindgraph://decisions",
|
|
3822
|
+
name: "Open Decisions",
|
|
3823
|
+
description: "Unresolved decisions awaiting resolution",
|
|
3824
|
+
mimeType: "application/json"
|
|
3825
|
+
}
|
|
3826
|
+
];
|
|
3827
|
+
RESOURCE_TEMPLATES = [
|
|
3828
|
+
{
|
|
3829
|
+
uriTemplate: "mindgraph://node/{uid}",
|
|
3830
|
+
name: "Graph Node",
|
|
3831
|
+
description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
|
|
3832
|
+
mimeType: "application/json"
|
|
3833
|
+
},
|
|
3834
|
+
{
|
|
3835
|
+
uriTemplate: "mindgraph://search/{query}",
|
|
3836
|
+
name: "Search Results",
|
|
3837
|
+
description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
|
|
3838
|
+
mimeType: "application/json"
|
|
3839
|
+
},
|
|
3840
|
+
{
|
|
3841
|
+
uriTemplate: "mindgraph://layer/{layer}",
|
|
3842
|
+
name: "Layer Nodes",
|
|
3843
|
+
description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
|
|
3844
|
+
mimeType: "application/json"
|
|
3845
|
+
}
|
|
3846
|
+
];
|
|
3847
|
+
RESOURCE_GOVERNANCE_TOOL = "mindgraph_retrieve";
|
|
1710
3848
|
}
|
|
1711
3849
|
});
|
|
1712
3850
|
|
|
@@ -1719,28 +3857,31 @@ async function main() {
|
|
|
1719
3857
|
`MindGraph MCP server v0.7.1 running on stdio (${BASE_URL})`
|
|
1720
3858
|
);
|
|
1721
3859
|
}
|
|
1722
|
-
var import_server, import_stdio, import_types,
|
|
3860
|
+
var import_server, import_stdio, import_types, import_mindgraph2, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, server, GOVERNANCE_CONFIG, PROMPTS;
|
|
1723
3861
|
var init_src = __esm({
|
|
1724
3862
|
"src/index.ts"() {
|
|
1725
3863
|
"use strict";
|
|
1726
3864
|
import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
1727
3865
|
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
1728
3866
|
import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
1729
|
-
|
|
3867
|
+
import_mindgraph2 = require("mindgraph");
|
|
1730
3868
|
init_tools();
|
|
1731
3869
|
init_generated_tools();
|
|
1732
3870
|
init_governance();
|
|
3871
|
+
init_resources();
|
|
1733
3872
|
API_KEY = process.env.MINDGRAPH_API_KEY;
|
|
1734
3873
|
BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
|
|
1735
3874
|
AGENT_ID = process.env.MINDGRAPH_AGENT_ID || "mcp";
|
|
1736
3875
|
ORG_ID = process.env.MINDGRAPH_ORG_ID;
|
|
3876
|
+
PROFILE = process.env.MINDGRAPH_PROFILE || "general";
|
|
3877
|
+
HARNESS = process.env.MINDGRAPH_HARNESS || "generic";
|
|
1737
3878
|
if (!API_KEY) {
|
|
1738
3879
|
console.error(
|
|
1739
3880
|
"Error: MINDGRAPH_API_KEY environment variable is required.\nGet your API key at https://mindgraph.cloud/dashboard/keys\nOr run: npx mindgraph-mcp init"
|
|
1740
3881
|
);
|
|
1741
3882
|
process.exit(1);
|
|
1742
3883
|
}
|
|
1743
|
-
client = new
|
|
3884
|
+
client = new import_mindgraph2.MindGraph({
|
|
1744
3885
|
baseUrl: BASE_URL,
|
|
1745
3886
|
apiKey: API_KEY,
|
|
1746
3887
|
orgId: ORG_ID,
|
|
@@ -1749,7 +3890,7 @@ var init_src = __esm({
|
|
|
1749
3890
|
// window where npm may still resolve the preceding SDK declaration file.
|
|
1750
3891
|
telemetrySurface: "mcp"
|
|
1751
3892
|
});
|
|
1752
|
-
INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph
|
|
3893
|
+
INSTRUCTIONS = `You have access to a persistent knowledge graph via MindGraph, plus any ontology tools generated by the active schema.
|
|
1753
3894
|
|
|
1754
3895
|
## #1 RULE: Go straight to the user's topic
|
|
1755
3896
|
|
|
@@ -1812,22 +3953,42 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
1812
3953
|
- Questions to track \u2192 \`mindgraph_reason\` action "open_question"
|
|
1813
3954
|
- Goals or projects \u2192 \`mindgraph_commit\` action "goal"/"project"
|
|
1814
3955
|
- Decisions \u2192 \`mindgraph_commit\` action "open_decision"
|
|
3956
|
+
- Durable engineering work that must survive sessions/harnesses \u2192 \`mindgraph_plan\`
|
|
3957
|
+
- Typed code anchors, attached memory/work, callers/callees, or impact \u2192 \`mindgraph_code\`
|
|
3958
|
+
- Reusable learning from a material attempt \u2192 \`mindgraph_capture\` action "lesson"
|
|
1815
3959
|
- Long-form content \u2192 \`mindgraph_ingest\`
|
|
1816
3960
|
- Cross-document synthesis over a project corpus \u2192 \`mindgraph_synthesize\` (action "signals" for structural signals; action "run" to spawn a synthesis job that writes Article nodes)
|
|
1817
3961
|
|
|
1818
3962
|
## Tool tips
|
|
1819
3963
|
|
|
3964
|
+
- Use the harness todo list to organize only the current harness's local execution. Use \`mindgraph_plan\` when work is authoritative across sessions/harnesses, needs graph relationships or evidence, or must be visible to other agents. Do not maintain the same item in both.
|
|
3965
|
+
- If a local harness task remains unfinished and should become authoritative outside that local list, promote it once with a stable idempotency key when the graph-managed composite actions are available; otherwise do not copy it.
|
|
1820
3966
|
- \`capture\`: "entity"/"observation" for facts, "journal" for personal/informal
|
|
1821
3967
|
- \`reason\`: "claim" when evidence exists, "open_question"/"hypothesis" otherwise
|
|
1822
3968
|
- \`retrieve\`: "context" returns graph nodes only by default \u2014 set include_chunks=true for source text
|
|
1823
3969
|
- \`retrieve\`: "context" (FTS default) \u2192 "semantic" (conceptual fallback) \u2192 "hybrid" (both)
|
|
1824
3970
|
- \`retrieve\`: "neighborhood"/"chain"/"path"/"subgraph" for graph exploration
|
|
3971
|
+
- \`code\`: pass path + symbol + signature when names are overloaded; ambiguity never writes
|
|
1825
3972
|
- Confidence: 0.9+ for facts, 0.5-0.8 for uncertain, <0.5 for speculation
|
|
1826
3973
|
- Don't narrate tool usage \u2014 capture naturally as part of conversation`;
|
|
3974
|
+
if (PROFILE === "coding") {
|
|
3975
|
+
INSTRUCTIONS += `
|
|
3976
|
+
|
|
3977
|
+
## Coding profile
|
|
3978
|
+
|
|
3979
|
+
- 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.
|
|
3980
|
+
- Capture corrections, decisions with rationale, fixes that survived meaningful failed attempts, durable invariants/preferences, and blockers with evidence.
|
|
3981
|
+
- Do not capture routine mechanics, transient command output, facts cheaply derivable from code, unsupported speculation as high-confidence lessons, or completion without evidence.
|
|
3982
|
+
- Adapter context is owned by ${HARNESS}; omit invocation_context because hooks replace it.`;
|
|
3983
|
+
} else {
|
|
3984
|
+
INSTRUCTIONS = INSTRUCTIONS.split("\n").filter(
|
|
3985
|
+
(line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
|
|
3986
|
+
).join("\n");
|
|
3987
|
+
}
|
|
1827
3988
|
server = new import_server.Server(
|
|
1828
3989
|
{
|
|
1829
3990
|
name: "mindgraph",
|
|
1830
|
-
version: "0.
|
|
3991
|
+
version: "0.14.0"
|
|
1831
3992
|
},
|
|
1832
3993
|
{
|
|
1833
3994
|
capabilities: {
|
|
@@ -1840,21 +4001,25 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
1840
4001
|
);
|
|
1841
4002
|
server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
|
|
1842
4003
|
const generated = await getGeneratedTools(client);
|
|
1843
|
-
return { tools: [...
|
|
4004
|
+
return { tools: [...toolsForProfile(PROFILE), ...generated.tools] };
|
|
1844
4005
|
});
|
|
4006
|
+
GOVERNANCE_CONFIG = {
|
|
4007
|
+
baseUrl: BASE_URL,
|
|
4008
|
+
apiKey: API_KEY,
|
|
4009
|
+
orgId: ORG_ID,
|
|
4010
|
+
agentId: AGENT_ID,
|
|
4011
|
+
get disabled() {
|
|
4012
|
+
return process.env.MINDGRAPH_GOVERNANCE === "off";
|
|
4013
|
+
}
|
|
4014
|
+
};
|
|
1845
4015
|
server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
|
|
1846
4016
|
const { name, arguments: args } = request.params;
|
|
1847
|
-
const
|
|
4017
|
+
const rawArgs = { ...args || {} };
|
|
4018
|
+
const toolArgs = { ...rawArgs };
|
|
1848
4019
|
if (!toolArgs.agent_id) {
|
|
1849
4020
|
toolArgs.agent_id = AGENT_ID;
|
|
1850
4021
|
}
|
|
1851
|
-
const governance = await checkMcpGovernance(name, toolArgs,
|
|
1852
|
-
baseUrl: BASE_URL,
|
|
1853
|
-
apiKey: API_KEY,
|
|
1854
|
-
orgId: ORG_ID,
|
|
1855
|
-
agentId: AGENT_ID,
|
|
1856
|
-
disabled: process.env.MINDGRAPH_GOVERNANCE === "off"
|
|
1857
|
-
});
|
|
4022
|
+
const governance = await checkMcpGovernance(name, toolArgs, GOVERNANCE_CONFIG);
|
|
1858
4023
|
if (!governance.allowed) {
|
|
1859
4024
|
return {
|
|
1860
4025
|
content: [{ type: "text", text: JSON.stringify({ error: governance.message, ...governance }) }],
|
|
@@ -1864,7 +4029,7 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
1864
4029
|
const generated = await getGeneratedTools(client);
|
|
1865
4030
|
const desc = generated.byName.get(name);
|
|
1866
4031
|
if (desc) {
|
|
1867
|
-
return handleGeneratedTool(client, desc,
|
|
4032
|
+
return handleGeneratedTool(client, desc, rawArgs);
|
|
1868
4033
|
}
|
|
1869
4034
|
return handleTool(client, name, toolArgs);
|
|
1870
4035
|
});
|
|
@@ -2014,189 +4179,24 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
2014
4179
|
- Any hypotheses or theories discussed \u2192 mindgraph_reason action "hypothesis"/"theory"
|
|
2015
4180
|
|
|
2016
4181
|
Conversation:
|
|
2017
|
-
${conversation}`
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
]
|
|
2021
|
-
};
|
|
2022
|
-
}
|
|
2023
|
-
default:
|
|
2024
|
-
throw new Error(`Unknown prompt: ${name}`);
|
|
2025
|
-
}
|
|
2026
|
-
});
|
|
2027
|
-
server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
|
|
2028
|
-
return {
|
|
2029
|
-
resources: [
|
|
2030
|
-
{
|
|
2031
|
-
uri: "mindgraph://stats",
|
|
2032
|
-
name: "Graph Statistics",
|
|
2033
|
-
description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
|
|
2034
|
-
mimeType: "application/json"
|
|
2035
|
-
},
|
|
2036
|
-
{
|
|
2037
|
-
uri: "mindgraph://goals",
|
|
2038
|
-
name: "Active Goals",
|
|
2039
|
-
description: "All active goals in the knowledge graph",
|
|
2040
|
-
mimeType: "application/json"
|
|
2041
|
-
},
|
|
2042
|
-
{
|
|
2043
|
-
uri: "mindgraph://questions",
|
|
2044
|
-
name: "Open Questions",
|
|
2045
|
-
description: "All open questions in the knowledge graph",
|
|
2046
|
-
mimeType: "application/json"
|
|
2047
|
-
},
|
|
2048
|
-
{
|
|
2049
|
-
uri: "mindgraph://contradictions",
|
|
2050
|
-
name: "Contradictions",
|
|
2051
|
-
description: "Contradictory claims in the knowledge graph",
|
|
2052
|
-
mimeType: "application/json"
|
|
2053
|
-
},
|
|
2054
|
-
{
|
|
2055
|
-
uri: "mindgraph://decisions",
|
|
2056
|
-
name: "Open Decisions",
|
|
2057
|
-
description: "Unresolved decisions awaiting resolution",
|
|
2058
|
-
mimeType: "application/json"
|
|
2059
|
-
}
|
|
2060
|
-
]
|
|
2061
|
-
};
|
|
2062
|
-
});
|
|
2063
|
-
server.setRequestHandler(import_types.ListResourceTemplatesRequestSchema, async () => {
|
|
2064
|
-
return {
|
|
2065
|
-
resourceTemplates: [
|
|
2066
|
-
{
|
|
2067
|
-
uriTemplate: "mindgraph://node/{uid}",
|
|
2068
|
-
name: "Graph Node",
|
|
2069
|
-
description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
|
|
2070
|
-
mimeType: "application/json"
|
|
2071
|
-
},
|
|
2072
|
-
{
|
|
2073
|
-
uriTemplate: "mindgraph://search/{query}",
|
|
2074
|
-
name: "Search Results",
|
|
2075
|
-
description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
|
|
2076
|
-
mimeType: "application/json"
|
|
2077
|
-
},
|
|
2078
|
-
{
|
|
2079
|
-
uriTemplate: "mindgraph://layer/{layer}",
|
|
2080
|
-
name: "Layer Nodes",
|
|
2081
|
-
description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
|
|
2082
|
-
mimeType: "application/json"
|
|
2083
|
-
}
|
|
2084
|
-
]
|
|
2085
|
-
};
|
|
2086
|
-
});
|
|
2087
|
-
server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
|
|
2088
|
-
const { uri } = request.params;
|
|
2089
|
-
switch (uri) {
|
|
2090
|
-
case "mindgraph://stats": {
|
|
2091
|
-
const stats = await client.stats();
|
|
2092
|
-
return {
|
|
2093
|
-
contents: [
|
|
2094
|
-
{
|
|
2095
|
-
uri,
|
|
2096
|
-
mimeType: "application/json",
|
|
2097
|
-
text: JSON.stringify(stats, null, 2)
|
|
2098
|
-
}
|
|
2099
|
-
]
|
|
2100
|
-
};
|
|
2101
|
-
}
|
|
2102
|
-
case "mindgraph://goals": {
|
|
2103
|
-
const goals = await client.getGoals();
|
|
2104
|
-
return {
|
|
2105
|
-
contents: [
|
|
2106
|
-
{
|
|
2107
|
-
uri,
|
|
2108
|
-
mimeType: "application/json",
|
|
2109
|
-
text: JSON.stringify(goals, null, 2)
|
|
2110
|
-
}
|
|
2111
|
-
]
|
|
2112
|
-
};
|
|
2113
|
-
}
|
|
2114
|
-
case "mindgraph://questions": {
|
|
2115
|
-
const questions = await client.getOpenQuestions();
|
|
2116
|
-
return {
|
|
2117
|
-
contents: [
|
|
2118
|
-
{
|
|
2119
|
-
uri,
|
|
2120
|
-
mimeType: "application/json",
|
|
2121
|
-
text: JSON.stringify(questions, null, 2)
|
|
2122
|
-
}
|
|
2123
|
-
]
|
|
2124
|
-
};
|
|
2125
|
-
}
|
|
2126
|
-
case "mindgraph://contradictions": {
|
|
2127
|
-
const contradictions = await client.getContradictions();
|
|
2128
|
-
return {
|
|
2129
|
-
contents: [
|
|
2130
|
-
{
|
|
2131
|
-
uri,
|
|
2132
|
-
mimeType: "application/json",
|
|
2133
|
-
text: JSON.stringify(contradictions, null, 2)
|
|
2134
|
-
}
|
|
2135
|
-
]
|
|
2136
|
-
};
|
|
2137
|
-
}
|
|
2138
|
-
case "mindgraph://decisions": {
|
|
2139
|
-
const decisions = await client.getOpenDecisions();
|
|
2140
|
-
return {
|
|
2141
|
-
contents: [
|
|
2142
|
-
{
|
|
2143
|
-
uri,
|
|
2144
|
-
mimeType: "application/json",
|
|
2145
|
-
text: JSON.stringify(decisions, null, 2)
|
|
2146
|
-
}
|
|
2147
|
-
]
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
}
|
|
2151
|
-
const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
|
|
2152
|
-
if (nodeMatch) {
|
|
2153
|
-
const uid = decodeURIComponent(nodeMatch[1]);
|
|
2154
|
-
const node = await client.getNode(uid);
|
|
2155
|
-
const edges = await client.getEdges({ from_uid: uid });
|
|
2156
|
-
const inEdges = await client.getEdges({ to_uid: uid });
|
|
2157
|
-
return {
|
|
2158
|
-
contents: [
|
|
2159
|
-
{
|
|
2160
|
-
uri,
|
|
2161
|
-
mimeType: "application/json",
|
|
2162
|
-
text: JSON.stringify(
|
|
2163
|
-
{ node, outgoing_edges: edges, incoming_edges: inEdges },
|
|
2164
|
-
null,
|
|
2165
|
-
2
|
|
2166
|
-
)
|
|
2167
|
-
}
|
|
2168
|
-
]
|
|
2169
|
-
};
|
|
2170
|
-
}
|
|
2171
|
-
const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
|
|
2172
|
-
if (searchMatch) {
|
|
2173
|
-
const query = decodeURIComponent(searchMatch[1]);
|
|
2174
|
-
const results = await client.search(query, { limit: 20 });
|
|
2175
|
-
return {
|
|
2176
|
-
contents: [
|
|
2177
|
-
{
|
|
2178
|
-
uri,
|
|
2179
|
-
mimeType: "application/json",
|
|
2180
|
-
text: JSON.stringify(results, null, 2)
|
|
2181
|
-
}
|
|
2182
|
-
]
|
|
2183
|
-
};
|
|
2184
|
-
}
|
|
2185
|
-
const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
|
|
2186
|
-
if (layerMatch) {
|
|
2187
|
-
const layer = decodeURIComponent(layerMatch[1]);
|
|
2188
|
-
const nodes = await client.getNodes({ layer, limit: 50 });
|
|
2189
|
-
return {
|
|
2190
|
-
contents: [
|
|
2191
|
-
{
|
|
2192
|
-
uri,
|
|
2193
|
-
mimeType: "application/json",
|
|
2194
|
-
text: JSON.stringify(nodes, null, 2)
|
|
2195
|
-
}
|
|
2196
|
-
]
|
|
2197
|
-
};
|
|
4182
|
+
${conversation}`
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
]
|
|
4186
|
+
};
|
|
4187
|
+
}
|
|
4188
|
+
default:
|
|
4189
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
2198
4190
|
}
|
|
2199
|
-
|
|
4191
|
+
});
|
|
4192
|
+
server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
|
|
4193
|
+
return { resources: RESOURCES };
|
|
4194
|
+
});
|
|
4195
|
+
server.setRequestHandler(import_types.ListResourceTemplatesRequestSchema, async () => {
|
|
4196
|
+
return { resourceTemplates: RESOURCE_TEMPLATES };
|
|
4197
|
+
});
|
|
4198
|
+
server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
|
|
4199
|
+
return readGovernedResource(client, request.params.uri, GOVERNANCE_CONFIG);
|
|
2200
4200
|
});
|
|
2201
4201
|
main().catch((error) => {
|
|
2202
4202
|
console.error("Fatal error:", error);
|
|
@@ -2206,30 +4206,670 @@ ${conversation}`
|
|
|
2206
4206
|
});
|
|
2207
4207
|
|
|
2208
4208
|
// src/cli.ts
|
|
2209
|
-
var
|
|
2210
|
-
var
|
|
2211
|
-
var
|
|
2212
|
-
var
|
|
4209
|
+
var fs5 = __toESM(require("fs"));
|
|
4210
|
+
var path5 = __toESM(require("path"));
|
|
4211
|
+
var os4 = __toESM(require("os"));
|
|
4212
|
+
var crypto4 = __toESM(require("crypto"));
|
|
2213
4213
|
var import_child_process = require("child_process");
|
|
4214
|
+
var import_mindgraph3 = require("mindgraph");
|
|
4215
|
+
|
|
4216
|
+
// src/hook-env.ts
|
|
4217
|
+
var import_node_fs = __toESM(require("fs"));
|
|
4218
|
+
var import_node_os = __toESM(require("os"));
|
|
4219
|
+
var import_node_path = __toESM(require("path"));
|
|
4220
|
+
function hookEnvPath(dir) {
|
|
4221
|
+
return import_node_path.default.join(dir || import_node_path.default.join(import_node_os.default.homedir(), ".mindgraph"), "hooks.json");
|
|
4222
|
+
}
|
|
4223
|
+
function loadHookEnv(dir) {
|
|
4224
|
+
try {
|
|
4225
|
+
const raw = import_node_fs.default.readFileSync(hookEnvPath(dir), "utf8");
|
|
4226
|
+
const parsed = JSON.parse(raw);
|
|
4227
|
+
return {
|
|
4228
|
+
baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : void 0,
|
|
4229
|
+
apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : void 0
|
|
4230
|
+
};
|
|
4231
|
+
} catch {
|
|
4232
|
+
return {};
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
function saveHookEnv(env, dir) {
|
|
4236
|
+
const file = hookEnvPath(dir);
|
|
4237
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(file), { recursive: true });
|
|
4238
|
+
const existing = loadHookEnv(dir);
|
|
4239
|
+
const merged = {
|
|
4240
|
+
baseUrl: env.baseUrl ?? existing.baseUrl,
|
|
4241
|
+
apiKey: env.apiKey ?? existing.apiKey
|
|
4242
|
+
};
|
|
4243
|
+
import_node_fs.default.writeFileSync(file, `${JSON.stringify(merged, null, 2)}
|
|
4244
|
+
`, {
|
|
4245
|
+
mode: 384
|
|
4246
|
+
});
|
|
4247
|
+
import_node_fs.default.chmodSync(file, 384);
|
|
4248
|
+
return file;
|
|
4249
|
+
}
|
|
4250
|
+
|
|
4251
|
+
// src/cli-args.ts
|
|
4252
|
+
function parseArgs(argv) {
|
|
4253
|
+
const args = argv.slice(2);
|
|
4254
|
+
let command = "serve";
|
|
4255
|
+
let apiKey = process.env.MINDGRAPH_API_KEY;
|
|
4256
|
+
let baseUrl = process.env.MINDGRAPH_BASE_URL;
|
|
4257
|
+
let scope = "project";
|
|
4258
|
+
let projectDir;
|
|
4259
|
+
let harness;
|
|
4260
|
+
let hooks = false;
|
|
4261
|
+
for (let i = 0; i < args.length; i++) {
|
|
4262
|
+
const arg = args[i];
|
|
4263
|
+
switch (arg) {
|
|
4264
|
+
case "--api-key":
|
|
4265
|
+
apiKey = args[++i];
|
|
4266
|
+
break;
|
|
4267
|
+
case "--base-url":
|
|
4268
|
+
baseUrl = args[++i];
|
|
4269
|
+
break;
|
|
4270
|
+
case "--scope": {
|
|
4271
|
+
const value = args[++i];
|
|
4272
|
+
if (value !== "user" && value !== "project") {
|
|
4273
|
+
throw new Error("--scope must be user or project");
|
|
4274
|
+
}
|
|
4275
|
+
scope = value;
|
|
4276
|
+
break;
|
|
4277
|
+
}
|
|
4278
|
+
case "--project-dir":
|
|
4279
|
+
projectDir = args[++i];
|
|
4280
|
+
break;
|
|
4281
|
+
case "--harness":
|
|
4282
|
+
harness = args[++i];
|
|
4283
|
+
break;
|
|
4284
|
+
case "--hooks":
|
|
4285
|
+
hooks = true;
|
|
4286
|
+
break;
|
|
4287
|
+
case "--owner":
|
|
4288
|
+
++i;
|
|
4289
|
+
break;
|
|
4290
|
+
case "--help":
|
|
4291
|
+
case "-h":
|
|
4292
|
+
command = "help";
|
|
4293
|
+
break;
|
|
4294
|
+
default:
|
|
4295
|
+
if (!arg.startsWith("-")) {
|
|
4296
|
+
command = arg;
|
|
4297
|
+
}
|
|
4298
|
+
break;
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
return { command, apiKey, baseUrl, scope, projectDir, harness, hooks };
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4304
|
+
// src/claude-hooks.ts
|
|
4305
|
+
var import_node_crypto2 = __toESM(require("crypto"));
|
|
4306
|
+
var import_node_fs3 = __toESM(require("fs"));
|
|
4307
|
+
var import_node_os3 = __toESM(require("os"));
|
|
4308
|
+
var import_node_path3 = __toESM(require("path"));
|
|
4309
|
+
var import_node_child_process2 = require("child_process");
|
|
4310
|
+
init_codegraph();
|
|
4311
|
+
function defaultLedger(sessionId) {
|
|
4312
|
+
return {
|
|
4313
|
+
harness: "claude-code",
|
|
4314
|
+
harnessSessionId: sessionId,
|
|
4315
|
+
materialToolCalls: 0,
|
|
4316
|
+
mutatingOperations: 0,
|
|
4317
|
+
memoryWritten: false,
|
|
4318
|
+
iterationCheckpointed: false,
|
|
4319
|
+
stopNudged: false,
|
|
4320
|
+
nativeTasksCreated: 0,
|
|
4321
|
+
nativeTasksTerminal: 0,
|
|
4322
|
+
baselineWorktree: "unknown",
|
|
4323
|
+
updatedAt: Date.now()
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
function runtimeDir(options) {
|
|
4327
|
+
return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path3.default.join(import_node_os3.default.homedir(), ".mindgraph", "runtime");
|
|
4328
|
+
}
|
|
4329
|
+
function ledgerPath(sessionId, options) {
|
|
4330
|
+
const key = import_node_crypto2.default.createHash("sha256").update(`claude-code\0${sessionId}`).digest("hex");
|
|
4331
|
+
return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
|
|
4332
|
+
}
|
|
4333
|
+
function waitBriefly(ms) {
|
|
4334
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
4335
|
+
}
|
|
4336
|
+
function withLedger(sessionId, options, update) {
|
|
4337
|
+
const file = ledgerPath(sessionId, options);
|
|
4338
|
+
import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true, mode: 448 });
|
|
4339
|
+
try {
|
|
4340
|
+
import_node_fs3.default.chmodSync(import_node_path3.default.dirname(file), 448);
|
|
4341
|
+
} catch {
|
|
4342
|
+
}
|
|
4343
|
+
const lock = `${file}.lock`;
|
|
4344
|
+
let lockFd;
|
|
4345
|
+
for (let attempt = 0; attempt < 50; attempt++) {
|
|
4346
|
+
try {
|
|
4347
|
+
lockFd = import_node_fs3.default.openSync(lock, "wx", 384);
|
|
4348
|
+
break;
|
|
4349
|
+
} catch (error) {
|
|
4350
|
+
if (error.code !== "EEXIST") throw error;
|
|
4351
|
+
try {
|
|
4352
|
+
if (Date.now() - import_node_fs3.default.statSync(lock).mtimeMs > 1e4) import_node_fs3.default.unlinkSync(lock);
|
|
4353
|
+
} catch {
|
|
4354
|
+
}
|
|
4355
|
+
waitBriefly(10);
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
if (lockFd === void 0) {
|
|
4359
|
+
return update(defaultLedger(sessionId));
|
|
4360
|
+
}
|
|
4361
|
+
try {
|
|
4362
|
+
let ledger = defaultLedger(sessionId);
|
|
4363
|
+
try {
|
|
4364
|
+
ledger = {
|
|
4365
|
+
...ledger,
|
|
4366
|
+
...JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"))
|
|
4367
|
+
};
|
|
4368
|
+
} catch {
|
|
4369
|
+
}
|
|
4370
|
+
const result = update(ledger);
|
|
4371
|
+
ledger.updatedAt = Date.now();
|
|
4372
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto2.default.randomUUID()}.tmp`;
|
|
4373
|
+
import_node_fs3.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
|
|
4374
|
+
`, { mode: 384 });
|
|
4375
|
+
import_node_fs3.default.renameSync(temporary, file);
|
|
4376
|
+
try {
|
|
4377
|
+
import_node_fs3.default.chmodSync(file, 384);
|
|
4378
|
+
} catch {
|
|
4379
|
+
}
|
|
4380
|
+
return result;
|
|
4381
|
+
} finally {
|
|
4382
|
+
import_node_fs3.default.closeSync(lockFd);
|
|
4383
|
+
try {
|
|
4384
|
+
import_node_fs3.default.unlinkSync(lock);
|
|
4385
|
+
} catch {
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
function readLedger(sessionId, options) {
|
|
4390
|
+
return withLedger(sessionId, options, (ledger) => ({ ...ledger }));
|
|
4391
|
+
}
|
|
4392
|
+
function gitValue(cwd, args) {
|
|
4393
|
+
try {
|
|
4394
|
+
return (0, import_node_child_process2.execFileSync)("git", ["-C", cwd, ...args], {
|
|
4395
|
+
encoding: "utf8",
|
|
4396
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4397
|
+
timeout: 750
|
|
4398
|
+
}).trim();
|
|
4399
|
+
} catch {
|
|
4400
|
+
return void 0;
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
function worktreeState(cwd) {
|
|
4404
|
+
const status = gitValue(cwd, ["status", "--porcelain"]);
|
|
4405
|
+
return status === void 0 ? "unknown" : status ? "dirty" : "clean";
|
|
4406
|
+
}
|
|
4407
|
+
async function invocationContext(input, options) {
|
|
4408
|
+
const repository = await new CodegraphAdapter({
|
|
4409
|
+
cwd: input.cwd,
|
|
4410
|
+
workspaceFile: options.workspaceFile
|
|
4411
|
+
}).resolveRepository();
|
|
4412
|
+
return {
|
|
4413
|
+
harness: "claude-code",
|
|
4414
|
+
harnessSessionId: input.session_id,
|
|
4415
|
+
cwd: input.cwd,
|
|
4416
|
+
repoId: repository.repoId,
|
|
4417
|
+
branch: gitValue(input.cwd, ["branch", "--show-current"]),
|
|
4418
|
+
commit: gitValue(input.cwd, ["rev-parse", "HEAD"]),
|
|
4419
|
+
worktreeState: worktreeState(input.cwd),
|
|
4420
|
+
model: input.model,
|
|
4421
|
+
injectedBy: "hook"
|
|
4422
|
+
};
|
|
4423
|
+
}
|
|
4424
|
+
function object(value) {
|
|
4425
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4426
|
+
}
|
|
4427
|
+
function responsePayload(value) {
|
|
4428
|
+
const direct = object(value);
|
|
4429
|
+
if (!direct) return void 0;
|
|
4430
|
+
if (Array.isArray(direct.content)) {
|
|
4431
|
+
for (const item of direct.content) {
|
|
4432
|
+
const block = object(item);
|
|
4433
|
+
if (block?.type === "text" && typeof block.text === "string") {
|
|
4434
|
+
try {
|
|
4435
|
+
const parsed = JSON.parse(block.text);
|
|
4436
|
+
const payload = object(parsed);
|
|
4437
|
+
if (payload) return payload;
|
|
4438
|
+
} catch {
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
return direct;
|
|
4444
|
+
}
|
|
4445
|
+
function number(value) {
|
|
4446
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4447
|
+
}
|
|
4448
|
+
function string(value) {
|
|
4449
|
+
return typeof value === "string" && value ? value : void 0;
|
|
4450
|
+
}
|
|
4451
|
+
function boundedBrief(brief) {
|
|
4452
|
+
const encoded = JSON.stringify(brief, null, 2);
|
|
4453
|
+
const capped = encoded.length <= 9e3 ? encoded : `${encoded.slice(0, 8900)}
|
|
4454
|
+
\u2026[bounded work brief truncated]`;
|
|
4455
|
+
return [
|
|
4456
|
+
"MindGraph durable work brief (authoritative across sessions/harnesses):",
|
|
4457
|
+
capped,
|
|
4458
|
+
"Use the harness todo list only for local execution details; do not duplicate this durable Task there."
|
|
4459
|
+
].join("\n");
|
|
4460
|
+
}
|
|
4461
|
+
async function repositoryScopeUids(input, client2, options, agentId) {
|
|
4462
|
+
if (!client2.entity) return [];
|
|
4463
|
+
const adapter = new CodegraphAdapter({
|
|
4464
|
+
cwd: input.cwd,
|
|
4465
|
+
workspaceFile: options.workspaceFile
|
|
4466
|
+
});
|
|
4467
|
+
const configured = await adapter.resolveRepositories();
|
|
4468
|
+
const repositories = configured.length > 0 ? configured : [await adapter.resolveRepository()];
|
|
4469
|
+
const resolved = await Promise.all(
|
|
4470
|
+
repositories.map(
|
|
4471
|
+
(repository) => client2.entity({
|
|
4472
|
+
action: "resolve_identity",
|
|
4473
|
+
identity: {
|
|
4474
|
+
namespace: "external.code",
|
|
4475
|
+
key_version: 1,
|
|
4476
|
+
key: repositoryIdentityKey(repository)
|
|
4477
|
+
},
|
|
4478
|
+
agent_id: agentId
|
|
4479
|
+
}).catch(() => void 0)
|
|
4480
|
+
)
|
|
4481
|
+
);
|
|
4482
|
+
return [
|
|
4483
|
+
...new Set(
|
|
4484
|
+
resolved.flatMap((response) => {
|
|
4485
|
+
const payload = object(response);
|
|
4486
|
+
return payload?.status === "existing" && string(payload.uid) ? [string(payload.uid)] : [];
|
|
4487
|
+
})
|
|
4488
|
+
)
|
|
4489
|
+
];
|
|
4490
|
+
}
|
|
4491
|
+
async function sessionStart(input, client2, options) {
|
|
4492
|
+
const agentId = options.agentId || process.env.MINDGRAPH_AGENT_ID || "claude-code";
|
|
4493
|
+
const opened = object(
|
|
4494
|
+
await client2.session({
|
|
4495
|
+
action: "open",
|
|
4496
|
+
label: `Claude Code ${input.session_id}`,
|
|
4497
|
+
harness: "claude-code",
|
|
4498
|
+
harness_session_id: input.session_id,
|
|
4499
|
+
model: input.model,
|
|
4500
|
+
agent_id: agentId
|
|
4501
|
+
})
|
|
4502
|
+
);
|
|
4503
|
+
const sessionUid = string(opened?.uid);
|
|
4504
|
+
const scopeUids = await repositoryScopeUids(input, client2, options, agentId);
|
|
4505
|
+
let brief = object(
|
|
4506
|
+
await client2.plan({
|
|
4507
|
+
action: "resume_work",
|
|
4508
|
+
session_uid: sessionUid,
|
|
4509
|
+
...scopeUids.length > 0 ? { scope_uids: scopeUids } : {},
|
|
4510
|
+
agent_id: agentId
|
|
4511
|
+
})
|
|
4512
|
+
);
|
|
4513
|
+
const task = object(brief?.task);
|
|
4514
|
+
const lease = object(brief?.lease);
|
|
4515
|
+
if (sessionUid && string(task?.uid) && number(task?.version)) {
|
|
4516
|
+
try {
|
|
4517
|
+
const claimed = object(
|
|
4518
|
+
await client2.plan({
|
|
4519
|
+
action: "claim_task",
|
|
4520
|
+
task_uid: task.uid,
|
|
4521
|
+
session_uid: sessionUid,
|
|
4522
|
+
expected_version: task.version,
|
|
4523
|
+
idempotency_key: `session-start:${input.session_id}:${task.uid}:${lease?.lease_epoch || 0}`,
|
|
4524
|
+
agent_id: agentId
|
|
4525
|
+
})
|
|
4526
|
+
);
|
|
4527
|
+
brief = object(
|
|
4528
|
+
await client2.plan({
|
|
4529
|
+
action: "resume_work",
|
|
4530
|
+
task_uid: task.uid,
|
|
4531
|
+
session_uid: sessionUid,
|
|
4532
|
+
...scopeUids.length > 0 ? { scope_uids: scopeUids } : {},
|
|
4533
|
+
agent_id: agentId
|
|
4534
|
+
})
|
|
4535
|
+
);
|
|
4536
|
+
if (brief && claimed) brief.claim = claimed;
|
|
4537
|
+
} catch {
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
withLedger(input.session_id, options, (ledger) => {
|
|
4541
|
+
ledger.sessionUid = sessionUid;
|
|
4542
|
+
ledger.baselineWorktree = worktreeState(input.cwd);
|
|
4543
|
+
const selectedTask = object(brief?.task);
|
|
4544
|
+
const selectedLease = object(brief?.lease) || object(brief?.claim);
|
|
4545
|
+
const executions = Array.isArray(brief?.recent_executions) ? brief?.recent_executions : [];
|
|
4546
|
+
const latest = object(executions[0]);
|
|
4547
|
+
ledger.taskUid = string(selectedTask?.uid);
|
|
4548
|
+
ledger.taskVersion = number(selectedTask?.version);
|
|
4549
|
+
ledger.leaseEpoch = number(selectedLease?.lease_epoch);
|
|
4550
|
+
ledger.leaseExpiresAt = number(selectedLease?.lease_expires_at);
|
|
4551
|
+
ledger.executionUid = string(object(brief?.active_execution)?.uid) || string(latest?.uid);
|
|
4552
|
+
return void 0;
|
|
4553
|
+
});
|
|
4554
|
+
return {
|
|
4555
|
+
hookSpecificOutput: {
|
|
4556
|
+
hookEventName: "SessionStart",
|
|
4557
|
+
additionalContext: boundedBrief(brief || { status: "no_eligible_work" })
|
|
4558
|
+
}
|
|
4559
|
+
};
|
|
4560
|
+
}
|
|
4561
|
+
async function preTool(input, options) {
|
|
4562
|
+
const original = { ...input.tool_input || {} };
|
|
4563
|
+
const ledger = readLedger(input.session_id, options);
|
|
4564
|
+
const updatedInput = {
|
|
4565
|
+
...original,
|
|
4566
|
+
invocation_context: await invocationContext(input, options)
|
|
4567
|
+
};
|
|
4568
|
+
if (ledger.sessionUid) updatedInput.session_uid = ledger.sessionUid;
|
|
4569
|
+
if (ledger.taskUid && updatedInput.task_uid === void 0) {
|
|
4570
|
+
updatedInput.task_uid = ledger.taskUid;
|
|
4571
|
+
}
|
|
4572
|
+
if (ledger.taskUid && input.tool_name?.includes("mindgraph_capture") && updatedInput.work_uid === void 0) {
|
|
4573
|
+
updatedInput.work_uid = ledger.taskUid;
|
|
4574
|
+
}
|
|
4575
|
+
if (ledger.executionUid && updatedInput.execution_uid === void 0) {
|
|
4576
|
+
updatedInput.execution_uid = ledger.executionUid;
|
|
4577
|
+
}
|
|
4578
|
+
if (ledger.leaseEpoch !== void 0 && updatedInput.lease_epoch === void 0) {
|
|
4579
|
+
updatedInput.lease_epoch = ledger.leaseEpoch;
|
|
4580
|
+
}
|
|
4581
|
+
if (ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
|
|
4582
|
+
updatedInput.expected_version = ledger.taskVersion;
|
|
4583
|
+
}
|
|
4584
|
+
return {
|
|
4585
|
+
hookSpecificOutput: {
|
|
4586
|
+
hookEventName: "PreToolUse",
|
|
4587
|
+
updatedInput
|
|
4588
|
+
}
|
|
4589
|
+
};
|
|
4590
|
+
}
|
|
4591
|
+
async function postTool(input, client2, options) {
|
|
4592
|
+
const name = input.tool_name || "";
|
|
4593
|
+
const args = input.tool_input || {};
|
|
4594
|
+
const action = string(args.action);
|
|
4595
|
+
const payload = responsePayload(input.tool_response);
|
|
4596
|
+
const nowSeconds = (options.now || (() => Date.now() / 1e3))();
|
|
4597
|
+
let heartbeat;
|
|
4598
|
+
withLedger(input.session_id, options, (ledger) => {
|
|
4599
|
+
ledger.materialToolCalls += 1;
|
|
4600
|
+
if (["Edit", "Write", "NotebookEdit"].includes(name) || name === "Bash" && action !== "read") {
|
|
4601
|
+
ledger.mutatingOperations += 1;
|
|
4602
|
+
}
|
|
4603
|
+
if (name.includes("mindgraph_capture") && !payload?.error) {
|
|
4604
|
+
ledger.memoryWritten = true;
|
|
4605
|
+
}
|
|
4606
|
+
if (name.includes("mindgraph_plan")) {
|
|
4607
|
+
if (action === "start_iteration" && !payload?.error) {
|
|
4608
|
+
ledger.executionUid = string(payload?.execution_uid) || ledger.executionUid;
|
|
4609
|
+
}
|
|
4610
|
+
if (["checkpoint_iteration", "block_task", "complete_task", "abandon_iteration"].includes(
|
|
4611
|
+
action || ""
|
|
4612
|
+
) && !payload?.error) {
|
|
4613
|
+
ledger.iterationCheckpointed = true;
|
|
4614
|
+
if (action !== "block_task") ledger.executionUid = void 0;
|
|
4615
|
+
}
|
|
4616
|
+
ledger.taskVersion = number(payload?.task_version) || number(payload?.version) || ledger.taskVersion;
|
|
4617
|
+
ledger.leaseEpoch = number(payload?.lease_epoch) || ledger.leaseEpoch;
|
|
4618
|
+
ledger.leaseExpiresAt = number(payload?.lease_expires_at) || ledger.leaseExpiresAt;
|
|
4619
|
+
}
|
|
4620
|
+
if (ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0 && ledger.leaseExpiresAt !== void 0 && ledger.leaseExpiresAt - nowSeconds <= 60) {
|
|
4621
|
+
heartbeat = {
|
|
4622
|
+
taskUid: ledger.taskUid,
|
|
4623
|
+
sessionUid: ledger.sessionUid,
|
|
4624
|
+
taskVersion: ledger.taskVersion,
|
|
4625
|
+
leaseEpoch: ledger.leaseEpoch
|
|
4626
|
+
};
|
|
4627
|
+
}
|
|
4628
|
+
return void 0;
|
|
4629
|
+
});
|
|
4630
|
+
if (heartbeat) {
|
|
4631
|
+
try {
|
|
4632
|
+
const response = object(
|
|
4633
|
+
await client2.plan({
|
|
4634
|
+
action: "heartbeat",
|
|
4635
|
+
task_uid: heartbeat.taskUid,
|
|
4636
|
+
session_uid: heartbeat.sessionUid,
|
|
4637
|
+
expected_version: heartbeat.taskVersion,
|
|
4638
|
+
lease_epoch: heartbeat.leaseEpoch,
|
|
4639
|
+
idempotency_key: `hook-heartbeat:${input.session_id}:${heartbeat.taskUid}:${Math.floor(nowSeconds / 60)}`,
|
|
4640
|
+
agent_id: options.agentId || "claude-code"
|
|
4641
|
+
})
|
|
4642
|
+
);
|
|
4643
|
+
withLedger(input.session_id, options, (ledger) => {
|
|
4644
|
+
ledger.leaseExpiresAt = number(response?.lease_expires_at) || ledger.leaseExpiresAt;
|
|
4645
|
+
return void 0;
|
|
4646
|
+
});
|
|
4647
|
+
} catch {
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
return {};
|
|
4651
|
+
}
|
|
4652
|
+
function stop(input, options) {
|
|
4653
|
+
if (input.stop_hook_active) return {};
|
|
4654
|
+
return withLedger(input.session_id, options, (ledger) => {
|
|
4655
|
+
const changed = ledger.mutatingOperations >= 2 || ledger.baselineWorktree !== "unknown" && worktreeState(input.cwd) !== ledger.baselineWorktree;
|
|
4656
|
+
const substantial = Boolean(ledger.executionUid) || changed;
|
|
4657
|
+
if (!substantial || ledger.iterationCheckpointed || ledger.memoryWritten || ledger.stopNudged) {
|
|
4658
|
+
return {};
|
|
4659
|
+
}
|
|
4660
|
+
ledger.stopNudged = true;
|
|
4661
|
+
return {
|
|
4662
|
+
decision: "block",
|
|
4663
|
+
reason: "Substantial work is still uncheckpointed. Checkpoint the active iteration, capture one durable lesson/decision/risk, or explicitly state that nothing durable changed. If a local harness task should become authoritative outside this list, promote it once with a stable idempotency key; otherwise do not copy it."
|
|
4664
|
+
};
|
|
4665
|
+
});
|
|
4666
|
+
}
|
|
4667
|
+
async function sessionEnd(input, client2, options) {
|
|
4668
|
+
const ledger = readLedger(input.session_id, options);
|
|
4669
|
+
if (ledger.executionUid && ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0) {
|
|
4670
|
+
try {
|
|
4671
|
+
await client2.plan({
|
|
4672
|
+
action: "abandon_iteration",
|
|
4673
|
+
task_uid: ledger.taskUid,
|
|
4674
|
+
execution_uid: ledger.executionUid,
|
|
4675
|
+
session_uid: ledger.sessionUid,
|
|
4676
|
+
expected_version: ledger.taskVersion,
|
|
4677
|
+
lease_epoch: ledger.leaseEpoch,
|
|
4678
|
+
release_lease: true,
|
|
4679
|
+
summary: `Claude Code SessionEnd: ${input.reason || "interrupted"}`,
|
|
4680
|
+
idempotency_key: `session-end:${input.session_id}:${ledger.executionUid}`,
|
|
4681
|
+
agent_id: options.agentId || "claude-code"
|
|
4682
|
+
});
|
|
4683
|
+
} catch {
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
if (ledger.sessionUid) {
|
|
4687
|
+
try {
|
|
4688
|
+
await client2.session({
|
|
4689
|
+
action: "close",
|
|
4690
|
+
session_uid: ledger.sessionUid,
|
|
4691
|
+
agent_id: options.agentId || "claude-code"
|
|
4692
|
+
});
|
|
4693
|
+
} catch {
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
return {};
|
|
4697
|
+
}
|
|
4698
|
+
async function runClaudeHook(input, client2, options = {}) {
|
|
4699
|
+
switch (input.hook_event_name) {
|
|
4700
|
+
case "SessionStart":
|
|
4701
|
+
return sessionStart(input, client2, options);
|
|
4702
|
+
case "PreToolUse":
|
|
4703
|
+
if (!input.tool_name?.startsWith("mcp__mindgraph__")) return {};
|
|
4704
|
+
return preTool(input, options);
|
|
4705
|
+
case "PostToolUse":
|
|
4706
|
+
return postTool(input, client2, options);
|
|
4707
|
+
case "TaskCreated":
|
|
4708
|
+
withLedger(input.session_id, options, (ledger) => {
|
|
4709
|
+
ledger.nativeTasksCreated += 1;
|
|
4710
|
+
return void 0;
|
|
4711
|
+
});
|
|
4712
|
+
return {};
|
|
4713
|
+
case "TaskCompleted":
|
|
4714
|
+
withLedger(input.session_id, options, (ledger) => {
|
|
4715
|
+
ledger.nativeTasksTerminal += 1;
|
|
4716
|
+
return void 0;
|
|
4717
|
+
});
|
|
4718
|
+
return {};
|
|
4719
|
+
case "Stop":
|
|
4720
|
+
return stop(input, options);
|
|
4721
|
+
case "SessionEnd":
|
|
4722
|
+
return sessionEnd(input, client2, options);
|
|
4723
|
+
default:
|
|
4724
|
+
return {};
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
async function readHookInput(stream = process.stdin) {
|
|
4728
|
+
const chunks = [];
|
|
4729
|
+
for await (const chunk of stream) {
|
|
4730
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
4731
|
+
}
|
|
4732
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4735
|
+
// src/hook-installer.ts
|
|
4736
|
+
var import_node_fs4 = __toESM(require("fs"));
|
|
4737
|
+
var import_node_os4 = __toESM(require("os"));
|
|
4738
|
+
var import_node_path4 = __toESM(require("path"));
|
|
4739
|
+
var import_node_crypto3 = __toESM(require("crypto"));
|
|
4740
|
+
var OWNER_MARKER = "--owner mindgraph";
|
|
4741
|
+
var COMMAND = "npx -y mindgraph-mcp@latest hook --harness claude-code --owner mindgraph";
|
|
4742
|
+
function settingsPath(scope, projectDir = process.cwd()) {
|
|
4743
|
+
return scope === "user" ? import_node_path4.default.join(import_node_os4.default.homedir(), ".claude", "settings.json") : import_node_path4.default.join(projectDir, ".claude", "settings.json");
|
|
4744
|
+
}
|
|
4745
|
+
function readSettings(file) {
|
|
4746
|
+
if (!import_node_fs4.default.existsSync(file)) return {};
|
|
4747
|
+
return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
|
|
4748
|
+
}
|
|
4749
|
+
function ownedHook(timeout) {
|
|
4750
|
+
return {
|
|
4751
|
+
type: "command",
|
|
4752
|
+
command: COMMAND,
|
|
4753
|
+
timeout,
|
|
4754
|
+
statusMessage: "Syncing MindGraph session context"
|
|
4755
|
+
};
|
|
4756
|
+
}
|
|
4757
|
+
function desiredEntries() {
|
|
4758
|
+
return {
|
|
4759
|
+
SessionStart: [
|
|
4760
|
+
{
|
|
4761
|
+
matcher: "startup|resume|clear|compact|fork",
|
|
4762
|
+
hooks: [ownedHook(8)]
|
|
4763
|
+
}
|
|
4764
|
+
],
|
|
4765
|
+
PreToolUse: [
|
|
4766
|
+
{
|
|
4767
|
+
matcher: "mcp__mindgraph__.*",
|
|
4768
|
+
hooks: [ownedHook(3)]
|
|
4769
|
+
}
|
|
4770
|
+
],
|
|
4771
|
+
PostToolUse: [{ matcher: ".*", hooks: [ownedHook(5)] }],
|
|
4772
|
+
TaskCreated: [{ hooks: [ownedHook(3)] }],
|
|
4773
|
+
TaskCompleted: [{ hooks: [ownedHook(3)] }],
|
|
4774
|
+
Stop: [{ hooks: [ownedHook(5)] }],
|
|
4775
|
+
SessionEnd: [{ hooks: [ownedHook(8)] }]
|
|
4776
|
+
};
|
|
4777
|
+
}
|
|
4778
|
+
function hookCommand(value) {
|
|
4779
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4780
|
+
const command = value.command;
|
|
4781
|
+
return typeof command === "string" ? command : void 0;
|
|
4782
|
+
}
|
|
4783
|
+
function entryHasOwnedHook(value) {
|
|
4784
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
4785
|
+
const hooks = value.hooks;
|
|
4786
|
+
return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
|
|
4787
|
+
}
|
|
4788
|
+
function writeSettings(file, settings) {
|
|
4789
|
+
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
|
|
4790
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto3.default.randomUUID()}.tmp`;
|
|
4791
|
+
import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
|
|
4792
|
+
`, {
|
|
4793
|
+
mode: 384
|
|
4794
|
+
});
|
|
4795
|
+
import_node_fs4.default.renameSync(temporary, file);
|
|
4796
|
+
}
|
|
4797
|
+
function installClaudeHooks(scope, projectDir = process.cwd()) {
|
|
4798
|
+
const file = settingsPath(scope, projectDir);
|
|
4799
|
+
const settings = readSettings(file);
|
|
4800
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
4801
|
+
let added = 0;
|
|
4802
|
+
for (const [event, wanted] of Object.entries(desiredEntries())) {
|
|
4803
|
+
const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
4804
|
+
for (const entry of wanted) {
|
|
4805
|
+
if (!current.some(entryHasOwnedHook)) {
|
|
4806
|
+
current.push(entry);
|
|
4807
|
+
added += 1;
|
|
4808
|
+
}
|
|
4809
|
+
}
|
|
4810
|
+
hooks[event] = current;
|
|
4811
|
+
}
|
|
4812
|
+
settings.hooks = hooks;
|
|
4813
|
+
writeSettings(file, settings);
|
|
4814
|
+
return { path: file, added };
|
|
4815
|
+
}
|
|
4816
|
+
function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
|
|
4817
|
+
const file = settingsPath(scope, projectDir);
|
|
4818
|
+
if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
|
|
4819
|
+
const settings = readSettings(file);
|
|
4820
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
4821
|
+
let removed = 0;
|
|
4822
|
+
for (const [event, value] of Object.entries(hooks)) {
|
|
4823
|
+
if (!Array.isArray(value)) continue;
|
|
4824
|
+
const retainedEntries = [];
|
|
4825
|
+
for (const rawEntry of value) {
|
|
4826
|
+
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
|
|
4827
|
+
retainedEntries.push(rawEntry);
|
|
4828
|
+
continue;
|
|
4829
|
+
}
|
|
4830
|
+
const entry = { ...rawEntry };
|
|
4831
|
+
if (!Array.isArray(entry.hooks)) {
|
|
4832
|
+
retainedEntries.push(entry);
|
|
4833
|
+
continue;
|
|
4834
|
+
}
|
|
4835
|
+
const retainedHooks = entry.hooks.filter((hook) => {
|
|
4836
|
+
const owned = hookCommand(hook)?.includes(OWNER_MARKER) || false;
|
|
4837
|
+
if (owned) removed += 1;
|
|
4838
|
+
return !owned;
|
|
4839
|
+
});
|
|
4840
|
+
if (retainedHooks.length > 0) {
|
|
4841
|
+
entry.hooks = retainedHooks;
|
|
4842
|
+
retainedEntries.push(entry);
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
if (retainedEntries.length > 0) hooks[event] = retainedEntries;
|
|
4846
|
+
else delete hooks[event];
|
|
4847
|
+
}
|
|
4848
|
+
settings.hooks = hooks;
|
|
4849
|
+
writeSettings(file, settings);
|
|
4850
|
+
return { path: file, removed };
|
|
4851
|
+
}
|
|
4852
|
+
|
|
4853
|
+
// src/cli.ts
|
|
2214
4854
|
function getClaudeDesktopConfigPath() {
|
|
2215
4855
|
switch (process.platform) {
|
|
2216
4856
|
case "darwin":
|
|
2217
|
-
return
|
|
2218
|
-
|
|
4857
|
+
return path5.join(
|
|
4858
|
+
os4.homedir(),
|
|
2219
4859
|
"Library",
|
|
2220
4860
|
"Application Support",
|
|
2221
4861
|
"Claude",
|
|
2222
4862
|
"claude_desktop_config.json"
|
|
2223
4863
|
);
|
|
2224
4864
|
case "win32":
|
|
2225
|
-
return
|
|
2226
|
-
process.env.APPDATA ||
|
|
4865
|
+
return path5.join(
|
|
4866
|
+
process.env.APPDATA || path5.join(os4.homedir(), "AppData", "Roaming"),
|
|
2227
4867
|
"Claude",
|
|
2228
4868
|
"claude_desktop_config.json"
|
|
2229
4869
|
);
|
|
2230
4870
|
case "linux":
|
|
2231
|
-
return
|
|
2232
|
-
|
|
4871
|
+
return path5.join(
|
|
4872
|
+
os4.homedir(),
|
|
2233
4873
|
".config",
|
|
2234
4874
|
"Claude",
|
|
2235
4875
|
"claude_desktop_config.json"
|
|
@@ -2239,16 +4879,16 @@ function getClaudeDesktopConfigPath() {
|
|
|
2239
4879
|
}
|
|
2240
4880
|
}
|
|
2241
4881
|
function readJsonFile(filePath) {
|
|
2242
|
-
if (!
|
|
2243
|
-
const content =
|
|
4882
|
+
if (!fs5.existsSync(filePath)) return {};
|
|
4883
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
2244
4884
|
return JSON.parse(content);
|
|
2245
4885
|
}
|
|
2246
4886
|
function writeJsonFile(filePath, data) {
|
|
2247
|
-
const dir =
|
|
2248
|
-
if (!
|
|
2249
|
-
|
|
4887
|
+
const dir = path5.dirname(filePath);
|
|
4888
|
+
if (!fs5.existsSync(dir)) {
|
|
4889
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
2250
4890
|
}
|
|
2251
|
-
|
|
4891
|
+
fs5.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
2252
4892
|
}
|
|
2253
4893
|
function getMcpServerEntry(apiKey, baseUrl) {
|
|
2254
4894
|
const env = { MINDGRAPH_API_KEY: apiKey };
|
|
@@ -2278,7 +4918,16 @@ function installClaudeDesktop(apiKey, baseUrl) {
|
|
|
2278
4918
|
console.log("Restart Claude Desktop to activate.");
|
|
2279
4919
|
}
|
|
2280
4920
|
function installClaudeCode(apiKey, baseUrl) {
|
|
2281
|
-
const envArgs = [
|
|
4921
|
+
const envArgs = [
|
|
4922
|
+
`--env`,
|
|
4923
|
+
`MINDGRAPH_API_KEY=${apiKey}`,
|
|
4924
|
+
"--env",
|
|
4925
|
+
"MINDGRAPH_PROFILE=coding",
|
|
4926
|
+
"--env",
|
|
4927
|
+
"MINDGRAPH_HARNESS=claude-code",
|
|
4928
|
+
"--env",
|
|
4929
|
+
"MINDGRAPH_AGENT_ID=claude-code"
|
|
4930
|
+
];
|
|
2282
4931
|
if (baseUrl) {
|
|
2283
4932
|
envArgs.push("--env", `MINDGRAPH_BASE_URL=${baseUrl}`);
|
|
2284
4933
|
}
|
|
@@ -2308,7 +4957,7 @@ function installClaudeCode(apiKey, baseUrl) {
|
|
|
2308
4957
|
}
|
|
2309
4958
|
function uninstallClaudeDesktop() {
|
|
2310
4959
|
const configPath = getClaudeDesktopConfigPath();
|
|
2311
|
-
if (!
|
|
4960
|
+
if (!fs5.existsSync(configPath)) {
|
|
2312
4961
|
console.log("Claude Desktop config not found. Nothing to remove.");
|
|
2313
4962
|
return;
|
|
2314
4963
|
}
|
|
@@ -2335,7 +4984,7 @@ function uninstallClaudeCode() {
|
|
|
2335
4984
|
}
|
|
2336
4985
|
function printStatus() {
|
|
2337
4986
|
const desktopPath = getClaudeDesktopConfigPath();
|
|
2338
|
-
if (
|
|
4987
|
+
if (fs5.existsSync(desktopPath)) {
|
|
2339
4988
|
const config = readJsonFile(desktopPath);
|
|
2340
4989
|
const servers = config.mcpServers;
|
|
2341
4990
|
if (servers && "mindgraph" in servers) {
|
|
@@ -2353,6 +5002,34 @@ function printStatus() {
|
|
|
2353
5002
|
`MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
|
|
2354
5003
|
);
|
|
2355
5004
|
}
|
|
5005
|
+
function withHooksFlag() {
|
|
5006
|
+
return process.argv.includes("--hooks");
|
|
5007
|
+
}
|
|
5008
|
+
function finishCodeInstall(apiKey, baseUrl, withHooks) {
|
|
5009
|
+
if (withHooks) {
|
|
5010
|
+
const result = installClaudeHooks("user");
|
|
5011
|
+
console.log(
|
|
5012
|
+
`Installed ${result.added} MindGraph Claude Code hook entries in ${result.path}`
|
|
5013
|
+
);
|
|
5014
|
+
const envPath = saveHookEnv({ apiKey, baseUrl });
|
|
5015
|
+
console.log(`Saved hook connection settings to ${envPath} (mode 600)`);
|
|
5016
|
+
} else {
|
|
5017
|
+
console.log(
|
|
5018
|
+
"Tip: run with --hooks (or run install-hooks) to add the Claude Code session hooks \u2014 work-brief injection, provenance, reflection checkpoint."
|
|
5019
|
+
);
|
|
5020
|
+
}
|
|
5021
|
+
printCodegraphStatus();
|
|
5022
|
+
}
|
|
5023
|
+
function printCodegraphStatus() {
|
|
5024
|
+
try {
|
|
5025
|
+
(0, import_child_process.execFileSync)("codegraph", ["--version"], { stdio: "pipe" });
|
|
5026
|
+
console.log("Code intelligence: codegraph detected \u2014 anchors and structural recall are enabled.");
|
|
5027
|
+
} catch {
|
|
5028
|
+
console.log(
|
|
5029
|
+
"Optional: enable code intelligence by installing codegraph (https://github.com/colbymchenry/codegraph) and running `codegraph init` in your repositories. Without it, memory and work tools still function; code anchoring degrades gracefully."
|
|
5030
|
+
);
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
2356
5033
|
function printUsage() {
|
|
2357
5034
|
console.log(`
|
|
2358
5035
|
mindgraph-mcp \u2014 MCP server for MindGraph knowledge graphs
|
|
@@ -2361,14 +5038,19 @@ USAGE:
|
|
|
2361
5038
|
mindgraph-mcp Start the MCP server (stdio transport)
|
|
2362
5039
|
mindgraph-mcp init Interactive setup for Claude Desktop
|
|
2363
5040
|
mindgraph-mcp install Install into Claude Desktop config
|
|
2364
|
-
mindgraph-mcp install-code Install into Claude Code
|
|
5041
|
+
mindgraph-mcp install-code Install into Claude Code (--hooks: + session hooks)
|
|
2365
5042
|
mindgraph-mcp uninstall Remove from Claude Desktop config
|
|
2366
5043
|
mindgraph-mcp uninstall-code Remove from Claude Code
|
|
5044
|
+
mindgraph-mcp install-hooks --harness claude-code [--scope user|project]
|
|
5045
|
+
mindgraph-mcp uninstall-hooks --harness claude-code [--scope user|project]
|
|
2367
5046
|
mindgraph-mcp status Show installation status
|
|
2368
5047
|
|
|
2369
5048
|
OPTIONS:
|
|
2370
5049
|
--api-key <key> MindGraph API key (or set MINDGRAPH_API_KEY env var)
|
|
2371
5050
|
--base-url <url> Custom API base URL (default: https://api.mindgraph.cloud)
|
|
5051
|
+
--scope <scope> Hook settings scope: project (default) or user
|
|
5052
|
+
--project-dir <dir> Project root for project-scoped hooks
|
|
5053
|
+
--harness <name> Hook harness (currently claude-code)
|
|
2372
5054
|
--help, -h Show this help message
|
|
2373
5055
|
|
|
2374
5056
|
EXAMPLES:
|
|
@@ -2384,38 +5066,11 @@ EXAMPLES:
|
|
|
2384
5066
|
Get your API key at: https://mindgraph.cloud/dashboard/keys
|
|
2385
5067
|
`);
|
|
2386
5068
|
}
|
|
2387
|
-
function parseArgs(argv) {
|
|
2388
|
-
const args = argv.slice(2);
|
|
2389
|
-
let command = "serve";
|
|
2390
|
-
let apiKey = process.env.MINDGRAPH_API_KEY;
|
|
2391
|
-
let baseUrl = process.env.MINDGRAPH_BASE_URL;
|
|
2392
|
-
for (let i = 0; i < args.length; i++) {
|
|
2393
|
-
const arg = args[i];
|
|
2394
|
-
switch (arg) {
|
|
2395
|
-
case "--api-key":
|
|
2396
|
-
apiKey = args[++i];
|
|
2397
|
-
break;
|
|
2398
|
-
case "--base-url":
|
|
2399
|
-
baseUrl = args[++i];
|
|
2400
|
-
break;
|
|
2401
|
-
case "--help":
|
|
2402
|
-
case "-h":
|
|
2403
|
-
command = "help";
|
|
2404
|
-
break;
|
|
2405
|
-
default:
|
|
2406
|
-
if (!arg.startsWith("-")) {
|
|
2407
|
-
command = arg;
|
|
2408
|
-
}
|
|
2409
|
-
break;
|
|
2410
|
-
}
|
|
2411
|
-
}
|
|
2412
|
-
return { command, apiKey, baseUrl };
|
|
2413
|
-
}
|
|
2414
5069
|
var DASHBOARD_URL = process.env.MINDGRAPH_DASHBOARD_URL || "https://mindgraph.cloud";
|
|
2415
5070
|
var POLL_INTERVAL = 2e3;
|
|
2416
5071
|
var POLL_TIMEOUT = 10 * 60 * 1e3;
|
|
2417
5072
|
function generateCode() {
|
|
2418
|
-
return
|
|
5073
|
+
return crypto4.randomBytes(32).toString("hex");
|
|
2419
5074
|
}
|
|
2420
5075
|
function openBrowser(url) {
|
|
2421
5076
|
try {
|
|
@@ -2452,7 +5107,7 @@ async function pollForKey(code) {
|
|
|
2452
5107
|
} catch (e) {
|
|
2453
5108
|
if (e instanceof Error && e.message.includes("Session")) throw e;
|
|
2454
5109
|
}
|
|
2455
|
-
await new Promise((
|
|
5110
|
+
await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL));
|
|
2456
5111
|
}
|
|
2457
5112
|
throw new Error("Timed out waiting for authorization.");
|
|
2458
5113
|
}
|
|
@@ -2462,7 +5117,7 @@ async function interactiveInit(baseUrl) {
|
|
|
2462
5117
|
input: process.stdin,
|
|
2463
5118
|
output: process.stdout
|
|
2464
5119
|
});
|
|
2465
|
-
const ask = (q) => new Promise((
|
|
5120
|
+
const ask = (q) => new Promise((resolve2) => rl.question(q, resolve2));
|
|
2466
5121
|
console.log("\nMindGraph MCP Server Setup\n");
|
|
2467
5122
|
console.log(" 1. Sign in via browser (recommended)");
|
|
2468
5123
|
console.log(" 2. Enter API key manually");
|
|
@@ -2509,10 +5164,12 @@ async function interactiveInit(baseUrl) {
|
|
|
2509
5164
|
break;
|
|
2510
5165
|
case "2":
|
|
2511
5166
|
installClaudeCode(apiKey, baseUrl);
|
|
5167
|
+
finishCodeInstall(apiKey, baseUrl, withHooksFlag());
|
|
2512
5168
|
break;
|
|
2513
5169
|
case "3":
|
|
2514
5170
|
installClaudeDesktop(apiKey, baseUrl);
|
|
2515
5171
|
installClaudeCode(apiKey, baseUrl);
|
|
5172
|
+
finishCodeInstall(apiKey, baseUrl, withHooksFlag());
|
|
2516
5173
|
break;
|
|
2517
5174
|
default:
|
|
2518
5175
|
console.error("Invalid choice.");
|
|
@@ -2521,7 +5178,7 @@ async function interactiveInit(baseUrl) {
|
|
|
2521
5178
|
console.log("\nDone! Your knowledge graph is now connected.");
|
|
2522
5179
|
}
|
|
2523
5180
|
async function main2() {
|
|
2524
|
-
const { command, apiKey, baseUrl } = parseArgs(process.argv);
|
|
5181
|
+
const { command, apiKey, baseUrl, scope, projectDir, harness, hooks } = parseArgs(process.argv);
|
|
2525
5182
|
switch (command) {
|
|
2526
5183
|
case "help":
|
|
2527
5184
|
printUsage();
|
|
@@ -2546,6 +5203,7 @@ async function main2() {
|
|
|
2546
5203
|
process.exit(1);
|
|
2547
5204
|
}
|
|
2548
5205
|
installClaudeCode(apiKey, baseUrl);
|
|
5206
|
+
finishCodeInstall(apiKey, baseUrl, withHooksFlag());
|
|
2549
5207
|
break;
|
|
2550
5208
|
case "uninstall":
|
|
2551
5209
|
uninstallClaudeDesktop();
|
|
@@ -2553,6 +5211,61 @@ async function main2() {
|
|
|
2553
5211
|
case "uninstall-code":
|
|
2554
5212
|
uninstallClaudeCode();
|
|
2555
5213
|
break;
|
|
5214
|
+
case "install-hooks": {
|
|
5215
|
+
if ((harness || "claude-code") !== "claude-code") {
|
|
5216
|
+
throw new Error("only --harness claude-code is available in this release");
|
|
5217
|
+
}
|
|
5218
|
+
const result = installClaudeHooks(scope, projectDir);
|
|
5219
|
+
console.log(
|
|
5220
|
+
`Installed ${result.added} MindGraph Claude Code hook entries in ${result.path}`
|
|
5221
|
+
);
|
|
5222
|
+
if (apiKey || baseUrl) {
|
|
5223
|
+
const envPath = saveHookEnv({ apiKey, baseUrl });
|
|
5224
|
+
console.log(`Saved hook connection settings to ${envPath} (mode 600)`);
|
|
5225
|
+
} else {
|
|
5226
|
+
console.log(
|
|
5227
|
+
"Note: hooks resolve MINDGRAPH_API_KEY/MINDGRAPH_BASE_URL from the environment or ~/.mindgraph/hooks.json \u2014 pass --api-key/--base-url to persist them now."
|
|
5228
|
+
);
|
|
5229
|
+
}
|
|
5230
|
+
break;
|
|
5231
|
+
}
|
|
5232
|
+
case "uninstall-hooks": {
|
|
5233
|
+
if ((harness || "claude-code") !== "claude-code") {
|
|
5234
|
+
throw new Error("only --harness claude-code is available in this release");
|
|
5235
|
+
}
|
|
5236
|
+
const result = uninstallClaudeHooks(scope, projectDir);
|
|
5237
|
+
console.log(
|
|
5238
|
+
`Removed ${result.removed} MindGraph Claude Code hooks from ${result.path}`
|
|
5239
|
+
);
|
|
5240
|
+
break;
|
|
5241
|
+
}
|
|
5242
|
+
case "hook": {
|
|
5243
|
+
const stored = loadHookEnv();
|
|
5244
|
+
const hookApiKey = apiKey ?? stored.apiKey;
|
|
5245
|
+
const hookBaseUrl = baseUrl ?? stored.baseUrl;
|
|
5246
|
+
if (!hookApiKey) {
|
|
5247
|
+
process.stdout.write("{}\n");
|
|
5248
|
+
break;
|
|
5249
|
+
}
|
|
5250
|
+
try {
|
|
5251
|
+
const input = await readHookInput();
|
|
5252
|
+
const client2 = new import_mindgraph3.MindGraph({
|
|
5253
|
+
baseUrl: hookBaseUrl || "https://api.mindgraph.cloud",
|
|
5254
|
+
apiKey: hookApiKey,
|
|
5255
|
+
orgId: process.env.MINDGRAPH_ORG_ID,
|
|
5256
|
+
maxRetries: 0,
|
|
5257
|
+
telemetrySurface: "mcp"
|
|
5258
|
+
});
|
|
5259
|
+
const output = await runClaudeHook(input, client2, {
|
|
5260
|
+
agentId: process.env.MINDGRAPH_AGENT_ID || "claude-code"
|
|
5261
|
+
});
|
|
5262
|
+
process.stdout.write(`${JSON.stringify(output)}
|
|
5263
|
+
`);
|
|
5264
|
+
} catch {
|
|
5265
|
+
process.stdout.write("{}\n");
|
|
5266
|
+
}
|
|
5267
|
+
break;
|
|
5268
|
+
}
|
|
2556
5269
|
case "status":
|
|
2557
5270
|
printStatus();
|
|
2558
5271
|
break;
|