indelible-mcp 4.9.7 → 4.9.8
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/package.json +1 -1
- package/src/index.js +138 -28
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1489,6 +1489,13 @@ function appendReceipt(receipt, meta = {}) {
|
|
|
1489
1489
|
trigger: meta.trigger || "interactive",
|
|
1490
1490
|
// interactive | auto (background/hook)
|
|
1491
1491
|
label: meta.label || null,
|
|
1492
|
+
// g-363 self-reconciling ledger (Ben's ask, 2026-07-16): persist the content
|
|
1493
|
+
// identity so a stranded receipt can ALWAYS be reconciled by content — even
|
|
1494
|
+
// months later, after the 60-min tx-cache has pruned. This is the durable
|
|
1495
|
+
// targetKey for findByContentKey; null for saves with no single content hash
|
|
1496
|
+
// (e.g. session deltas). Cheap to write, and it removes the biggest
|
|
1497
|
+
// "cannot learn content key" unresolvable bucket for good.
|
|
1498
|
+
contentHash: receipt.contentHash ?? receipt.content_hash ?? meta.contentHash ?? null,
|
|
1492
1499
|
surfaced: false
|
|
1493
1500
|
// has the IDE shown this entry yet?
|
|
1494
1501
|
};
|
|
@@ -4326,17 +4333,37 @@ function computeMapLayout(dated) {
|
|
|
4326
4333
|
};
|
|
4327
4334
|
const globalTF = /* @__PURE__ */ new Map();
|
|
4328
4335
|
for (const s of dated) for (const w of grams(s.title)) globalTF.set(w, (globalTF.get(w) || 0) + 1);
|
|
4329
|
-
function nameOf(members, nTerms) {
|
|
4336
|
+
function nameOf(members, nTerms, allowFallback = false) {
|
|
4330
4337
|
const tf = /* @__PURE__ */ new Map();
|
|
4331
4338
|
for (const s of members) for (const w of grams(s.title)) tf.set(w, (tf.get(w) || 0) + 1);
|
|
4332
|
-
const
|
|
4339
|
+
const minF = Math.min(3, Math.max(1, Math.round(members.length * 0.2)));
|
|
4340
|
+
const scored = [...tf.entries()].filter(([w, f]) => f >= minF).map(([w, f]) => [w, (w.includes(" ") ? 1.6 : 1) * f * f / (globalTF.get(w) || 1)]).sort((a, b) => b[1] - a[1]);
|
|
4333
4341
|
const picked = [];
|
|
4334
4342
|
for (const [w] of scored) {
|
|
4335
4343
|
if (picked.some((p) => p.includes(w) || w.includes(p))) continue;
|
|
4336
4344
|
picked.push(w);
|
|
4337
4345
|
if (picked.length >= nTerms) break;
|
|
4338
4346
|
}
|
|
4339
|
-
return picked.join(" \xB7 ")
|
|
4347
|
+
if (picked.length) return picked.join(" \xB7 ");
|
|
4348
|
+
if (!allowFallback) return "(misc)";
|
|
4349
|
+
let best = "";
|
|
4350
|
+
let bestScore = -1;
|
|
4351
|
+
for (const s of members) {
|
|
4352
|
+
let sc = 0;
|
|
4353
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4354
|
+
for (const w of grams(s.title)) {
|
|
4355
|
+
if (!seen.has(w)) {
|
|
4356
|
+
seen.add(w);
|
|
4357
|
+
sc += (tf.get(w) || 0) / (globalTF.get(w) || 1);
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
if (sc > bestScore) {
|
|
4361
|
+
bestScore = sc;
|
|
4362
|
+
best = s.title || "";
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
const distinct = tok(best).sort((a, b) => (globalTF.get(a) || 1) - (globalTF.get(b) || 1)).slice(0, nTerms);
|
|
4366
|
+
return distinct.length ? distinct.join(" \xB7 ") : "(untitled)";
|
|
4340
4367
|
}
|
|
4341
4368
|
const centroidOf = (members) => ({
|
|
4342
4369
|
cx: members.reduce((a, s) => a + s.x, 0) / (members.length || 1),
|
|
@@ -4344,7 +4371,7 @@ function computeMapLayout(dated) {
|
|
|
4344
4371
|
cz: members.reduce((a, s) => a + s.z, 0) / (members.length || 1)
|
|
4345
4372
|
});
|
|
4346
4373
|
function kmeansLocal(members, k) {
|
|
4347
|
-
if (members.length < k *
|
|
4374
|
+
if (members.length < k * 3) return null;
|
|
4348
4375
|
const c0 = [members[Math.floor(rnd() * members.length)].v.slice()];
|
|
4349
4376
|
while (c0.length < k) {
|
|
4350
4377
|
const d = members.map((s) => Math.min(...c0.map((c) => dist2(s.v, c))));
|
|
@@ -4386,7 +4413,12 @@ function computeMapLayout(dated) {
|
|
|
4386
4413
|
const subLabels = [];
|
|
4387
4414
|
for (let c = 0; c < K; c++) {
|
|
4388
4415
|
const members = dated.filter((s) => s.c === c);
|
|
4389
|
-
const label = nameOf(
|
|
4416
|
+
const label = nameOf(
|
|
4417
|
+
members,
|
|
4418
|
+
3,
|
|
4419
|
+
/* allowFallback */
|
|
4420
|
+
true
|
|
4421
|
+
);
|
|
4390
4422
|
const mDates = members.map((s) => s.date).sort();
|
|
4391
4423
|
clusterInfo.push({
|
|
4392
4424
|
c,
|
|
@@ -4397,11 +4429,13 @@ function computeMapLayout(dated) {
|
|
|
4397
4429
|
last_touched: mDates.length ? mDates[mDates.length - 1].slice(0, 10) : null,
|
|
4398
4430
|
...centroidOf(members)
|
|
4399
4431
|
});
|
|
4400
|
-
const
|
|
4432
|
+
const subK = members.length >= 30 ? 3 : 2;
|
|
4433
|
+
const subFloor = members.length >= 75 ? 25 : Math.max(4, Math.round(members.length * 0.15));
|
|
4434
|
+
const sub = members.length >= 12 ? kmeansLocal(members, subK) : null;
|
|
4401
4435
|
if (sub) {
|
|
4402
|
-
for (let sc = 0; sc <
|
|
4436
|
+
for (let sc = 0; sc < subK; sc++) {
|
|
4403
4437
|
const m = members.filter((_, i) => sub[i] === sc);
|
|
4404
|
-
if (m.length <
|
|
4438
|
+
if (m.length < subFloor) continue;
|
|
4405
4439
|
const sl = nameOf(m, 2);
|
|
4406
4440
|
if (sl === "(misc)" || sl === label) continue;
|
|
4407
4441
|
subLabels.push({ label: sl, count: m.length, ...centroidOf(m) });
|
|
@@ -4648,8 +4682,8 @@ function draw(){
|
|
|
4648
4682
|
const vg=g.createRadialGradient(W/2,H/2,H*0.2,W/2,H/2,Math.max(W,H)*0.75);
|
|
4649
4683
|
vg.addColorStop(0,'#0e0a07');vg.addColorStop(1,'#070503');g.fillStyle=vg;g.fillRect(0,0,W,H);
|
|
4650
4684
|
projected=P.map(p=>({p,q:proj(p)}));
|
|
4651
|
-
// time thread
|
|
4652
|
-
g.strokeStyle='rgba(244,168,58,0.04)';g.beginPath();
|
|
4685
|
+
// time thread \u2014 opacity scales with map size (a few segments need more alpha than 5,000)
|
|
4686
|
+
g.strokeStyle='rgba(244,168,58,'+Math.min(0.28,Math.max(0.04,4/P.length))+')';g.beginPath();
|
|
4653
4687
|
projected.forEach((e,i)=>{i?g.lineTo(e.q.X,e.q.Y):g.moveTo(e.q.X,e.q.Y)});g.stroke();
|
|
4654
4688
|
// points: crisp everywhere. Heat = the DOT'S OWN color from the density ramp
|
|
4655
4689
|
// (no additive accumulation \u2014 that both lagged and blew the core out to mush).
|
|
@@ -5596,7 +5630,27 @@ async function _run({ dir, key, address, contentKey, build, broadcast, checkConf
|
|
|
5596
5630
|
});
|
|
5597
5631
|
prune(dir, now()).catch((e) => process.stderr.write(`[build-cache] prune failed (non-fatal): ${e?.message}
|
|
5598
5632
|
`));
|
|
5599
|
-
|
|
5633
|
+
let write;
|
|
5634
|
+
try {
|
|
5635
|
+
write = await broadcast(fresh.txHex);
|
|
5636
|
+
} catch (err) {
|
|
5637
|
+
let seen = null;
|
|
5638
|
+
try {
|
|
5639
|
+
seen = await checkConfirmation2(fresh.txId);
|
|
5640
|
+
} catch {
|
|
5641
|
+
}
|
|
5642
|
+
if (seen && (seen.confirmed || seen.inMempool || seen.exists === true)) {
|
|
5643
|
+
process.stderr.write(`[g-363] broadcast ack failed but ${fresh.txId.slice(0, 12)}\u2026 is on-chain/mempool \u2014 slow-ack recovered, no rival (${err?.message || "timeout"})
|
|
5644
|
+
`);
|
|
5645
|
+
return {
|
|
5646
|
+
...fresh,
|
|
5647
|
+
write: { txid: fresh.txId, via: "chain-verified", source: "slow-ack-recovered", confirmed: !!seen.confirmed },
|
|
5648
|
+
reused: false,
|
|
5649
|
+
alreadyOnChain: true
|
|
5650
|
+
};
|
|
5651
|
+
}
|
|
5652
|
+
throw err;
|
|
5653
|
+
}
|
|
5600
5654
|
if (write?.txid && fresh.txId && write.txid.toLowerCase() !== fresh.txId.toLowerCase()) {
|
|
5601
5655
|
process.stderr.write(`[g-363 TRIPWIRE] bridge-computed txid ${write.txid} differs from built txid ${fresh.txId} \u2014 possible transit mutation, receipt kept as built
|
|
5602
5656
|
`);
|
|
@@ -5853,7 +5907,7 @@ async function saveFile(filePath, options = {}) {
|
|
|
5853
5907
|
}
|
|
5854
5908
|
const chunkCount = encrypted.length <= MAX_CHUNK_SIZE ? 1 : Math.ceil(encrypted.length / MAX_CHUNK_SIZE);
|
|
5855
5909
|
const receipt = buildReceipt(_finalWrite, { txId: masterTxId, fee: _saveFee, txSize: _finalSize });
|
|
5856
|
-
appendReceipt(receipt, { trigger: "interactive", saveType: "file" });
|
|
5910
|
+
appendReceipt(receipt, { trigger: "interactive", saveType: "file", contentHash: `sha256:${contentHash}` });
|
|
5857
5911
|
return {
|
|
5858
5912
|
success: true,
|
|
5859
5913
|
txId: masterTxId,
|
|
@@ -7117,22 +7171,25 @@ async function findByContentKey({
|
|
|
7117
7171
|
excludeTxids = [],
|
|
7118
7172
|
maxScan = 400,
|
|
7119
7173
|
confirmedOnly = false,
|
|
7120
|
-
stopAtFirst = true
|
|
7174
|
+
stopAtFirst = true,
|
|
7175
|
+
matchCap = 10
|
|
7121
7176
|
}) {
|
|
7122
|
-
if (!address || !targetKey) return { matches: [], scanned: 0, truncated: false };
|
|
7177
|
+
if (!address || !targetKey) return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "absent" };
|
|
7123
7178
|
const exclude = new Set(excludeTxids.map((t) => String(t).toLowerCase()));
|
|
7124
7179
|
let history = [];
|
|
7125
7180
|
try {
|
|
7126
7181
|
history = await getHistory(address);
|
|
7127
7182
|
} catch {
|
|
7128
|
-
return { matches: [], scanned: 0, truncated: false, error: "history-unreachable" };
|
|
7183
|
+
return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "inconclusive-history-unreachable", error: "history-unreachable" };
|
|
7129
7184
|
}
|
|
7130
|
-
if (!Array.isArray(history)) return { matches: [], scanned: 0, truncated: false };
|
|
7185
|
+
if (!Array.isArray(history)) return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "absent" };
|
|
7131
7186
|
const ordered = [...history].sort((a, b) => (b.height || 0) - (a.height || 0));
|
|
7132
7187
|
const truncated = ordered.length > maxScan;
|
|
7133
7188
|
const candidates = ordered.slice(0, maxScan);
|
|
7134
7189
|
const matches = [];
|
|
7135
7190
|
let scanned = 0;
|
|
7191
|
+
let fetchFailures = 0;
|
|
7192
|
+
let confirmedFound = false;
|
|
7136
7193
|
for (const h of candidates) {
|
|
7137
7194
|
const txid = (h.tx_hash || h.txid || "").toString();
|
|
7138
7195
|
if (!txid || exclude.has(txid.toLowerCase())) continue;
|
|
@@ -7142,23 +7199,62 @@ async function findByContentKey({
|
|
|
7142
7199
|
try {
|
|
7143
7200
|
payload = await fetchPayload(txid);
|
|
7144
7201
|
} catch {
|
|
7202
|
+
fetchFailures++;
|
|
7145
7203
|
continue;
|
|
7146
7204
|
}
|
|
7147
7205
|
if (payloadContentKey(payload) === targetKey) {
|
|
7148
|
-
|
|
7149
|
-
|
|
7206
|
+
const height = h.height || null;
|
|
7207
|
+
matches.push({ txid, height });
|
|
7208
|
+
if (height > 0) confirmedFound = true;
|
|
7209
|
+
if (stopAtFirst && confirmedFound) break;
|
|
7210
|
+
if (matches.length >= matchCap) break;
|
|
7150
7211
|
}
|
|
7151
7212
|
}
|
|
7152
|
-
|
|
7213
|
+
matches.sort((a, b) => {
|
|
7214
|
+
const ac = a.height > 0 ? 1 : 0, bc = b.height > 0 ? 1 : 0;
|
|
7215
|
+
if (ac !== bc) return bc - ac;
|
|
7216
|
+
return (b.height || 0) - (a.height || 0);
|
|
7217
|
+
});
|
|
7218
|
+
const result = stopAtFirst && matches.length ? [matches[0]] : matches;
|
|
7219
|
+
let classification;
|
|
7220
|
+
if (result.length) classification = "found";
|
|
7221
|
+
else if (truncated) classification = "inconclusive-truncated";
|
|
7222
|
+
else if (fetchFailures > 0) classification = "inconclusive-fetch-degraded";
|
|
7223
|
+
else classification = "absent";
|
|
7224
|
+
return { matches: result, scanned, truncated, fetchFailures, classification };
|
|
7153
7225
|
}
|
|
7154
7226
|
|
|
7155
7227
|
// mcp-server/tools/load_file.js
|
|
7156
7228
|
var TX_CACHE_DIR3 = join12(homedir12(), ".indelible", "tx-cache");
|
|
7229
|
+
var SAVE_LOG_PATH = join12(homedir12(), ".indelible", "save-log.jsonl");
|
|
7230
|
+
async function contentKeyFromSaveLog(txid) {
|
|
7231
|
+
try {
|
|
7232
|
+
const raw = await readFile6(SAVE_LOG_PATH, "utf-8");
|
|
7233
|
+
for (const line of raw.split("\n")) {
|
|
7234
|
+
if (!line.trim()) continue;
|
|
7235
|
+
let e;
|
|
7236
|
+
try {
|
|
7237
|
+
e = JSON.parse(line);
|
|
7238
|
+
} catch {
|
|
7239
|
+
continue;
|
|
7240
|
+
}
|
|
7241
|
+
if (e.txid === txid && e.contentHash) return String(e.contentHash).replace(/^sha256:/, "");
|
|
7242
|
+
}
|
|
7243
|
+
} catch {
|
|
7244
|
+
}
|
|
7245
|
+
return null;
|
|
7246
|
+
}
|
|
7157
7247
|
async function healStrandedTxid(strandedTxId, config) {
|
|
7158
7248
|
try {
|
|
7249
|
+
let targetKey = null;
|
|
7159
7250
|
const cachedRaw = await readFile6(join12(TX_CACHE_DIR3, `${strandedTxId.trim()}.json`), "utf-8").catch(() => null);
|
|
7160
|
-
if (
|
|
7161
|
-
|
|
7251
|
+
if (cachedRaw) {
|
|
7252
|
+
try {
|
|
7253
|
+
targetKey = payloadContentKey(JSON.parse(cachedRaw));
|
|
7254
|
+
} catch {
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
if (!targetKey) targetKey = await contentKeyFromSaveLog(strandedTxId.trim());
|
|
7162
7258
|
const address = config?.address;
|
|
7163
7259
|
if (!targetKey || !address) return null;
|
|
7164
7260
|
const { matches } = await findByContentKey({
|
|
@@ -8502,7 +8598,7 @@ function redactSecrets2(s) {
|
|
|
8502
8598
|
return redactCredentials(String(s));
|
|
8503
8599
|
}
|
|
8504
8600
|
async function reportBug(args2 = {}, mcpVersion) {
|
|
8505
|
-
const { summary, description, severity = "medium", tool_name = null, last_error = null } = args2;
|
|
8601
|
+
const { summary, description, severity = "medium", tool_name = null, last_error = null, still_file = false } = args2;
|
|
8506
8602
|
if (!summary || !description) {
|
|
8507
8603
|
return { success: false, error: "summary and description are required (you supply the narrative; the tool supplies the facts)." };
|
|
8508
8604
|
}
|
|
@@ -8514,6 +8610,10 @@ async function reportBug(args2 = {}, mcpVersion) {
|
|
|
8514
8610
|
severity: ["low", "medium", "high"].includes(severity) ? severity : "medium",
|
|
8515
8611
|
tool_name: tool_name ? String(tool_name).slice(0, 80) : null,
|
|
8516
8612
|
last_error: redactSecrets2(last_error),
|
|
8613
|
+
// User override: "no, my case is genuinely different." When true, the intake
|
|
8614
|
+
// files a real ticket + pages the team even if the report matches a known,
|
|
8615
|
+
// already-fixed issue — so a false match can never trap a real report.
|
|
8616
|
+
still_file: still_file === true,
|
|
8517
8617
|
// Machine-pulled facts (the AI cannot get these wrong)
|
|
8518
8618
|
mcp_version: getMcpVersion(mcpVersion),
|
|
8519
8619
|
wallet_address: getWalletAddress(config),
|
|
@@ -8533,9 +8633,17 @@ async function reportBug(args2 = {}, mcpVersion) {
|
|
|
8533
8633
|
}
|
|
8534
8634
|
const data = await resp.json().catch(() => ({}));
|
|
8535
8635
|
if (data.known_issue) {
|
|
8536
|
-
return {
|
|
8636
|
+
return {
|
|
8637
|
+
success: true,
|
|
8638
|
+
status: "known_issue",
|
|
8639
|
+
issue_id: data.issue_id || null,
|
|
8640
|
+
message: data.message || "Matched a known, already-fixed issue.",
|
|
8641
|
+
// The team was already notified (the intake never swallows), but if the
|
|
8642
|
+
// narrative describes something genuinely different, the user can escalate.
|
|
8643
|
+
override_hint: "If this does not match your situation, call report_bug again with still_file: true to open a ticket and page the team anyway."
|
|
8644
|
+
};
|
|
8537
8645
|
}
|
|
8538
|
-
return { success: true, status: "filed", ticket_id: data.ticket_id || null, message: data.message || "Bug report sent to the Indelible team." };
|
|
8646
|
+
return { success: true, status: "filed", ticket_id: data.ticket_id || null, forced: !!data.forced, message: data.message || "Bug report sent to the Indelible team." };
|
|
8539
8647
|
} catch (err) {
|
|
8540
8648
|
return { success: false, error: `could not reach intake (${err.message}). Fallback: save_file the report on-chain and share the txid.` };
|
|
8541
8649
|
}
|
|
@@ -9121,7 +9229,7 @@ Commands:
|
|
|
9121
9229
|
}
|
|
9122
9230
|
function printHelp() {
|
|
9123
9231
|
console.log(`
|
|
9124
|
-
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.9.
|
|
9232
|
+
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.9.8)
|
|
9125
9233
|
|
|
9126
9234
|
Setup:
|
|
9127
9235
|
indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
|
|
@@ -9428,7 +9536,7 @@ function readStdin() {
|
|
|
9428
9536
|
}
|
|
9429
9537
|
var SERVER_INFO = {
|
|
9430
9538
|
name: "indelible",
|
|
9431
|
-
version: "4.9.
|
|
9539
|
+
version: "4.9.8",
|
|
9432
9540
|
description: "Blockchain-backed memory and code storage for Claude Code"
|
|
9433
9541
|
};
|
|
9434
9542
|
var TOOLS = [
|
|
@@ -9754,7 +9862,8 @@ var TOOLS = [
|
|
|
9754
9862
|
description: { type: "string", description: "What you were doing, what went wrong, and exact repro steps." },
|
|
9755
9863
|
severity: { type: "string", enum: ["low", "medium", "high"], description: "User-facing impact." },
|
|
9756
9864
|
tool_name: { type: "string", description: "Which Indelible tool/command failed (optional)." },
|
|
9757
|
-
last_error: { type: "string", description: "The exact error string you saw (optional)." }
|
|
9865
|
+
last_error: { type: "string", description: "The exact error string you saw (optional)." },
|
|
9866
|
+
still_file: { type: "boolean", description: "Set true ONLY to override a known-issue match \u2014 files a ticket and pages the team even if the report looks like an already-fixed issue. Use when the user says their case is genuinely different." }
|
|
9758
9867
|
},
|
|
9759
9868
|
required: ["summary", "description"]
|
|
9760
9869
|
}
|
|
@@ -9909,7 +10018,8 @@ async function handleMcpRequest(request) {
|
|
|
9909
10018
|
description: args2?.description,
|
|
9910
10019
|
severity: args2?.severity || "medium",
|
|
9911
10020
|
tool_name: args2?.tool_name || null,
|
|
9912
|
-
last_error: args2?.last_error || null
|
|
10021
|
+
last_error: args2?.last_error || null,
|
|
10022
|
+
still_file: args2?.still_file === true
|
|
9913
10023
|
});
|
|
9914
10024
|
break;
|
|
9915
10025
|
case "share_session":
|