mindgraph-mcp 0.14.9 → 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 +625 -170
- package/dist/index.js +120 -59
- 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
|
}
|
|
@@ -4822,7 +4907,7 @@ async function main() {
|
|
|
4822
4907
|
const transport = new import_stdio.StdioServerTransport();
|
|
4823
4908
|
await server.connect(transport);
|
|
4824
4909
|
console.error(
|
|
4825
|
-
`MindGraph MCP server
|
|
4910
|
+
`MindGraph MCP server v${"0.15.0"} running on stdio (${BASE_URL})`
|
|
4826
4911
|
);
|
|
4827
4912
|
}
|
|
4828
4913
|
var import_server, import_stdio, import_types, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, server, GOVERNANCE_CONFIG, PROMPTS;
|
|
@@ -4833,13 +4918,14 @@ var init_src = __esm({
|
|
|
4833
4918
|
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4834
4919
|
import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
4835
4920
|
init_dist();
|
|
4921
|
+
init_hook_env();
|
|
4836
4922
|
init_tools();
|
|
4837
4923
|
init_generated_tools();
|
|
4838
4924
|
init_governance();
|
|
4839
4925
|
init_resources();
|
|
4840
4926
|
API_KEY = process.env.MINDGRAPH_API_KEY;
|
|
4841
4927
|
BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
|
|
4842
|
-
AGENT_ID = process.env.MINDGRAPH_AGENT_ID ||
|
|
4928
|
+
AGENT_ID = process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId || stableAgentId();
|
|
4843
4929
|
ORG_ID = process.env.MINDGRAPH_ORG_ID;
|
|
4844
4930
|
PROFILE = process.env.MINDGRAPH_PROFILE || "general";
|
|
4845
4931
|
HARNESS = process.env.MINDGRAPH_HARNESS || "generic";
|
|
@@ -4961,7 +5047,7 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
4961
5047
|
server = new import_server.Server(
|
|
4962
5048
|
{
|
|
4963
5049
|
name: "mindgraph",
|
|
4964
|
-
version: "0.
|
|
5050
|
+
version: "0.15.0"
|
|
4965
5051
|
},
|
|
4966
5052
|
{
|
|
4967
5053
|
capabilities: {
|
|
@@ -5182,46 +5268,10 @@ ${conversation}`
|
|
|
5182
5268
|
var fs5 = __toESM(require("fs"));
|
|
5183
5269
|
var path5 = __toESM(require("path"));
|
|
5184
5270
|
var os4 = __toESM(require("os"));
|
|
5185
|
-
var
|
|
5271
|
+
var crypto5 = __toESM(require("crypto"));
|
|
5186
5272
|
var import_child_process = require("child_process");
|
|
5187
5273
|
init_dist();
|
|
5188
|
-
|
|
5189
|
-
// src/hook-env.ts
|
|
5190
|
-
var import_node_fs = __toESM(require("fs"));
|
|
5191
|
-
var import_node_os = __toESM(require("os"));
|
|
5192
|
-
var import_node_path = __toESM(require("path"));
|
|
5193
|
-
function hookEnvPath(dir) {
|
|
5194
|
-
return import_node_path.default.join(dir || import_node_path.default.join(import_node_os.default.homedir(), ".mindgraph"), "hooks.json");
|
|
5195
|
-
}
|
|
5196
|
-
function loadHookEnv(dir) {
|
|
5197
|
-
try {
|
|
5198
|
-
const raw = import_node_fs.default.readFileSync(hookEnvPath(dir), "utf8");
|
|
5199
|
-
const parsed = JSON.parse(raw);
|
|
5200
|
-
return {
|
|
5201
|
-
baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : void 0,
|
|
5202
|
-
apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : void 0,
|
|
5203
|
-
agentId: typeof parsed.agentId === "string" ? parsed.agentId : void 0
|
|
5204
|
-
};
|
|
5205
|
-
} catch {
|
|
5206
|
-
return {};
|
|
5207
|
-
}
|
|
5208
|
-
}
|
|
5209
|
-
function saveHookEnv(env, dir) {
|
|
5210
|
-
const file = hookEnvPath(dir);
|
|
5211
|
-
import_node_fs.default.mkdirSync(import_node_path.default.dirname(file), { recursive: true });
|
|
5212
|
-
const existing = loadHookEnv(dir);
|
|
5213
|
-
const merged = {
|
|
5214
|
-
baseUrl: env.baseUrl ?? existing.baseUrl,
|
|
5215
|
-
apiKey: env.apiKey ?? existing.apiKey,
|
|
5216
|
-
agentId: env.agentId ?? existing.agentId
|
|
5217
|
-
};
|
|
5218
|
-
import_node_fs.default.writeFileSync(file, `${JSON.stringify(merged, null, 2)}
|
|
5219
|
-
`, {
|
|
5220
|
-
mode: 384
|
|
5221
|
-
});
|
|
5222
|
-
import_node_fs.default.chmodSync(file, 384);
|
|
5223
|
-
return file;
|
|
5224
|
-
}
|
|
5274
|
+
init_hook_env();
|
|
5225
5275
|
|
|
5226
5276
|
// src/cli-args.ts
|
|
5227
5277
|
function parseArgs(argv) {
|
|
@@ -5292,12 +5342,14 @@ function classifyMcpAddFailure(e) {
|
|
|
5292
5342
|
}
|
|
5293
5343
|
|
|
5294
5344
|
// src/hook-core.ts
|
|
5295
|
-
var
|
|
5345
|
+
var import_node_crypto3 = __toESM(require("crypto"));
|
|
5296
5346
|
var import_node_fs3 = __toESM(require("fs"));
|
|
5297
5347
|
var import_node_os3 = __toESM(require("os"));
|
|
5298
5348
|
var import_node_path3 = __toESM(require("path"));
|
|
5299
5349
|
var import_node_child_process2 = require("child_process");
|
|
5300
5350
|
init_codegraph();
|
|
5351
|
+
init_code_tool();
|
|
5352
|
+
init_hook_env();
|
|
5301
5353
|
var NOOP = { kind: "noop" };
|
|
5302
5354
|
function defaultLedger(sessionId, options) {
|
|
5303
5355
|
return {
|
|
@@ -5319,7 +5371,7 @@ function runtimeDir(options) {
|
|
|
5319
5371
|
}
|
|
5320
5372
|
function ledgerPath(sessionId, options) {
|
|
5321
5373
|
const harness = "harness" in options ? options.harness : "claude-code";
|
|
5322
|
-
const key =
|
|
5374
|
+
const key = import_node_crypto3.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
|
|
5323
5375
|
return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
|
|
5324
5376
|
}
|
|
5325
5377
|
function waitBriefly(ms) {
|
|
@@ -5334,14 +5386,14 @@ function withLedger(sessionId, options, update) {
|
|
|
5334
5386
|
}
|
|
5335
5387
|
const lock = `${file}.lock`;
|
|
5336
5388
|
let lockFd;
|
|
5337
|
-
for (let attempt = 0; attempt <
|
|
5389
|
+
for (let attempt = 0; attempt < 400; attempt++) {
|
|
5338
5390
|
try {
|
|
5339
5391
|
lockFd = import_node_fs3.default.openSync(lock, "wx", 384);
|
|
5340
5392
|
break;
|
|
5341
5393
|
} catch (error) {
|
|
5342
5394
|
if (error.code !== "EEXIST") throw error;
|
|
5343
5395
|
try {
|
|
5344
|
-
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);
|
|
5345
5397
|
} catch {
|
|
5346
5398
|
}
|
|
5347
5399
|
waitBriefly(10);
|
|
@@ -5361,7 +5413,7 @@ function withLedger(sessionId, options, update) {
|
|
|
5361
5413
|
}
|
|
5362
5414
|
const result = update(ledger);
|
|
5363
5415
|
ledger.updatedAt = Date.now();
|
|
5364
|
-
const temporary = `${file}.${process.pid}.${
|
|
5416
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto3.default.randomUUID()}.tmp`;
|
|
5365
5417
|
import_node_fs3.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
|
|
5366
5418
|
`, { mode: 384 });
|
|
5367
5419
|
import_node_fs3.default.renameSync(temporary, file);
|
|
@@ -5396,17 +5448,13 @@ function worktreeState(cwd) {
|
|
|
5396
5448
|
const status = gitValue(cwd, ["status", "--porcelain"]);
|
|
5397
5449
|
return status === void 0 ? "unknown" : status ? "dirty" : "clean";
|
|
5398
5450
|
}
|
|
5399
|
-
|
|
5400
|
-
const repository = await new CodegraphAdapter({
|
|
5401
|
-
cwd: input.cwd,
|
|
5402
|
-
workspaceFile: options.workspaceFile
|
|
5403
|
-
}).resolveRepository();
|
|
5451
|
+
function invocationContext(input, options, repoId) {
|
|
5404
5452
|
return {
|
|
5405
5453
|
harness: options.harness,
|
|
5406
5454
|
harnessSessionId: input.session_id,
|
|
5407
5455
|
harnessTurnId: input.turn_id,
|
|
5408
5456
|
cwd: input.cwd,
|
|
5409
|
-
repoId
|
|
5457
|
+
repoId,
|
|
5410
5458
|
branch: gitValue(input.cwd, ["branch", "--show-current"]),
|
|
5411
5459
|
commit: gitValue(input.cwd, ["rev-parse", "HEAD"]),
|
|
5412
5460
|
worktreeState: worktreeState(input.cwd),
|
|
@@ -5414,6 +5462,9 @@ async function invocationContext(input, options) {
|
|
|
5414
5462
|
injectedBy: "hook"
|
|
5415
5463
|
};
|
|
5416
5464
|
}
|
|
5465
|
+
function resolveAgentId(options) {
|
|
5466
|
+
return options.agentId || process.env.MINDGRAPH_AGENT_ID || stableAgentId();
|
|
5467
|
+
}
|
|
5417
5468
|
function object(value) {
|
|
5418
5469
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5419
5470
|
}
|
|
@@ -5441,56 +5492,198 @@ function number(value) {
|
|
|
5441
5492
|
function string(value) {
|
|
5442
5493
|
return typeof value === "string" && value ? value : void 0;
|
|
5443
5494
|
}
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
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)}
|
|
5447
5617
|
\u2026[bounded work brief truncated]`;
|
|
5448
|
-
return [
|
|
5449
|
-
"MindGraph durable work brief (authoritative across sessions/harnesses):",
|
|
5450
|
-
capped,
|
|
5451
|
-
"Use the harness todo list only for local execution details; do not duplicate this durable Task there."
|
|
5452
|
-
].join("\n");
|
|
5453
5618
|
}
|
|
5454
5619
|
async function repositoryScopeUids(input, client2, options, agentId) {
|
|
5455
|
-
if (!client2.entity) return [];
|
|
5620
|
+
if (!client2.entity) return { uids: [], failures: [] };
|
|
5456
5621
|
const adapter = new CodegraphAdapter({
|
|
5457
5622
|
cwd: input.cwd,
|
|
5458
5623
|
workspaceFile: options.workspaceFile
|
|
5459
5624
|
});
|
|
5460
5625
|
const configured = await adapter.resolveRepositories();
|
|
5461
5626
|
const repositories = configured.length > 0 ? configured : [await adapter.resolveRepository()];
|
|
5627
|
+
const failures = [];
|
|
5462
5628
|
const resolved = await Promise.all(
|
|
5463
|
-
repositories.map(
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
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
|
+
})
|
|
5474
5669
|
);
|
|
5475
|
-
return
|
|
5476
|
-
...new Set(
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
return payload?.status === "existing" && string(payload.uid) ? [string(payload.uid)] : [];
|
|
5480
|
-
})
|
|
5481
|
-
)
|
|
5482
|
-
];
|
|
5670
|
+
return {
|
|
5671
|
+
uids: [...new Set(resolved.filter((uid) => Boolean(uid)))],
|
|
5672
|
+
failures
|
|
5673
|
+
};
|
|
5483
5674
|
}
|
|
5484
|
-
function isOwnPriorWork(brief, agentId) {
|
|
5675
|
+
function isOwnPriorWork(brief, agentId, harness) {
|
|
5485
5676
|
const reason = string(brief?.selection_reason);
|
|
5486
5677
|
if (reason === "owned_live_lease" || reason === "same_agent_session_rebind") {
|
|
5487
5678
|
return true;
|
|
5488
5679
|
}
|
|
5489
|
-
|
|
5680
|
+
const owner = string(object(brief?.lease)?.lease_owner_agent_id);
|
|
5681
|
+
return owner === agentId || owner === harness;
|
|
5490
5682
|
}
|
|
5491
5683
|
async function sessionStart(input, client2, options) {
|
|
5492
|
-
const agentId = options
|
|
5684
|
+
const agentId = resolveAgentId(options);
|
|
5493
5685
|
const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
|
|
5686
|
+
const nowSeconds = (options.now || (() => Date.now() / 1e3))();
|
|
5494
5687
|
const opened = object(
|
|
5495
5688
|
await client2.session({
|
|
5496
5689
|
action: "open",
|
|
@@ -5502,7 +5695,21 @@ async function sessionStart(input, client2, options) {
|
|
|
5502
5695
|
})
|
|
5503
5696
|
);
|
|
5504
5697
|
const sessionUid = string(opened?.uid);
|
|
5505
|
-
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;
|
|
5506
5713
|
let brief = object(
|
|
5507
5714
|
await client2.plan({
|
|
5508
5715
|
action: "resume_work",
|
|
@@ -5513,9 +5720,19 @@ async function sessionStart(input, client2, options) {
|
|
|
5513
5720
|
);
|
|
5514
5721
|
const task = object(brief?.task);
|
|
5515
5722
|
const lease = object(brief?.lease);
|
|
5516
|
-
|
|
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)) {
|
|
5517
5734
|
try {
|
|
5518
|
-
|
|
5735
|
+
claimed = object(
|
|
5519
5736
|
await client2.plan({
|
|
5520
5737
|
action: "claim_task",
|
|
5521
5738
|
task_uid: task.uid,
|
|
@@ -5525,35 +5742,63 @@ async function sessionStart(input, client2, options) {
|
|
|
5525
5742
|
agent_id: agentId
|
|
5526
5743
|
})
|
|
5527
5744
|
);
|
|
5528
|
-
brief = object(
|
|
5529
|
-
await client2.plan({
|
|
5530
|
-
action: "resume_work",
|
|
5531
|
-
task_uid: task.uid,
|
|
5532
|
-
session_uid: sessionUid,
|
|
5533
|
-
...scopeUids.length > 0 ? { scope_uids: scopeUids } : {},
|
|
5534
|
-
agent_id: agentId
|
|
5535
|
-
})
|
|
5536
|
-
);
|
|
5537
|
-
if (brief && claimed) brief.claim = claimed;
|
|
5538
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;
|
|
5539
5763
|
}
|
|
5540
5764
|
}
|
|
5541
|
-
const
|
|
5542
|
-
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");
|
|
5543
5773
|
const shouldInject = withLedger(input.session_id, options, (ledger) => {
|
|
5544
5774
|
const changed = ledger.lastBriefHash !== briefHash;
|
|
5545
5775
|
const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
|
|
5546
5776
|
ledger.sessionUid = sessionUid;
|
|
5547
|
-
ledger.baselineWorktree =
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
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
|
+
}
|
|
5557
5802
|
ledger.lastBriefHash = briefHash;
|
|
5558
5803
|
return changed || reinjectCompact;
|
|
5559
5804
|
});
|
|
@@ -5562,82 +5807,173 @@ async function sessionStart(input, client2, options) {
|
|
|
5562
5807
|
async function preTool(input, options) {
|
|
5563
5808
|
const original = { ...input.tool_input || {} };
|
|
5564
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
|
+
}
|
|
5565
5823
|
const updatedInput = {
|
|
5566
5824
|
...original,
|
|
5567
|
-
invocation_context:
|
|
5825
|
+
invocation_context: invocationContext(input, options, repoId)
|
|
5568
5826
|
};
|
|
5569
5827
|
if (ledger.sessionUid) updatedInput.session_uid = ledger.sessionUid;
|
|
5570
5828
|
if (ledger.taskUid && updatedInput.task_uid === void 0) {
|
|
5571
5829
|
updatedInput.task_uid = ledger.taskUid;
|
|
5572
5830
|
}
|
|
5573
|
-
|
|
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) {
|
|
5574
5836
|
updatedInput.work_uid = ledger.taskUid;
|
|
5575
5837
|
}
|
|
5576
|
-
if (ledger.executionUid && updatedInput.execution_uid === void 0) {
|
|
5838
|
+
if (targetsLedgerTask && (fencedAction || isCapture) && ledger.executionUid && updatedInput.execution_uid === void 0) {
|
|
5577
5839
|
updatedInput.execution_uid = ledger.executionUid;
|
|
5578
5840
|
}
|
|
5579
|
-
if (ledger.leaseEpoch !== void 0 && updatedInput.lease_epoch === void 0) {
|
|
5841
|
+
if (targetsLedgerTask && fencedAction && ledger.leaseEpoch !== void 0 && updatedInput.lease_epoch === void 0) {
|
|
5580
5842
|
updatedInput.lease_epoch = ledger.leaseEpoch;
|
|
5581
5843
|
}
|
|
5582
|
-
if (ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
|
|
5844
|
+
if (targetsLedgerTask && fencedAction && ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
|
|
5583
5845
|
updatedInput.expected_version = ledger.taskVersion;
|
|
5584
5846
|
}
|
|
5585
5847
|
return { kind: "rewrite", updatedInput };
|
|
5586
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
|
+
]);
|
|
5587
5858
|
async function postTool(input, client2, options) {
|
|
5588
5859
|
const name = input.tool_name || "";
|
|
5589
5860
|
const args = input.tool_input || {};
|
|
5590
5861
|
const action = string(args.action);
|
|
5591
5862
|
const payload = responsePayload(input.tool_response);
|
|
5592
5863
|
const nowSeconds = (options.now || (() => Date.now() / 1e3))();
|
|
5593
|
-
let
|
|
5864
|
+
let renewal;
|
|
5594
5865
|
withLedger(input.session_id, options, (ledger) => {
|
|
5595
5866
|
ledger.materialToolCalls += 1;
|
|
5596
|
-
if (["Edit", "Write", "NotebookEdit", "apply_patch"].includes(name)
|
|
5867
|
+
if (["Edit", "Write", "NotebookEdit", "apply_patch"].includes(name)) {
|
|
5597
5868
|
ledger.mutatingOperations += 1;
|
|
5598
5869
|
}
|
|
5599
|
-
if (
|
|
5600
|
-
|
|
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
|
+
}
|
|
5601
5874
|
}
|
|
5602
5875
|
if (name.includes("mindgraph_plan")) {
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
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
|
+
}
|
|
5615
5916
|
}
|
|
5616
|
-
if (ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0 && ledger.leaseExpiresAt !== void 0 &&
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
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
|
+
}
|
|
5623
5941
|
}
|
|
5624
5942
|
return void 0;
|
|
5625
5943
|
});
|
|
5626
|
-
if (
|
|
5944
|
+
if (renewal) {
|
|
5945
|
+
const active = renewal;
|
|
5627
5946
|
try {
|
|
5628
5947
|
const response = object(
|
|
5629
|
-
|
|
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({
|
|
5630
5961
|
action: "heartbeat",
|
|
5631
|
-
task_uid:
|
|
5632
|
-
session_uid:
|
|
5633
|
-
expected_version:
|
|
5634
|
-
lease_epoch:
|
|
5635
|
-
idempotency_key: `hook-heartbeat:${input.session_id}:${
|
|
5636
|
-
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)
|
|
5637
5968
|
})
|
|
5638
5969
|
);
|
|
5639
5970
|
withLedger(input.session_id, options, (ledger) => {
|
|
5640
|
-
|
|
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;
|
|
5641
5977
|
return void 0;
|
|
5642
5978
|
});
|
|
5643
5979
|
} catch {
|
|
@@ -5645,6 +5981,9 @@ async function postTool(input, client2, options) {
|
|
|
5645
5981
|
}
|
|
5646
5982
|
return NOOP;
|
|
5647
5983
|
}
|
|
5984
|
+
function payloadIsError(payload) {
|
|
5985
|
+
return Boolean(payload && payload.error);
|
|
5986
|
+
}
|
|
5648
5987
|
function stop(input, options) {
|
|
5649
5988
|
if (input.stop_hook_active) return NOOP;
|
|
5650
5989
|
return withLedger(input.session_id, options, (ledger) => {
|
|
@@ -5675,17 +6014,20 @@ async function sessionEnd(input, client2, options) {
|
|
|
5675
6014
|
release_lease: true,
|
|
5676
6015
|
summary: `${harnessLabel} SessionEnd: ${input.reason || "interrupted"}`,
|
|
5677
6016
|
idempotency_key: `session-end:${input.session_id}:${ledger.executionUid}`,
|
|
5678
|
-
agent_id: options
|
|
6017
|
+
agent_id: resolveAgentId(options)
|
|
5679
6018
|
});
|
|
5680
6019
|
} catch {
|
|
5681
6020
|
}
|
|
5682
6021
|
}
|
|
5683
|
-
|
|
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)) {
|
|
5684
6026
|
try {
|
|
5685
6027
|
await client2.session({
|
|
5686
6028
|
action: "close",
|
|
5687
6029
|
session_uid: ledger.sessionUid,
|
|
5688
|
-
agent_id: options
|
|
6030
|
+
agent_id: resolveAgentId(options)
|
|
5689
6031
|
});
|
|
5690
6032
|
} catch {
|
|
5691
6033
|
}
|
|
@@ -5795,7 +6137,7 @@ async function runCodexHook(input, client2, options = {}) {
|
|
|
5795
6137
|
var import_node_fs4 = __toESM(require("fs"));
|
|
5796
6138
|
var import_node_os4 = __toESM(require("os"));
|
|
5797
6139
|
var import_node_path4 = __toESM(require("path"));
|
|
5798
|
-
var
|
|
6140
|
+
var import_node_crypto4 = __toESM(require("crypto"));
|
|
5799
6141
|
var OWNER_MARKER = "--owner mindgraph";
|
|
5800
6142
|
var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
|
|
5801
6143
|
function command(harness) {
|
|
@@ -5839,11 +6181,14 @@ function desiredEntries(harness) {
|
|
|
5839
6181
|
// inherited 20s edge in both harnesses: the hooks correctly failed
|
|
5840
6182
|
// open, but no brief reached the model. The same installed runner
|
|
5841
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.
|
|
5842
6187
|
hooks: [
|
|
5843
6188
|
ownedHook(
|
|
5844
6189
|
harness,
|
|
5845
6190
|
30,
|
|
5846
|
-
harness === "codex" ?
|
|
6191
|
+
harness === "codex" ? 1e4 : void 0
|
|
5847
6192
|
)
|
|
5848
6193
|
]
|
|
5849
6194
|
}
|
|
@@ -5851,13 +6196,23 @@ function desiredEntries(harness) {
|
|
|
5851
6196
|
PreToolUse: [
|
|
5852
6197
|
{
|
|
5853
6198
|
matcher: "mcp__mindgraph__.*",
|
|
5854
|
-
|
|
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)]
|
|
5855
6203
|
}
|
|
5856
6204
|
],
|
|
5857
|
-
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)] }],
|
|
5858
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.
|
|
5859
6214
|
SessionEnd: [
|
|
5860
|
-
{ hooks: [ownedHook(harness, harness === "codex" ? 3 :
|
|
6215
|
+
{ hooks: [ownedHook(harness, harness === "codex" ? 3 : 30)] }
|
|
5861
6216
|
]
|
|
5862
6217
|
};
|
|
5863
6218
|
return harness === "claude-code" ? {
|
|
@@ -5878,7 +6233,7 @@ function entryHasOwnedHook(value) {
|
|
|
5878
6233
|
}
|
|
5879
6234
|
function writeSettings(file, settings) {
|
|
5880
6235
|
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
|
|
5881
|
-
const temporary = `${file}.${process.pid}.${
|
|
6236
|
+
const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
|
|
5882
6237
|
import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
|
|
5883
6238
|
`, {
|
|
5884
6239
|
mode: 384
|
|
@@ -5956,6 +6311,52 @@ function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
|
5956
6311
|
}
|
|
5957
6312
|
|
|
5958
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
|
+
}
|
|
5959
6360
|
function getClaudeDesktopConfigPath() {
|
|
5960
6361
|
switch (process.platform) {
|
|
5961
6362
|
case "darwin":
|
|
@@ -6031,7 +6432,10 @@ function installClaudeCode(apiKey, baseUrl, agentId) {
|
|
|
6031
6432
|
"--env",
|
|
6032
6433
|
"MINDGRAPH_HARNESS=claude-code",
|
|
6033
6434
|
"--env",
|
|
6034
|
-
|
|
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()}`
|
|
6035
6439
|
];
|
|
6036
6440
|
if (baseUrl) {
|
|
6037
6441
|
envArgs.push("--env", `MINDGRAPH_BASE_URL=${baseUrl}`);
|
|
@@ -6121,6 +6525,23 @@ function printStatus() {
|
|
|
6121
6525
|
console.log(
|
|
6122
6526
|
`MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
|
|
6123
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
|
+
}
|
|
6124
6545
|
}
|
|
6125
6546
|
function withHooksFlag() {
|
|
6126
6547
|
return process.argv.includes("--hooks");
|
|
@@ -6133,7 +6554,16 @@ function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
|
|
|
6133
6554
|
console.log(
|
|
6134
6555
|
`Installed ${result.added} and refreshed ${result.updated} MindGraph Claude Code hook entries in ${result.path}`
|
|
6135
6556
|
);
|
|
6136
|
-
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
|
+
});
|
|
6137
6567
|
console.log(`Saved hook connection settings to ${envPath} (mode 600)`);
|
|
6138
6568
|
} else {
|
|
6139
6569
|
console.log(
|
|
@@ -6192,7 +6622,7 @@ var DASHBOARD_URL = process.env.MINDGRAPH_DASHBOARD_URL || "https://mindgraph.cl
|
|
|
6192
6622
|
var POLL_INTERVAL = 2e3;
|
|
6193
6623
|
var POLL_TIMEOUT = 10 * 60 * 1e3;
|
|
6194
6624
|
function generateCode() {
|
|
6195
|
-
return
|
|
6625
|
+
return crypto5.randomBytes(32).toString("hex");
|
|
6196
6626
|
}
|
|
6197
6627
|
function openBrowser(url) {
|
|
6198
6628
|
try {
|
|
@@ -6355,10 +6785,19 @@ async function main2() {
|
|
|
6355
6785
|
`Installed ${result.added} and refreshed ${result.updated} MindGraph ${hookHarness === "codex" ? "Codex" : "Claude Code"} hook entries in ${result.path}`
|
|
6356
6786
|
);
|
|
6357
6787
|
printCodegraphStatus();
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
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) {
|
|
6362
6801
|
console.log(
|
|
6363
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."
|
|
6364
6803
|
);
|
|
@@ -6385,6 +6824,7 @@ async function main2() {
|
|
|
6385
6824
|
const hookApiKey = apiKey ?? stored.apiKey;
|
|
6386
6825
|
const hookBaseUrl = baseUrl ?? stored.baseUrl;
|
|
6387
6826
|
if (!hookApiKey) {
|
|
6827
|
+
recordHookHealth(hookHarness, "no-api-key");
|
|
6388
6828
|
process.stdout.write("{}\n");
|
|
6389
6829
|
break;
|
|
6390
6830
|
}
|
|
@@ -6393,12 +6833,19 @@ async function main2() {
|
|
|
6393
6833
|
const client2 = new MindGraph({
|
|
6394
6834
|
baseUrl: hookBaseUrl || "https://api.mindgraph.cloud",
|
|
6395
6835
|
apiKey: hookApiKey,
|
|
6396
|
-
|
|
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,
|
|
6397
6841
|
maxRetries: 0,
|
|
6398
6842
|
telemetrySurface: "mcp"
|
|
6399
6843
|
});
|
|
6400
6844
|
const hookOptions = {
|
|
6401
|
-
|
|
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
|
|
6402
6849
|
};
|
|
6403
6850
|
const output = hookHarness === "codex" ? await runCodexHook(
|
|
6404
6851
|
input,
|
|
@@ -6411,7 +6858,15 @@ async function main2() {
|
|
|
6411
6858
|
);
|
|
6412
6859
|
process.stdout.write(`${JSON.stringify(output)}
|
|
6413
6860
|
`);
|
|
6414
|
-
|
|
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
|
+
);
|
|
6415
6870
|
process.stdout.write("{}\n");
|
|
6416
6871
|
}
|
|
6417
6872
|
break;
|