mindgraph-mcp 0.22.0 → 0.24.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/README.md +37 -1
- package/dist/cli.js +915 -258
- package/dist/index.js +263 -136
- package/dist/server.d.mts +4 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +32 -2
- package/dist/server.mjs +32 -2
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -35,14 +35,14 @@ function errorFields(body) {
|
|
|
35
35
|
...typeof fields.retriable === "boolean" ? { retriable: fields.retriable } : {}
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
-
function canRetryRequest(method,
|
|
38
|
+
function canRetryRequest(method, path9, body) {
|
|
39
39
|
if (method === "GET" || method === "HEAD") return true;
|
|
40
40
|
if (method !== "POST") return false;
|
|
41
|
-
if (READ_POSTS.has(
|
|
41
|
+
if (READ_POSTS.has(path9)) return true;
|
|
42
42
|
const fields = body && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
43
43
|
if (typeof fields.action !== "string") return false;
|
|
44
|
-
if (Object.prototype.hasOwnProperty.call(READ_ACTIONS,
|
|
45
|
-
return
|
|
44
|
+
if (Object.prototype.hasOwnProperty.call(READ_ACTIONS, path9) && READ_ACTIONS[path9].includes(fields.action)) return true;
|
|
45
|
+
return path9 === "/agent/plan" && IDEMPOTENT_WORK.has(fields.action) && typeof fields.idempotency_key === "string" && fields.idempotency_key.trim().length > 0;
|
|
46
46
|
}
|
|
47
47
|
function permitsRetry(fields) {
|
|
48
48
|
return fields.retriable !== false && ![
|
|
@@ -179,8 +179,8 @@ var init_dist = __esm({
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
// ---- HTTP helpers ----
|
|
182
|
-
async request(method,
|
|
183
|
-
const url = `${this.baseUrl}${
|
|
182
|
+
async request(method, path9, body) {
|
|
183
|
+
const url = `${this.baseUrl}${path9}`;
|
|
184
184
|
const requestId = createRequestId();
|
|
185
185
|
const init = {
|
|
186
186
|
method,
|
|
@@ -195,12 +195,12 @@ var init_dist = __esm({
|
|
|
195
195
|
}
|
|
196
196
|
const retrySafe = canRetryRequest(
|
|
197
197
|
method,
|
|
198
|
-
|
|
198
|
+
path9,
|
|
199
199
|
typeof init.body === "string" ? JSON.parse(init.body) : void 0
|
|
200
200
|
);
|
|
201
201
|
let lastError;
|
|
202
202
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
203
|
-
const res = await this.fetchWithDeadline(url, init, method,
|
|
203
|
+
const res = await this.fetchWithDeadline(url, init, method, path9);
|
|
204
204
|
if (!res.ok) {
|
|
205
205
|
const text22 = await res.text().catch(() => "");
|
|
206
206
|
let parsed;
|
|
@@ -210,7 +210,7 @@ var init_dist = __esm({
|
|
|
210
210
|
parsed = text22;
|
|
211
211
|
}
|
|
212
212
|
const err6 = new MindGraphError(
|
|
213
|
-
`${method} ${
|
|
213
|
+
`${method} ${path9} failed: ${res.status}`,
|
|
214
214
|
res.status,
|
|
215
215
|
parsed
|
|
216
216
|
);
|
|
@@ -234,7 +234,7 @@ var init_dist = __esm({
|
|
|
234
234
|
* "timeout"`, `status: 0`). Without it the request is sent unchanged — no
|
|
235
235
|
* AbortSignal is attached, preserving the pre-0.16 behaviour.
|
|
236
236
|
*/
|
|
237
|
-
async fetchWithDeadline(url, init, method,
|
|
237
|
+
async fetchWithDeadline(url, init, method, path9) {
|
|
238
238
|
if (this.timeoutMs === void 0) {
|
|
239
239
|
return fetch(url, init);
|
|
240
240
|
}
|
|
@@ -245,7 +245,7 @@ var init_dist = __esm({
|
|
|
245
245
|
} catch (error2) {
|
|
246
246
|
if (controller.signal.aborted) {
|
|
247
247
|
throw new MindGraphError(
|
|
248
|
-
`${method} ${
|
|
248
|
+
`${method} ${path9} timed out after ${this.timeoutMs}ms`,
|
|
249
249
|
0,
|
|
250
250
|
{ error: "timeout", code: "timeout", retriable: false }
|
|
251
251
|
);
|
|
@@ -255,17 +255,17 @@ var init_dist = __esm({
|
|
|
255
255
|
clearTimeout(timer);
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
|
-
get(
|
|
259
|
-
return this.request("GET",
|
|
258
|
+
get(path9) {
|
|
259
|
+
return this.request("GET", path9);
|
|
260
260
|
}
|
|
261
|
-
post(
|
|
262
|
-
return this.request("POST",
|
|
261
|
+
post(path9, body) {
|
|
262
|
+
return this.request("POST", path9, body);
|
|
263
263
|
}
|
|
264
|
-
patch(
|
|
265
|
-
return this.request("PATCH",
|
|
264
|
+
patch(path9, body) {
|
|
265
|
+
return this.request("PATCH", path9, body);
|
|
266
266
|
}
|
|
267
|
-
del(
|
|
268
|
-
return this.request("DELETE",
|
|
267
|
+
del(path9) {
|
|
268
|
+
return this.request("DELETE", path9);
|
|
269
269
|
}
|
|
270
270
|
// ---- Health ----
|
|
271
271
|
async health() {
|
|
@@ -1184,7 +1184,9 @@ function loadHookEnv(dir) {
|
|
|
1184
1184
|
baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : void 0,
|
|
1185
1185
|
apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : void 0,
|
|
1186
1186
|
agentId: typeof parsed.agentId === "string" ? parsed.agentId : void 0,
|
|
1187
|
-
orgId: typeof parsed.orgId === "string" ? parsed.orgId : void 0
|
|
1187
|
+
orgId: typeof parsed.orgId === "string" ? parsed.orgId : void 0,
|
|
1188
|
+
promptRecall: typeof parsed.promptRecall === "boolean" ? parsed.promptRecall : void 0,
|
|
1189
|
+
capture: typeof parsed.capture === "boolean" ? parsed.capture : void 0
|
|
1188
1190
|
};
|
|
1189
1191
|
} catch {
|
|
1190
1192
|
return {};
|
|
@@ -1198,7 +1200,9 @@ function saveHookEnv(env, dir) {
|
|
|
1198
1200
|
baseUrl: env.baseUrl ?? existing.baseUrl,
|
|
1199
1201
|
apiKey: env.apiKey ?? existing.apiKey,
|
|
1200
1202
|
agentId: env.agentId ?? existing.agentId,
|
|
1201
|
-
orgId: env.orgId === null ? void 0 : env.orgId ?? existing.orgId
|
|
1203
|
+
orgId: env.orgId === null ? void 0 : env.orgId ?? existing.orgId,
|
|
1204
|
+
promptRecall: env.promptRecall ?? existing.promptRecall,
|
|
1205
|
+
capture: env.capture ?? existing.capture
|
|
1202
1206
|
};
|
|
1203
1207
|
import_node_fs.default.writeFileSync(file, `${JSON.stringify(merged, null, 2)}
|
|
1204
1208
|
`, {
|
|
@@ -1236,14 +1240,14 @@ function unavailabilityCaveats(reason, message) {
|
|
|
1236
1240
|
}
|
|
1237
1241
|
return caveats;
|
|
1238
1242
|
}
|
|
1239
|
-
function normalizePath(
|
|
1240
|
-
return
|
|
1243
|
+
function normalizePath(path9) {
|
|
1244
|
+
return path9.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
1241
1245
|
}
|
|
1242
1246
|
function normalizeSignature(signature) {
|
|
1243
1247
|
return signature.replace(/\s+/g, " ").trim();
|
|
1244
1248
|
}
|
|
1245
|
-
function languageFromPath(
|
|
1246
|
-
const extension = (0, import_node_path2.extname)(
|
|
1249
|
+
function languageFromPath(path9) {
|
|
1250
|
+
const extension = (0, import_node_path2.extname)(path9).toLowerCase();
|
|
1247
1251
|
return {
|
|
1248
1252
|
".c": "c",
|
|
1249
1253
|
".cc": "cpp",
|
|
@@ -2560,7 +2564,114 @@ var init_code_tool = __esm({
|
|
|
2560
2564
|
}
|
|
2561
2565
|
});
|
|
2562
2566
|
|
|
2567
|
+
// src/call-ledger.ts
|
|
2568
|
+
function callsDir(options) {
|
|
2569
|
+
return import_node_path3.default.join(
|
|
2570
|
+
options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path3.default.join(import_node_os3.default.homedir(), ".mindgraph", "runtime"),
|
|
2571
|
+
"calls"
|
|
2572
|
+
);
|
|
2573
|
+
}
|
|
2574
|
+
function stableJson(value) {
|
|
2575
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
2576
|
+
if (value && typeof value === "object") {
|
|
2577
|
+
return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
|
|
2578
|
+
}
|
|
2579
|
+
return JSON.stringify(value) ?? "null";
|
|
2580
|
+
}
|
|
2581
|
+
function bareToolName(name) {
|
|
2582
|
+
const cut = name.lastIndexOf("__");
|
|
2583
|
+
return cut === -1 ? name : name.slice(cut + 2);
|
|
2584
|
+
}
|
|
2585
|
+
function callKey(toolName, toolInput) {
|
|
2586
|
+
return import_node_crypto4.default.createHash("sha256").update(`${bareToolName(toolName)}\0${stableJson(toolInput)}`).digest("hex");
|
|
2587
|
+
}
|
|
2588
|
+
function sessionKey(sessionId) {
|
|
2589
|
+
return import_node_crypto4.default.createHash("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
2590
|
+
}
|
|
2591
|
+
function prune(dir, now) {
|
|
2592
|
+
let names;
|
|
2593
|
+
try {
|
|
2594
|
+
names = import_node_fs3.default.readdirSync(dir);
|
|
2595
|
+
} catch {
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
for (const name of names) {
|
|
2599
|
+
const file = import_node_path3.default.join(dir, name);
|
|
2600
|
+
try {
|
|
2601
|
+
if (now - import_node_fs3.default.statSync(file).mtimeMs > TTL_MS) import_node_fs3.default.unlinkSync(file);
|
|
2602
|
+
} catch {
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
function recordPendingCall(toolName, toolInput, harnessSessionId, fills, options = {}) {
|
|
2607
|
+
const now = (options.now ?? Date.now)();
|
|
2608
|
+
const dir = callsDir(options);
|
|
2609
|
+
try {
|
|
2610
|
+
import_node_fs3.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2611
|
+
prune(dir, now);
|
|
2612
|
+
const record = {
|
|
2613
|
+
harnessSessionId,
|
|
2614
|
+
projectDir: options.projectDir ?? process.env.CLAUDE_PROJECT_DIR,
|
|
2615
|
+
fills,
|
|
2616
|
+
createdAt: now
|
|
2617
|
+
};
|
|
2618
|
+
const file = import_node_path3.default.join(
|
|
2619
|
+
dir,
|
|
2620
|
+
`${callKey(toolName, toolInput)}.${sessionKey(harnessSessionId)}.json`
|
|
2621
|
+
);
|
|
2622
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
2623
|
+
import_node_fs3.default.writeFileSync(temporary, JSON.stringify(record), { mode: 384 });
|
|
2624
|
+
import_node_fs3.default.renameSync(temporary, file);
|
|
2625
|
+
} catch {
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
function resolvePendingCall(toolName, toolInput, options = {}) {
|
|
2629
|
+
const now = (options.now ?? Date.now)();
|
|
2630
|
+
const dir = callsDir(options);
|
|
2631
|
+
const prefix = `${callKey(toolName, toolInput)}.`;
|
|
2632
|
+
let names;
|
|
2633
|
+
try {
|
|
2634
|
+
names = import_node_fs3.default.readdirSync(dir).filter((name) => name.startsWith(prefix) && name.endsWith(".json"));
|
|
2635
|
+
} catch {
|
|
2636
|
+
return void 0;
|
|
2637
|
+
}
|
|
2638
|
+
let matches = [];
|
|
2639
|
+
for (const name of names) {
|
|
2640
|
+
try {
|
|
2641
|
+
const record = JSON.parse(
|
|
2642
|
+
import_node_fs3.default.readFileSync(import_node_path3.default.join(dir, name), "utf8")
|
|
2643
|
+
);
|
|
2644
|
+
if (typeof record.createdAt === "number" && now - record.createdAt <= TTL_MS && record.fills && typeof record.fills === "object") {
|
|
2645
|
+
matches.push(record);
|
|
2646
|
+
}
|
|
2647
|
+
} catch {
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
if (matches.length > 1) {
|
|
2651
|
+
const projectDir = options.projectDir ?? process.env.CLAUDE_PROJECT_DIR;
|
|
2652
|
+
matches = projectDir ? matches.filter((record) => record.projectDir === projectDir) : [];
|
|
2653
|
+
}
|
|
2654
|
+
return matches.length === 1 ? matches[0].fills : void 0;
|
|
2655
|
+
}
|
|
2656
|
+
var import_node_crypto4, import_node_fs3, import_node_os3, import_node_path3, TTL_MS;
|
|
2657
|
+
var init_call_ledger = __esm({
|
|
2658
|
+
"src/call-ledger.ts"() {
|
|
2659
|
+
"use strict";
|
|
2660
|
+
import_node_crypto4 = __toESM(require("crypto"));
|
|
2661
|
+
import_node_fs3 = __toESM(require("fs"));
|
|
2662
|
+
import_node_os3 = __toESM(require("os"));
|
|
2663
|
+
import_node_path3 = __toESM(require("path"));
|
|
2664
|
+
TTL_MS = 10 * 6e4;
|
|
2665
|
+
}
|
|
2666
|
+
});
|
|
2667
|
+
|
|
2563
2668
|
// src/hook-installer.ts
|
|
2669
|
+
function storedFeatures(homeDir) {
|
|
2670
|
+
const { promptRecall: promptRecall2, capture } = loadHookEnv(
|
|
2671
|
+
import_node_path5.default.join(homeDir || import_node_os5.default.homedir(), ".mindgraph")
|
|
2672
|
+
);
|
|
2673
|
+
return { promptRecall: promptRecall2, capture };
|
|
2674
|
+
}
|
|
2564
2675
|
function command(harness) {
|
|
2565
2676
|
return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
|
|
2566
2677
|
}
|
|
@@ -2568,24 +2679,24 @@ function windowsCommand(harness) {
|
|
|
2568
2679
|
return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
|
|
2569
2680
|
}
|
|
2570
2681
|
function installHookRunner(sourceFile, homeDir, version) {
|
|
2571
|
-
const target =
|
|
2572
|
-
|
|
2573
|
-
const temporary = `${target}.${process.pid}.${
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
const versionPath =
|
|
2682
|
+
const target = import_node_path5.default.join(homeDir || import_node_os5.default.homedir(), RUNNER_RELATIVE);
|
|
2683
|
+
import_node_fs6.default.mkdirSync(import_node_path5.default.dirname(target), { recursive: true });
|
|
2684
|
+
const temporary = `${target}.${process.pid}.${import_node_crypto6.default.randomUUID()}.tmp`;
|
|
2685
|
+
import_node_fs6.default.copyFileSync(sourceFile, temporary);
|
|
2686
|
+
import_node_fs6.default.renameSync(temporary, target);
|
|
2687
|
+
const versionPath = import_node_path5.default.join(homeDir || import_node_os5.default.homedir(), RUNNER_VERSION_RELATIVE);
|
|
2577
2688
|
if (version) {
|
|
2578
|
-
|
|
2689
|
+
import_node_fs6.default.writeFileSync(versionPath, `${version}
|
|
2579
2690
|
`);
|
|
2580
2691
|
} else {
|
|
2581
|
-
|
|
2692
|
+
import_node_fs6.default.rmSync(versionPath, { force: true });
|
|
2582
2693
|
}
|
|
2583
2694
|
return target;
|
|
2584
2695
|
}
|
|
2585
2696
|
function installedHookRunnerVersion(homeDir) {
|
|
2586
2697
|
try {
|
|
2587
|
-
const raw =
|
|
2588
|
-
|
|
2698
|
+
const raw = import_node_fs6.default.readFileSync(
|
|
2699
|
+
import_node_path5.default.join(homeDir || import_node_os5.default.homedir(), RUNNER_VERSION_RELATIVE),
|
|
2589
2700
|
"utf8"
|
|
2590
2701
|
).trim();
|
|
2591
2702
|
return raw || void 0;
|
|
@@ -2610,11 +2721,11 @@ function refreshHookRunner(serverVersion, sourceBundle, homeDir) {
|
|
|
2610
2721
|
if (process.env.MINDGRAPH_HOOK_AUTOUPDATE === "off") {
|
|
2611
2722
|
return { refreshed: false, reason: "disabled" };
|
|
2612
2723
|
}
|
|
2613
|
-
const home = homeDir ||
|
|
2614
|
-
if (!
|
|
2724
|
+
const home = homeDir || import_node_os5.default.homedir();
|
|
2725
|
+
if (!import_node_fs6.default.existsSync(import_node_path5.default.join(home, RUNNER_RELATIVE))) {
|
|
2615
2726
|
return { refreshed: false, reason: "no_runner" };
|
|
2616
2727
|
}
|
|
2617
|
-
if (!
|
|
2728
|
+
if (!import_node_fs6.default.existsSync(sourceBundle)) {
|
|
2618
2729
|
return { refreshed: false, reason: "no_source" };
|
|
2619
2730
|
}
|
|
2620
2731
|
const server2 = parseSemver(serverVersion);
|
|
@@ -2645,9 +2756,16 @@ function refreshOwnedClaudeHooks(projectDir = process.cwd(), homeDir) {
|
|
|
2645
2756
|
for (const scope of scopes) {
|
|
2646
2757
|
try {
|
|
2647
2758
|
const file = settingsPath("claude-code", scope, projectDir, void 0, homeDir);
|
|
2648
|
-
if (!
|
|
2649
|
-
if (!
|
|
2650
|
-
installHooks(
|
|
2759
|
+
if (!import_node_fs6.default.existsSync(file)) continue;
|
|
2760
|
+
if (!import_node_fs6.default.readFileSync(file, "utf8").includes(OWNER_MARKER)) continue;
|
|
2761
|
+
installHooks(
|
|
2762
|
+
"claude-code",
|
|
2763
|
+
scope,
|
|
2764
|
+
projectDir,
|
|
2765
|
+
void 0,
|
|
2766
|
+
homeDir,
|
|
2767
|
+
storedFeatures(homeDir)
|
|
2768
|
+
);
|
|
2651
2769
|
refreshed.push(file);
|
|
2652
2770
|
} catch {
|
|
2653
2771
|
}
|
|
@@ -2659,15 +2777,15 @@ function versionSkewNote(serverVersion, homeDir) {
|
|
|
2659
2777
|
if (!runnerVersion || runnerVersion === serverVersion) return void 0;
|
|
2660
2778
|
return `NOTE: the installed MindGraph hook runner is v${runnerVersion} but this MCP server is v${serverVersion} \u2014 hook behavior and tool contracts may be out of sync. Tell the user to run: npx -y mindgraph-mcp@latest install-hooks (refreshes the pinned runner).`;
|
|
2661
2779
|
}
|
|
2662
|
-
function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME ||
|
|
2780
|
+
function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path5.default.join(import_node_os5.default.homedir(), ".codex"), homeDir) {
|
|
2663
2781
|
if (harness === "codex") {
|
|
2664
|
-
return scope === "user" ?
|
|
2782
|
+
return scope === "user" ? import_node_path5.default.join(codexHomeDir, "hooks.json") : import_node_path5.default.join(projectDir, ".codex", "hooks.json");
|
|
2665
2783
|
}
|
|
2666
|
-
return scope === "user" ?
|
|
2784
|
+
return scope === "user" ? import_node_path5.default.join(homeDir || import_node_os5.default.homedir(), ".claude", "settings.json") : import_node_path5.default.join(projectDir, ".claude", "settings.json");
|
|
2667
2785
|
}
|
|
2668
2786
|
function readSettings(file) {
|
|
2669
|
-
if (!
|
|
2670
|
-
return JSON.parse(
|
|
2787
|
+
if (!import_node_fs6.default.existsSync(file)) return {};
|
|
2788
|
+
return JSON.parse(import_node_fs6.default.readFileSync(file, "utf8"));
|
|
2671
2789
|
}
|
|
2672
2790
|
function ownedHook(harness, timeout, additionalContextLimit) {
|
|
2673
2791
|
return {
|
|
@@ -2679,7 +2797,7 @@ function ownedHook(harness, timeout, additionalContextLimit) {
|
|
|
2679
2797
|
...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
|
|
2680
2798
|
};
|
|
2681
2799
|
}
|
|
2682
|
-
function desiredEntries(harness) {
|
|
2800
|
+
function desiredEntries(harness, features = {}) {
|
|
2683
2801
|
const common = {
|
|
2684
2802
|
SessionStart: [
|
|
2685
2803
|
{
|
|
@@ -2711,7 +2829,17 @@ function desiredEntries(harness) {
|
|
|
2711
2829
|
],
|
|
2712
2830
|
// PostToolUse awaits the lease renewal round-trip, which fires exactly
|
|
2713
2831
|
// when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
|
|
2714
|
-
|
|
2832
|
+
// Claude Code: only the tools the hook acts on (MindGraph calls, edits,
|
|
2833
|
+
// and the shell/agent calls that keep a lease alive). Read, Grep and Glob
|
|
2834
|
+
// are most of a session's tool calls and the hook did nothing useful for
|
|
2835
|
+
// them but start a process (roadmap H8). Codex names its tools
|
|
2836
|
+
// differently, so it keeps the catch-all.
|
|
2837
|
+
PostToolUse: [
|
|
2838
|
+
{
|
|
2839
|
+
matcher: harness === "claude-code" ? CLAUDE_POST_TOOL_MATCHER : ".*",
|
|
2840
|
+
hooks: [ownedHook(harness, 10)]
|
|
2841
|
+
}
|
|
2842
|
+
],
|
|
2715
2843
|
Stop: [{ hooks: [ownedHook(harness, 5)] }],
|
|
2716
2844
|
// SessionEnd makes up to two sequential cloud calls (abandon_iteration +
|
|
2717
2845
|
// session close) — the SAME cold-tenant latency SessionStart needs 30s
|
|
@@ -2722,11 +2850,17 @@ function desiredEntries(harness) {
|
|
|
2722
2850
|
{ hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
|
|
2723
2851
|
]
|
|
2724
2852
|
};
|
|
2725
|
-
|
|
2853
|
+
if (harness !== "claude-code") return common;
|
|
2854
|
+
return {
|
|
2726
2855
|
...common,
|
|
2727
2856
|
TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
|
|
2728
|
-
TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
|
|
2729
|
-
|
|
2857
|
+
TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }],
|
|
2858
|
+
// Bounded recall per prompt: the core gives up at 4s and fails open, so
|
|
2859
|
+
// a 5s budget never holds the prompt longer than that.
|
|
2860
|
+
...features.promptRecall !== false ? { UserPromptSubmit: [{ hooks: [ownedHook(harness, 5)] }] } : {},
|
|
2861
|
+
// Opt-in transcript capture before compaction (cold-tenant ingest).
|
|
2862
|
+
...features.capture === true ? { PreCompact: [{ hooks: [ownedHook(harness, 30)] }] } : {}
|
|
2863
|
+
};
|
|
2730
2864
|
}
|
|
2731
2865
|
function hookCommand(value) {
|
|
2732
2866
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
@@ -2739,21 +2873,30 @@ function entryHasOwnedHook(value) {
|
|
|
2739
2873
|
return Array.isArray(hooks) && hooks.some((hook) => hookCommand(hook)?.includes(OWNER_MARKER));
|
|
2740
2874
|
}
|
|
2741
2875
|
function writeSettings(file, settings) {
|
|
2742
|
-
|
|
2743
|
-
const temporary = `${file}.${process.pid}.${
|
|
2744
|
-
|
|
2876
|
+
import_node_fs6.default.mkdirSync(import_node_path5.default.dirname(file), { recursive: true });
|
|
2877
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto6.default.randomUUID()}.tmp`;
|
|
2878
|
+
import_node_fs6.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
|
|
2745
2879
|
`, {
|
|
2746
2880
|
mode: 384
|
|
2747
2881
|
});
|
|
2748
|
-
|
|
2882
|
+
import_node_fs6.default.renameSync(temporary, file);
|
|
2749
2883
|
}
|
|
2750
|
-
function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir, homeDir) {
|
|
2884
|
+
function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir, homeDir, features = {}) {
|
|
2751
2885
|
const file = settingsPath(harness, scope, projectDir, codexHomeDir, homeDir);
|
|
2752
2886
|
const settings = readSettings(file);
|
|
2753
2887
|
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
2754
2888
|
let added = 0;
|
|
2755
2889
|
let updated = 0;
|
|
2756
|
-
|
|
2890
|
+
const desired = desiredEntries(harness, features);
|
|
2891
|
+
for (const optional of ["UserPromptSubmit", "PreCompact"]) {
|
|
2892
|
+
if (optional in desired || !Array.isArray(hooks[optional])) continue;
|
|
2893
|
+
const kept = hooks[optional].filter(
|
|
2894
|
+
(entry) => !entryHasOwnedHook(entry)
|
|
2895
|
+
);
|
|
2896
|
+
if (kept.length > 0) hooks[optional] = kept;
|
|
2897
|
+
else delete hooks[optional];
|
|
2898
|
+
}
|
|
2899
|
+
for (const [event, wanted] of Object.entries(desired)) {
|
|
2757
2900
|
const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
2758
2901
|
const kept = current.filter((entry) => !entryHasOwnedHook(entry));
|
|
2759
2902
|
const hadOwned = kept.length !== current.length;
|
|
@@ -2770,7 +2913,7 @@ function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir,
|
|
|
2770
2913
|
}
|
|
2771
2914
|
function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
|
|
2772
2915
|
const file = settingsPath(harness, scope, projectDir, codexHomeDir);
|
|
2773
|
-
if (!
|
|
2916
|
+
if (!import_node_fs6.default.existsSync(file)) return { path: file, removed: 0 };
|
|
2774
2917
|
const settings = readSettings(file);
|
|
2775
2918
|
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
2776
2919
|
let removed = 0;
|
|
@@ -2804,8 +2947,8 @@ function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir
|
|
|
2804
2947
|
writeSettings(file, settings);
|
|
2805
2948
|
return { path: file, removed };
|
|
2806
2949
|
}
|
|
2807
|
-
function installClaudeHooks(scope, projectDir = process.cwd()) {
|
|
2808
|
-
return installHooks("claude-code", scope, projectDir);
|
|
2950
|
+
function installClaudeHooks(scope, projectDir = process.cwd(), features = {}) {
|
|
2951
|
+
return installHooks("claude-code", scope, projectDir, void 0, void 0, features);
|
|
2809
2952
|
}
|
|
2810
2953
|
function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
|
|
2811
2954
|
return uninstallHooks("claude-code", scope, projectDir);
|
|
@@ -2816,17 +2959,19 @@ function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
|
2816
2959
|
function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
2817
2960
|
return uninstallHooks("codex", scope, projectDir, codexHomeDir);
|
|
2818
2961
|
}
|
|
2819
|
-
var
|
|
2962
|
+
var import_node_fs6, import_node_os5, import_node_path5, import_node_crypto6, OWNER_MARKER, RUNNER_RELATIVE, RUNNER_VERSION_RELATIVE, CLAUDE_POST_TOOL_MATCHER;
|
|
2820
2963
|
var init_hook_installer = __esm({
|
|
2821
2964
|
"src/hook-installer.ts"() {
|
|
2822
2965
|
"use strict";
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2966
|
+
import_node_fs6 = __toESM(require("fs"));
|
|
2967
|
+
import_node_os5 = __toESM(require("os"));
|
|
2968
|
+
import_node_path5 = __toESM(require("path"));
|
|
2969
|
+
import_node_crypto6 = __toESM(require("crypto"));
|
|
2970
|
+
init_hook_env();
|
|
2827
2971
|
OWNER_MARKER = "--owner mindgraph";
|
|
2828
2972
|
RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
|
|
2829
2973
|
RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
|
|
2974
|
+
CLAUDE_POST_TOOL_MATCHER = "mcp__mindgraph__.*|Edit|MultiEdit|Write|NotebookEdit|Bash|Task|Agent";
|
|
2830
2975
|
}
|
|
2831
2976
|
});
|
|
2832
2977
|
|
|
@@ -2834,14 +2979,14 @@ var init_hook_installer = __esm({
|
|
|
2834
2979
|
function obj(value) {
|
|
2835
2980
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2836
2981
|
}
|
|
2837
|
-
function
|
|
2982
|
+
function str2(value) {
|
|
2838
2983
|
return typeof value === "string" && value ? value : void 0;
|
|
2839
2984
|
}
|
|
2840
2985
|
function num(value) {
|
|
2841
2986
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2842
2987
|
}
|
|
2843
2988
|
function edgeTypeName(value) {
|
|
2844
|
-
return
|
|
2989
|
+
return str2(value) ?? str2(obj(value)?.Custom);
|
|
2845
2990
|
}
|
|
2846
2991
|
function renderBudget(requested) {
|
|
2847
2992
|
if (requested === void 0 || !Number.isFinite(requested)) return RENDER_CHAR_BUDGET;
|
|
@@ -2851,7 +2996,7 @@ function renderBudget(requested) {
|
|
|
2851
2996
|
);
|
|
2852
2997
|
}
|
|
2853
2998
|
function clip(value, max, state) {
|
|
2854
|
-
const text2 =
|
|
2999
|
+
const text2 = str2(value);
|
|
2855
3000
|
if (!text2) return void 0;
|
|
2856
3001
|
if (text2.length <= max) return text2;
|
|
2857
3002
|
state.bounded = true;
|
|
@@ -2860,13 +3005,13 @@ function clip(value, max, state) {
|
|
|
2860
3005
|
function isNodeShaped(value) {
|
|
2861
3006
|
const node = obj(value);
|
|
2862
3007
|
return Boolean(
|
|
2863
|
-
node &&
|
|
3008
|
+
node && str2(node.uid) && (str2(node.label) || str2(node.node_type) || obj(node.node_type))
|
|
2864
3009
|
);
|
|
2865
3010
|
}
|
|
2866
3011
|
function nodeDisplayType(node) {
|
|
2867
3012
|
const props = obj(node.props);
|
|
2868
|
-
const plain =
|
|
2869
|
-
return (plain && plain !== "Custom" ? plain : void 0) ??
|
|
3013
|
+
const plain = str2(node.node_type);
|
|
3014
|
+
return (plain && plain !== "Custom" ? plain : void 0) ?? str2(obj(node.node_type)?.Custom) ?? str2(props?.object_type) ?? str2(props?.type_name) ?? str2(obj(props?.data)?.domain_type) ?? plain ?? "Node";
|
|
2870
3015
|
}
|
|
2871
3016
|
function nodeFields(node) {
|
|
2872
3017
|
const props = obj(node.props);
|
|
@@ -2886,10 +3031,10 @@ function statusTag(node) {
|
|
|
2886
3031
|
const tags = [];
|
|
2887
3032
|
if (node.superseded === true) {
|
|
2888
3033
|
tags.push(
|
|
2889
|
-
node.superseded_by ? `SUPERSEDED by ${
|
|
3034
|
+
node.superseded_by ? `SUPERSEDED by ${str2(node.superseded_by)}` : "SUPERSEDED"
|
|
2890
3035
|
);
|
|
2891
3036
|
}
|
|
2892
|
-
const truth =
|
|
3037
|
+
const truth = str2(node.truth_status) ?? str2(obj(node.props)?.truth_status);
|
|
2893
3038
|
if (truth && truth !== "accepted") tags.push(`truth: ${truth}`);
|
|
2894
3039
|
if (node.currently_valid === false) tags.push("OUTSIDE ITS VALIDITY WINDOW");
|
|
2895
3040
|
if (node.valid_at_time === false) tags.push("NOT VALID AT THE REQUESTED TIME");
|
|
@@ -2899,7 +3044,7 @@ function firstQuote(node, state) {
|
|
|
2899
3044
|
const chunks = Array.isArray(node.source_chunks) ? node.source_chunks : [];
|
|
2900
3045
|
for (const raw of chunks) {
|
|
2901
3046
|
const chunk = obj(raw);
|
|
2902
|
-
const quote =
|
|
3047
|
+
const quote = str2(chunk?.quote) ?? str2(obj(chunk?.anchor)?.exact);
|
|
2903
3048
|
if (quote) return clip(quote, QUOTE_MAX, state);
|
|
2904
3049
|
}
|
|
2905
3050
|
return void 0;
|
|
@@ -2907,7 +3052,7 @@ function firstQuote(node, state) {
|
|
|
2907
3052
|
function sourceTag(node) {
|
|
2908
3053
|
const docs = Array.isArray(node.source_documents) ? node.source_documents : [];
|
|
2909
3054
|
const first = obj(docs[0]);
|
|
2910
|
-
const title =
|
|
3055
|
+
const title = str2(first?.title);
|
|
2911
3056
|
if (!title) return void 0;
|
|
2912
3057
|
const extra = docs.length > 1 ? ` (+${docs.length - 1} more)` : "";
|
|
2913
3058
|
return `from: ${title}${extra}`;
|
|
@@ -2933,7 +3078,7 @@ function nodeLines(node, state) {
|
|
|
2933
3078
|
confidence !== void 0 ? `confidence ${confidence}` : void 0,
|
|
2934
3079
|
score !== void 0 ? `score ${Math.round(score * 100) / 100}` : void 0
|
|
2935
3080
|
].filter(Boolean).join(", ");
|
|
2936
|
-
const head = `- **${label}** [${
|
|
3081
|
+
const head = `- **${label}** [${str2(node.uid) || "?"}] (${parenthetical})${statusTag(node)}`;
|
|
2937
3082
|
const summary = nodeSummaryText(node, state);
|
|
2938
3083
|
const lines = [summary && summary !== label ? `${head}: ${summary}` : head];
|
|
2939
3084
|
const fields = layer7Fields(node, state);
|
|
@@ -2947,7 +3092,7 @@ function nodeLines(node, state) {
|
|
|
2947
3092
|
const rendered2 = believers.slice(0, 3).map((raw) => {
|
|
2948
3093
|
const believer = obj(raw);
|
|
2949
3094
|
const conf = num(believer?.confidence);
|
|
2950
|
-
return `${
|
|
3095
|
+
return `${str2(believer?.agent_label) || "?"}${conf !== void 0 ? ` (${conf})` : ""}`;
|
|
2951
3096
|
}).join(", ");
|
|
2952
3097
|
lines.push(` - believed by: ${rendered2}`);
|
|
2953
3098
|
}
|
|
@@ -2978,8 +3123,8 @@ function relationshipItems(edges, labelByUid, state, options) {
|
|
|
2978
3123
|
const edge = obj(raw);
|
|
2979
3124
|
if (!edge) continue;
|
|
2980
3125
|
const type = edgeTypeName(edge.edge_type) || "?";
|
|
2981
|
-
const from =
|
|
2982
|
-
const to =
|
|
3126
|
+
const from = str2(edge.from_uid);
|
|
3127
|
+
const to = str2(edge.to_uid);
|
|
2983
3128
|
if (CURATION_EDGE_TYPES.has(type)) continue;
|
|
2984
3129
|
if (!from || !to || from === to) continue;
|
|
2985
3130
|
const fromLabel = labelByUid.get(from) ?? (options?.fallbackToUid ? `[${from}]` : void 0);
|
|
@@ -3051,7 +3196,7 @@ function policySections(payload, state) {
|
|
|
3051
3196
|
if (policies.length === 0) return [];
|
|
3052
3197
|
const items = policies.map((raw) => obj(raw)).filter((policy) => Boolean(policy)).map((policy) => {
|
|
3053
3198
|
const rules = Array.isArray(policy.prose_rules) ? policy.prose_rules.map(String).join("; ") : policy.prose_rules;
|
|
3054
|
-
return `- ${
|
|
3199
|
+
return `- ${str2(policy.name) || "(unnamed policy)"}: ${clip(rules, SUMMARY_MAX, state) || "(structured rules apply)"}`;
|
|
3055
3200
|
});
|
|
3056
3201
|
return [{ heading: "## Applicable policies", items }];
|
|
3057
3202
|
}
|
|
@@ -3068,10 +3213,18 @@ function hiddenSections(payload) {
|
|
|
3068
3213
|
function renderContext(response, maxChars) {
|
|
3069
3214
|
const payload = obj(response);
|
|
3070
3215
|
if (!payload) return void 0;
|
|
3216
|
+
if (typeof payload.context_text === "string" && payload.context_text.length > 0) {
|
|
3217
|
+
const text2 = payload.context_text;
|
|
3218
|
+
if (maxChars !== void 0 && text2.length > maxChars) {
|
|
3219
|
+
return `${text2.slice(0, maxChars)}
|
|
3220
|
+
[truncated to ${maxChars} characters]`;
|
|
3221
|
+
}
|
|
3222
|
+
return text2;
|
|
3223
|
+
}
|
|
3071
3224
|
const graph = obj(payload.graph);
|
|
3072
3225
|
const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
|
3073
3226
|
const shaped = rawNodes.filter(isNodeShaped);
|
|
3074
|
-
const nodes = shaped.filter((node) => !INFRA_NODE_TYPES.has(
|
|
3227
|
+
const nodes = shaped.filter((node) => !INFRA_NODE_TYPES.has(str2(node.node_type) || ""));
|
|
3075
3228
|
const infraOnly = shaped.length - nodes.length;
|
|
3076
3229
|
const articles = Array.isArray(payload.articles) ? payload.articles : [];
|
|
3077
3230
|
const chunks = Array.isArray(payload.chunks) ? payload.chunks : [];
|
|
@@ -3081,7 +3234,7 @@ function renderContext(response, maxChars) {
|
|
|
3081
3234
|
sections.push({
|
|
3082
3235
|
heading: "## Knowledge articles",
|
|
3083
3236
|
items: articles.map((raw) => obj(raw)).filter((article) => Boolean(article)).map(
|
|
3084
|
-
(article) => `### ${
|
|
3237
|
+
(article) => `### ${str2(article.label) || "(untitled)"} [${str2(article.uid) || "?"}]
|
|
3085
3238
|
${clip(article.content, ARTICLE_MAX, state) || ""}`
|
|
3086
3239
|
)
|
|
3087
3240
|
});
|
|
@@ -3089,7 +3242,7 @@ ${clip(article.content, ARTICLE_MAX, state) || ""}`
|
|
|
3089
3242
|
if (nodes.length > 0) {
|
|
3090
3243
|
sections.push(...groupedNodeSections(nodes, state, "## Knowledge graph\n\n"));
|
|
3091
3244
|
const labelByUid = /* @__PURE__ */ new Map();
|
|
3092
|
-
for (const node of nodes) labelByUid.set(
|
|
3245
|
+
for (const node of nodes) labelByUid.set(str2(node.uid), str2(node.label) || "?");
|
|
3093
3246
|
const relationships = relationshipItems(
|
|
3094
3247
|
Array.isArray(graph?.edges) ? graph.edges : [],
|
|
3095
3248
|
labelByUid,
|
|
@@ -3108,7 +3261,7 @@ ${clip(article.content, ARTICLE_MAX, state) || ""}`
|
|
|
3108
3261
|
sections.push({
|
|
3109
3262
|
heading: "## Source excerpts",
|
|
3110
3263
|
items: chunks.map((raw) => obj(raw)).filter((chunk) => Boolean(chunk)).map(
|
|
3111
|
-
(chunk) => `- [${
|
|
3264
|
+
(chunk) => `- [${str2(chunk.chunk_uid) || "?"}] from "${str2(chunk.document_title) || "unknown document"}": "${clip(chunk.content, CHUNK_MAX, state) || ""}"`
|
|
3112
3265
|
)
|
|
3113
3266
|
});
|
|
3114
3267
|
}
|
|
@@ -3132,15 +3285,15 @@ function resumeEntry(raw, state, fallbackType) {
|
|
|
3132
3285
|
if (!wrapper) return void 0;
|
|
3133
3286
|
const value = obj(wrapper.node) ?? wrapper;
|
|
3134
3287
|
if (isNodeShaped(value)) return nodeLines(value, state);
|
|
3135
|
-
const uid =
|
|
3288
|
+
const uid = str2(value.uid);
|
|
3136
3289
|
const props = obj(value.props);
|
|
3137
3290
|
const label = clip(value.label, LABEL_MAX, state) ?? clip(value.name, LABEL_MAX, state) ?? clip(value.title, LABEL_MAX, state) ?? clip(value.action, LABEL_MAX, state) ?? fallbackType;
|
|
3138
|
-
const status =
|
|
3291
|
+
const status = str2(value.status) ?? str2(props?.status);
|
|
3139
3292
|
const summary = clip(value.summary, SUMMARY_MAX, state) ?? clip(value.description, SUMMARY_MAX, state) ?? clip(value.content, SUMMARY_MAX, state) ?? clip(value.outcome_summary, SUMMARY_MAX, state) ?? clip(props?.summary, SUMMARY_MAX, state);
|
|
3140
3293
|
const details = [
|
|
3141
3294
|
status ? `status ${status}` : void 0,
|
|
3142
|
-
|
|
3143
|
-
|
|
3295
|
+
str2(value.path) ? `path ${str2(value.path)}` : void 0,
|
|
3296
|
+
str2(value.symbol) ? `symbol ${str2(value.symbol)}` : void 0
|
|
3144
3297
|
].filter(Boolean);
|
|
3145
3298
|
const head = `- **${label}**${uid ? ` [${uid}]` : ""}${details.length > 0 ? ` (${details.join(", ")})` : ""}`;
|
|
3146
3299
|
return summary && summary !== label ? `${head}: ${summary}` : head;
|
|
@@ -3153,7 +3306,7 @@ function renderResumeContext(response, maxChars) {
|
|
|
3153
3306
|
const payload = obj(response);
|
|
3154
3307
|
if (!payload) return void 0;
|
|
3155
3308
|
const state = { bounded: false, omitted: 0 };
|
|
3156
|
-
const status =
|
|
3309
|
+
const status = str2(payload.status);
|
|
3157
3310
|
if (!payload.task && status === "no_eligible_work") {
|
|
3158
3311
|
const scopedOut = Array.isArray(payload.scoped_out_tasks) ? payload.scoped_out_tasks.length : 0;
|
|
3159
3312
|
const note = scopedOut > 0 ? ` ${scopedOut} task(s) exist outside the requested scope.` : "";
|
|
@@ -3288,17 +3441,17 @@ function renderTraversal(response, maxChars) {
|
|
|
3288
3441
|
const state = { bounded: false, omitted: 0 };
|
|
3289
3442
|
const sections = [];
|
|
3290
3443
|
const labelByUid = /* @__PURE__ */ new Map();
|
|
3291
|
-
for (const node of nodes) labelByUid.set(
|
|
3444
|
+
for (const node of nodes) labelByUid.set(str2(node.uid), str2(node.label) || "?");
|
|
3292
3445
|
if (paths.length > 0) {
|
|
3293
|
-
const items = paths.map((raw) => obj(raw)).filter((
|
|
3294
|
-
const uids = Array.isArray(
|
|
3295
|
-
const labels = Array.isArray(
|
|
3446
|
+
const items = paths.map((raw) => obj(raw)).filter((path9) => Boolean(path9)).map((path9, index) => {
|
|
3447
|
+
const uids = Array.isArray(path9.node_uids) ? path9.node_uids.map(String) : [];
|
|
3448
|
+
const labels = Array.isArray(path9.labels) ? path9.labels.map(String) : Array.isArray(path9.nodes) ? path9.nodes.map((hop) => {
|
|
3296
3449
|
const hopNode = obj(hop);
|
|
3297
|
-
return
|
|
3450
|
+
return str2(hopNode?.label) || labelByUid.get(str2(hopNode?.uid) || "") || str2(hop) || "?";
|
|
3298
3451
|
}) : [];
|
|
3299
3452
|
const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
|
|
3300
|
-
const cost = num(
|
|
3301
|
-
const confidence = num(
|
|
3453
|
+
const cost = num(path9.cost) ?? num(path9.path_cost);
|
|
3454
|
+
const confidence = num(path9.path_confidence);
|
|
3302
3455
|
const meta = [
|
|
3303
3456
|
cost !== void 0 ? `cost ${cost}` : void 0,
|
|
3304
3457
|
confidence !== void 0 ? `confidence ${confidence}` : void 0
|
|
@@ -3312,12 +3465,12 @@ function renderTraversal(response, maxChars) {
|
|
|
3312
3465
|
if (hasSteps) {
|
|
3313
3466
|
const stepItems = rawSteps.map((raw) => obj(raw)).filter((step) => Boolean(step)).map((step) => {
|
|
3314
3467
|
const depth = num(step.depth);
|
|
3315
|
-
const uid =
|
|
3316
|
-
const parent =
|
|
3317
|
-
const label =
|
|
3468
|
+
const uid = str2(step.node_uid) ?? str2(step.uid) ?? str2(step.edge_to_uid) ?? "?";
|
|
3469
|
+
const parent = str2(step.parent_uid) ?? str2(step.edge_from_uid);
|
|
3470
|
+
const label = str2(step.label) || "(unlabeled)";
|
|
3318
3471
|
const relation = edgeTypeName(step.edge_type);
|
|
3319
|
-
const role =
|
|
3320
|
-
const type =
|
|
3472
|
+
const role = str2(step.traversal_role);
|
|
3473
|
+
const type = str2(step.node_type) ?? str2(obj(step.node_type)?.Custom);
|
|
3321
3474
|
const cost = num(step.path_cost);
|
|
3322
3475
|
const confidence = num(step.path_confidence);
|
|
3323
3476
|
const prefix = [
|
|
@@ -3336,9 +3489,9 @@ function renderTraversal(response, maxChars) {
|
|
|
3336
3489
|
if (stepItems.length > 0) {
|
|
3337
3490
|
sections.push({ heading: "## Traversal steps", items: stepItems });
|
|
3338
3491
|
} else {
|
|
3339
|
-
const start =
|
|
3340
|
-
const end =
|
|
3341
|
-
const mode2 =
|
|
3492
|
+
const start = str2(body.start_uid) || "?";
|
|
3493
|
+
const end = str2(body.end_uid);
|
|
3494
|
+
const mode2 = str2(body.mode);
|
|
3342
3495
|
sections.push({
|
|
3343
3496
|
heading: mode2 === "path" && end ? `No path found from [${start}] to [${end}].` : `No traversal steps returned from [${start}].`,
|
|
3344
3497
|
items: []
|
|
@@ -3356,7 +3509,7 @@ function renderTraversal(response, maxChars) {
|
|
|
3356
3509
|
sections.push({ heading: "## Relationships", items: relationships });
|
|
3357
3510
|
}
|
|
3358
3511
|
}
|
|
3359
|
-
const mode =
|
|
3512
|
+
const mode = str2(body.mode);
|
|
3360
3513
|
return assemble(
|
|
3361
3514
|
`# Graph traversal${mode ? ` (${mode})` : ""}`,
|
|
3362
3515
|
sections,
|
|
@@ -3367,9 +3520,9 @@ function renderTraversal(response, maxChars) {
|
|
|
3367
3520
|
function neighborLine(value) {
|
|
3368
3521
|
const node = obj(value);
|
|
3369
3522
|
if (!node) return void 0;
|
|
3370
|
-
const label =
|
|
3371
|
-
const uid =
|
|
3372
|
-
const type =
|
|
3523
|
+
const label = str2(node.label) || "(unlabeled)";
|
|
3524
|
+
const uid = str2(node.uid);
|
|
3525
|
+
const type = str2(node.node_type);
|
|
3373
3526
|
return `- ${label}${uid ? ` [${uid}]` : ""}${type ? ` (${type})` : ""}`;
|
|
3374
3527
|
}
|
|
3375
3528
|
function renderOntologyAnswer(response) {
|
|
@@ -3389,20 +3542,20 @@ function renderOntologyAnswer(response) {
|
|
|
3389
3542
|
sections.push(...groupedNodeSections(objects, state));
|
|
3390
3543
|
}
|
|
3391
3544
|
const labelByUid = /* @__PURE__ */ new Map();
|
|
3392
|
-
for (const node of objects) labelByUid.set(
|
|
3545
|
+
for (const node of objects) labelByUid.set(str2(node.uid), str2(node.label) || "?");
|
|
3393
3546
|
const graph = obj(payload.graph);
|
|
3394
3547
|
for (const raw of Array.isArray(graph?.nodes) ? graph.nodes : []) {
|
|
3395
3548
|
const node = obj(raw);
|
|
3396
|
-
const uid =
|
|
3397
|
-
if (uid && !labelByUid.has(uid)) labelByUid.set(uid,
|
|
3549
|
+
const uid = str2(node?.uid);
|
|
3550
|
+
if (uid && !labelByUid.has(uid)) labelByUid.set(uid, str2(node?.label) || "?");
|
|
3398
3551
|
}
|
|
3399
3552
|
if (cognitive) {
|
|
3400
3553
|
for (const rawNodes of Object.values(cognitive)) {
|
|
3401
3554
|
if (!Array.isArray(rawNodes)) continue;
|
|
3402
3555
|
for (const raw of rawNodes) {
|
|
3403
3556
|
const node = obj(raw);
|
|
3404
|
-
const uid =
|
|
3405
|
-
if (uid && !labelByUid.has(uid)) labelByUid.set(uid,
|
|
3557
|
+
const uid = str2(node?.uid);
|
|
3558
|
+
if (uid && !labelByUid.has(uid)) labelByUid.set(uid, str2(node?.label) || "?");
|
|
3406
3559
|
}
|
|
3407
3560
|
}
|
|
3408
3561
|
}
|
|
@@ -3423,9 +3576,9 @@ function renderOntologyAnswer(response) {
|
|
|
3423
3576
|
if (provenance.length > 0) {
|
|
3424
3577
|
const items = provenance.map((raw) => obj(raw)).filter((entry) => Boolean(entry)).map((entry) => {
|
|
3425
3578
|
const span = clip(entry.text_span, QUOTE_MAX, state) || "";
|
|
3426
|
-
const by =
|
|
3427
|
-
const suffix = `${by ? ` (ingested by ${by})` : ""} [source ${
|
|
3428
|
-
return
|
|
3579
|
+
const by = str2(entry.ingested_by_name);
|
|
3580
|
+
const suffix = `${by ? ` (ingested by ${by})` : ""} [source ${str2(entry.source_uid) || "?"}]`;
|
|
3581
|
+
return str2(entry.span_kind) === "anchor" ? `- "${span}"${suffix}` : `- context: ${span}${suffix}`;
|
|
3429
3582
|
});
|
|
3430
3583
|
sections.push({ heading: "## Sources", items });
|
|
3431
3584
|
}
|
|
@@ -3471,20 +3624,20 @@ function renderJobs(response) {
|
|
|
3471
3624
|
const items = shown.map((job) => {
|
|
3472
3625
|
const progress = obj(job.progress);
|
|
3473
3626
|
const failure = obj(job.error_details);
|
|
3474
|
-
const message =
|
|
3627
|
+
const message = str2(failure?.message) ?? str2(job.error);
|
|
3475
3628
|
const done = num(progress?.processed_chunks);
|
|
3476
3629
|
const totalChunks = num(progress?.total_chunks);
|
|
3477
3630
|
const bits = [
|
|
3478
|
-
|
|
3631
|
+
str2(job.status) || "?",
|
|
3479
3632
|
done !== void 0 && totalChunks !== void 0 ? `${done}/${totalChunks} chunks` : void 0,
|
|
3480
3633
|
num(progress?.nodes_created) !== void 0 ? `${num(progress?.nodes_created)} nodes` : void 0,
|
|
3481
3634
|
num(job.queue_position) !== void 0 ? `queue #${num(job.queue_position)}` : void 0,
|
|
3482
3635
|
message ? `error: ${clip(message, 160, state)}` : void 0,
|
|
3483
|
-
|
|
3636
|
+
str2(failure?.code) ? `code: ${clip(failure?.code, 80, state)}` : void 0,
|
|
3484
3637
|
num(failure?.status) !== void 0 ? `status: ${num(failure?.status)}` : void 0,
|
|
3485
3638
|
typeof failure?.retriable === "boolean" ? `retriable: ${failure.retriable}` : void 0
|
|
3486
3639
|
].filter(Boolean);
|
|
3487
|
-
return `- [${
|
|
3640
|
+
return `- [${str2(job.id) || "?"}] ${clip(job.title, 120, state) || "(untitled)"} \u2014 ${bits.join(", ")}`;
|
|
3488
3641
|
});
|
|
3489
3642
|
return assemble(
|
|
3490
3643
|
`# Ingestion jobs (${shown.length}${shown.length < sorted.length ? ` of ${sorted.length}` : ""})`,
|
|
@@ -3500,13 +3653,13 @@ function renderSignals(response) {
|
|
|
3500
3653
|
for (const [key, value] of Object.entries(payload)) {
|
|
3501
3654
|
if (!Array.isArray(value) || value.length === 0) continue;
|
|
3502
3655
|
const items = value.map((raw) => obj(raw)).filter((item) => Boolean(item)).map((item) => {
|
|
3503
|
-
const fromLabel =
|
|
3504
|
-
const toLabel =
|
|
3656
|
+
const fromLabel = str2(item.from_label);
|
|
3657
|
+
const toLabel = str2(item.to_label);
|
|
3505
3658
|
const anyLabelKey = Object.keys(item).find(
|
|
3506
|
-
(k) => k.endsWith("_label") &&
|
|
3659
|
+
(k) => k.endsWith("_label") && str2(item[k])
|
|
3507
3660
|
);
|
|
3508
|
-
const label =
|
|
3509
|
-
const uid =
|
|
3661
|
+
const label = str2(item.label) ?? str2(item.title) ?? str2(item.name) ?? (fromLabel && toLabel ? `${fromLabel} \u2194 ${toLabel}` : void 0) ?? (anyLabelKey ? str2(item[anyLabelKey]) : void 0) ?? str2(item.uid) ?? str2(item.target_uid) ?? "(item)";
|
|
3662
|
+
const uid = str2(item.uid) ?? str2(item.target_uid);
|
|
3510
3663
|
const numbers = Object.entries(item).filter(([, v]) => typeof v === "number").slice(0, 4).map(([k, v]) => `${k} ${Math.round(v * 100) / 100}`);
|
|
3511
3664
|
const lists = Object.entries(item).filter(([, v]) => Array.isArray(v) && v.length > 0).slice(0, 2).map(([k, v]) => `${k}: ${v.length}`);
|
|
3512
3665
|
const meta = [...numbers, ...lists].join(", ");
|
|
@@ -4025,19 +4178,19 @@ async function handleHostedTool(name, args, client2, identity) {
|
|
|
4025
4178
|
try {
|
|
4026
4179
|
switch (name) {
|
|
4027
4180
|
case "search": {
|
|
4028
|
-
const query =
|
|
4181
|
+
const query = str3(args.query).trim();
|
|
4029
4182
|
if (!query) return err3("query is required");
|
|
4030
4183
|
const nodes = await contextNodes(client2, query, SEARCH_LIMIT);
|
|
4031
4184
|
const results = nodes.slice(0, SEARCH_LIMIT).map((node) => ({
|
|
4032
|
-
id:
|
|
4033
|
-
title:
|
|
4034
|
-
url: `${EXPLORER_URL}${encodeURIComponent(
|
|
4035
|
-
snippet:
|
|
4185
|
+
id: str3(node.uid),
|
|
4186
|
+
title: str3(node.label),
|
|
4187
|
+
url: `${EXPLORER_URL}${encodeURIComponent(str3(node.uid))}`,
|
|
4188
|
+
snippet: str3(node.summary)
|
|
4036
4189
|
}));
|
|
4037
4190
|
return twin({ results });
|
|
4038
4191
|
}
|
|
4039
4192
|
case "fetch": {
|
|
4040
|
-
const id =
|
|
4193
|
+
const id = str3(args.id).trim();
|
|
4041
4194
|
if (!id) return err3("id is required");
|
|
4042
4195
|
let node;
|
|
4043
4196
|
try {
|
|
@@ -4088,7 +4241,7 @@ async function handleHostedTool(name, args, client2, identity) {
|
|
|
4088
4241
|
return toolError(e);
|
|
4089
4242
|
}
|
|
4090
4243
|
}
|
|
4091
|
-
var EXPLORER_URL, SEARCH_LIMIT, FETCH_TEXT_LIMIT, READ_TOOLS, EVOLVE_TOOLS, SEARCH_OUTPUT_SCHEMA, FETCH_OUTPUT_SCHEMA, HOSTED_TOOLS, HOSTED_TOOL_NAMES,
|
|
4244
|
+
var EXPLORER_URL, SEARCH_LIMIT, FETCH_TEXT_LIMIT, READ_TOOLS, EVOLVE_TOOLS, SEARCH_OUTPUT_SCHEMA, FETCH_OUTPUT_SCHEMA, HOSTED_TOOLS, HOSTED_TOOL_NAMES, str3;
|
|
4092
4245
|
var init_hosted_tools = __esm({
|
|
4093
4246
|
"src/hosted-tools.ts"() {
|
|
4094
4247
|
"use strict";
|
|
@@ -4179,7 +4332,7 @@ var init_hosted_tools = __esm({
|
|
|
4179
4332
|
}
|
|
4180
4333
|
];
|
|
4181
4334
|
HOSTED_TOOL_NAMES = new Set(HOSTED_TOOLS.map((tool) => tool.name));
|
|
4182
|
-
|
|
4335
|
+
str3 = (value) => typeof value === "string" ? value : "";
|
|
4183
4336
|
}
|
|
4184
4337
|
});
|
|
4185
4338
|
|
|
@@ -4350,15 +4503,15 @@ function obj2(value) {
|
|
|
4350
4503
|
function text(value) {
|
|
4351
4504
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4352
4505
|
}
|
|
4353
|
-
function
|
|
4354
|
-
if (Array.isArray(value)) return `[${value.map(
|
|
4506
|
+
function stableJson2(value) {
|
|
4507
|
+
if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
|
|
4355
4508
|
if (value && typeof value === "object") {
|
|
4356
|
-
return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${
|
|
4509
|
+
return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson2(item)}`).join(",")}}`;
|
|
4357
4510
|
}
|
|
4358
4511
|
return JSON.stringify(value);
|
|
4359
4512
|
}
|
|
4360
4513
|
function sha2562(value) {
|
|
4361
|
-
return (0,
|
|
4514
|
+
return (0, import_node_crypto7.createHash)("sha256").update(value).digest("hex");
|
|
4362
4515
|
}
|
|
4363
4516
|
function outputBudget(value) {
|
|
4364
4517
|
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OUTPUT_CHARS;
|
|
@@ -4414,7 +4567,7 @@ function memoryStatus(config = {}) {
|
|
|
4414
4567
|
]
|
|
4415
4568
|
};
|
|
4416
4569
|
}
|
|
4417
|
-
function
|
|
4570
|
+
function nodeType2(node) {
|
|
4418
4571
|
const raw = node.node_type;
|
|
4419
4572
|
if (typeof raw === "string") return raw;
|
|
4420
4573
|
return text(obj2(raw)?.Custom) ?? "Node";
|
|
@@ -4427,7 +4580,7 @@ function compactItem(raw, fallbackType) {
|
|
|
4427
4580
|
if (!uid) return void 0;
|
|
4428
4581
|
return defined({
|
|
4429
4582
|
uid,
|
|
4430
|
-
type: fallbackType ??
|
|
4583
|
+
type: fallbackType ?? nodeType2(value),
|
|
4431
4584
|
label: text(value.label) ?? text(value.document_title),
|
|
4432
4585
|
truth_status: text(value.truth_status) ?? text(obj2(value.props)?.truth_status),
|
|
4433
4586
|
superseded: value.superseded === true ? true : void 0,
|
|
@@ -4664,7 +4817,7 @@ async function handleMemoryTool(client2, args, config = {}) {
|
|
|
4664
4817
|
scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0
|
|
4665
4818
|
});
|
|
4666
4819
|
const contextId = `ctx_${sha2562(
|
|
4667
|
-
|
|
4820
|
+
stableJson2({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
|
|
4668
4821
|
).slice(0, 24)}`;
|
|
4669
4822
|
const bounded = candidates.length > items.length || rendered2.includes("item(s) omitted");
|
|
4670
4823
|
const result = {
|
|
@@ -4709,11 +4862,11 @@ async function handleMemoryTool(client2, args, config = {}) {
|
|
|
4709
4862
|
structuredContent: result
|
|
4710
4863
|
};
|
|
4711
4864
|
}
|
|
4712
|
-
var
|
|
4865
|
+
var import_node_crypto7, RESULT_SCHEMA_VERSION, DEFAULT_OUTPUT_CHARS, MIN_OUTPUT_CHARS, MAX_OUTPUT_CHARS, HIDDEN_CONTEXT_NODE_TYPES, HIDDEN_CONTEXT_EDGE_TYPES, MEMORY_TOOL;
|
|
4713
4866
|
var init_memory_tool = __esm({
|
|
4714
4867
|
"src/memory-tool.ts"() {
|
|
4715
4868
|
"use strict";
|
|
4716
|
-
|
|
4869
|
+
import_node_crypto7 = require("crypto");
|
|
4717
4870
|
init_render();
|
|
4718
4871
|
RESULT_SCHEMA_VERSION = "mindgraph.memory.result.v1";
|
|
4719
4872
|
DEFAULT_OUTPUT_CHARS = 8e3;
|
|
@@ -4983,12 +5136,12 @@ var init_resources = __esm({
|
|
|
4983
5136
|
|
|
4984
5137
|
// src/sync-tool.ts
|
|
4985
5138
|
function safePath(root, candidate) {
|
|
4986
|
-
const absolute =
|
|
4987
|
-
const relative2 =
|
|
4988
|
-
if (relative2.startsWith("..") ||
|
|
5139
|
+
const absolute = import_node_path6.default.resolve(root, candidate);
|
|
5140
|
+
const relative2 = import_node_path6.default.relative(root, absolute);
|
|
5141
|
+
if (relative2.startsWith("..") || import_node_path6.default.isAbsolute(relative2)) {
|
|
4989
5142
|
throw new Error(`path escapes repository root: ${candidate}`);
|
|
4990
5143
|
}
|
|
4991
|
-
if (
|
|
5144
|
+
if (import_node_fs7.default.existsSync(absolute)) {
|
|
4992
5145
|
if (!pathContains(realPathOf(root), realPathOf(absolute))) {
|
|
4993
5146
|
throw new Error(`path escapes repository root via symlink: ${candidate}`);
|
|
4994
5147
|
}
|
|
@@ -4997,26 +5150,26 @@ function safePath(root, candidate) {
|
|
|
4997
5150
|
}
|
|
4998
5151
|
function realPathOf(target) {
|
|
4999
5152
|
try {
|
|
5000
|
-
return
|
|
5153
|
+
return import_node_fs7.default.realpathSync(target);
|
|
5001
5154
|
} catch {
|
|
5002
|
-
return
|
|
5155
|
+
return import_node_path6.default.resolve(target);
|
|
5003
5156
|
}
|
|
5004
5157
|
}
|
|
5005
5158
|
function markdownFiles(target, realRoot, visited = /* @__PURE__ */ new Set()) {
|
|
5006
|
-
if (!
|
|
5159
|
+
if (!import_node_fs7.default.existsSync(target)) return [];
|
|
5007
5160
|
const real = realPathOf(target);
|
|
5008
5161
|
if (!pathContains(realRoot, real)) return [];
|
|
5009
|
-
const stat =
|
|
5162
|
+
const stat = import_node_fs7.default.statSync(target);
|
|
5010
5163
|
if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
|
|
5011
5164
|
if (!stat.isDirectory() || visited.has(real)) return [];
|
|
5012
5165
|
visited.add(real);
|
|
5013
|
-
return
|
|
5166
|
+
return import_node_fs7.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path6.default.join(target, entry.name), realRoot, visited));
|
|
5014
5167
|
}
|
|
5015
5168
|
function fileDescriptor(root, absolute) {
|
|
5016
|
-
const content =
|
|
5169
|
+
const content = import_node_fs7.default.readFileSync(absolute, "utf8");
|
|
5017
5170
|
return {
|
|
5018
|
-
logical_path:
|
|
5019
|
-
content_hash:
|
|
5171
|
+
logical_path: import_node_path6.default.relative(root, absolute).split(import_node_path6.default.sep).join("/"),
|
|
5172
|
+
content_hash: import_node_crypto8.default.createHash("sha256").update(content).digest("hex"),
|
|
5020
5173
|
bytes: Buffer.byteLength(content),
|
|
5021
5174
|
content
|
|
5022
5175
|
};
|
|
@@ -5085,7 +5238,7 @@ async function handleSyncTool(client2, args) {
|
|
|
5085
5238
|
}
|
|
5086
5239
|
if (action === "status" && request.logical_path && request.file_present !== false) {
|
|
5087
5240
|
const absolute = safePath(root, String(request.logical_path));
|
|
5088
|
-
request.file_present =
|
|
5241
|
+
request.file_present = import_node_fs7.default.existsSync(absolute);
|
|
5089
5242
|
if (request.file_present && !request.content_hash) {
|
|
5090
5243
|
request.content_hash = fileDescriptor(root, absolute).content_hash;
|
|
5091
5244
|
}
|
|
@@ -5129,13 +5282,13 @@ async function scanRepository(remote, repository, requested, provider, agentId)
|
|
|
5129
5282
|
files: results
|
|
5130
5283
|
};
|
|
5131
5284
|
}
|
|
5132
|
-
var
|
|
5285
|
+
var import_node_crypto8, import_node_fs7, import_node_path6, ok3, err4, SYNC_TOOL;
|
|
5133
5286
|
var init_sync_tool = __esm({
|
|
5134
5287
|
"src/sync-tool.ts"() {
|
|
5135
5288
|
"use strict";
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5289
|
+
import_node_crypto8 = __toESM(require("crypto"));
|
|
5290
|
+
import_node_fs7 = __toESM(require("fs"));
|
|
5291
|
+
import_node_path6 = __toESM(require("path"));
|
|
5139
5292
|
init_codegraph();
|
|
5140
5293
|
init_error_detail();
|
|
5141
5294
|
ok3 = (data) => ({
|
|
@@ -5217,10 +5370,10 @@ var init_sync_tool = __esm({
|
|
|
5217
5370
|
});
|
|
5218
5371
|
|
|
5219
5372
|
// src/tools.ts
|
|
5220
|
-
function
|
|
5221
|
-
if (Array.isArray(value)) return `[${value.map(
|
|
5373
|
+
function stableJson3(value) {
|
|
5374
|
+
if (Array.isArray(value)) return `[${value.map(stableJson3).join(",")}]`;
|
|
5222
5375
|
if (value && typeof value === "object") {
|
|
5223
|
-
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${
|
|
5376
|
+
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson3(item)}`).join(",")}}`;
|
|
5224
5377
|
}
|
|
5225
5378
|
return JSON.stringify(value);
|
|
5226
5379
|
}
|
|
@@ -5234,8 +5387,8 @@ function defaultWriteIdempotencyKey(tool, args) {
|
|
|
5234
5387
|
([key]) => key !== "invocation_context" && key !== "idempotency_key"
|
|
5235
5388
|
)
|
|
5236
5389
|
);
|
|
5237
|
-
return (0,
|
|
5238
|
-
|
|
5390
|
+
return (0, import_node_crypto9.createHash)("sha256").update(
|
|
5391
|
+
stableJson3({
|
|
5239
5392
|
harness_session_id: sessionId,
|
|
5240
5393
|
harness_turn_id: context?.harnessTurnId ?? null,
|
|
5241
5394
|
tool,
|
|
@@ -6040,6 +6193,8 @@ async function handleRetrieve(client2, args, profile) {
|
|
|
6040
6193
|
salience_min,
|
|
6041
6194
|
article_limit,
|
|
6042
6195
|
valid_at,
|
|
6196
|
+
as_of,
|
|
6197
|
+
retrieval_mode,
|
|
6043
6198
|
created_after,
|
|
6044
6199
|
created_before,
|
|
6045
6200
|
explain,
|
|
@@ -6068,9 +6223,15 @@ async function handleRetrieve(client2, args, profile) {
|
|
|
6068
6223
|
// the advertised knob must not get a silent no-op).
|
|
6069
6224
|
...article_limit !== void 0 ? { article_limit } : include_documents === false || profile === "coding" && !include_documents ? { article_limit: 0 } : {},
|
|
6070
6225
|
valid_at,
|
|
6226
|
+
as_of,
|
|
6227
|
+
retrieval_mode,
|
|
6071
6228
|
include_graph,
|
|
6072
6229
|
graph_expansion_limit: graph_expansion_limit ?? 3,
|
|
6073
|
-
graph_max_depth: graph_max_depth ?? 2
|
|
6230
|
+
graph_max_depth: graph_max_depth ?? 2,
|
|
6231
|
+
// Evidence plan R2: the server's canonical rendering under a token
|
|
6232
|
+
// budget derived from the character budget; the local renderer is
|
|
6233
|
+
// the fallback for servers that predate it.
|
|
6234
|
+
...format === "json" ? {} : { format: "text", max_tokens: Math.max(256, Math.floor((max_output_chars ?? 24e3) / 4)) }
|
|
6074
6235
|
}),
|
|
6075
6236
|
(raw) => renderContext(raw, max_output_chars)
|
|
6076
6237
|
);
|
|
@@ -6520,11 +6681,11 @@ async function handleOntology(client2, args) {
|
|
|
6520
6681
|
return err5(`Unknown ontology action: ${action}`);
|
|
6521
6682
|
}
|
|
6522
6683
|
}
|
|
6523
|
-
var
|
|
6684
|
+
var import_node_crypto9, RESOLUTION_PROPERTIES, TOOLS, INVOCATION_CONTEXT_SCHEMA;
|
|
6524
6685
|
var init_tools = __esm({
|
|
6525
6686
|
"src/tools.ts"() {
|
|
6526
6687
|
"use strict";
|
|
6527
|
-
|
|
6688
|
+
import_node_crypto9 = require("crypto");
|
|
6528
6689
|
init_error_detail();
|
|
6529
6690
|
init_code_tool();
|
|
6530
6691
|
init_codegraph();
|
|
@@ -7249,6 +7410,15 @@ var init_tools = __esm({
|
|
|
7249
7410
|
type: "string",
|
|
7250
7411
|
description: "ISO-8601 date (e.g. '2021-06') for context: nodes with validity windows are annotated as of this date instead of today"
|
|
7251
7412
|
},
|
|
7413
|
+
as_of: {
|
|
7414
|
+
type: "string",
|
|
7415
|
+
description: "ISO-8601 date for context: answer with what had been STATED by this date (a node is admitted when one of its sources was stated on or before it; fact dates never exclude it). Use for 'what did we know / what was the value as of <date>' questions."
|
|
7416
|
+
},
|
|
7417
|
+
retrieval_mode: {
|
|
7418
|
+
type: "string",
|
|
7419
|
+
enum: ["normal", "exhaustive"],
|
|
7420
|
+
description: "context: 'exhaustive' widens retrieval for list/count questions ('how many different \u2026 across', 'what two \u2026'); omitted lets the server's cue decide."
|
|
7421
|
+
},
|
|
7252
7422
|
created_after: {
|
|
7253
7423
|
type: "number",
|
|
7254
7424
|
description: "Unix seconds \u2014 'recent' action: only nodes ingested after this time"
|
|
@@ -7719,7 +7889,12 @@ function buildMindGraphServer(wiring) {
|
|
|
7719
7889
|
});
|
|
7720
7890
|
server2.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
|
|
7721
7891
|
const { name, arguments: args } = request.params;
|
|
7722
|
-
const rawArgs = {
|
|
7892
|
+
const rawArgs = {
|
|
7893
|
+
...args || {},
|
|
7894
|
+
// H1: session, lease and invocation fields the local hook recorded for
|
|
7895
|
+
// this exact call (stdio only; hosted mode has no local hook).
|
|
7896
|
+
...hosted ? {} : wiring.resolveHookFields?.(name, args || {})
|
|
7897
|
+
};
|
|
7723
7898
|
const toolArgs = { ...rawArgs };
|
|
7724
7899
|
let governanceConfig = wiring.governance;
|
|
7725
7900
|
if (hosted) {
|
|
@@ -7804,18 +7979,19 @@ async function main() {
|
|
|
7804
7979
|
const transport = new import_stdio.StdioServerTransport();
|
|
7805
7980
|
await server.connect(transport);
|
|
7806
7981
|
console.error(
|
|
7807
|
-
`MindGraph MCP server v${"0.
|
|
7982
|
+
`MindGraph MCP server v${"0.24.0"} running on stdio (${BASE_URL})`
|
|
7808
7983
|
);
|
|
7809
7984
|
}
|
|
7810
|
-
var import_stdio,
|
|
7985
|
+
var import_stdio, import_node_path7, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, RUNNER_REFRESH, HOOK_SKEW_NOTE, GOVERNANCE_CONFIG, MEMORY_RUNTIME_CONFIG, server;
|
|
7811
7986
|
var init_src = __esm({
|
|
7812
7987
|
"src/index.ts"() {
|
|
7813
7988
|
"use strict";
|
|
7989
|
+
init_call_ledger();
|
|
7814
7990
|
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7815
7991
|
init_dist();
|
|
7816
7992
|
init_hook_env();
|
|
7817
7993
|
init_server();
|
|
7818
|
-
|
|
7994
|
+
import_node_path7 = __toESM(require("path"));
|
|
7819
7995
|
init_hook_installer();
|
|
7820
7996
|
API_KEY = process.env.MINDGRAPH_API_KEY;
|
|
7821
7997
|
BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
|
|
@@ -7908,7 +8084,7 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
7908
8084
|
- Capture corrections, decisions with rationale, fixes that survived meaningful failed attempts, durable invariants/preferences, and blockers with evidence.
|
|
7909
8085
|
- If a write returns \`near_duplicate\`, choose \`reuse_uid\` or \`supersedes_uid\`; use \`confirm_new\` only for genuinely distinct facts.
|
|
7910
8086
|
- Do not capture routine mechanics, transient command output, facts cheaply derivable from code, unsupported speculation as high-confidence lessons, or completion without evidence.
|
|
7911
|
-
- Adapter context is owned by ${HARNESS}; omit invocation_context because
|
|
8087
|
+
- Adapter context is owned by ${HARNESS}; omit invocation_context because the adapter supplies it.`;
|
|
7912
8088
|
} else {
|
|
7913
8089
|
INSTRUCTIONS = INSTRUCTIONS.split("\n").filter(
|
|
7914
8090
|
(line) => !line.includes("mindgraph_code") && !line.includes("`code`") && !line.includes("Typed code anchors")
|
|
@@ -7916,7 +8092,7 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
7916
8092
|
}
|
|
7917
8093
|
RUNNER_REFRESH = (() => {
|
|
7918
8094
|
try {
|
|
7919
|
-
return refreshHookRunner("0.
|
|
8095
|
+
return refreshHookRunner("0.24.0", import_node_path7.default.join(__dirname, "cli.js"));
|
|
7920
8096
|
} catch {
|
|
7921
8097
|
return void 0;
|
|
7922
8098
|
}
|
|
@@ -7930,12 +8106,12 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
7930
8106
|
}
|
|
7931
8107
|
})();
|
|
7932
8108
|
console.error(
|
|
7933
|
-
`[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.
|
|
8109
|
+
`[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.24.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
|
|
7934
8110
|
);
|
|
7935
8111
|
}
|
|
7936
8112
|
HOOK_SKEW_NOTE = (() => {
|
|
7937
8113
|
try {
|
|
7938
|
-
return versionSkewNote("0.
|
|
8114
|
+
return versionSkewNote("0.24.0");
|
|
7939
8115
|
} catch {
|
|
7940
8116
|
return void 0;
|
|
7941
8117
|
}
|
|
@@ -7956,7 +8132,7 @@ ${HOOK_SKEW_NOTE}`;
|
|
|
7956
8132
|
}
|
|
7957
8133
|
};
|
|
7958
8134
|
MEMORY_RUNTIME_CONFIG = {
|
|
7959
|
-
serverVersion: "0.
|
|
8135
|
+
serverVersion: "0.24.0",
|
|
7960
8136
|
baseUrl: BASE_URL,
|
|
7961
8137
|
profile: PROFILE,
|
|
7962
8138
|
harness: HARNESS,
|
|
@@ -7965,12 +8141,13 @@ ${HOOK_SKEW_NOTE}`;
|
|
|
7965
8141
|
};
|
|
7966
8142
|
server = buildMindGraphServer({
|
|
7967
8143
|
client,
|
|
7968
|
-
serverVersion: "0.
|
|
8144
|
+
serverVersion: "0.24.0",
|
|
7969
8145
|
instructions: INSTRUCTIONS,
|
|
7970
8146
|
profile: PROFILE,
|
|
7971
8147
|
agentId: AGENT_ID,
|
|
7972
8148
|
governance: GOVERNANCE_CONFIG,
|
|
7973
|
-
memory: MEMORY_RUNTIME_CONFIG
|
|
8149
|
+
memory: MEMORY_RUNTIME_CONFIG,
|
|
8150
|
+
resolveHookFields: (name, args) => resolvePendingCall(name, args)
|
|
7974
8151
|
});
|
|
7975
8152
|
main().catch((error2) => {
|
|
7976
8153
|
console.error("Fatal error:", error2);
|
|
@@ -7980,10 +8157,10 @@ ${HOOK_SKEW_NOTE}`;
|
|
|
7980
8157
|
});
|
|
7981
8158
|
|
|
7982
8159
|
// src/cli.ts
|
|
7983
|
-
var
|
|
7984
|
-
var
|
|
7985
|
-
var
|
|
7986
|
-
var
|
|
8160
|
+
var fs8 = __toESM(require("fs"));
|
|
8161
|
+
var path8 = __toESM(require("path"));
|
|
8162
|
+
var os5 = __toESM(require("os"));
|
|
8163
|
+
var crypto6 = __toESM(require("crypto"));
|
|
7987
8164
|
var import_child_process = require("child_process");
|
|
7988
8165
|
init_dist();
|
|
7989
8166
|
init_hook_env();
|
|
@@ -8001,6 +8178,9 @@ function parseArgs(argv) {
|
|
|
8001
8178
|
let agentId = process.env.MINDGRAPH_AGENT_ID;
|
|
8002
8179
|
const positionals = [];
|
|
8003
8180
|
let dir;
|
|
8181
|
+
let promptRecall2;
|
|
8182
|
+
let capture;
|
|
8183
|
+
let sessionId;
|
|
8004
8184
|
for (let i = 0; i < args.length; i++) {
|
|
8005
8185
|
const arg = args[i];
|
|
8006
8186
|
switch (arg) {
|
|
@@ -8033,6 +8213,21 @@ function parseArgs(argv) {
|
|
|
8033
8213
|
case "--hooks":
|
|
8034
8214
|
hooks = true;
|
|
8035
8215
|
break;
|
|
8216
|
+
case "--prompt-recall":
|
|
8217
|
+
promptRecall2 = true;
|
|
8218
|
+
break;
|
|
8219
|
+
case "--no-prompt-recall":
|
|
8220
|
+
promptRecall2 = false;
|
|
8221
|
+
break;
|
|
8222
|
+
case "--capture":
|
|
8223
|
+
capture = true;
|
|
8224
|
+
break;
|
|
8225
|
+
case "--no-capture":
|
|
8226
|
+
capture = false;
|
|
8227
|
+
break;
|
|
8228
|
+
case "--session-id":
|
|
8229
|
+
sessionId = args[++i];
|
|
8230
|
+
break;
|
|
8036
8231
|
case "--owner":
|
|
8037
8232
|
++i;
|
|
8038
8233
|
break;
|
|
@@ -8061,7 +8256,10 @@ function parseArgs(argv) {
|
|
|
8061
8256
|
hooks,
|
|
8062
8257
|
agentId,
|
|
8063
8258
|
positionals,
|
|
8064
|
-
dir
|
|
8259
|
+
dir,
|
|
8260
|
+
promptRecall: promptRecall2,
|
|
8261
|
+
capture,
|
|
8262
|
+
sessionId
|
|
8065
8263
|
};
|
|
8066
8264
|
}
|
|
8067
8265
|
function mcpAddFailureOutput(e) {
|
|
@@ -8444,15 +8642,210 @@ async function skillsPush(api, skillPath) {
|
|
|
8444
8642
|
}
|
|
8445
8643
|
|
|
8446
8644
|
// src/hook-core.ts
|
|
8447
|
-
var
|
|
8448
|
-
var
|
|
8449
|
-
var
|
|
8450
|
-
var
|
|
8645
|
+
var import_node_crypto5 = __toESM(require("crypto"));
|
|
8646
|
+
var import_node_fs5 = __toESM(require("fs"));
|
|
8647
|
+
var import_node_os4 = __toESM(require("os"));
|
|
8648
|
+
var import_node_path4 = __toESM(require("path"));
|
|
8451
8649
|
var import_node_child_process2 = require("child_process");
|
|
8452
8650
|
init_codegraph();
|
|
8453
8651
|
init_code_tool();
|
|
8652
|
+
init_call_ledger();
|
|
8454
8653
|
init_error_detail();
|
|
8455
8654
|
init_hook_env();
|
|
8655
|
+
|
|
8656
|
+
// src/hook-memory.ts
|
|
8657
|
+
var import_node_fs4 = __toESM(require("fs"));
|
|
8658
|
+
var PROMPT_RECALL_LIMIT = 5;
|
|
8659
|
+
var PROMPT_RECALL_MAX_CHARS = 4e3;
|
|
8660
|
+
var MIN_PROMPT_RECALL_CHARS = 12;
|
|
8661
|
+
var CAPTURE_MAX_CHARS = 2e5;
|
|
8662
|
+
var STOPWORDS = new Set(
|
|
8663
|
+
"a an and are as at be but by can could did do does for from had has have how i if in into is it its me my of on or our please should so that the their them then there these they this to us was we were what when where which who why will with would you your about just also any some tell know want need let lets like get got make yes no not ok okay sure may might must up down out off over under again after before while without within including include use using used keep now next here than very more most other each both all one two been being am done going go".split(" ")
|
|
8664
|
+
);
|
|
8665
|
+
var WORD = /[A-Za-z0-9][A-Za-z0-9_.\-/#+]*/g;
|
|
8666
|
+
var SECRET_PATTERNS = [
|
|
8667
|
+
[/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, "[REDACTED]"],
|
|
8668
|
+
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, "[REDACTED]"],
|
|
8669
|
+
[/\b(?:mg|sk|pk|rk)[_-](?:live[_-]|test[_-]|ant[_-]|proj[_-])?[A-Za-z0-9_-]{16,}/g, "[REDACTED]"],
|
|
8670
|
+
[/\b(?:gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{20,})/g, "[REDACTED]"],
|
|
8671
|
+
[/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g, "[REDACTED]"],
|
|
8672
|
+
[/\bxox[abprs]-[A-Za-z0-9-]{10,}/g, "[REDACTED]"],
|
|
8673
|
+
[/\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/gi, "Bearer [REDACTED]"],
|
|
8674
|
+
[
|
|
8675
|
+
/\b((?:api[_-]?key|secret|token|password|passwd|access[_-]?key|client[_-]?secret)\b["']?\s*[:=]\s*)["']?[^\s"',;]{8,}["']?/gi,
|
|
8676
|
+
"$1[REDACTED]"
|
|
8677
|
+
]
|
|
8678
|
+
];
|
|
8679
|
+
function redactSecrets(text2) {
|
|
8680
|
+
let out = text2;
|
|
8681
|
+
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
8682
|
+
out = out.replace(pattern, replacement);
|
|
8683
|
+
}
|
|
8684
|
+
return out;
|
|
8685
|
+
}
|
|
8686
|
+
function promptRecallQuery(prompt, maxTerms = 8) {
|
|
8687
|
+
if (typeof prompt !== "string") return "";
|
|
8688
|
+
const trimmed = prompt.trim();
|
|
8689
|
+
if (trimmed.length < MIN_PROMPT_RECALL_CHARS || trimmed.startsWith("/")) return "";
|
|
8690
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8691
|
+
for (const match of redactSecrets(trimmed.slice(0, 600)).matchAll(WORD)) {
|
|
8692
|
+
const key = match[0].toLowerCase().replace(/^[.\-/]+|[.\-/]+$/g, "");
|
|
8693
|
+
if (key.length < 2 || STOPWORDS.has(key) || key === "redacted" || seen.has(key)) continue;
|
|
8694
|
+
seen.add(key);
|
|
8695
|
+
if (seen.size >= maxTerms) break;
|
|
8696
|
+
}
|
|
8697
|
+
return [...seen].join(" ");
|
|
8698
|
+
}
|
|
8699
|
+
function promptRecallRequest(query) {
|
|
8700
|
+
return {
|
|
8701
|
+
query,
|
|
8702
|
+
node_limit: PROMPT_RECALL_LIMIT,
|
|
8703
|
+
chunk_limit: 0,
|
|
8704
|
+
graph_expansion_limit: 3,
|
|
8705
|
+
graph_max_depth: 2
|
|
8706
|
+
};
|
|
8707
|
+
}
|
|
8708
|
+
function wordParts(text2) {
|
|
8709
|
+
return text2.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
8710
|
+
}
|
|
8711
|
+
var sameWord = (left, right) => left === right || left.length >= 4 && right.length >= 4 && (left.startsWith(right) || right.startsWith(left));
|
|
8712
|
+
function recallTermOverlap(query, text2) {
|
|
8713
|
+
const hay = wordParts(text2);
|
|
8714
|
+
let matched = 0;
|
|
8715
|
+
let usable = 0;
|
|
8716
|
+
for (const term of new Set(query.split(/\s+/).filter(Boolean))) {
|
|
8717
|
+
const parts = wordParts(term).filter((part) => part.length >= 3 && !/^\d+$/.test(part));
|
|
8718
|
+
if (parts.length === 0) continue;
|
|
8719
|
+
usable += 1;
|
|
8720
|
+
if (parts.some((part) => hay.some((word) => sameWord(word, part)))) matched += 1;
|
|
8721
|
+
}
|
|
8722
|
+
return { matched, usable };
|
|
8723
|
+
}
|
|
8724
|
+
function relevant(query, text2) {
|
|
8725
|
+
if (query === void 0) return true;
|
|
8726
|
+
const { matched, usable } = recallTermOverlap(query, text2);
|
|
8727
|
+
return usable > 0 && matched >= Math.min(2, usable);
|
|
8728
|
+
}
|
|
8729
|
+
var rec = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8730
|
+
var str = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
8731
|
+
var oneLine = (value, max) => {
|
|
8732
|
+
const flat = value.replace(/\s+/g, " ").trim();
|
|
8733
|
+
return flat.length <= max ? flat : `${flat.slice(0, max - 1)}\u2026`;
|
|
8734
|
+
};
|
|
8735
|
+
var INFRA_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article", "Session"]);
|
|
8736
|
+
function nodeType(node) {
|
|
8737
|
+
const plain = str(node.node_type);
|
|
8738
|
+
return (plain && plain !== "Custom" ? plain : void 0) ?? str(rec(node.node_type)?.Custom) ?? plain ?? "Node";
|
|
8739
|
+
}
|
|
8740
|
+
function renderPromptRecall(response, query) {
|
|
8741
|
+
const payload = rec(response);
|
|
8742
|
+
if (!payload) return void 0;
|
|
8743
|
+
const lines = [];
|
|
8744
|
+
const nodes = Array.isArray(rec(payload.graph)?.nodes) ? rec(payload.graph).nodes : [];
|
|
8745
|
+
for (const raw of nodes) {
|
|
8746
|
+
const node = rec(raw);
|
|
8747
|
+
if (!node || lines.length >= PROMPT_RECALL_LIMIT) continue;
|
|
8748
|
+
const type = nodeType(node);
|
|
8749
|
+
if (INFRA_TYPES.has(type)) continue;
|
|
8750
|
+
const props = rec(node.props);
|
|
8751
|
+
const label = str(node.label) ?? str(props?.label) ?? str(props?.name);
|
|
8752
|
+
if (!label) continue;
|
|
8753
|
+
const summary = str(node.summary) ?? str(props?.summary) ?? str(props?.description) ?? str(props?.content);
|
|
8754
|
+
const informative = summary && summary !== label ? summary : void 0;
|
|
8755
|
+
if (!informative && type === "Entity") continue;
|
|
8756
|
+
if (!relevant(query, `${label} ${informative ?? ""}`)) continue;
|
|
8757
|
+
const docs = Array.isArray(node.source_documents) ? node.source_documents : [];
|
|
8758
|
+
const source = str(rec(docs[0])?.title);
|
|
8759
|
+
let line = `- [${type}] ${oneLine(label, 140)}`;
|
|
8760
|
+
if (node.superseded === true) line += " (superseded)";
|
|
8761
|
+
if (summary && summary !== label) line += ` \u2014 ${oneLine(summary, 240)}`;
|
|
8762
|
+
if (source) line += ` (source: ${oneLine(source, 100)})`;
|
|
8763
|
+
lines.push(line);
|
|
8764
|
+
}
|
|
8765
|
+
const articles = Array.isArray(payload.articles) ? payload.articles : [];
|
|
8766
|
+
for (const raw of articles) {
|
|
8767
|
+
const article = rec(raw);
|
|
8768
|
+
if (!article || lines.length >= PROMPT_RECALL_LIMIT) continue;
|
|
8769
|
+
const label = str(article.label);
|
|
8770
|
+
if (!label) continue;
|
|
8771
|
+
const content = str(article.content);
|
|
8772
|
+
if (!relevant(query, `${label} ${content ?? ""}`)) continue;
|
|
8773
|
+
lines.push(`- [Article] ${oneLine(label, 140)}${content ? ` \u2014 ${oneLine(content, 240)}` : ""}`);
|
|
8774
|
+
}
|
|
8775
|
+
if (lines.length === 0) return void 0;
|
|
8776
|
+
let block = "MindGraph recall (bounded):";
|
|
8777
|
+
for (const line of lines) {
|
|
8778
|
+
if (block.length + 1 + line.length > PROMPT_RECALL_MAX_CHARS) break;
|
|
8779
|
+
block += `
|
|
8780
|
+
${line}`;
|
|
8781
|
+
}
|
|
8782
|
+
return block;
|
|
8783
|
+
}
|
|
8784
|
+
function messageText(content) {
|
|
8785
|
+
if (typeof content === "string") return content;
|
|
8786
|
+
if (!Array.isArray(content)) return void 0;
|
|
8787
|
+
const parts = content.map((block) => rec(block)).filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text);
|
|
8788
|
+
return parts.length > 0 ? parts.join("\n") : void 0;
|
|
8789
|
+
}
|
|
8790
|
+
function stripHarnessNoise(text2) {
|
|
8791
|
+
return text2.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").replace(/<local-command-(stdout|stderr)>[\s\S]*?<\/local-command-\1>/g, "").replace(/<command-(name|message|args)>[\s\S]*?<\/command-\1>/g, "").replace(/<local-command-caveat>[\s\S]*?<\/local-command-caveat>/g, "").trim();
|
|
8792
|
+
}
|
|
8793
|
+
function extractTranscriptTurns(jsonl) {
|
|
8794
|
+
const turns = [];
|
|
8795
|
+
for (const line of jsonl.split("\n")) {
|
|
8796
|
+
if (!line.trim()) continue;
|
|
8797
|
+
let entry;
|
|
8798
|
+
try {
|
|
8799
|
+
entry = rec(JSON.parse(line));
|
|
8800
|
+
} catch {
|
|
8801
|
+
continue;
|
|
8802
|
+
}
|
|
8803
|
+
if (!entry || entry.type !== "user" && entry.type !== "assistant") continue;
|
|
8804
|
+
if (entry.isMeta === true || entry.isSidechain === true || entry.isCompactSummary === true) continue;
|
|
8805
|
+
const message = rec(entry.message);
|
|
8806
|
+
const raw = messageText(message?.content);
|
|
8807
|
+
if (!raw) continue;
|
|
8808
|
+
const text2 = stripHarnessNoise(raw);
|
|
8809
|
+
if (!text2) continue;
|
|
8810
|
+
turns.push({ role: entry.type, text: text2 });
|
|
8811
|
+
}
|
|
8812
|
+
return turns;
|
|
8813
|
+
}
|
|
8814
|
+
function renderTranscript(turns) {
|
|
8815
|
+
const rendered2 = turns.map((turn) => `${turn.role === "user" ? "User" : "Assistant"}: ${redactSecrets(turn.text)}`).join("\n\n");
|
|
8816
|
+
if (rendered2.length <= CAPTURE_MAX_CHARS) return rendered2;
|
|
8817
|
+
const tail = rendered2.slice(rendered2.length - CAPTURE_MAX_CHARS);
|
|
8818
|
+
const start = tail.search(/\n\n(User|Assistant): /);
|
|
8819
|
+
return start >= 0 ? tail.slice(start + 2) : tail;
|
|
8820
|
+
}
|
|
8821
|
+
function readTranscriptSince(file, offset = 0) {
|
|
8822
|
+
const buffer = import_node_fs4.default.readFileSync(file);
|
|
8823
|
+
const start = offset > buffer.length ? 0 : offset;
|
|
8824
|
+
const lastNewline = buffer.lastIndexOf(10);
|
|
8825
|
+
if (lastNewline < start) return { text: "", nextOffset: start };
|
|
8826
|
+
return {
|
|
8827
|
+
text: buffer.subarray(start, lastNewline + 1).toString("utf8"),
|
|
8828
|
+
nextOffset: lastNewline + 1
|
|
8829
|
+
};
|
|
8830
|
+
}
|
|
8831
|
+
function buildCaptureRequest(transcriptPath, sessionId, offset, ids) {
|
|
8832
|
+
const { text: text2, nextOffset } = readTranscriptSince(transcriptPath, offset);
|
|
8833
|
+
const turns = extractTranscriptTurns(text2);
|
|
8834
|
+
const content = renderTranscript(turns);
|
|
8835
|
+
if (!content) return { nextOffset, turns: 0 };
|
|
8836
|
+
return {
|
|
8837
|
+
body: {
|
|
8838
|
+
content,
|
|
8839
|
+
title: `Claude Code session ${sessionId}`,
|
|
8840
|
+
...ids.sessionUid ? { session_uid: ids.sessionUid } : {},
|
|
8841
|
+
...ids.agentId ? { agent_id: ids.agentId } : {}
|
|
8842
|
+
},
|
|
8843
|
+
nextOffset,
|
|
8844
|
+
turns: turns.length
|
|
8845
|
+
};
|
|
8846
|
+
}
|
|
8847
|
+
|
|
8848
|
+
// src/hook-core.ts
|
|
8456
8849
|
var NOOP = { kind: "noop" };
|
|
8457
8850
|
function defaultLedger(sessionId, options) {
|
|
8458
8851
|
return {
|
|
@@ -8470,33 +8863,33 @@ function defaultLedger(sessionId, options) {
|
|
|
8470
8863
|
};
|
|
8471
8864
|
}
|
|
8472
8865
|
function runtimeDir(options) {
|
|
8473
|
-
return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
8866
|
+
return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path4.default.join(import_node_os4.default.homedir(), ".mindgraph", "runtime");
|
|
8474
8867
|
}
|
|
8475
8868
|
function ledgerPath(sessionId, options) {
|
|
8476
8869
|
const harness = "harness" in options ? options.harness : "claude-code";
|
|
8477
|
-
const key =
|
|
8478
|
-
return
|
|
8870
|
+
const key = import_node_crypto5.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
|
|
8871
|
+
return import_node_path4.default.join(runtimeDir(options), `${key}.json`);
|
|
8479
8872
|
}
|
|
8480
8873
|
function waitBriefly(ms) {
|
|
8481
8874
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
8482
8875
|
}
|
|
8483
8876
|
function withLedger(sessionId, options, update) {
|
|
8484
8877
|
const file = ledgerPath(sessionId, options);
|
|
8485
|
-
|
|
8878
|
+
import_node_fs5.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true, mode: 448 });
|
|
8486
8879
|
try {
|
|
8487
|
-
|
|
8880
|
+
import_node_fs5.default.chmodSync(import_node_path4.default.dirname(file), 448);
|
|
8488
8881
|
} catch {
|
|
8489
8882
|
}
|
|
8490
8883
|
const lock = `${file}.lock`;
|
|
8491
8884
|
let lockFd;
|
|
8492
8885
|
for (let attempt = 0; attempt < 400; attempt++) {
|
|
8493
8886
|
try {
|
|
8494
|
-
lockFd =
|
|
8887
|
+
lockFd = import_node_fs5.default.openSync(lock, "wx", 384);
|
|
8495
8888
|
break;
|
|
8496
8889
|
} catch (error2) {
|
|
8497
8890
|
if (error2.code !== "EEXIST") throw error2;
|
|
8498
8891
|
try {
|
|
8499
|
-
if (Date.now() -
|
|
8892
|
+
if (Date.now() - import_node_fs5.default.statSync(lock).mtimeMs > 3e3) import_node_fs5.default.unlinkSync(lock);
|
|
8500
8893
|
} catch {
|
|
8501
8894
|
}
|
|
8502
8895
|
waitBriefly(10);
|
|
@@ -8510,25 +8903,25 @@ function withLedger(sessionId, options, update) {
|
|
|
8510
8903
|
try {
|
|
8511
8904
|
ledger = {
|
|
8512
8905
|
...ledger,
|
|
8513
|
-
...JSON.parse(
|
|
8906
|
+
...JSON.parse(import_node_fs5.default.readFileSync(file, "utf8"))
|
|
8514
8907
|
};
|
|
8515
8908
|
} catch {
|
|
8516
8909
|
}
|
|
8517
8910
|
const result = update(ledger);
|
|
8518
8911
|
ledger.updatedAt = Date.now();
|
|
8519
|
-
const temporary = `${file}.${process.pid}.${
|
|
8520
|
-
|
|
8912
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
|
|
8913
|
+
import_node_fs5.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
|
|
8521
8914
|
`, { mode: 384 });
|
|
8522
|
-
|
|
8915
|
+
import_node_fs5.default.renameSync(temporary, file);
|
|
8523
8916
|
try {
|
|
8524
|
-
|
|
8917
|
+
import_node_fs5.default.chmodSync(file, 384);
|
|
8525
8918
|
} catch {
|
|
8526
8919
|
}
|
|
8527
8920
|
return result;
|
|
8528
8921
|
} finally {
|
|
8529
|
-
|
|
8922
|
+
import_node_fs5.default.closeSync(lockFd);
|
|
8530
8923
|
try {
|
|
8531
|
-
|
|
8924
|
+
import_node_fs5.default.unlinkSync(lock);
|
|
8532
8925
|
} catch {
|
|
8533
8926
|
}
|
|
8534
8927
|
}
|
|
@@ -8798,30 +9191,30 @@ function hasMindgraphServer(servers) {
|
|
|
8798
9191
|
return false;
|
|
8799
9192
|
}
|
|
8800
9193
|
function pathIsWithin(ancestor, descendant) {
|
|
8801
|
-
const base =
|
|
8802
|
-
const target =
|
|
8803
|
-
return target === base || target.startsWith(base +
|
|
9194
|
+
const base = import_node_path4.default.resolve(ancestor);
|
|
9195
|
+
const target = import_node_path4.default.resolve(descendant);
|
|
9196
|
+
return target === base || target.startsWith(base + import_node_path4.default.sep);
|
|
8804
9197
|
}
|
|
8805
9198
|
function claudeMcpRegistrationNote(input, options) {
|
|
8806
9199
|
if (options.harness !== "claude-code") return void 0;
|
|
8807
9200
|
if (process.env.MINDGRAPH_SKIP_MCP_REGISTRATION_CHECK === "1") return void 0;
|
|
8808
9201
|
try {
|
|
8809
|
-
const configPath = options.claudeConfigPath ||
|
|
8810
|
-
if (!
|
|
8811
|
-
const config = JSON.parse(
|
|
9202
|
+
const configPath = options.claudeConfigPath || import_node_path4.default.join(import_node_os4.default.homedir(), ".claude.json");
|
|
9203
|
+
if (!import_node_fs5.default.existsSync(configPath)) return void 0;
|
|
9204
|
+
const config = JSON.parse(import_node_fs5.default.readFileSync(configPath, "utf8"));
|
|
8812
9205
|
if (hasMindgraphServer(config.mcpServers)) return void 0;
|
|
8813
|
-
const cwd =
|
|
9206
|
+
const cwd = import_node_path4.default.resolve(input.cwd || ".");
|
|
8814
9207
|
for (const [projectDir, project] of Object.entries(config.projects || {})) {
|
|
8815
9208
|
if (!pathIsWithin(projectDir, cwd)) continue;
|
|
8816
9209
|
if (hasMindgraphServer(project?.mcpServers)) return void 0;
|
|
8817
9210
|
}
|
|
8818
|
-
for (let dir = cwd; ; dir =
|
|
8819
|
-
const candidate =
|
|
8820
|
-
if (
|
|
8821
|
-
const parsed = JSON.parse(
|
|
9211
|
+
for (let dir = cwd; ; dir = import_node_path4.default.dirname(dir)) {
|
|
9212
|
+
const candidate = import_node_path4.default.join(dir, ".mcp.json");
|
|
9213
|
+
if (import_node_fs5.default.existsSync(candidate)) {
|
|
9214
|
+
const parsed = JSON.parse(import_node_fs5.default.readFileSync(candidate, "utf8"));
|
|
8822
9215
|
if (hasMindgraphServer(parsed.mcpServers)) return void 0;
|
|
8823
9216
|
}
|
|
8824
|
-
if (dir ===
|
|
9217
|
+
if (dir === import_node_path4.default.dirname(dir)) break;
|
|
8825
9218
|
}
|
|
8826
9219
|
} catch {
|
|
8827
9220
|
return void 0;
|
|
@@ -8924,7 +9317,7 @@ async function sessionStart(input, client2, options) {
|
|
|
8924
9317
|
const contextText = registrationNote ? `${registrationNote}
|
|
8925
9318
|
|
|
8926
9319
|
${briefText}` : briefText;
|
|
8927
|
-
const briefHash =
|
|
9320
|
+
const briefHash = import_node_crypto5.default.createHash("sha256").update(contextText).digest("hex");
|
|
8928
9321
|
const shouldInject = withLedger(input.session_id, options, (ledger) => {
|
|
8929
9322
|
const changed = ledger.lastBriefHash !== briefHash;
|
|
8930
9323
|
const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
|
|
@@ -8999,6 +9392,20 @@ async function preTool(input, options) {
|
|
|
8999
9392
|
if (targetsLedgerTask && fencedAction && ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
|
|
9000
9393
|
updatedInput.expected_version = ledger.taskVersion;
|
|
9001
9394
|
}
|
|
9395
|
+
if (options.harness === "claude-code") {
|
|
9396
|
+
const fills = Object.fromEntries(
|
|
9397
|
+
Object.entries(updatedInput).filter(
|
|
9398
|
+
([key, value]) => key === "invocation_context" || original[key] !== value
|
|
9399
|
+
)
|
|
9400
|
+
);
|
|
9401
|
+
recordPendingCall(
|
|
9402
|
+
input.tool_name || "",
|
|
9403
|
+
original,
|
|
9404
|
+
input.session_id,
|
|
9405
|
+
fills,
|
|
9406
|
+
{ runtimeDir: options.runtimeDir, now: options.now }
|
|
9407
|
+
);
|
|
9408
|
+
}
|
|
9002
9409
|
return { kind: "rewrite", updatedInput };
|
|
9003
9410
|
}
|
|
9004
9411
|
var FENCED_PLAN_ACTIONS = /* @__PURE__ */ new Set([
|
|
@@ -9170,6 +9577,9 @@ function stop(input, options) {
|
|
|
9170
9577
|
}
|
|
9171
9578
|
async function sessionEnd(input, client2, options) {
|
|
9172
9579
|
const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
|
|
9580
|
+
if (options.harness === "claude-code") {
|
|
9581
|
+
await captureTranscript(input, client2, options);
|
|
9582
|
+
}
|
|
9173
9583
|
const ledger = readLedger(input.session_id, options);
|
|
9174
9584
|
if (ledger.executionUid && ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0) {
|
|
9175
9585
|
try {
|
|
@@ -9203,7 +9613,104 @@ async function sessionEnd(input, client2, options) {
|
|
|
9203
9613
|
}
|
|
9204
9614
|
return NOOP;
|
|
9205
9615
|
}
|
|
9616
|
+
function withDeadline(promise, ms) {
|
|
9617
|
+
let timer;
|
|
9618
|
+
const deadline = new Promise((_, reject) => {
|
|
9619
|
+
timer = setTimeout(() => reject(new Error("deadline")), ms);
|
|
9620
|
+
timer.unref?.();
|
|
9621
|
+
});
|
|
9622
|
+
return Promise.race([promise, deadline]).finally(() => clearTimeout(timer));
|
|
9623
|
+
}
|
|
9624
|
+
async function promptRecall(input, client2, options) {
|
|
9625
|
+
if (options.promptRecall === false || process.env.MINDGRAPH_HOOK_PROMPT_RECALL === "off" || !client2.retrieveContext) {
|
|
9626
|
+
return NOOP;
|
|
9627
|
+
}
|
|
9628
|
+
const query = promptRecallQuery(input.prompt);
|
|
9629
|
+
if (!query) return NOOP;
|
|
9630
|
+
try {
|
|
9631
|
+
const response = await withDeadline(
|
|
9632
|
+
client2.retrieveContext(promptRecallRequest(query)),
|
|
9633
|
+
options.promptRecallTimeoutMs ?? 4e3
|
|
9634
|
+
);
|
|
9635
|
+
const block = renderPromptRecall(response, query);
|
|
9636
|
+
if (!block) return NOOP;
|
|
9637
|
+
const hash = import_node_crypto5.default.createHash("sha256").update(block).digest("hex");
|
|
9638
|
+
const fresh = withLedger(input.session_id, options, (ledger) => {
|
|
9639
|
+
if (ledger.lastRecallHash === hash) return false;
|
|
9640
|
+
ledger.lastRecallHash = hash;
|
|
9641
|
+
return true;
|
|
9642
|
+
});
|
|
9643
|
+
return fresh ? { kind: "context", context: block, event: "UserPromptSubmit" } : NOOP;
|
|
9644
|
+
} catch {
|
|
9645
|
+
return NOOP;
|
|
9646
|
+
}
|
|
9647
|
+
}
|
|
9648
|
+
async function captureTranscript(input, client2, options) {
|
|
9649
|
+
const enabled = process.env.MINDGRAPH_HOOK_CAPTURE === "on" || options.capture === true && process.env.MINDGRAPH_HOOK_CAPTURE !== "off";
|
|
9650
|
+
if (!enabled || !client2.ingestSession || !input.transcript_path) return;
|
|
9651
|
+
try {
|
|
9652
|
+
const agentId = resolveAgentId(options);
|
|
9653
|
+
const ledger = readLedger(input.session_id, options);
|
|
9654
|
+
let sessionUid = ledger.sessionUid;
|
|
9655
|
+
const prepared = buildCaptureRequest(
|
|
9656
|
+
input.transcript_path,
|
|
9657
|
+
input.session_id,
|
|
9658
|
+
ledger.captureOffset ?? 0,
|
|
9659
|
+
{ sessionUid, agentId }
|
|
9660
|
+
);
|
|
9661
|
+
if (prepared.body && !sessionUid) {
|
|
9662
|
+
const opened = object(
|
|
9663
|
+
await client2.session({
|
|
9664
|
+
action: "open",
|
|
9665
|
+
label: `Claude Code ${input.session_id}`,
|
|
9666
|
+
harness: options.harness,
|
|
9667
|
+
harness_session_id: input.session_id,
|
|
9668
|
+
model: input.model,
|
|
9669
|
+
agent_id: agentId
|
|
9670
|
+
})
|
|
9671
|
+
);
|
|
9672
|
+
sessionUid = string(opened?.uid);
|
|
9673
|
+
if (sessionUid) prepared.body.session_uid = sessionUid;
|
|
9674
|
+
}
|
|
9675
|
+
if (prepared.body) await client2.ingestSession(prepared.body);
|
|
9676
|
+
withLedger(input.session_id, options, (current) => {
|
|
9677
|
+
if (sessionUid) current.sessionUid ??= sessionUid;
|
|
9678
|
+
current.captureOffset = Math.max(current.captureOffset ?? 0, prepared.nextOffset);
|
|
9679
|
+
return void 0;
|
|
9680
|
+
});
|
|
9681
|
+
} catch (error2) {
|
|
9682
|
+
console.error(
|
|
9683
|
+
`mindgraph-hook: transcript capture failed (${error2?.message || "unknown error"})`
|
|
9684
|
+
);
|
|
9685
|
+
}
|
|
9686
|
+
}
|
|
9206
9687
|
async function runHookCore(input, client2, options) {
|
|
9688
|
+
const started = performance.now();
|
|
9689
|
+
const output = await dispatchHook(input, client2, options);
|
|
9690
|
+
if (input.session_id && !(input.hook_event_name === "PreToolUse" && output.kind === "noop")) {
|
|
9691
|
+
try {
|
|
9692
|
+
const elapsed = Math.round(performance.now() - started);
|
|
9693
|
+
const injected = output.kind === "context" ? output.context.length : 0;
|
|
9694
|
+
withLedger(input.session_id, options, (ledger) => {
|
|
9695
|
+
const stats = ledger.hookStats ??= {};
|
|
9696
|
+
const entry = stats[input.hook_event_name] ??= {
|
|
9697
|
+
count: 0,
|
|
9698
|
+
totalMs: 0,
|
|
9699
|
+
maxMs: 0,
|
|
9700
|
+
injectedChars: 0
|
|
9701
|
+
};
|
|
9702
|
+
entry.count += 1;
|
|
9703
|
+
entry.totalMs += elapsed;
|
|
9704
|
+
entry.maxMs = Math.max(entry.maxMs, elapsed);
|
|
9705
|
+
entry.injectedChars += injected;
|
|
9706
|
+
return void 0;
|
|
9707
|
+
});
|
|
9708
|
+
} catch {
|
|
9709
|
+
}
|
|
9710
|
+
}
|
|
9711
|
+
return output;
|
|
9712
|
+
}
|
|
9713
|
+
async function dispatchHook(input, client2, options) {
|
|
9207
9714
|
switch (input.hook_event_name) {
|
|
9208
9715
|
case "SessionStart":
|
|
9209
9716
|
return sessionStart(input, client2, options);
|
|
@@ -9228,6 +9735,11 @@ async function runHookCore(input, client2, options) {
|
|
|
9228
9735
|
return stop(input, options);
|
|
9229
9736
|
case "SessionEnd":
|
|
9230
9737
|
return sessionEnd(input, client2, options);
|
|
9738
|
+
case "UserPromptSubmit":
|
|
9739
|
+
return promptRecall(input, client2, options);
|
|
9740
|
+
case "PreCompact":
|
|
9741
|
+
await captureTranscript(input, client2, options);
|
|
9742
|
+
return NOOP;
|
|
9231
9743
|
default:
|
|
9232
9744
|
return NOOP;
|
|
9233
9745
|
}
|
|
@@ -9239,6 +9751,54 @@ async function readHookInput(stream = process.stdin) {
|
|
|
9239
9751
|
}
|
|
9240
9752
|
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
9241
9753
|
}
|
|
9754
|
+
function previewCapture(transcriptPath, sessionId, options = {}) {
|
|
9755
|
+
let ledger = {};
|
|
9756
|
+
try {
|
|
9757
|
+
ledger = JSON.parse(
|
|
9758
|
+
import_node_fs5.default.readFileSync(
|
|
9759
|
+
ledgerPath(sessionId, { ...options, harness: "claude-code" }),
|
|
9760
|
+
"utf8"
|
|
9761
|
+
)
|
|
9762
|
+
);
|
|
9763
|
+
} catch {
|
|
9764
|
+
}
|
|
9765
|
+
const offset = ledger.captureOffset ?? 0;
|
|
9766
|
+
return {
|
|
9767
|
+
...buildCaptureRequest(transcriptPath, sessionId, offset, {
|
|
9768
|
+
sessionUid: ledger.sessionUid,
|
|
9769
|
+
agentId: options.agentId || process.env.MINDGRAPH_AGENT_ID || stableAgentId()
|
|
9770
|
+
}),
|
|
9771
|
+
offset
|
|
9772
|
+
};
|
|
9773
|
+
}
|
|
9774
|
+
function readHookStats(sessionId, options = {}) {
|
|
9775
|
+
const dir = runtimeDir(options);
|
|
9776
|
+
let best;
|
|
9777
|
+
let names = [];
|
|
9778
|
+
try {
|
|
9779
|
+
names = import_node_fs5.default.readdirSync(dir).filter((name) => /^[0-9a-f]{64}\.json$/.test(name));
|
|
9780
|
+
} catch {
|
|
9781
|
+
return void 0;
|
|
9782
|
+
}
|
|
9783
|
+
for (const name of names) {
|
|
9784
|
+
try {
|
|
9785
|
+
const ledger = JSON.parse(import_node_fs5.default.readFileSync(import_node_path4.default.join(dir, name), "utf8"));
|
|
9786
|
+
if (!ledger.harnessSessionId || !ledger.hookStats) continue;
|
|
9787
|
+
if (sessionId && ledger.harnessSessionId !== sessionId) continue;
|
|
9788
|
+
const updatedAt = Number(ledger.updatedAt) || 0;
|
|
9789
|
+
if (!best || updatedAt > best.updatedAt) {
|
|
9790
|
+
best = {
|
|
9791
|
+
harness: String(ledger.harness ?? "unknown"),
|
|
9792
|
+
sessionId: ledger.harnessSessionId,
|
|
9793
|
+
updatedAt,
|
|
9794
|
+
events: ledger.hookStats
|
|
9795
|
+
};
|
|
9796
|
+
}
|
|
9797
|
+
} catch {
|
|
9798
|
+
}
|
|
9799
|
+
}
|
|
9800
|
+
return best;
|
|
9801
|
+
}
|
|
9242
9802
|
|
|
9243
9803
|
// src/claude-hooks.ts
|
|
9244
9804
|
async function runClaudeHook(input, client2, options = {}) {
|
|
@@ -9253,17 +9813,12 @@ async function runClaudeHook(input, client2, options = {}) {
|
|
|
9253
9813
|
case "context":
|
|
9254
9814
|
return {
|
|
9255
9815
|
hookSpecificOutput: {
|
|
9256
|
-
hookEventName: "SessionStart",
|
|
9816
|
+
hookEventName: output.event ?? "SessionStart",
|
|
9257
9817
|
additionalContext: output.context
|
|
9258
9818
|
}
|
|
9259
9819
|
};
|
|
9260
9820
|
case "rewrite":
|
|
9261
|
-
return {
|
|
9262
|
-
hookSpecificOutput: {
|
|
9263
|
-
hookEventName: "PreToolUse",
|
|
9264
|
-
updatedInput: output.updatedInput
|
|
9265
|
-
}
|
|
9266
|
-
};
|
|
9821
|
+
return {};
|
|
9267
9822
|
case "block":
|
|
9268
9823
|
return { decision: "block", reason: output.reason };
|
|
9269
9824
|
case "noop":
|
|
@@ -9283,7 +9838,7 @@ async function runCodexHook(input, client2, options = {}) {
|
|
|
9283
9838
|
case "context":
|
|
9284
9839
|
return {
|
|
9285
9840
|
hookSpecificOutput: {
|
|
9286
|
-
hookEventName: "SessionStart",
|
|
9841
|
+
hookEventName: output.event ?? "SessionStart",
|
|
9287
9842
|
additionalContext: output.context
|
|
9288
9843
|
}
|
|
9289
9844
|
};
|
|
@@ -9306,15 +9861,15 @@ async function runCodexHook(input, client2, options = {}) {
|
|
|
9306
9861
|
init_hook_installer();
|
|
9307
9862
|
function recordHookHealth(harness, error2) {
|
|
9308
9863
|
try {
|
|
9309
|
-
const dir = process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
9310
|
-
|
|
9311
|
-
const file =
|
|
9864
|
+
const dir = process.env.MINDGRAPH_RUNTIME_DIR || path8.join(os5.homedir(), ".mindgraph", "runtime");
|
|
9865
|
+
fs8.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
9866
|
+
const file = path8.join(dir, "hook-health.json");
|
|
9312
9867
|
let failures = 0;
|
|
9313
9868
|
try {
|
|
9314
|
-
failures = Number(JSON.parse(
|
|
9869
|
+
failures = Number(JSON.parse(fs8.readFileSync(file, "utf8")).consecutiveFailures) || 0;
|
|
9315
9870
|
} catch {
|
|
9316
9871
|
}
|
|
9317
|
-
|
|
9872
|
+
fs8.writeFileSync(
|
|
9318
9873
|
file,
|
|
9319
9874
|
`${JSON.stringify({
|
|
9320
9875
|
harness,
|
|
@@ -9330,14 +9885,14 @@ function recordHookHealth(harness, error2) {
|
|
|
9330
9885
|
}
|
|
9331
9886
|
function clearHookHealth(harness) {
|
|
9332
9887
|
try {
|
|
9333
|
-
const file =
|
|
9334
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
9888
|
+
const file = path8.join(
|
|
9889
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path8.join(os5.homedir(), ".mindgraph", "runtime"),
|
|
9335
9890
|
"hook-health.json"
|
|
9336
9891
|
);
|
|
9337
|
-
if (!
|
|
9338
|
-
const current = JSON.parse(
|
|
9892
|
+
if (!fs8.existsSync(file)) return;
|
|
9893
|
+
const current = JSON.parse(fs8.readFileSync(file, "utf8"));
|
|
9339
9894
|
if (!Number(current.consecutiveFailures)) return;
|
|
9340
|
-
|
|
9895
|
+
fs8.writeFileSync(
|
|
9341
9896
|
file,
|
|
9342
9897
|
`${JSON.stringify({
|
|
9343
9898
|
harness,
|
|
@@ -9353,22 +9908,22 @@ function clearHookHealth(harness) {
|
|
|
9353
9908
|
function getClaudeDesktopConfigPath() {
|
|
9354
9909
|
switch (process.platform) {
|
|
9355
9910
|
case "darwin":
|
|
9356
|
-
return
|
|
9357
|
-
|
|
9911
|
+
return path8.join(
|
|
9912
|
+
os5.homedir(),
|
|
9358
9913
|
"Library",
|
|
9359
9914
|
"Application Support",
|
|
9360
9915
|
"Claude",
|
|
9361
9916
|
"claude_desktop_config.json"
|
|
9362
9917
|
);
|
|
9363
9918
|
case "win32":
|
|
9364
|
-
return
|
|
9365
|
-
process.env.APPDATA ||
|
|
9919
|
+
return path8.join(
|
|
9920
|
+
process.env.APPDATA || path8.join(os5.homedir(), "AppData", "Roaming"),
|
|
9366
9921
|
"Claude",
|
|
9367
9922
|
"claude_desktop_config.json"
|
|
9368
9923
|
);
|
|
9369
9924
|
case "linux":
|
|
9370
|
-
return
|
|
9371
|
-
|
|
9925
|
+
return path8.join(
|
|
9926
|
+
os5.homedir(),
|
|
9372
9927
|
".config",
|
|
9373
9928
|
"Claude",
|
|
9374
9929
|
"claude_desktop_config.json"
|
|
@@ -9378,16 +9933,16 @@ function getClaudeDesktopConfigPath() {
|
|
|
9378
9933
|
}
|
|
9379
9934
|
}
|
|
9380
9935
|
function readJsonFile(filePath) {
|
|
9381
|
-
if (!
|
|
9382
|
-
const content =
|
|
9936
|
+
if (!fs8.existsSync(filePath)) return {};
|
|
9937
|
+
const content = fs8.readFileSync(filePath, "utf-8");
|
|
9383
9938
|
return JSON.parse(content);
|
|
9384
9939
|
}
|
|
9385
9940
|
function writeJsonFile(filePath, data) {
|
|
9386
|
-
const dir =
|
|
9387
|
-
if (!
|
|
9388
|
-
|
|
9941
|
+
const dir = path8.dirname(filePath);
|
|
9942
|
+
if (!fs8.existsSync(dir)) {
|
|
9943
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
9389
9944
|
}
|
|
9390
|
-
|
|
9945
|
+
fs8.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
9391
9946
|
}
|
|
9392
9947
|
function getMcpServerEntry(apiKey, baseUrl) {
|
|
9393
9948
|
const env = { MINDGRAPH_API_KEY: apiKey };
|
|
@@ -9474,7 +10029,7 @@ function installClaudeCode(apiKey, baseUrl, agentId) {
|
|
|
9474
10029
|
}
|
|
9475
10030
|
function uninstallClaudeDesktop() {
|
|
9476
10031
|
const configPath = getClaudeDesktopConfigPath();
|
|
9477
|
-
if (!
|
|
10032
|
+
if (!fs8.existsSync(configPath)) {
|
|
9478
10033
|
console.log("Claude Desktop config not found. Nothing to remove.");
|
|
9479
10034
|
return;
|
|
9480
10035
|
}
|
|
@@ -9501,7 +10056,7 @@ function uninstallClaudeCode() {
|
|
|
9501
10056
|
}
|
|
9502
10057
|
function printStatus() {
|
|
9503
10058
|
const desktopPath = getClaudeDesktopConfigPath();
|
|
9504
|
-
if (
|
|
10059
|
+
if (fs8.existsSync(desktopPath)) {
|
|
9505
10060
|
const config = readJsonFile(desktopPath);
|
|
9506
10061
|
const servers = config.mcpServers;
|
|
9507
10062
|
if (servers && "mindgraph" in servers) {
|
|
@@ -9519,12 +10074,12 @@ function printStatus() {
|
|
|
9519
10074
|
`MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
|
|
9520
10075
|
);
|
|
9521
10076
|
try {
|
|
9522
|
-
const healthFile =
|
|
9523
|
-
process.env.MINDGRAPH_RUNTIME_DIR ||
|
|
10077
|
+
const healthFile = path8.join(
|
|
10078
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path8.join(os5.homedir(), ".mindgraph", "runtime"),
|
|
9524
10079
|
"hook-health.json"
|
|
9525
10080
|
);
|
|
9526
|
-
if (
|
|
9527
|
-
const health = JSON.parse(
|
|
10081
|
+
if (fs8.existsSync(healthFile)) {
|
|
10082
|
+
const health = JSON.parse(fs8.readFileSync(healthFile, "utf8"));
|
|
9528
10083
|
const failures = Number(health.consecutiveFailures) || 0;
|
|
9529
10084
|
console.log(
|
|
9530
10085
|
failures > 0 ? `Hooks: FAILING (${failures} consecutive; last: ${health.lastError} at ${health.lastFailureAt})` : `Hooks: healthy (last recovery ${health.lastOkAt || "unknown"})`
|
|
@@ -9539,17 +10094,46 @@ function printStatus() {
|
|
|
9539
10094
|
function withHooksFlag() {
|
|
9540
10095
|
return process.argv.includes("--hooks");
|
|
9541
10096
|
}
|
|
10097
|
+
function claudeHookFeatures() {
|
|
10098
|
+
const flags = parseArgs(process.argv);
|
|
10099
|
+
const stored = loadHookEnv();
|
|
10100
|
+
return {
|
|
10101
|
+
promptRecall: flags.promptRecall ?? stored.promptRecall ?? true,
|
|
10102
|
+
capture: flags.capture ?? stored.capture ?? false
|
|
10103
|
+
};
|
|
10104
|
+
}
|
|
10105
|
+
function printClaudeHookFeatures(features) {
|
|
10106
|
+
console.log(
|
|
10107
|
+
`Prompt recall (UserPromptSubmit): ${features.promptRecall ? "on" : "off"}; transcript capture (PreCompact/SessionEnd): ${features.capture ? "ON \u2014 user and assistant text (never tool calls or tool output), secrets redacted, is sent to MindGraph /ingest/session" : "off (enable with --capture)"}`
|
|
10108
|
+
);
|
|
10109
|
+
}
|
|
10110
|
+
function latestTranscript(cwd) {
|
|
10111
|
+
const dir = path8.join(
|
|
10112
|
+
os5.homedir(),
|
|
10113
|
+
".claude",
|
|
10114
|
+
"projects",
|
|
10115
|
+
cwd.replace(/[^A-Za-z0-9]/g, "-")
|
|
10116
|
+
);
|
|
10117
|
+
try {
|
|
10118
|
+
return fs8.readdirSync(dir).filter((name) => name.endsWith(".jsonl")).map((name) => path8.join(dir, name)).sort((a, b) => fs8.statSync(b).mtimeMs - fs8.statSync(a).mtimeMs)[0];
|
|
10119
|
+
} catch {
|
|
10120
|
+
return void 0;
|
|
10121
|
+
}
|
|
10122
|
+
}
|
|
9542
10123
|
function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
|
|
9543
10124
|
if (withHooks) {
|
|
9544
|
-
const runner = installHookRunner(__filename, void 0, "0.
|
|
10125
|
+
const runner = installHookRunner(__filename, void 0, "0.24.0");
|
|
9545
10126
|
console.log(`Installed pinned hook runner at ${runner}`);
|
|
9546
|
-
const
|
|
10127
|
+
const features = claudeHookFeatures();
|
|
10128
|
+
const result = installClaudeHooks("user", void 0, features);
|
|
9547
10129
|
console.log(
|
|
9548
10130
|
`Installed ${result.added} and refreshed ${result.updated} MindGraph Claude Code hook entries in ${result.path}`
|
|
9549
10131
|
);
|
|
10132
|
+
printClaudeHookFeatures(features);
|
|
9550
10133
|
const envPath = saveHookEnv({
|
|
9551
10134
|
apiKey,
|
|
9552
10135
|
baseUrl,
|
|
10136
|
+
...features,
|
|
9553
10137
|
// Default the stable per-user identity only when nothing is stored —
|
|
9554
10138
|
// an explicit or previously persisted agentId must survive reinstalls.
|
|
9555
10139
|
agentId: agentId || process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId || stableAgentId(),
|
|
@@ -9588,6 +10172,12 @@ USAGE:
|
|
|
9588
10172
|
mindgraph-mcp uninstall-code Remove from Claude Code
|
|
9589
10173
|
mindgraph-mcp install-hooks --harness claude-code|codex [--scope user|project]
|
|
9590
10174
|
mindgraph-mcp uninstall-hooks --harness claude-code|codex [--scope user|project]
|
|
10175
|
+
mindgraph-mcp hooks preview [transcript_path] [--session-id <id>]
|
|
10176
|
+
Print exactly what transcript capture would
|
|
10177
|
+
send (POST /ingest/session); sends nothing.
|
|
10178
|
+
mindgraph-mcp hooks stats [--session-id <id>]
|
|
10179
|
+
Per-event hook calls, time and characters
|
|
10180
|
+
injected for a session (latest by default).
|
|
9591
10181
|
mindgraph-mcp status Show installation status
|
|
9592
10182
|
mindgraph-mcp skills pull [--dir .claude/skills]
|
|
9593
10183
|
Write your org's published+granted skills as
|
|
@@ -9610,6 +10200,10 @@ OPTIONS:
|
|
|
9610
10200
|
--scope <scope> Hook settings scope: project (default) or user
|
|
9611
10201
|
--project-dir <dir> Project root for project-scoped hooks
|
|
9612
10202
|
--harness <name> Hook harness (claude-code or codex)
|
|
10203
|
+
--no-prompt-recall Claude hooks: omit per-prompt recall (UserPromptSubmit)
|
|
10204
|
+
--capture Claude hooks: send user/assistant text to MindGraph
|
|
10205
|
+
before compaction and at session end (off by default;
|
|
10206
|
+
--no-capture turns it back off)
|
|
9613
10207
|
--help, -h Show this help message
|
|
9614
10208
|
|
|
9615
10209
|
EXAMPLES:
|
|
@@ -9629,7 +10223,7 @@ var DASHBOARD_URL = process.env.MINDGRAPH_DASHBOARD_URL || "https://mindgraph.cl
|
|
|
9629
10223
|
var POLL_INTERVAL = 2e3;
|
|
9630
10224
|
var POLL_TIMEOUT = 10 * 60 * 1e3;
|
|
9631
10225
|
function generateCode() {
|
|
9632
|
-
return
|
|
10226
|
+
return crypto6.randomBytes(32).toString("hex");
|
|
9633
10227
|
}
|
|
9634
10228
|
function openBrowser(url) {
|
|
9635
10229
|
try {
|
|
@@ -9856,16 +10450,19 @@ ${total} skill(s) synced.`);
|
|
|
9856
10450
|
if (hookHarness !== "claude-code" && hookHarness !== "codex") {
|
|
9857
10451
|
throw new Error("--harness must be claude-code or codex");
|
|
9858
10452
|
}
|
|
9859
|
-
const runner = installHookRunner(__filename, void 0, "0.
|
|
10453
|
+
const runner = installHookRunner(__filename, void 0, "0.24.0");
|
|
9860
10454
|
console.log(`Installed pinned hook runner at ${runner}`);
|
|
9861
|
-
const
|
|
10455
|
+
const features = hookHarness === "claude-code" ? claudeHookFeatures() : void 0;
|
|
10456
|
+
const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir, features);
|
|
9862
10457
|
console.log(
|
|
9863
10458
|
`Installed ${result.added} and refreshed ${result.updated} MindGraph ${hookHarness === "codex" ? "Codex" : "Claude Code"} hook entries in ${result.path}`
|
|
9864
10459
|
);
|
|
10460
|
+
if (features) printClaudeHookFeatures(features);
|
|
9865
10461
|
printCodegraphStatus();
|
|
9866
10462
|
const envPath = saveHookEnv({
|
|
9867
10463
|
apiKey,
|
|
9868
10464
|
baseUrl,
|
|
10465
|
+
...features || {},
|
|
9869
10466
|
// Default the stable per-user identity only when nothing is stored —
|
|
9870
10467
|
// an explicit or previously persisted agentId must survive
|
|
9871
10468
|
// reinstalls.
|
|
@@ -9917,9 +10514,14 @@ ${total} skill(s) synced.`);
|
|
|
9917
10514
|
// DEFAULT org while the MCP tools write to the pinned one.
|
|
9918
10515
|
orgId: process.env.MINDGRAPH_ORG_ID || stored.orgId,
|
|
9919
10516
|
maxRetries: 0,
|
|
9920
|
-
telemetrySurface: "mcp"
|
|
10517
|
+
telemetrySurface: "mcp",
|
|
10518
|
+
// Prompt recall must never hold the prompt: abort the request at
|
|
10519
|
+
// the same 4s deadline the core races it against.
|
|
10520
|
+
...input.hook_event_name === "UserPromptSubmit" ? { timeoutMs: 4e3 } : {}
|
|
9921
10521
|
});
|
|
9922
10522
|
const hookOptions = {
|
|
10523
|
+
promptRecall: stored.promptRecall,
|
|
10524
|
+
capture: stored.capture,
|
|
9923
10525
|
// No harness-name fallback here: hook-core derives a stable
|
|
9924
10526
|
// per-user default shared by both harnesses, which is what lets a
|
|
9925
10527
|
// task claimed under Codex rebind under Claude Code.
|
|
@@ -9934,9 +10536,11 @@ ${total} skill(s) synced.`);
|
|
|
9934
10536
|
client2,
|
|
9935
10537
|
hookOptions
|
|
9936
10538
|
);
|
|
9937
|
-
process.stdout.write(`${JSON.stringify(output)}
|
|
9938
|
-
`);
|
|
9939
10539
|
clearHookHealth(hookHarness);
|
|
10540
|
+
process.stdout.write(`${JSON.stringify(output)}
|
|
10541
|
+
`, () => {
|
|
10542
|
+
if (input.hook_event_name === "UserPromptSubmit") process.exit(0);
|
|
10543
|
+
});
|
|
9940
10544
|
} catch (error2) {
|
|
9941
10545
|
recordHookHealth(
|
|
9942
10546
|
hookHarness,
|
|
@@ -9949,6 +10553,59 @@ ${total} skill(s) synced.`);
|
|
|
9949
10553
|
}
|
|
9950
10554
|
break;
|
|
9951
10555
|
}
|
|
10556
|
+
case "hooks": {
|
|
10557
|
+
if (positionals[0] === "stats") {
|
|
10558
|
+
const stats = readHookStats(parseArgs(process.argv).sessionId);
|
|
10559
|
+
if (!stats) {
|
|
10560
|
+
console.error(
|
|
10561
|
+
"No hook statistics recorded yet. They are kept per session once the hooks from this version have run."
|
|
10562
|
+
);
|
|
10563
|
+
process.exit(1);
|
|
10564
|
+
}
|
|
10565
|
+
const rows = Object.entries(stats.events).sort(([a], [b]) => a.localeCompare(b));
|
|
10566
|
+
const injected = rows.reduce((sum, [, row]) => sum + row.injectedChars, 0);
|
|
10567
|
+
console.log(`Session ${stats.sessionId} (${stats.harness}), last active ${new Date(stats.updatedAt).toISOString()}`);
|
|
10568
|
+
console.log("event calls avg ms max ms injected chars");
|
|
10569
|
+
for (const [event, row] of rows) {
|
|
10570
|
+
console.log(
|
|
10571
|
+
`${event.padEnd(18)} ${String(row.count).padStart(6)} ${String(Math.round(row.totalMs / Math.max(row.count, 1))).padStart(8)} ${String(row.maxMs).padStart(8)} ${String(row.injectedChars).padStart(16)}`
|
|
10572
|
+
);
|
|
10573
|
+
}
|
|
10574
|
+
console.log(
|
|
10575
|
+
`Injected into context: ${injected} characters (about ${Math.round(injected / 4)} tokens). Times are measured inside the hook and exclude process start.`
|
|
10576
|
+
);
|
|
10577
|
+
break;
|
|
10578
|
+
}
|
|
10579
|
+
if (positionals[0] !== "preview") {
|
|
10580
|
+
console.error(
|
|
10581
|
+
"Usage: mindgraph-mcp hooks preview [transcript_path] [--session-id <id>]\n mindgraph-mcp hooks stats [--session-id <id>]"
|
|
10582
|
+
);
|
|
10583
|
+
process.exit(1);
|
|
10584
|
+
}
|
|
10585
|
+
const transcript = positionals[1] || latestTranscript(process.cwd());
|
|
10586
|
+
if (!transcript || !fs8.existsSync(transcript)) {
|
|
10587
|
+
console.error(
|
|
10588
|
+
"No transcript found. Pass a Claude Code transcript path (~/.claude/projects/<dir>/<session>.jsonl)."
|
|
10589
|
+
);
|
|
10590
|
+
process.exit(1);
|
|
10591
|
+
}
|
|
10592
|
+
const sessionId = parseArgs(process.argv).sessionId || path8.basename(transcript, ".jsonl");
|
|
10593
|
+
const preview = previewCapture(transcript, sessionId, {
|
|
10594
|
+
agentId: process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId
|
|
10595
|
+
});
|
|
10596
|
+
const enabled = loadHookEnv().capture === true;
|
|
10597
|
+
console.error(
|
|
10598
|
+
`Transcript: ${transcript}
|
|
10599
|
+
Session: ${sessionId}
|
|
10600
|
+
Capture is ${enabled ? "ON" : "OFF (nothing is sent; enable with install-hooks --capture)"}
|
|
10601
|
+
Would send ${preview.turns} turn(s) from byte ${preview.offset} as POST /ingest/session (nothing sent by this preview):`
|
|
10602
|
+
);
|
|
10603
|
+
process.stdout.write(
|
|
10604
|
+
`${JSON.stringify(preview.body ?? null, null, 2)}
|
|
10605
|
+
`
|
|
10606
|
+
);
|
|
10607
|
+
break;
|
|
10608
|
+
}
|
|
9952
10609
|
case "status":
|
|
9953
10610
|
printStatus();
|
|
9954
10611
|
break;
|