mindgraph-mcp 0.14.8 → 0.15.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 +2 -2
- package/dist/cli.js +672 -173
- package/dist/index.js +166 -62
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -915,6 +915,61 @@ var init_dist = __esm({
|
|
|
915
915
|
}
|
|
916
916
|
});
|
|
917
917
|
|
|
918
|
+
// src/hook-env.ts
|
|
919
|
+
function stableAgentId() {
|
|
920
|
+
let user = "user";
|
|
921
|
+
try {
|
|
922
|
+
user = import_node_os.default.userInfo().username || "user";
|
|
923
|
+
} catch {
|
|
924
|
+
}
|
|
925
|
+
const digest = import_node_crypto.default.createHash("sha256").update(`${user}@${import_node_os.default.hostname()}`).digest("hex").slice(0, 12);
|
|
926
|
+
return `u-${digest}`;
|
|
927
|
+
}
|
|
928
|
+
function hookEnvPath(dir) {
|
|
929
|
+
return import_node_path.default.join(dir || import_node_path.default.join(import_node_os.default.homedir(), ".mindgraph"), "hooks.json");
|
|
930
|
+
}
|
|
931
|
+
function loadHookEnv(dir) {
|
|
932
|
+
try {
|
|
933
|
+
const raw = import_node_fs.default.readFileSync(hookEnvPath(dir), "utf8");
|
|
934
|
+
const parsed = JSON.parse(raw);
|
|
935
|
+
return {
|
|
936
|
+
baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : void 0,
|
|
937
|
+
apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : void 0,
|
|
938
|
+
agentId: typeof parsed.agentId === "string" ? parsed.agentId : void 0,
|
|
939
|
+
orgId: typeof parsed.orgId === "string" ? parsed.orgId : void 0
|
|
940
|
+
};
|
|
941
|
+
} catch {
|
|
942
|
+
return {};
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
function saveHookEnv(env, dir) {
|
|
946
|
+
const file = hookEnvPath(dir);
|
|
947
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(file), { recursive: true });
|
|
948
|
+
const existing = loadHookEnv(dir);
|
|
949
|
+
const merged = {
|
|
950
|
+
baseUrl: env.baseUrl ?? existing.baseUrl,
|
|
951
|
+
apiKey: env.apiKey ?? existing.apiKey,
|
|
952
|
+
agentId: env.agentId ?? existing.agentId,
|
|
953
|
+
orgId: env.orgId === null ? void 0 : env.orgId ?? existing.orgId
|
|
954
|
+
};
|
|
955
|
+
import_node_fs.default.writeFileSync(file, `${JSON.stringify(merged, null, 2)}
|
|
956
|
+
`, {
|
|
957
|
+
mode: 384
|
|
958
|
+
});
|
|
959
|
+
import_node_fs.default.chmodSync(file, 384);
|
|
960
|
+
return file;
|
|
961
|
+
}
|
|
962
|
+
var import_node_crypto, import_node_fs, import_node_os, import_node_path;
|
|
963
|
+
var init_hook_env = __esm({
|
|
964
|
+
"src/hook-env.ts"() {
|
|
965
|
+
"use strict";
|
|
966
|
+
import_node_crypto = __toESM(require("crypto"));
|
|
967
|
+
import_node_fs = __toESM(require("fs"));
|
|
968
|
+
import_node_os = __toESM(require("os"));
|
|
969
|
+
import_node_path = __toESM(require("path"));
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
|
|
918
973
|
// src/codegraph.ts
|
|
919
974
|
function unavailabilityCaveats(reason, message) {
|
|
920
975
|
const caveats = [message || "codegraph status failed"];
|
|
@@ -1095,7 +1150,7 @@ function codeRefIdentityKey(ref) {
|
|
|
1095
1150
|
}
|
|
1096
1151
|
};
|
|
1097
1152
|
}
|
|
1098
|
-
var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2,
|
|
1153
|
+
var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto2, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
|
|
1099
1154
|
var init_codegraph = __esm({
|
|
1100
1155
|
"src/codegraph.ts"() {
|
|
1101
1156
|
"use strict";
|
|
@@ -1104,7 +1159,7 @@ var init_codegraph = __esm({
|
|
|
1104
1159
|
import_promises = require("fs/promises");
|
|
1105
1160
|
import_node_os2 = require("os");
|
|
1106
1161
|
import_node_path2 = require("path");
|
|
1107
|
-
|
|
1162
|
+
import_node_crypto2 = require("crypto");
|
|
1108
1163
|
DEFAULT_TIMEOUT_MS = 5e3;
|
|
1109
1164
|
DEFAULT_STATUS_TTL_MS = 6e4;
|
|
1110
1165
|
MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
@@ -1204,7 +1259,7 @@ var init_codegraph = __esm({
|
|
|
1204
1259
|
} catch {
|
|
1205
1260
|
}
|
|
1206
1261
|
(0, import_node_fs2.mkdirSync)(identityDir, { recursive: true, mode: 448 });
|
|
1207
|
-
const generated = (0,
|
|
1262
|
+
const generated = (0, import_node_crypto2.randomUUID)();
|
|
1208
1263
|
try {
|
|
1209
1264
|
(0, import_node_fs2.writeFileSync)(identityFile, `${generated}
|
|
1210
1265
|
`, {
|
|
@@ -1617,10 +1672,28 @@ function errorDetail(e) {
|
|
|
1617
1672
|
}
|
|
1618
1673
|
return e instanceof Error ? e.message : String(e);
|
|
1619
1674
|
}
|
|
1675
|
+
function conflictState(e) {
|
|
1676
|
+
if (!(e instanceof MindGraphError) || !e.body || typeof e.body !== "object") {
|
|
1677
|
+
return {};
|
|
1678
|
+
}
|
|
1679
|
+
const body = e.body;
|
|
1680
|
+
const lifted = {};
|
|
1681
|
+
for (const key of CONFLICT_STATE_KEYS) {
|
|
1682
|
+
if (body[key] !== void 0) lifted[key] = body[key];
|
|
1683
|
+
}
|
|
1684
|
+
return lifted;
|
|
1685
|
+
}
|
|
1686
|
+
var CONFLICT_STATE_KEYS;
|
|
1620
1687
|
var init_error_detail = __esm({
|
|
1621
1688
|
"src/error-detail.ts"() {
|
|
1622
1689
|
"use strict";
|
|
1623
1690
|
init_dist();
|
|
1691
|
+
CONFLICT_STATE_KEYS = [
|
|
1692
|
+
"current_version",
|
|
1693
|
+
"current_epoch",
|
|
1694
|
+
"lease_expires_at",
|
|
1695
|
+
"lease_owner_agent_id"
|
|
1696
|
+
];
|
|
1624
1697
|
}
|
|
1625
1698
|
});
|
|
1626
1699
|
|
|
@@ -2144,7 +2217,7 @@ function fileDescriptor(root, absolute) {
|
|
|
2144
2217
|
const content = import_node_fs5.default.readFileSync(absolute, "utf8");
|
|
2145
2218
|
return {
|
|
2146
2219
|
logical_path: import_node_path5.default.relative(root, absolute).split(import_node_path5.default.sep).join("/"),
|
|
2147
|
-
content_hash:
|
|
2220
|
+
content_hash: import_node_crypto5.default.createHash("sha256").update(content).digest("hex"),
|
|
2148
2221
|
bytes: Buffer.byteLength(content),
|
|
2149
2222
|
content
|
|
2150
2223
|
};
|
|
@@ -2252,11 +2325,11 @@ async function scanRepository(remote, repository, requested, provider, agentId)
|
|
|
2252
2325
|
files: results
|
|
2253
2326
|
};
|
|
2254
2327
|
}
|
|
2255
|
-
var
|
|
2328
|
+
var import_node_crypto5, import_node_fs5, import_node_path5, ok2, err2, SYNC_TOOL;
|
|
2256
2329
|
var init_sync_tool = __esm({
|
|
2257
2330
|
"src/sync-tool.ts"() {
|
|
2258
2331
|
"use strict";
|
|
2259
|
-
|
|
2332
|
+
import_node_crypto5 = __toESM(require("crypto"));
|
|
2260
2333
|
import_node_fs5 = __toESM(require("fs"));
|
|
2261
2334
|
import_node_path5 = __toESM(require("path"));
|
|
2262
2335
|
init_codegraph();
|
|
@@ -2405,6 +2478,18 @@ async function handleTool(client2, name, args) {
|
|
|
2405
2478
|
].includes(action);
|
|
2406
2479
|
return attach ? await attachCodeRefsToToolResult(client2, result, args) : result;
|
|
2407
2480
|
} catch (e) {
|
|
2481
|
+
const lifted = conflictState(e);
|
|
2482
|
+
if (Object.keys(lifted).length > 0) {
|
|
2483
|
+
return {
|
|
2484
|
+
content: [
|
|
2485
|
+
{
|
|
2486
|
+
type: "text",
|
|
2487
|
+
text: JSON.stringify({ error: errorDetail(e), ...lifted })
|
|
2488
|
+
}
|
|
2489
|
+
],
|
|
2490
|
+
isError: true
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2408
2493
|
return err3(errorDetail(e));
|
|
2409
2494
|
}
|
|
2410
2495
|
}
|
|
@@ -2651,6 +2736,38 @@ async function handleCommit(client2, args) {
|
|
|
2651
2736
|
return err3(`Unknown commit action: ${action}`);
|
|
2652
2737
|
}
|
|
2653
2738
|
}
|
|
2739
|
+
async function repositoryScopeUidsForTask(client2, options) {
|
|
2740
|
+
const scopeUids = [...new Set(options.scopeUids ?? [])];
|
|
2741
|
+
const cwd = options.invocationContext?.cwd;
|
|
2742
|
+
const repoId = options.invocationContext?.repoId;
|
|
2743
|
+
if (!options.repo && !cwd && !repoId) {
|
|
2744
|
+
return options.scopeUids;
|
|
2745
|
+
}
|
|
2746
|
+
const agentId = options.agentId ?? process.env.MINDGRAPH_AGENT_ID ?? "mcp";
|
|
2747
|
+
const fallbackSpaceUid = options.repoSpaceUid ?? `space:agent:${agentId}`;
|
|
2748
|
+
const env = { ...process.env };
|
|
2749
|
+
if (!options.repo && repoId) {
|
|
2750
|
+
env.MINDGRAPH_REPO_ID = repoId;
|
|
2751
|
+
}
|
|
2752
|
+
const adapter = new CodegraphAdapter({
|
|
2753
|
+
cwd: cwd ?? process.cwd(),
|
|
2754
|
+
env
|
|
2755
|
+
});
|
|
2756
|
+
const repository = await adapter.resolveRepository(options.repo, fallbackSpaceUid);
|
|
2757
|
+
const anchored = await anchorRepository(
|
|
2758
|
+
client2,
|
|
2759
|
+
repository,
|
|
2760
|
+
repository.spaceUid ?? fallbackSpaceUid,
|
|
2761
|
+
agentId
|
|
2762
|
+
);
|
|
2763
|
+
if (!anchored.uid) {
|
|
2764
|
+
throw new Error(
|
|
2765
|
+
`repository scope ${repository.repoId} could not be materialized (${anchored.status ?? "missing uid"})`
|
|
2766
|
+
);
|
|
2767
|
+
}
|
|
2768
|
+
scopeUids.push(anchored.uid);
|
|
2769
|
+
return [...new Set(scopeUids)];
|
|
2770
|
+
}
|
|
2654
2771
|
async function handlePlan(client2, args) {
|
|
2655
2772
|
const {
|
|
2656
2773
|
action,
|
|
@@ -2681,6 +2798,9 @@ async function handlePlan(client2, args) {
|
|
|
2681
2798
|
release_lease,
|
|
2682
2799
|
override_reason,
|
|
2683
2800
|
scope_uids,
|
|
2801
|
+
repo,
|
|
2802
|
+
repo_space_uid,
|
|
2803
|
+
invocation_context,
|
|
2684
2804
|
limit,
|
|
2685
2805
|
confidence,
|
|
2686
2806
|
salience,
|
|
@@ -2710,7 +2830,14 @@ async function handlePlan(client2, args) {
|
|
|
2710
2830
|
case "checkpoint_iteration":
|
|
2711
2831
|
case "block_task":
|
|
2712
2832
|
case "complete_task":
|
|
2713
|
-
case "abandon_iteration":
|
|
2833
|
+
case "abandon_iteration": {
|
|
2834
|
+
const resolvedScopeUids = action === "create_task" ? await repositoryScopeUidsForTask(client2, {
|
|
2835
|
+
scopeUids: scope_uids,
|
|
2836
|
+
repo,
|
|
2837
|
+
repoSpaceUid: repo_space_uid,
|
|
2838
|
+
invocationContext: invocation_context,
|
|
2839
|
+
agentId: agent_id
|
|
2840
|
+
}) : scope_uids;
|
|
2714
2841
|
return ok3(
|
|
2715
2842
|
await client2.plan({
|
|
2716
2843
|
action,
|
|
@@ -2738,7 +2865,7 @@ async function handlePlan(client2, args) {
|
|
|
2738
2865
|
allow_takeover,
|
|
2739
2866
|
release_lease,
|
|
2740
2867
|
override_reason,
|
|
2741
|
-
scope_uids,
|
|
2868
|
+
scope_uids: resolvedScopeUids,
|
|
2742
2869
|
limit,
|
|
2743
2870
|
input_snapshot,
|
|
2744
2871
|
output_snapshot,
|
|
@@ -2753,6 +2880,7 @@ async function handlePlan(client2, args) {
|
|
|
2753
2880
|
agent_id
|
|
2754
2881
|
})
|
|
2755
2882
|
);
|
|
2883
|
+
}
|
|
2756
2884
|
// Execution tracking
|
|
2757
2885
|
case "start_execution":
|
|
2758
2886
|
return ok3(
|
|
@@ -3310,6 +3438,7 @@ var init_tools = __esm({
|
|
|
3310
3438
|
"use strict";
|
|
3311
3439
|
init_error_detail();
|
|
3312
3440
|
init_code_tool();
|
|
3441
|
+
init_codegraph();
|
|
3313
3442
|
init_sync_tool();
|
|
3314
3443
|
TOOLS = [
|
|
3315
3444
|
{
|
|
@@ -3774,7 +3903,7 @@ var init_tools = __esm({
|
|
|
3774
3903
|
scope_uids: {
|
|
3775
3904
|
type: "array",
|
|
3776
3905
|
items: { type: "string" },
|
|
3777
|
-
description: "
|
|
3906
|
+
description: "Required target UIDs for create_task and ordinary target UIDs that bound deterministic resume selection"
|
|
3778
3907
|
},
|
|
3779
3908
|
limit: {
|
|
3780
3909
|
type: "number",
|
|
@@ -4778,7 +4907,7 @@ async function main() {
|
|
|
4778
4907
|
const transport = new import_stdio.StdioServerTransport();
|
|
4779
4908
|
await server.connect(transport);
|
|
4780
4909
|
console.error(
|
|
4781
|
-
`MindGraph MCP server
|
|
4910
|
+
`MindGraph MCP server v${"0.15.0"} running on stdio (${BASE_URL})`
|
|
4782
4911
|
);
|
|
4783
4912
|
}
|
|
4784
4913
|
var import_server, import_stdio, import_types, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, server, GOVERNANCE_CONFIG, PROMPTS;
|
|
@@ -4789,13 +4918,14 @@ var init_src = __esm({
|
|
|
4789
4918
|
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4790
4919
|
import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
4791
4920
|
init_dist();
|
|
4921
|
+
init_hook_env();
|
|
4792
4922
|
init_tools();
|
|
4793
4923
|
init_generated_tools();
|
|
4794
4924
|
init_governance();
|
|
4795
4925
|
init_resources();
|
|
4796
4926
|
API_KEY = process.env.MINDGRAPH_API_KEY;
|
|
4797
4927
|
BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
|
|
4798
|
-
AGENT_ID = process.env.MINDGRAPH_AGENT_ID ||
|
|
4928
|
+
AGENT_ID = process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId || stableAgentId();
|
|
4799
4929
|
ORG_ID = process.env.MINDGRAPH_ORG_ID;
|
|
4800
4930
|
PROFILE = process.env.MINDGRAPH_PROFILE || "general";
|
|
4801
4931
|
HARNESS = process.env.MINDGRAPH_HARNESS || "generic";
|
|
@@ -4917,7 +5047,7 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
4917
5047
|
server = new import_server.Server(
|
|
4918
5048
|
{
|
|
4919
5049
|
name: "mindgraph",
|
|
4920
|
-
version: "0.
|
|
5050
|
+
version: "0.15.0"
|
|
4921
5051
|
},
|
|
4922
5052
|
{
|
|
4923
5053
|
capabilities: {
|
|
@@ -5138,46 +5268,10 @@ ${conversation}`
|
|
|
5138
5268
|
var fs5 = __toESM(require("fs"));
|
|
5139
5269
|
var path5 = __toESM(require("path"));
|
|
5140
5270
|
var os4 = __toESM(require("os"));
|
|
5141
|
-
var
|
|
5271
|
+
var crypto5 = __toESM(require("crypto"));
|
|
5142
5272
|
var import_child_process = require("child_process");
|
|
5143
5273
|
init_dist();
|
|
5144
|
-
|
|
5145
|
-
// src/hook-env.ts
|
|
5146
|
-
var import_node_fs = __toESM(require("fs"));
|
|
5147
|
-
var import_node_os = __toESM(require("os"));
|
|
5148
|
-
var import_node_path = __toESM(require("path"));
|
|
5149
|
-
function hookEnvPath(dir) {
|
|
5150
|
-
return import_node_path.default.join(dir || import_node_path.default.join(import_node_os.default.homedir(), ".mindgraph"), "hooks.json");
|
|
5151
|
-
}
|
|
5152
|
-
function loadHookEnv(dir) {
|
|
5153
|
-
try {
|
|
5154
|
-
const raw = import_node_fs.default.readFileSync(hookEnvPath(dir), "utf8");
|
|
5155
|
-
const parsed = JSON.parse(raw);
|
|
5156
|
-
return {
|
|
5157
|
-
baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : void 0,
|
|
5158
|
-
apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : void 0,
|
|
5159
|
-
agentId: typeof parsed.agentId === "string" ? parsed.agentId : void 0
|
|
5160
|
-
};
|
|
5161
|
-
} catch {
|
|
5162
|
-
return {};
|
|
5163
|
-
}
|
|
5164
|
-
}
|
|
5165
|
-
function saveHookEnv(env, dir) {
|
|
5166
|
-
const file = hookEnvPath(dir);
|
|
5167
|
-
import_node_fs.default.mkdirSync(import_node_path.default.dirname(file), { recursive: true });
|
|
5168
|
-
const existing = loadHookEnv(dir);
|
|
5169
|
-
const merged = {
|
|
5170
|
-
baseUrl: env.baseUrl ?? existing.baseUrl,
|
|
5171
|
-
apiKey: env.apiKey ?? existing.apiKey,
|
|
5172
|
-
agentId: env.agentId ?? existing.agentId
|
|
5173
|
-
};
|
|
5174
|
-
import_node_fs.default.writeFileSync(file, `${JSON.stringify(merged, null, 2)}
|
|
5175
|
-
`, {
|
|
5176
|
-
mode: 384
|
|
5177
|
-
});
|
|
5178
|
-
import_node_fs.default.chmodSync(file, 384);
|
|
5179
|
-
return file;
|
|
5180
|
-
}
|
|
5274
|
+
init_hook_env();
|
|
5181
5275
|
|
|
5182
5276
|
// src/cli-args.ts
|
|
5183
5277
|
function parseArgs(argv) {
|
|
@@ -5248,12 +5342,14 @@ function classifyMcpAddFailure(e) {
|
|
|
5248
5342
|
}
|
|
5249
5343
|
|
|
5250
5344
|
// src/hook-core.ts
|
|
5251
|
-
var
|
|
5345
|
+
var import_node_crypto3 = __toESM(require("crypto"));
|
|
5252
5346
|
var import_node_fs3 = __toESM(require("fs"));
|
|
5253
5347
|
var import_node_os3 = __toESM(require("os"));
|
|
5254
5348
|
var import_node_path3 = __toESM(require("path"));
|
|
5255
5349
|
var import_node_child_process2 = require("child_process");
|
|
5256
5350
|
init_codegraph();
|
|
5351
|
+
init_code_tool();
|
|
5352
|
+
init_hook_env();
|
|
5257
5353
|
var NOOP = { kind: "noop" };
|
|
5258
5354
|
function defaultLedger(sessionId, options) {
|
|
5259
5355
|
return {
|
|
@@ -5275,7 +5371,7 @@ function runtimeDir(options) {
|
|
|
5275
5371
|
}
|
|
5276
5372
|
function ledgerPath(sessionId, options) {
|
|
5277
5373
|
const harness = "harness" in options ? options.harness : "claude-code";
|
|
5278
|
-
const key =
|
|
5374
|
+
const key = import_node_crypto3.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
|
|
5279
5375
|
return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
|
|
5280
5376
|
}
|
|
5281
5377
|
function waitBriefly(ms) {
|
|
@@ -5290,14 +5386,14 @@ function withLedger(sessionId, options, update) {
|
|
|
5290
5386
|
}
|
|
5291
5387
|
const lock = `${file}.lock`;
|
|
5292
5388
|
let lockFd;
|
|
5293
|
-
for (let attempt = 0; attempt <
|
|
5389
|
+
for (let attempt = 0; attempt < 400; attempt++) {
|
|
5294
5390
|
try {
|
|
5295
5391
|
lockFd = import_node_fs3.default.openSync(lock, "wx", 384);
|
|
5296
5392
|
break;
|
|
5297
5393
|
} catch (error) {
|
|
5298
5394
|
if (error.code !== "EEXIST") throw error;
|
|
5299
5395
|
try {
|
|
5300
|
-
if (Date.now() - import_node_fs3.default.statSync(lock).mtimeMs >
|
|
5396
|
+
if (Date.now() - import_node_fs3.default.statSync(lock).mtimeMs > 3e3) import_node_fs3.default.unlinkSync(lock);
|
|
5301
5397
|
} catch {
|
|
5302
5398
|
}
|
|
5303
5399
|
waitBriefly(10);
|
|
@@ -5317,7 +5413,7 @@ function withLedger(sessionId, options, update) {
|
|
|
5317
5413
|
}
|
|
5318
5414
|
const result = update(ledger);
|
|
5319
5415
|
ledger.updatedAt = Date.now();
|
|
5320
|
-
const temporary = `${file}.${process.pid}.${
|
|
5416
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto3.default.randomUUID()}.tmp`;
|
|
5321
5417
|
import_node_fs3.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
|
|
5322
5418
|
`, { mode: 384 });
|
|
5323
5419
|
import_node_fs3.default.renameSync(temporary, file);
|
|
@@ -5352,17 +5448,13 @@ function worktreeState(cwd) {
|
|
|
5352
5448
|
const status = gitValue(cwd, ["status", "--porcelain"]);
|
|
5353
5449
|
return status === void 0 ? "unknown" : status ? "dirty" : "clean";
|
|
5354
5450
|
}
|
|
5355
|
-
|
|
5356
|
-
const repository = await new CodegraphAdapter({
|
|
5357
|
-
cwd: input.cwd,
|
|
5358
|
-
workspaceFile: options.workspaceFile
|
|
5359
|
-
}).resolveRepository();
|
|
5451
|
+
function invocationContext(input, options, repoId) {
|
|
5360
5452
|
return {
|
|
5361
5453
|
harness: options.harness,
|
|
5362
5454
|
harnessSessionId: input.session_id,
|
|
5363
5455
|
harnessTurnId: input.turn_id,
|
|
5364
5456
|
cwd: input.cwd,
|
|
5365
|
-
repoId
|
|
5457
|
+
repoId,
|
|
5366
5458
|
branch: gitValue(input.cwd, ["branch", "--show-current"]),
|
|
5367
5459
|
commit: gitValue(input.cwd, ["rev-parse", "HEAD"]),
|
|
5368
5460
|
worktreeState: worktreeState(input.cwd),
|
|
@@ -5370,6 +5462,9 @@ async function invocationContext(input, options) {
|
|
|
5370
5462
|
injectedBy: "hook"
|
|
5371
5463
|
};
|
|
5372
5464
|
}
|
|
5465
|
+
function resolveAgentId(options) {
|
|
5466
|
+
return options.agentId || process.env.MINDGRAPH_AGENT_ID || stableAgentId();
|
|
5467
|
+
}
|
|
5373
5468
|
function object(value) {
|
|
5374
5469
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5375
5470
|
}
|
|
@@ -5397,56 +5492,198 @@ function number(value) {
|
|
|
5397
5492
|
function string(value) {
|
|
5398
5493
|
return typeof value === "string" && value ? value : void 0;
|
|
5399
5494
|
}
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5495
|
+
var BRIEF_CHAR_BUDGET = 9e3;
|
|
5496
|
+
function renderBrief(brief, meta) {
|
|
5497
|
+
let bounded = false;
|
|
5498
|
+
const clipTracked = (value, max) => {
|
|
5499
|
+
const text = string(value);
|
|
5500
|
+
if (!text) return void 0;
|
|
5501
|
+
if (text.length <= max) return text;
|
|
5502
|
+
bounded = true;
|
|
5503
|
+
return `${text.slice(0, max - 1)}\u2026`;
|
|
5504
|
+
};
|
|
5505
|
+
const nodeLine = (value, summaryMax) => {
|
|
5506
|
+
const node = object(value);
|
|
5507
|
+
if (!node) return void 0;
|
|
5508
|
+
const label = clipTracked(node.label, 160) || "(unlabeled)";
|
|
5509
|
+
const summary = clipTracked(node.summary, summaryMax);
|
|
5510
|
+
const uid = string(node.uid);
|
|
5511
|
+
return `- ${label}${uid ? ` [${uid}]` : ""}${summary && summary !== label ? ` \u2014 ${summary}` : ""}`;
|
|
5512
|
+
};
|
|
5513
|
+
const lines = [
|
|
5514
|
+
"MindGraph durable work brief (authoritative across sessions/harnesses):"
|
|
5515
|
+
];
|
|
5516
|
+
if (meta.scopeNote) lines.push(meta.scopeNote);
|
|
5517
|
+
const task = object(brief?.task);
|
|
5518
|
+
const scopedOut = number(brief?.scoped_out_tasks);
|
|
5519
|
+
if (!task) {
|
|
5520
|
+
lines.push("No durable task is eligible for this session's scope.");
|
|
5521
|
+
if (scopedOut) {
|
|
5522
|
+
lines.push(
|
|
5523
|
+
`${scopedOut} durable task(s) exist outside this repository scope \u2014 resume one explicitly by task_uid if that is intended.`
|
|
5524
|
+
);
|
|
5525
|
+
}
|
|
5526
|
+
} else {
|
|
5527
|
+
const props = object(task.props);
|
|
5528
|
+
const details = [
|
|
5529
|
+
`status ${string(props?.status) || "unknown"}`,
|
|
5530
|
+
string(props?.priority) ? `priority ${string(props?.priority)}` : void 0,
|
|
5531
|
+
number(task.version) !== void 0 ? `v${number(task.version)}` : void 0
|
|
5532
|
+
].filter(Boolean);
|
|
5533
|
+
lines.push(
|
|
5534
|
+
`Task: ${clipTracked(task.label, 200) || "(unlabeled)"} [${string(task.uid) || "?"}] \u2014 ${details.join(", ")}`
|
|
5535
|
+
);
|
|
5536
|
+
const description = clipTracked(props?.description, 400);
|
|
5537
|
+
if (description) lines.push(` ${description}`);
|
|
5538
|
+
const reason = string(brief?.selection_reason);
|
|
5539
|
+
if (reason) lines.push(`Selection: ${reason}`);
|
|
5540
|
+
const lease = object(brief?.claim) || object(brief?.lease);
|
|
5541
|
+
if (meta.claimed) {
|
|
5542
|
+
const epoch = number(lease?.lease_epoch);
|
|
5543
|
+
lines.push(
|
|
5544
|
+
`Claimed: yes${epoch !== void 0 ? ` (lease epoch ${epoch})` : ""} \u2014 this session holds the lease.`
|
|
5545
|
+
);
|
|
5546
|
+
} else if (meta.claimFailed) {
|
|
5547
|
+
lines.push(
|
|
5548
|
+
"Claimed: no \u2014 the claim attempt failed (another session may hold the lease). Treat as context; claim_task explicitly before mutating it."
|
|
5549
|
+
);
|
|
5550
|
+
} else {
|
|
5551
|
+
lines.push(
|
|
5552
|
+
"Claimed: no \u2014 backlog context, not owned. claim_task deliberately before starting this work."
|
|
5553
|
+
);
|
|
5554
|
+
}
|
|
5555
|
+
const goal = nodeLine(brief?.goal, 200);
|
|
5556
|
+
if (goal) lines.push(`Goal:
|
|
5557
|
+
${goal}`);
|
|
5558
|
+
const steps = Array.isArray(brief?.next_steps) ? brief.next_steps : [];
|
|
5559
|
+
if (steps.length > 5) bounded = true;
|
|
5560
|
+
const stepLines = steps.slice(0, 5).map((step) => nodeLine(step, 160)).filter(Boolean);
|
|
5561
|
+
if (stepLines.length) lines.push(`Next steps:
|
|
5562
|
+
${stepLines.join("\n")}`);
|
|
5563
|
+
const blockers = Array.isArray(brief?.blockers) ? brief.blockers : [];
|
|
5564
|
+
if (blockers.length > 5) bounded = true;
|
|
5565
|
+
const blockerLines = blockers.slice(0, 5).map((blocker) => nodeLine(blocker, 200)).filter(Boolean);
|
|
5566
|
+
if (blockerLines.length) lines.push(`Blockers:
|
|
5567
|
+
${blockerLines.join("\n")}`);
|
|
5568
|
+
const active = object(brief?.active_execution);
|
|
5569
|
+
if (active) {
|
|
5570
|
+
const iteration = number(active.iteration) ?? number(object(active.props)?.iteration);
|
|
5571
|
+
lines.push(
|
|
5572
|
+
`Active execution: [${string(active.uid) || "?"}] iteration ${iteration ?? "?"} still running from a prior session \u2014 checkpoint or abandon it.`
|
|
5573
|
+
);
|
|
5574
|
+
}
|
|
5575
|
+
const poison = object(brief?.poison_task) || string(brief?.warning);
|
|
5576
|
+
if (poison) {
|
|
5577
|
+
lines.push(
|
|
5578
|
+
`Warning: ${typeof poison === "string" ? clipTracked(poison, 300) : "recent executions of this task repeatedly failed \u2014 investigate before retrying."}`
|
|
5579
|
+
);
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
const optional = [];
|
|
5583
|
+
const knowledge = Array.isArray(brief?.knowledge) ? brief.knowledge : [];
|
|
5584
|
+
if (knowledge.length > 5) bounded = true;
|
|
5585
|
+
const knowledgeLines = knowledge.slice(0, 5).map((item) => nodeLine(item, 350)).filter(Boolean);
|
|
5586
|
+
if (knowledgeLines.length) {
|
|
5587
|
+
optional.push(`Relevant knowledge:
|
|
5588
|
+
${knowledgeLines.join("\n")}`);
|
|
5589
|
+
}
|
|
5590
|
+
const targets = Array.isArray(brief?.code_targets) ? brief.code_targets : [];
|
|
5591
|
+
const targetLabels = targets.slice(0, 10).map((target) => clipTracked(object(target)?.label, 120)).filter(Boolean);
|
|
5592
|
+
if (targetLabels.length) {
|
|
5593
|
+
const extra = targets.length - targetLabels.length;
|
|
5594
|
+
if (extra > 0) bounded = true;
|
|
5595
|
+
optional.push(
|
|
5596
|
+
`Code targets: ${targetLabels.join(", ")}${extra > 0 ? ` (+${extra} more)` : ""}`
|
|
5597
|
+
);
|
|
5598
|
+
}
|
|
5599
|
+
const footer = task ? "Use the harness todo list only for local execution details; do not duplicate this durable Task there." : void 0;
|
|
5600
|
+
let used = lines.join("\n").length + (footer ? footer.length + 1 : 0);
|
|
5601
|
+
for (const section of optional) {
|
|
5602
|
+
if (used + section.length + 1 > BRIEF_CHAR_BUDGET) {
|
|
5603
|
+
bounded = true;
|
|
5604
|
+
break;
|
|
5605
|
+
}
|
|
5606
|
+
lines.push(section);
|
|
5607
|
+
used += section.length + 1;
|
|
5608
|
+
}
|
|
5609
|
+
if (bounded) {
|
|
5610
|
+
lines.push(
|
|
5611
|
+
"\u2026[bounded work brief truncated \u2014 fetch full node content by uid if needed]"
|
|
5612
|
+
);
|
|
5613
|
+
}
|
|
5614
|
+
if (footer) lines.push(footer);
|
|
5615
|
+
const rendered = lines.join("\n");
|
|
5616
|
+
return rendered.length <= BRIEF_CHAR_BUDGET ? rendered : `${rendered.slice(0, BRIEF_CHAR_BUDGET - 40)}
|
|
5403
5617
|
\u2026[bounded work brief truncated]`;
|
|
5404
|
-
return [
|
|
5405
|
-
"MindGraph durable work brief (authoritative across sessions/harnesses):",
|
|
5406
|
-
capped,
|
|
5407
|
-
"Use the harness todo list only for local execution details; do not duplicate this durable Task there."
|
|
5408
|
-
].join("\n");
|
|
5409
5618
|
}
|
|
5410
5619
|
async function repositoryScopeUids(input, client2, options, agentId) {
|
|
5411
|
-
if (!client2.entity) return [];
|
|
5620
|
+
if (!client2.entity) return { uids: [], failures: [] };
|
|
5412
5621
|
const adapter = new CodegraphAdapter({
|
|
5413
5622
|
cwd: input.cwd,
|
|
5414
5623
|
workspaceFile: options.workspaceFile
|
|
5415
5624
|
});
|
|
5416
5625
|
const configured = await adapter.resolveRepositories();
|
|
5417
5626
|
const repositories = configured.length > 0 ? configured : [await adapter.resolveRepository()];
|
|
5627
|
+
const failures = [];
|
|
5418
5628
|
const resolved = await Promise.all(
|
|
5419
|
-
repositories.map(
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5629
|
+
repositories.map(async (repository) => {
|
|
5630
|
+
try {
|
|
5631
|
+
const payload = object(
|
|
5632
|
+
await client2.entity({
|
|
5633
|
+
action: "resolve_identity",
|
|
5634
|
+
identity: {
|
|
5635
|
+
namespace: "external.code",
|
|
5636
|
+
key_version: 1,
|
|
5637
|
+
key: repositoryIdentityKey(repository)
|
|
5638
|
+
},
|
|
5639
|
+
agent_id: agentId
|
|
5640
|
+
})
|
|
5641
|
+
);
|
|
5642
|
+
if (payload?.status === "existing" && string(payload.uid)) {
|
|
5643
|
+
return string(payload.uid);
|
|
5644
|
+
}
|
|
5645
|
+
if (payload?.status === "absent") {
|
|
5646
|
+
const anchored = await anchorRepository(
|
|
5647
|
+
client2,
|
|
5648
|
+
repository,
|
|
5649
|
+
repository.spaceUid ?? `space:agent:${agentId}`,
|
|
5650
|
+
agentId
|
|
5651
|
+
);
|
|
5652
|
+
if (anchored?.uid) return anchored.uid;
|
|
5653
|
+
failures.push(
|
|
5654
|
+
`${repository.repoId}: anchor ${anchored?.status ?? "failed"}`
|
|
5655
|
+
);
|
|
5656
|
+
return void 0;
|
|
5657
|
+
}
|
|
5658
|
+
failures.push(
|
|
5659
|
+
`${repository.repoId}: ${string(payload?.status) || "unresolved"}`
|
|
5660
|
+
);
|
|
5661
|
+
return void 0;
|
|
5662
|
+
} catch (error) {
|
|
5663
|
+
failures.push(
|
|
5664
|
+
`${repository.repoId}: ${error?.message || "resolve failed"}`
|
|
5665
|
+
);
|
|
5666
|
+
return void 0;
|
|
5667
|
+
}
|
|
5668
|
+
})
|
|
5430
5669
|
);
|
|
5431
|
-
return
|
|
5432
|
-
...new Set(
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
return payload?.status === "existing" && string(payload.uid) ? [string(payload.uid)] : [];
|
|
5436
|
-
})
|
|
5437
|
-
)
|
|
5438
|
-
];
|
|
5670
|
+
return {
|
|
5671
|
+
uids: [...new Set(resolved.filter((uid) => Boolean(uid)))],
|
|
5672
|
+
failures
|
|
5673
|
+
};
|
|
5439
5674
|
}
|
|
5440
|
-
function isOwnPriorWork(brief, agentId) {
|
|
5675
|
+
function isOwnPriorWork(brief, agentId, harness) {
|
|
5441
5676
|
const reason = string(brief?.selection_reason);
|
|
5442
5677
|
if (reason === "owned_live_lease" || reason === "same_agent_session_rebind") {
|
|
5443
5678
|
return true;
|
|
5444
5679
|
}
|
|
5445
|
-
|
|
5680
|
+
const owner = string(object(brief?.lease)?.lease_owner_agent_id);
|
|
5681
|
+
return owner === agentId || owner === harness;
|
|
5446
5682
|
}
|
|
5447
5683
|
async function sessionStart(input, client2, options) {
|
|
5448
|
-
const agentId = options
|
|
5684
|
+
const agentId = resolveAgentId(options);
|
|
5449
5685
|
const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
|
|
5686
|
+
const nowSeconds = (options.now || (() => Date.now() / 1e3))();
|
|
5450
5687
|
const opened = object(
|
|
5451
5688
|
await client2.session({
|
|
5452
5689
|
action: "open",
|
|
@@ -5458,7 +5695,21 @@ async function sessionStart(input, client2, options) {
|
|
|
5458
5695
|
})
|
|
5459
5696
|
);
|
|
5460
5697
|
const sessionUid = string(opened?.uid);
|
|
5461
|
-
const
|
|
5698
|
+
const baselineWorktree = worktreeState(input.cwd);
|
|
5699
|
+
if (sessionUid) {
|
|
5700
|
+
withLedger(input.session_id, options, (ledger) => {
|
|
5701
|
+
ledger.sessionUid = sessionUid;
|
|
5702
|
+
ledger.baselineWorktree = baselineWorktree;
|
|
5703
|
+
return void 0;
|
|
5704
|
+
});
|
|
5705
|
+
}
|
|
5706
|
+
const scope = await repositoryScopeUids(input, client2, options, agentId);
|
|
5707
|
+
if (scope.failures.length > 0) {
|
|
5708
|
+
console.error(
|
|
5709
|
+
`mindgraph-hook: ${scope.failures.length} repository scope(s) failed to resolve (${scope.failures.join("; ")}); resume runs ${scope.uids.length > 0 ? "narrowed" : "UNSCOPED"}`
|
|
5710
|
+
);
|
|
5711
|
+
}
|
|
5712
|
+
const scopeUids = scope.uids;
|
|
5462
5713
|
let brief = object(
|
|
5463
5714
|
await client2.plan({
|
|
5464
5715
|
action: "resume_work",
|
|
@@ -5469,9 +5720,19 @@ async function sessionStart(input, client2, options) {
|
|
|
5469
5720
|
);
|
|
5470
5721
|
const task = object(brief?.task);
|
|
5471
5722
|
const lease = object(brief?.lease);
|
|
5472
|
-
|
|
5723
|
+
const prior = readLedger(input.session_id, options);
|
|
5724
|
+
const leaseExpiry = number(lease?.lease_expires_at);
|
|
5725
|
+
const leaseLive = leaseExpiry !== void 0 && leaseExpiry > nowSeconds;
|
|
5726
|
+
const heldByThisSession = leaseLive && prior.taskUid !== void 0 && prior.taskUid === string(task?.uid) && prior.leaseEpoch === number(lease?.lease_epoch);
|
|
5727
|
+
let claimed;
|
|
5728
|
+
let claimFailed = false;
|
|
5729
|
+
if (sessionUid && string(task?.uid) && number(task?.version) && isOwnPriorWork(brief, agentId, options.harness) && // A LIVE lease belongs to whichever session claimed it — claiming from
|
|
5730
|
+
// here would fence that session out mid-work (the released server
|
|
5731
|
+
// rebinds same-owner live leases without allow_takeover). Re-claim live
|
|
5732
|
+
// leases only when this very session already holds them (re-entry).
|
|
5733
|
+
(!leaseLive || heldByThisSession)) {
|
|
5473
5734
|
try {
|
|
5474
|
-
|
|
5735
|
+
claimed = object(
|
|
5475
5736
|
await client2.plan({
|
|
5476
5737
|
action: "claim_task",
|
|
5477
5738
|
task_uid: task.uid,
|
|
@@ -5481,35 +5742,63 @@ async function sessionStart(input, client2, options) {
|
|
|
5481
5742
|
agent_id: agentId
|
|
5482
5743
|
})
|
|
5483
5744
|
);
|
|
5484
|
-
brief = object(
|
|
5485
|
-
await client2.plan({
|
|
5486
|
-
action: "resume_work",
|
|
5487
|
-
task_uid: task.uid,
|
|
5488
|
-
session_uid: sessionUid,
|
|
5489
|
-
...scopeUids.length > 0 ? { scope_uids: scopeUids } : {},
|
|
5490
|
-
agent_id: agentId
|
|
5491
|
-
})
|
|
5492
|
-
);
|
|
5493
|
-
if (brief && claimed) brief.claim = claimed;
|
|
5494
5745
|
} catch {
|
|
5746
|
+
claimFailed = true;
|
|
5747
|
+
}
|
|
5748
|
+
if (claimed) {
|
|
5749
|
+
try {
|
|
5750
|
+
const reResumed = object(
|
|
5751
|
+
await client2.plan({
|
|
5752
|
+
action: "resume_work",
|
|
5753
|
+
task_uid: task.uid,
|
|
5754
|
+
session_uid: sessionUid,
|
|
5755
|
+
...scopeUids.length > 0 ? { scope_uids: scopeUids } : {},
|
|
5756
|
+
agent_id: agentId
|
|
5757
|
+
})
|
|
5758
|
+
);
|
|
5759
|
+
if (reResumed) brief = reResumed;
|
|
5760
|
+
} catch {
|
|
5761
|
+
}
|
|
5762
|
+
if (brief) brief.claim = claimed;
|
|
5495
5763
|
}
|
|
5496
5764
|
}
|
|
5497
|
-
const
|
|
5498
|
-
const
|
|
5765
|
+
const stillHeld = !claimed && claimFailed && heldByThisSession;
|
|
5766
|
+
const scopeNote = scope.failures.length > 0 ? `Scope: ${scopeUids.length} repositories resolved, ${scope.failures.length} FAILED \u2014 durable-work selection ran ${scopeUids.length > 0 ? "narrowed" : "unscoped"}; tasks in the failed repositories may be missing.` : void 0;
|
|
5767
|
+
const briefText = renderBrief(brief || { status: "no_eligible_work" }, {
|
|
5768
|
+
claimed: Boolean(claimed) || stillHeld,
|
|
5769
|
+
claimFailed: claimFailed && !stillHeld,
|
|
5770
|
+
scopeNote
|
|
5771
|
+
});
|
|
5772
|
+
const briefHash = import_node_crypto3.default.createHash("sha256").update(briefText).digest("hex");
|
|
5499
5773
|
const shouldInject = withLedger(input.session_id, options, (ledger) => {
|
|
5500
5774
|
const changed = ledger.lastBriefHash !== briefHash;
|
|
5501
5775
|
const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
|
|
5502
5776
|
ledger.sessionUid = sessionUid;
|
|
5503
|
-
ledger.baselineWorktree =
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5777
|
+
ledger.baselineWorktree = baselineWorktree;
|
|
5778
|
+
if (claimed) {
|
|
5779
|
+
const claimLease = object(brief?.claim) || object(brief?.lease);
|
|
5780
|
+
const selectedTask = object(brief?.task);
|
|
5781
|
+
ledger.taskUid = string(selectedTask?.uid);
|
|
5782
|
+
ledger.taskVersion = number(claimed.task_version) ?? number(selectedTask?.version) ?? void 0;
|
|
5783
|
+
ledger.leaseEpoch = number(claimLease?.lease_epoch);
|
|
5784
|
+
ledger.leaseExpiresAt = number(claimLease?.lease_expires_at);
|
|
5785
|
+
ledger.lastLeaseRenewalAt = nowSeconds;
|
|
5786
|
+
const running = [
|
|
5787
|
+
object(brief?.active_execution),
|
|
5788
|
+
...Array.isArray(brief?.recent_executions) ? brief.recent_executions.map(object) : []
|
|
5789
|
+
].find(
|
|
5790
|
+
(execution) => execution && (string(object(execution.props)?.status) ?? string(execution.status)) === "running"
|
|
5791
|
+
);
|
|
5792
|
+
ledger.executionUid = running ? string(running.uid) : void 0;
|
|
5793
|
+
} else if (claimFailed && ledger.taskUid !== void 0 && ledger.taskUid === string(object(brief?.task)?.uid) && ledger.lastLeaseRenewalAt !== void 0) {
|
|
5794
|
+
} else {
|
|
5795
|
+
ledger.taskUid = void 0;
|
|
5796
|
+
ledger.taskVersion = void 0;
|
|
5797
|
+
ledger.leaseEpoch = void 0;
|
|
5798
|
+
ledger.leaseExpiresAt = void 0;
|
|
5799
|
+
ledger.executionUid = void 0;
|
|
5800
|
+
ledger.lastLeaseRenewalAt = void 0;
|
|
5801
|
+
}
|
|
5513
5802
|
ledger.lastBriefHash = briefHash;
|
|
5514
5803
|
return changed || reinjectCompact;
|
|
5515
5804
|
});
|
|
@@ -5518,82 +5807,173 @@ async function sessionStart(input, client2, options) {
|
|
|
5518
5807
|
async function preTool(input, options) {
|
|
5519
5808
|
const original = { ...input.tool_input || {} };
|
|
5520
5809
|
const ledger = readLedger(input.session_id, options);
|
|
5810
|
+
let repoId = ledger.repoContextCwd === input.cwd && ledger.repoContextAt !== void 0 && Date.now() - ledger.repoContextAt < 3e5 ? ledger.repoContextRepoId : void 0;
|
|
5811
|
+
if (!repoId) {
|
|
5812
|
+
repoId = (await new CodegraphAdapter({
|
|
5813
|
+
cwd: input.cwd,
|
|
5814
|
+
workspaceFile: options.workspaceFile
|
|
5815
|
+
}).resolveRepository()).repoId;
|
|
5816
|
+
withLedger(input.session_id, options, (fresh) => {
|
|
5817
|
+
fresh.repoContextCwd = input.cwd;
|
|
5818
|
+
fresh.repoContextRepoId = repoId;
|
|
5819
|
+
fresh.repoContextAt = Date.now();
|
|
5820
|
+
return void 0;
|
|
5821
|
+
});
|
|
5822
|
+
}
|
|
5521
5823
|
const updatedInput = {
|
|
5522
5824
|
...original,
|
|
5523
|
-
invocation_context:
|
|
5825
|
+
invocation_context: invocationContext(input, options, repoId)
|
|
5524
5826
|
};
|
|
5525
5827
|
if (ledger.sessionUid) updatedInput.session_uid = ledger.sessionUid;
|
|
5526
5828
|
if (ledger.taskUid && updatedInput.task_uid === void 0) {
|
|
5527
5829
|
updatedInput.task_uid = ledger.taskUid;
|
|
5528
5830
|
}
|
|
5529
|
-
|
|
5831
|
+
const modelTask = string(original.task_uid);
|
|
5832
|
+
const targetsLedgerTask = ledger.taskUid !== void 0 && (modelTask === void 0 || modelTask === ledger.taskUid);
|
|
5833
|
+
const isCapture = Boolean(input.tool_name?.includes("mindgraph_capture"));
|
|
5834
|
+
const fencedAction = Boolean(input.tool_name?.includes("mindgraph_plan")) && FENCED_PLAN_ACTIONS.has(string(original.action) || "");
|
|
5835
|
+
if (ledger.taskUid && isCapture && updatedInput.work_uid === void 0) {
|
|
5530
5836
|
updatedInput.work_uid = ledger.taskUid;
|
|
5531
5837
|
}
|
|
5532
|
-
if (ledger.executionUid && updatedInput.execution_uid === void 0) {
|
|
5838
|
+
if (targetsLedgerTask && (fencedAction || isCapture) && ledger.executionUid && updatedInput.execution_uid === void 0) {
|
|
5533
5839
|
updatedInput.execution_uid = ledger.executionUid;
|
|
5534
5840
|
}
|
|
5535
|
-
if (ledger.leaseEpoch !== void 0 && updatedInput.lease_epoch === void 0) {
|
|
5841
|
+
if (targetsLedgerTask && fencedAction && ledger.leaseEpoch !== void 0 && updatedInput.lease_epoch === void 0) {
|
|
5536
5842
|
updatedInput.lease_epoch = ledger.leaseEpoch;
|
|
5537
5843
|
}
|
|
5538
|
-
if (ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
|
|
5844
|
+
if (targetsLedgerTask && fencedAction && ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
|
|
5539
5845
|
updatedInput.expected_version = ledger.taskVersion;
|
|
5540
5846
|
}
|
|
5541
5847
|
return { kind: "rewrite", updatedInput };
|
|
5542
5848
|
}
|
|
5849
|
+
var FENCED_PLAN_ACTIONS = /* @__PURE__ */ new Set([
|
|
5850
|
+
"claim_task",
|
|
5851
|
+
"heartbeat",
|
|
5852
|
+
"start_iteration",
|
|
5853
|
+
"checkpoint_iteration",
|
|
5854
|
+
"block_task",
|
|
5855
|
+
"complete_task",
|
|
5856
|
+
"abandon_iteration"
|
|
5857
|
+
]);
|
|
5543
5858
|
async function postTool(input, client2, options) {
|
|
5544
5859
|
const name = input.tool_name || "";
|
|
5545
5860
|
const args = input.tool_input || {};
|
|
5546
5861
|
const action = string(args.action);
|
|
5547
5862
|
const payload = responsePayload(input.tool_response);
|
|
5548
5863
|
const nowSeconds = (options.now || (() => Date.now() / 1e3))();
|
|
5549
|
-
let
|
|
5864
|
+
let renewal;
|
|
5550
5865
|
withLedger(input.session_id, options, (ledger) => {
|
|
5551
5866
|
ledger.materialToolCalls += 1;
|
|
5552
|
-
if (["Edit", "Write", "NotebookEdit", "apply_patch"].includes(name)
|
|
5867
|
+
if (["Edit", "Write", "NotebookEdit", "apply_patch"].includes(name)) {
|
|
5553
5868
|
ledger.mutatingOperations += 1;
|
|
5554
5869
|
}
|
|
5555
|
-
if (
|
|
5556
|
-
|
|
5870
|
+
if (!payload?.error) {
|
|
5871
|
+
if (name.includes("mindgraph_capture") && ["lesson", "journal"].includes(action || "") || name.includes("mindgraph_commit") && action === "resolve_decision" || name.includes("mindgraph_plan") && action === "assess_risk") {
|
|
5872
|
+
ledger.memoryWritten = true;
|
|
5873
|
+
}
|
|
5557
5874
|
}
|
|
5558
5875
|
if (name.includes("mindgraph_plan")) {
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5876
|
+
const targetTask = string(args.task_uid) || ledger.taskUid;
|
|
5877
|
+
const aboutLedgerTask = targetTask !== void 0 && targetTask === ledger.taskUid;
|
|
5878
|
+
if (!payload?.error) {
|
|
5879
|
+
if (action === "claim_task" && targetTask) {
|
|
5880
|
+
const rebindToOther = targetTask !== ledger.taskUid;
|
|
5881
|
+
ledger.taskUid = targetTask;
|
|
5882
|
+
ledger.taskVersion = number(payload?.task_version) ?? number(payload?.version) ?? (rebindToOther ? void 0 : ledger.taskVersion);
|
|
5883
|
+
ledger.leaseEpoch = number(payload?.lease_epoch) ?? (rebindToOther ? void 0 : ledger.leaseEpoch);
|
|
5884
|
+
ledger.leaseExpiresAt = number(payload?.lease_expires_at) ?? (rebindToOther ? void 0 : ledger.leaseExpiresAt);
|
|
5885
|
+
if (rebindToOther) ledger.executionUid = void 0;
|
|
5886
|
+
ledger.lastLeaseRenewalAt = nowSeconds;
|
|
5887
|
+
}
|
|
5888
|
+
if (action === "start_iteration") {
|
|
5889
|
+
ledger.executionUid = string(payload?.execution_uid) || ledger.executionUid;
|
|
5890
|
+
}
|
|
5891
|
+
if (["checkpoint_iteration", "block_task", "complete_task", "abandon_iteration"].includes(
|
|
5892
|
+
action || ""
|
|
5893
|
+
)) {
|
|
5894
|
+
ledger.iterationCheckpointed = true;
|
|
5895
|
+
if (action !== "block_task") ledger.executionUid = void 0;
|
|
5896
|
+
}
|
|
5897
|
+
if (action === "complete_task" && aboutLedgerTask) {
|
|
5898
|
+
ledger.taskUid = void 0;
|
|
5899
|
+
ledger.taskVersion = void 0;
|
|
5900
|
+
ledger.leaseEpoch = void 0;
|
|
5901
|
+
ledger.leaseExpiresAt = void 0;
|
|
5902
|
+
ledger.lastLeaseRenewalAt = void 0;
|
|
5903
|
+
} else if (aboutLedgerTask && action !== "claim_task") {
|
|
5904
|
+
ledger.taskVersion = number(payload?.task_version) ?? (string(payload?.uid) === ledger.taskUid ? number(payload?.version) : void 0) ?? ledger.taskVersion;
|
|
5905
|
+
ledger.leaseEpoch = number(payload?.lease_epoch) ?? ledger.leaseEpoch;
|
|
5906
|
+
if (number(payload?.lease_expires_at) !== void 0) {
|
|
5907
|
+
ledger.leaseExpiresAt = number(payload?.lease_expires_at);
|
|
5908
|
+
ledger.lastLeaseRenewalAt = nowSeconds;
|
|
5909
|
+
}
|
|
5910
|
+
}
|
|
5911
|
+
} else if (aboutLedgerTask) {
|
|
5912
|
+
ledger.taskVersion = number(payload?.current_version) ?? ledger.taskVersion;
|
|
5913
|
+
ledger.leaseEpoch = number(payload?.current_epoch) ?? ledger.leaseEpoch;
|
|
5914
|
+
ledger.leaseExpiresAt = number(payload?.lease_expires_at) ?? ledger.leaseExpiresAt;
|
|
5915
|
+
}
|
|
5571
5916
|
}
|
|
5572
|
-
if (ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0 && ledger.leaseExpiresAt !== void 0 &&
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5917
|
+
if (ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0 && ledger.leaseExpiresAt !== void 0 && // Only ledgers whose lease state was written by a claim THIS code made
|
|
5918
|
+
// may renew: 0.14.9 ledgers carry adopted-but-never-claimed fence
|
|
5919
|
+
// state, and a lapse-reclaim from one would auto-seize a task this
|
|
5920
|
+
// session never owned — the incident class all of this exists to kill.
|
|
5921
|
+
ledger.lastLeaseRenewalAt !== void 0) {
|
|
5922
|
+
const expiresIn = ledger.leaseExpiresAt - nowSeconds;
|
|
5923
|
+
const sinceRenewal = nowSeconds - ledger.lastLeaseRenewalAt;
|
|
5924
|
+
if (expiresIn <= 0) {
|
|
5925
|
+
renewal = {
|
|
5926
|
+
kind: "reclaim",
|
|
5927
|
+
taskUid: ledger.taskUid,
|
|
5928
|
+
sessionUid: ledger.sessionUid,
|
|
5929
|
+
taskVersion: ledger.taskVersion,
|
|
5930
|
+
leaseEpoch: ledger.leaseEpoch
|
|
5931
|
+
};
|
|
5932
|
+
} else if (expiresIn <= 60 || sinceRenewal >= 180) {
|
|
5933
|
+
renewal = {
|
|
5934
|
+
kind: "heartbeat",
|
|
5935
|
+
taskUid: ledger.taskUid,
|
|
5936
|
+
sessionUid: ledger.sessionUid,
|
|
5937
|
+
taskVersion: ledger.taskVersion,
|
|
5938
|
+
leaseEpoch: ledger.leaseEpoch
|
|
5939
|
+
};
|
|
5940
|
+
}
|
|
5579
5941
|
}
|
|
5580
5942
|
return void 0;
|
|
5581
5943
|
});
|
|
5582
|
-
if (
|
|
5944
|
+
if (renewal) {
|
|
5945
|
+
const active = renewal;
|
|
5583
5946
|
try {
|
|
5584
5947
|
const response = object(
|
|
5585
|
-
|
|
5948
|
+
active.kind === "reclaim" ? (
|
|
5949
|
+
// The lease already lapsed: a heartbeat can only 409. Re-claim
|
|
5950
|
+
// rebinds the same owner's expired lease (bumping the epoch) so
|
|
5951
|
+
// durable tracking survives a >TTL quiet stretch mid-session.
|
|
5952
|
+
await client2.plan({
|
|
5953
|
+
action: "claim_task",
|
|
5954
|
+
task_uid: active.taskUid,
|
|
5955
|
+
session_uid: active.sessionUid,
|
|
5956
|
+
expected_version: active.taskVersion,
|
|
5957
|
+
idempotency_key: `hook-reclaim:${input.session_id}:${active.taskUid}:${active.leaseEpoch}:${Math.floor(nowSeconds / 60)}`,
|
|
5958
|
+
agent_id: resolveAgentId(options)
|
|
5959
|
+
})
|
|
5960
|
+
) : await client2.plan({
|
|
5586
5961
|
action: "heartbeat",
|
|
5587
|
-
task_uid:
|
|
5588
|
-
session_uid:
|
|
5589
|
-
expected_version:
|
|
5590
|
-
lease_epoch:
|
|
5591
|
-
idempotency_key: `hook-heartbeat:${input.session_id}:${
|
|
5592
|
-
agent_id: options
|
|
5962
|
+
task_uid: active.taskUid,
|
|
5963
|
+
session_uid: active.sessionUid,
|
|
5964
|
+
expected_version: active.taskVersion,
|
|
5965
|
+
lease_epoch: active.leaseEpoch,
|
|
5966
|
+
idempotency_key: `hook-heartbeat:${input.session_id}:${active.taskUid}:${active.leaseEpoch}:${active.taskVersion}:${Math.floor(nowSeconds / 60)}`,
|
|
5967
|
+
agent_id: resolveAgentId(options)
|
|
5593
5968
|
})
|
|
5594
5969
|
);
|
|
5595
5970
|
withLedger(input.session_id, options, (ledger) => {
|
|
5596
|
-
|
|
5971
|
+
if (payloadIsError(response)) return void 0;
|
|
5972
|
+
if (ledger.taskUid !== active.taskUid) return void 0;
|
|
5973
|
+
ledger.taskVersion = number(response?.task_version) ?? ledger.taskVersion;
|
|
5974
|
+
ledger.leaseEpoch = number(response?.lease_epoch) ?? ledger.leaseEpoch;
|
|
5975
|
+
ledger.leaseExpiresAt = number(response?.lease_expires_at) ?? ledger.leaseExpiresAt;
|
|
5976
|
+
ledger.lastLeaseRenewalAt = nowSeconds;
|
|
5597
5977
|
return void 0;
|
|
5598
5978
|
});
|
|
5599
5979
|
} catch {
|
|
@@ -5601,6 +5981,9 @@ async function postTool(input, client2, options) {
|
|
|
5601
5981
|
}
|
|
5602
5982
|
return NOOP;
|
|
5603
5983
|
}
|
|
5984
|
+
function payloadIsError(payload) {
|
|
5985
|
+
return Boolean(payload && payload.error);
|
|
5986
|
+
}
|
|
5604
5987
|
function stop(input, options) {
|
|
5605
5988
|
if (input.stop_hook_active) return NOOP;
|
|
5606
5989
|
return withLedger(input.session_id, options, (ledger) => {
|
|
@@ -5631,17 +6014,20 @@ async function sessionEnd(input, client2, options) {
|
|
|
5631
6014
|
release_lease: true,
|
|
5632
6015
|
summary: `${harnessLabel} SessionEnd: ${input.reason || "interrupted"}`,
|
|
5633
6016
|
idempotency_key: `session-end:${input.session_id}:${ledger.executionUid}`,
|
|
5634
|
-
agent_id: options
|
|
6017
|
+
agent_id: resolveAgentId(options)
|
|
5635
6018
|
});
|
|
5636
6019
|
} catch {
|
|
5637
6020
|
}
|
|
5638
6021
|
}
|
|
5639
|
-
|
|
6022
|
+
const abandonRan = Boolean(
|
|
6023
|
+
ledger.executionUid && ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0
|
|
6024
|
+
);
|
|
6025
|
+
if (ledger.sessionUid && (options.harness !== "codex" || !abandonRan)) {
|
|
5640
6026
|
try {
|
|
5641
6027
|
await client2.session({
|
|
5642
6028
|
action: "close",
|
|
5643
6029
|
session_uid: ledger.sessionUid,
|
|
5644
|
-
agent_id: options
|
|
6030
|
+
agent_id: resolveAgentId(options)
|
|
5645
6031
|
});
|
|
5646
6032
|
} catch {
|
|
5647
6033
|
}
|
|
@@ -5751,7 +6137,7 @@ async function runCodexHook(input, client2, options = {}) {
|
|
|
5751
6137
|
var import_node_fs4 = __toESM(require("fs"));
|
|
5752
6138
|
var import_node_os4 = __toESM(require("os"));
|
|
5753
6139
|
var import_node_path4 = __toESM(require("path"));
|
|
5754
|
-
var
|
|
6140
|
+
var import_node_crypto4 = __toESM(require("crypto"));
|
|
5755
6141
|
var OWNER_MARKER = "--owner mindgraph";
|
|
5756
6142
|
var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
|
|
5757
6143
|
function command(harness) {
|
|
@@ -5795,11 +6181,14 @@ function desiredEntries(harness) {
|
|
|
5795
6181
|
// inherited 20s edge in both harnesses: the hooks correctly failed
|
|
5796
6182
|
// open, but no brief reached the model. The same installed runner
|
|
5797
6183
|
// completed in 16.84s once warm. Keep a 30s cold-tenant margin.
|
|
6184
|
+
// The declared Codex context limit must not undercut the brief's own
|
|
6185
|
+
// 9,000-char budget: at 3,000 the harness applied a SECOND, tighter
|
|
6186
|
+
// truncation the renderer knew nothing about.
|
|
5798
6187
|
hooks: [
|
|
5799
6188
|
ownedHook(
|
|
5800
6189
|
harness,
|
|
5801
6190
|
30,
|
|
5802
|
-
harness === "codex" ?
|
|
6191
|
+
harness === "codex" ? 1e4 : void 0
|
|
5803
6192
|
)
|
|
5804
6193
|
]
|
|
5805
6194
|
}
|
|
@@ -5807,13 +6196,23 @@ function desiredEntries(harness) {
|
|
|
5807
6196
|
PreToolUse: [
|
|
5808
6197
|
{
|
|
5809
6198
|
matcher: "mcp__mindgraph__.*",
|
|
5810
|
-
|
|
6199
|
+
// Repository resolution over a large multi-repo workspace plus three
|
|
6200
|
+
// git probes can exceed 3s on the first (uncached) call; a killed
|
|
6201
|
+
// PreToolUse means the call goes through untagged.
|
|
6202
|
+
hooks: [ownedHook(harness, 5)]
|
|
5811
6203
|
}
|
|
5812
6204
|
],
|
|
5813
|
-
PostToolUse
|
|
6205
|
+
// PostToolUse awaits the lease renewal round-trip, which fires exactly
|
|
6206
|
+
// when the tenant is cold (post-idle reclaim) — 5s killed it mid-claim.
|
|
6207
|
+
PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 10)] }],
|
|
5814
6208
|
Stop: [{ hooks: [ownedHook(harness, 5)] }],
|
|
6209
|
+
// SessionEnd makes up to two sequential cloud calls (abandon_iteration +
|
|
6210
|
+
// session close) — the SAME cold-tenant latency SessionStart needs 30s
|
|
6211
|
+
// for. Claude honors the configured budget; Codex clamps SessionEnd to a
|
|
6212
|
+
// 3s platform cap regardless of this value (see codex-hooks.ts wire
|
|
6213
|
+
// map), so the codex adapter instead makes at most ONE call there.
|
|
5815
6214
|
SessionEnd: [
|
|
5816
|
-
{ hooks: [ownedHook(harness, harness === "codex" ? 3 :
|
|
6215
|
+
{ hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
|
|
5817
6216
|
]
|
|
5818
6217
|
};
|
|
5819
6218
|
return harness === "claude-code" ? {
|
|
@@ -5834,7 +6233,7 @@ function entryHasOwnedHook(value) {
|
|
|
5834
6233
|
}
|
|
5835
6234
|
function writeSettings(file, settings) {
|
|
5836
6235
|
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
|
|
5837
|
-
const temporary = `${file}.${process.pid}.${
|
|
6236
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
5838
6237
|
import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
|
|
5839
6238
|
`, {
|
|
5840
6239
|
mode: 384
|
|
@@ -5912,6 +6311,52 @@ function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
|
5912
6311
|
}
|
|
5913
6312
|
|
|
5914
6313
|
// src/cli.ts
|
|
6314
|
+
function recordHookHealth(harness, error) {
|
|
6315
|
+
try {
|
|
6316
|
+
const dir = process.env.MINDGRAPH_RUNTIME_DIR || path5.join(os4.homedir(), ".mindgraph", "runtime");
|
|
6317
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
6318
|
+
const file = path5.join(dir, "hook-health.json");
|
|
6319
|
+
let failures = 0;
|
|
6320
|
+
try {
|
|
6321
|
+
failures = Number(JSON.parse(fs5.readFileSync(file, "utf8")).consecutiveFailures) || 0;
|
|
6322
|
+
} catch {
|
|
6323
|
+
}
|
|
6324
|
+
fs5.writeFileSync(
|
|
6325
|
+
file,
|
|
6326
|
+
`${JSON.stringify({
|
|
6327
|
+
harness,
|
|
6328
|
+
lastError: error.slice(0, 500),
|
|
6329
|
+
lastFailureAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6330
|
+
consecutiveFailures: failures + 1
|
|
6331
|
+
})}
|
|
6332
|
+
`,
|
|
6333
|
+
{ mode: 384 }
|
|
6334
|
+
);
|
|
6335
|
+
} catch {
|
|
6336
|
+
}
|
|
6337
|
+
}
|
|
6338
|
+
function clearHookHealth(harness) {
|
|
6339
|
+
try {
|
|
6340
|
+
const file = path5.join(
|
|
6341
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path5.join(os4.homedir(), ".mindgraph", "runtime"),
|
|
6342
|
+
"hook-health.json"
|
|
6343
|
+
);
|
|
6344
|
+
if (!fs5.existsSync(file)) return;
|
|
6345
|
+
const current = JSON.parse(fs5.readFileSync(file, "utf8"));
|
|
6346
|
+
if (!Number(current.consecutiveFailures)) return;
|
|
6347
|
+
fs5.writeFileSync(
|
|
6348
|
+
file,
|
|
6349
|
+
`${JSON.stringify({
|
|
6350
|
+
harness,
|
|
6351
|
+
lastOkAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6352
|
+
consecutiveFailures: 0
|
|
6353
|
+
})}
|
|
6354
|
+
`,
|
|
6355
|
+
{ mode: 384 }
|
|
6356
|
+
);
|
|
6357
|
+
} catch {
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
5915
6360
|
function getClaudeDesktopConfigPath() {
|
|
5916
6361
|
switch (process.platform) {
|
|
5917
6362
|
case "darwin":
|
|
@@ -5987,7 +6432,10 @@ function installClaudeCode(apiKey, baseUrl, agentId) {
|
|
|
5987
6432
|
"--env",
|
|
5988
6433
|
"MINDGRAPH_HARNESS=claude-code",
|
|
5989
6434
|
"--env",
|
|
5990
|
-
|
|
6435
|
+
// The registration, the hooks, and the serve path must all resolve the
|
|
6436
|
+
// SAME identity — a differing default here made the model's fenced tool
|
|
6437
|
+
// calls and the hook's lease two different agents on the server.
|
|
6438
|
+
`MINDGRAPH_AGENT_ID=${agentId || process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId || stableAgentId()}`
|
|
5991
6439
|
];
|
|
5992
6440
|
if (baseUrl) {
|
|
5993
6441
|
envArgs.push("--env", `MINDGRAPH_BASE_URL=${baseUrl}`);
|
|
@@ -6077,6 +6525,23 @@ function printStatus() {
|
|
|
6077
6525
|
console.log(
|
|
6078
6526
|
`MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
|
|
6079
6527
|
);
|
|
6528
|
+
try {
|
|
6529
|
+
const healthFile = path5.join(
|
|
6530
|
+
process.env.MINDGRAPH_RUNTIME_DIR || path5.join(os4.homedir(), ".mindgraph", "runtime"),
|
|
6531
|
+
"hook-health.json"
|
|
6532
|
+
);
|
|
6533
|
+
if (fs5.existsSync(healthFile)) {
|
|
6534
|
+
const health = JSON.parse(fs5.readFileSync(healthFile, "utf8"));
|
|
6535
|
+
const failures = Number(health.consecutiveFailures) || 0;
|
|
6536
|
+
console.log(
|
|
6537
|
+
failures > 0 ? `Hooks: FAILING (${failures} consecutive; last: ${health.lastError} at ${health.lastFailureAt})` : `Hooks: healthy (last recovery ${health.lastOkAt || "unknown"})`
|
|
6538
|
+
);
|
|
6539
|
+
} else {
|
|
6540
|
+
console.log("Hooks: no health marker (no hook failures recorded)");
|
|
6541
|
+
}
|
|
6542
|
+
} catch {
|
|
6543
|
+
console.log("Hooks: health marker unreadable");
|
|
6544
|
+
}
|
|
6080
6545
|
}
|
|
6081
6546
|
function withHooksFlag() {
|
|
6082
6547
|
return process.argv.includes("--hooks");
|
|
@@ -6089,7 +6554,16 @@ function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
|
|
|
6089
6554
|
console.log(
|
|
6090
6555
|
`Installed ${result.added} and refreshed ${result.updated} MindGraph Claude Code hook entries in ${result.path}`
|
|
6091
6556
|
);
|
|
6092
|
-
const envPath = saveHookEnv({
|
|
6557
|
+
const envPath = saveHookEnv({
|
|
6558
|
+
apiKey,
|
|
6559
|
+
baseUrl,
|
|
6560
|
+
// Default the stable per-user identity only when nothing is stored —
|
|
6561
|
+
// an explicit or previously persisted agentId must survive reinstalls.
|
|
6562
|
+
agentId: agentId || process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId || stableAgentId(),
|
|
6563
|
+
// The org pin follows the install environment exactly: installing
|
|
6564
|
+
// without MINDGRAPH_ORG_ID clears any stale pin (null = clear).
|
|
6565
|
+
orgId: process.env.MINDGRAPH_ORG_ID ?? null
|
|
6566
|
+
});
|
|
6093
6567
|
console.log(`Saved hook connection settings to ${envPath} (mode 600)`);
|
|
6094
6568
|
} else {
|
|
6095
6569
|
console.log(
|
|
@@ -6148,7 +6622,7 @@ var DASHBOARD_URL = process.env.MINDGRAPH_DASHBOARD_URL || "https://mindgraph.cl
|
|
|
6148
6622
|
var POLL_INTERVAL = 2e3;
|
|
6149
6623
|
var POLL_TIMEOUT = 10 * 60 * 1e3;
|
|
6150
6624
|
function generateCode() {
|
|
6151
|
-
return
|
|
6625
|
+
return crypto5.randomBytes(32).toString("hex");
|
|
6152
6626
|
}
|
|
6153
6627
|
function openBrowser(url) {
|
|
6154
6628
|
try {
|
|
@@ -6311,10 +6785,19 @@ async function main2() {
|
|
|
6311
6785
|
`Installed ${result.added} and refreshed ${result.updated} MindGraph ${hookHarness === "codex" ? "Codex" : "Claude Code"} hook entries in ${result.path}`
|
|
6312
6786
|
);
|
|
6313
6787
|
printCodegraphStatus();
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6788
|
+
const envPath = saveHookEnv({
|
|
6789
|
+
apiKey,
|
|
6790
|
+
baseUrl,
|
|
6791
|
+
// Default the stable per-user identity only when nothing is stored —
|
|
6792
|
+
// an explicit or previously persisted agentId must survive
|
|
6793
|
+
// reinstalls.
|
|
6794
|
+
agentId: agentId || process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId || stableAgentId(),
|
|
6795
|
+
// The org pin follows the install environment exactly: installing
|
|
6796
|
+
// without MINDGRAPH_ORG_ID clears any stale pin (null = clear).
|
|
6797
|
+
orgId: process.env.MINDGRAPH_ORG_ID ?? null
|
|
6798
|
+
});
|
|
6799
|
+
console.log(`Saved hook connection settings to ${envPath} (mode 600)`);
|
|
6800
|
+
if (!apiKey && !baseUrl) {
|
|
6318
6801
|
console.log(
|
|
6319
6802
|
"Note: hooks resolve MINDGRAPH_API_KEY/MINDGRAPH_BASE_URL from the environment or ~/.mindgraph/hooks.json \u2014 pass --api-key/--base-url to persist them now."
|
|
6320
6803
|
);
|
|
@@ -6341,6 +6824,7 @@ async function main2() {
|
|
|
6341
6824
|
const hookApiKey = apiKey ?? stored.apiKey;
|
|
6342
6825
|
const hookBaseUrl = baseUrl ?? stored.baseUrl;
|
|
6343
6826
|
if (!hookApiKey) {
|
|
6827
|
+
recordHookHealth(hookHarness, "no-api-key");
|
|
6344
6828
|
process.stdout.write("{}\n");
|
|
6345
6829
|
break;
|
|
6346
6830
|
}
|
|
@@ -6349,12 +6833,19 @@ async function main2() {
|
|
|
6349
6833
|
const client2 = new MindGraph({
|
|
6350
6834
|
baseUrl: hookBaseUrl || "https://api.mindgraph.cloud",
|
|
6351
6835
|
apiKey: hookApiKey,
|
|
6352
|
-
|
|
6836
|
+
// Hooks run with the harness's environment, which usually lacks
|
|
6837
|
+
// MINDGRAPH_ORG_ID even when the MCP registration pins it — on a
|
|
6838
|
+
// multi-org key the hooks would otherwise track work in the key's
|
|
6839
|
+
// DEFAULT org while the MCP tools write to the pinned one.
|
|
6840
|
+
orgId: process.env.MINDGRAPH_ORG_ID || stored.orgId,
|
|
6353
6841
|
maxRetries: 0,
|
|
6354
6842
|
telemetrySurface: "mcp"
|
|
6355
6843
|
});
|
|
6356
6844
|
const hookOptions = {
|
|
6357
|
-
|
|
6845
|
+
// No harness-name fallback here: hook-core derives a stable
|
|
6846
|
+
// per-user default shared by both harnesses, which is what lets a
|
|
6847
|
+
// task claimed under Codex rebind under Claude Code.
|
|
6848
|
+
agentId: process.env.MINDGRAPH_AGENT_ID || stored.agentId
|
|
6358
6849
|
};
|
|
6359
6850
|
const output = hookHarness === "codex" ? await runCodexHook(
|
|
6360
6851
|
input,
|
|
@@ -6367,7 +6858,15 @@ async function main2() {
|
|
|
6367
6858
|
);
|
|
6368
6859
|
process.stdout.write(`${JSON.stringify(output)}
|
|
6369
6860
|
`);
|
|
6370
|
-
|
|
6861
|
+
clearHookHealth(hookHarness);
|
|
6862
|
+
} catch (error) {
|
|
6863
|
+
recordHookHealth(
|
|
6864
|
+
hookHarness,
|
|
6865
|
+
error?.message || "hook failed"
|
|
6866
|
+
);
|
|
6867
|
+
console.error(
|
|
6868
|
+
`mindgraph-hook: failing open (${error?.message || "unknown error"})`
|
|
6869
|
+
);
|
|
6371
6870
|
process.stdout.write("{}\n");
|
|
6372
6871
|
}
|
|
6373
6872
|
break;
|