mindgraph-mcp 0.24.0 → 0.26.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 +11 -2
- package/dist/cli.js +2027 -1808
- package/dist/index.js +1743 -1515
- package/dist/server.d.mts +19 -3
- package/dist/server.d.ts +19 -3
- package/dist/server.js +1734 -1611
- package/dist/server.mjs +1726 -1605
- package/package.json +7 -3
package/dist/server.js
CHANGED
|
@@ -35,9 +35,7 @@ __export(server_exports, {
|
|
|
35
35
|
handleHostedRequest: () => handleHostedRequest
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(server_exports);
|
|
38
|
-
var import_server = require("@modelcontextprotocol/
|
|
39
|
-
var import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
|
|
40
|
-
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
38
|
+
var import_server = require("@modelcontextprotocol/server");
|
|
41
39
|
|
|
42
40
|
// src/error-detail.ts
|
|
43
41
|
var import_mindgraph = require("mindgraph");
|
|
@@ -145,12 +143,21 @@ function unwrapSearchResult(value) {
|
|
|
145
143
|
}
|
|
146
144
|
return void 0;
|
|
147
145
|
}
|
|
146
|
+
function supersededBy(node) {
|
|
147
|
+
const marker = node.superseded;
|
|
148
|
+
if (!marker) return void 0;
|
|
149
|
+
const head = obj(marker);
|
|
150
|
+
if (!head) return str(node.superseded_by) ?? "";
|
|
151
|
+
const label = (str(head.head_label) ?? str(head.head_uid) ?? "").replace(/\s+/g, " ").trim();
|
|
152
|
+
const short = label.length <= 80 ? label : `${label.slice(0, 79)}\u2026`;
|
|
153
|
+
const date = str(head.head_stated_at);
|
|
154
|
+
return short && date ? `${short} (${date.slice(0, 10)})` : short;
|
|
155
|
+
}
|
|
148
156
|
function statusTag(node) {
|
|
149
157
|
const tags = [];
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
);
|
|
158
|
+
const replacement = supersededBy(node);
|
|
159
|
+
if (replacement !== void 0) {
|
|
160
|
+
tags.push(replacement ? `SUPERSEDED by ${replacement}` : "SUPERSEDED");
|
|
154
161
|
}
|
|
155
162
|
const truth = str(node.truth_status) ?? str(obj(node.props)?.truth_status);
|
|
156
163
|
if (truth && truth !== "accepted") tags.push(`truth: ${truth}`);
|
|
@@ -420,13 +427,13 @@ function resumeSection(heading, values, state, fallbackType) {
|
|
|
420
427
|
const items = values.map((value) => resumeEntry(value, state, fallbackType)).filter((value) => Boolean(value));
|
|
421
428
|
return items.length > 0 ? { heading, items } : void 0;
|
|
422
429
|
}
|
|
423
|
-
function renderResumeContext(response, maxChars) {
|
|
430
|
+
function renderResumeContext(response, maxChars, repositoryNames = {}) {
|
|
424
431
|
const payload = obj(response);
|
|
425
432
|
if (!payload) return void 0;
|
|
426
433
|
const state = { bounded: false, omitted: 0 };
|
|
427
434
|
const status = str(payload.status);
|
|
428
435
|
if (!payload.task && status === "no_eligible_work") {
|
|
429
|
-
const scopedOut =
|
|
436
|
+
const scopedOut = typeof payload.scoped_out_tasks === "number" ? payload.scoped_out_tasks : 0;
|
|
430
437
|
const note = scopedOut > 0 ? ` ${scopedOut} task(s) exist outside the requested scope.` : "";
|
|
431
438
|
return assemble(
|
|
432
439
|
"# Resume context",
|
|
@@ -440,6 +447,17 @@ function renderResumeContext(response, maxChars) {
|
|
|
440
447
|
if (section) sections.push(section);
|
|
441
448
|
};
|
|
442
449
|
push(resumeSection("## Current task", [payload.task], state, "Task"));
|
|
450
|
+
const other = otherEligibleWork(payload, repositoryNames);
|
|
451
|
+
if (other.shown > 0) {
|
|
452
|
+
sections.push({
|
|
453
|
+
heading: `## Other eligible work (${other.shown} shown${other.more > 0 ? `, ${other.more} more not listed` : ""})`,
|
|
454
|
+
items: other.groups.flatMap(
|
|
455
|
+
(group) => group.tasks.map(
|
|
456
|
+
(task) => `- ${group.repository}: ${task.label} [${task.uid}]${task.detail ? ` \u2014 ${task.detail}` : ""}`
|
|
457
|
+
)
|
|
458
|
+
)
|
|
459
|
+
});
|
|
460
|
+
}
|
|
443
461
|
push(
|
|
444
462
|
resumeSection(
|
|
445
463
|
"## Goal and project",
|
|
@@ -511,6 +529,37 @@ function renderResumeContext(response, maxChars) {
|
|
|
511
529
|
if (sections.length === 0) return void 0;
|
|
512
530
|
return assemble("# Resume context", sections, state, maxChars);
|
|
513
531
|
}
|
|
532
|
+
function otherEligibleWork(brief, names = {}) {
|
|
533
|
+
const listed = Array.isArray(brief?.other_eligible) ? brief.other_eligible : [];
|
|
534
|
+
const groups = [];
|
|
535
|
+
let shown = 0;
|
|
536
|
+
for (const entry of listed) {
|
|
537
|
+
const task = obj(entry);
|
|
538
|
+
const uid = typeof task?.uid === "string" ? task.uid : void 0;
|
|
539
|
+
if (!task || !uid) continue;
|
|
540
|
+
const scopes = Array.isArray(task.scope_uids) ? task.scope_uids.map(String) : [];
|
|
541
|
+
const named = scopes.find((scope) => names[scope]);
|
|
542
|
+
const repository = named ? names[named] : "Other scopes";
|
|
543
|
+
let group = groups.find((candidate) => candidate.repository === repository);
|
|
544
|
+
if (!group) {
|
|
545
|
+
group = { repository, tasks: [] };
|
|
546
|
+
groups.push(group);
|
|
547
|
+
}
|
|
548
|
+
const label = typeof task.label === "string" && task.label ? task.label : "(unlabeled)";
|
|
549
|
+
const detail = [
|
|
550
|
+
typeof task.status === "string" ? task.status : void 0,
|
|
551
|
+
typeof task.priority === "string" && task.priority ? `priority ${task.priority}` : void 0
|
|
552
|
+
].filter(Boolean).join(", ");
|
|
553
|
+
group.tasks.push({
|
|
554
|
+
uid,
|
|
555
|
+
label: label.length > 160 ? `${label.slice(0, 159)}\u2026` : label,
|
|
556
|
+
detail
|
|
557
|
+
});
|
|
558
|
+
shown += 1;
|
|
559
|
+
}
|
|
560
|
+
const total = typeof brief?.eligible_total === "number" ? brief.eligible_total : 0;
|
|
561
|
+
return { groups, shown, more: Math.max(0, total - 1 - shown) };
|
|
562
|
+
}
|
|
514
563
|
function renderNodeList(response, title, limit, maxChars) {
|
|
515
564
|
let candidates = [];
|
|
516
565
|
if (Array.isArray(response)) {
|
|
@@ -561,15 +610,15 @@ function renderTraversal(response, maxChars) {
|
|
|
561
610
|
const labelByUid = /* @__PURE__ */ new Map();
|
|
562
611
|
for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
|
|
563
612
|
if (paths.length > 0) {
|
|
564
|
-
const items = paths.map((raw) => obj(raw)).filter((
|
|
565
|
-
const uids = Array.isArray(
|
|
566
|
-
const labels = Array.isArray(
|
|
613
|
+
const items = paths.map((raw) => obj(raw)).filter((path3) => Boolean(path3)).map((path3, index) => {
|
|
614
|
+
const uids = Array.isArray(path3.node_uids) ? path3.node_uids.map(String) : [];
|
|
615
|
+
const labels = Array.isArray(path3.labels) ? path3.labels.map(String) : Array.isArray(path3.nodes) ? path3.nodes.map((hop) => {
|
|
567
616
|
const hopNode = obj(hop);
|
|
568
617
|
return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
|
|
569
618
|
}) : [];
|
|
570
619
|
const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
|
|
571
|
-
const cost = num(
|
|
572
|
-
const confidence = num(
|
|
620
|
+
const cost = num(path3.cost) ?? num(path3.path_cost);
|
|
621
|
+
const confidence = num(path3.path_confidence);
|
|
573
622
|
const meta = [
|
|
574
623
|
cost !== void 0 ? `cost ${cost}` : void 0,
|
|
575
624
|
confidence !== void 0 ? `confidence ${confidence}` : void 0
|
|
@@ -1584,777 +1633,168 @@ ${conversation}`
|
|
|
1584
1633
|
}
|
|
1585
1634
|
|
|
1586
1635
|
// src/memory-tool.ts
|
|
1636
|
+
var import_node_crypto2 = require("crypto");
|
|
1637
|
+
|
|
1638
|
+
// src/workspace-scope.ts
|
|
1639
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
1640
|
+
var import_node_path2 = __toESM(require("path"));
|
|
1641
|
+
|
|
1642
|
+
// src/codegraph.ts
|
|
1643
|
+
var import_node_child_process = require("child_process");
|
|
1644
|
+
var import_node_fs = require("fs");
|
|
1645
|
+
var import_promises = require("fs/promises");
|
|
1646
|
+
var import_node_os = require("os");
|
|
1647
|
+
var import_node_path = require("path");
|
|
1587
1648
|
var import_node_crypto = require("crypto");
|
|
1588
|
-
var
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
name: "mindgraph_memory",
|
|
1596
|
-
description: "Read the portable MindGraph memory surface. Use action='context' with mode='resume' or 'continuity' for authoritative task-first work state, and mode='topic' with a short keyword query for prior knowledge relevant to the user's request. Use action='status' to inspect integration capabilities and limitations. This tool never writes; durable capture and correction use the existing typed MindGraph write tools.",
|
|
1597
|
-
inputSchema: {
|
|
1598
|
-
type: "object",
|
|
1599
|
-
properties: {
|
|
1600
|
-
action: {
|
|
1601
|
-
type: "string",
|
|
1602
|
-
enum: ["status", "context"],
|
|
1603
|
-
description: "Read integration status or bounded memory context"
|
|
1604
|
-
},
|
|
1605
|
-
mode: {
|
|
1606
|
-
type: "string",
|
|
1607
|
-
enum: ["topic", "continuity", "resume"],
|
|
1608
|
-
description: "Context strategy. topic uses query-shaped retrieval; continuity/resume use authoritative task-first resume_work."
|
|
1609
|
-
},
|
|
1610
|
-
query: {
|
|
1611
|
-
type: "string",
|
|
1612
|
-
description: "Required for mode='topic': 1-3 discriminating BM25 keywords, not a full sentence"
|
|
1613
|
-
},
|
|
1614
|
-
project_uid: { type: "string", description: "Optional project scope" },
|
|
1615
|
-
task_uid: { type: "string", description: "Optional task scope for resume context" },
|
|
1616
|
-
session_uid: { type: "string", description: "Optional durable session scope" },
|
|
1617
|
-
scope_uids: {
|
|
1618
|
-
type: "array",
|
|
1619
|
-
items: { type: "string" },
|
|
1620
|
-
description: "Optional explicit graph scopes for continuity/resume"
|
|
1621
|
-
},
|
|
1622
|
-
node_types: {
|
|
1623
|
-
type: "array",
|
|
1624
|
-
items: { type: "string" },
|
|
1625
|
-
description: "Optional node-type filter for topic context"
|
|
1626
|
-
},
|
|
1627
|
-
layer: {
|
|
1628
|
-
type: "string",
|
|
1629
|
-
enum: ["reality", "epistemic", "intent", "action", "memory", "agent"],
|
|
1630
|
-
description: "Optional cognitive-layer filter for topic context"
|
|
1631
|
-
},
|
|
1632
|
-
limit: {
|
|
1633
|
-
type: "integer",
|
|
1634
|
-
minimum: 1,
|
|
1635
|
-
maximum: 50,
|
|
1636
|
-
default: 8,
|
|
1637
|
-
description: "Maximum direct graph items requested before bounded expansion"
|
|
1638
|
-
},
|
|
1639
|
-
include_chunks: {
|
|
1640
|
-
type: "boolean",
|
|
1641
|
-
default: false,
|
|
1642
|
-
description: "Include bounded source excerpts when topic summaries are insufficient"
|
|
1643
|
-
},
|
|
1644
|
-
include_documents: {
|
|
1645
|
-
type: "boolean",
|
|
1646
|
-
default: false,
|
|
1647
|
-
description: "Include bounded knowledge articles for topic context"
|
|
1648
|
-
},
|
|
1649
|
-
valid_at: {
|
|
1650
|
-
type: "string",
|
|
1651
|
-
description: "Optional ISO timestamp for time-aware topic retrieval"
|
|
1652
|
-
},
|
|
1653
|
-
graph_expansion_limit: {
|
|
1654
|
-
type: "integer",
|
|
1655
|
-
minimum: 0,
|
|
1656
|
-
maximum: 20,
|
|
1657
|
-
default: 3,
|
|
1658
|
-
description: "Bounded graph neighbors added after direct topic matches"
|
|
1659
|
-
},
|
|
1660
|
-
graph_max_depth: {
|
|
1661
|
-
type: "integer",
|
|
1662
|
-
minimum: 0,
|
|
1663
|
-
maximum: 5,
|
|
1664
|
-
default: 2,
|
|
1665
|
-
description: "Maximum graph-expansion depth for topic context"
|
|
1666
|
-
},
|
|
1667
|
-
max_output_chars: {
|
|
1668
|
-
type: "integer",
|
|
1669
|
-
minimum: MIN_OUTPUT_CHARS,
|
|
1670
|
-
maximum: MAX_OUTPUT_CHARS,
|
|
1671
|
-
default: DEFAULT_OUTPUT_CHARS,
|
|
1672
|
-
description: "Hard character budget for model-ready context; whole items are retained or omitted"
|
|
1673
|
-
},
|
|
1674
|
-
agent_id: { type: "string", description: "Agent identity" }
|
|
1675
|
-
},
|
|
1676
|
-
required: ["action"]
|
|
1677
|
-
},
|
|
1678
|
-
outputSchema: {
|
|
1679
|
-
type: "object",
|
|
1680
|
-
properties: {
|
|
1681
|
-
schema_version: { type: "string", const: RESULT_SCHEMA_VERSION },
|
|
1682
|
-
action: { type: "string", enum: ["status", "context"] },
|
|
1683
|
-
profile: { type: "string" },
|
|
1684
|
-
server: { type: "object" },
|
|
1685
|
-
integration: { type: "object" },
|
|
1686
|
-
capabilities: { type: "object" },
|
|
1687
|
-
degradation: { type: "array", items: { type: "object" } },
|
|
1688
|
-
context_id: { type: "string" },
|
|
1689
|
-
mode: { type: "string", enum: ["topic", "continuity", "resume"] },
|
|
1690
|
-
scope: { type: "object" },
|
|
1691
|
-
rendered_context: { type: "string" },
|
|
1692
|
-
rendered_hash: { type: "string" },
|
|
1693
|
-
items: { type: "array", items: { type: "object" } },
|
|
1694
|
-
retrieval_trace: { type: "object" },
|
|
1695
|
-
budget: { type: "object" },
|
|
1696
|
-
freshness: { type: "object" },
|
|
1697
|
-
warnings: { type: "array", items: { type: "object" } }
|
|
1698
|
-
},
|
|
1699
|
-
required: ["schema_version", "action"],
|
|
1700
|
-
oneOf: [
|
|
1701
|
-
{
|
|
1702
|
-
properties: { action: { const: "status" } },
|
|
1703
|
-
required: ["profile", "server", "integration", "scope", "capabilities", "degradation"]
|
|
1704
|
-
},
|
|
1705
|
-
{
|
|
1706
|
-
properties: { action: { const: "context" } },
|
|
1707
|
-
required: [
|
|
1708
|
-
"context_id",
|
|
1709
|
-
"mode",
|
|
1710
|
-
"scope",
|
|
1711
|
-
"rendered_context",
|
|
1712
|
-
"rendered_hash",
|
|
1713
|
-
"items",
|
|
1714
|
-
"retrieval_trace",
|
|
1715
|
-
"budget",
|
|
1716
|
-
"freshness",
|
|
1717
|
-
"warnings"
|
|
1718
|
-
]
|
|
1719
|
-
}
|
|
1720
|
-
]
|
|
1721
|
-
},
|
|
1722
|
-
annotations: {
|
|
1723
|
-
readOnlyHint: true,
|
|
1724
|
-
destructiveHint: false,
|
|
1725
|
-
idempotentHint: true,
|
|
1726
|
-
openWorldHint: false
|
|
1649
|
+
var UnknownRepositoryError = class extends Error {
|
|
1650
|
+
constructor(repo) {
|
|
1651
|
+
super(
|
|
1652
|
+
`unknown repository "${repo}": not declared in .mindgraph/workspace.json, not the current repository, and no such path exists`
|
|
1653
|
+
);
|
|
1654
|
+
this.repo = repo;
|
|
1655
|
+
this.name = "UnknownRepositoryError";
|
|
1727
1656
|
}
|
|
1657
|
+
repo;
|
|
1728
1658
|
};
|
|
1729
|
-
function
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1659
|
+
function invocationCwd(args) {
|
|
1660
|
+
const context = args.invocation_context;
|
|
1661
|
+
if (context && typeof context === "object" && !Array.isArray(context)) {
|
|
1662
|
+
const cwd = context.cwd;
|
|
1663
|
+
if (typeof cwd === "string" && cwd) return cwd;
|
|
1664
|
+
}
|
|
1665
|
+
return process.cwd();
|
|
1734
1666
|
}
|
|
1735
|
-
function
|
|
1736
|
-
|
|
1737
|
-
if (
|
|
1738
|
-
|
|
1667
|
+
function unavailabilityCaveats(reason, message) {
|
|
1668
|
+
const caveats = [message || "codegraph status failed"];
|
|
1669
|
+
if (reason === "absent") {
|
|
1670
|
+
caveats.push(
|
|
1671
|
+
"Optional: install codegraph (https://github.com/colbymchenry/codegraph) and run `codegraph init` in this repository to enable code anchoring and structural recall. Memory and work tools are unaffected."
|
|
1672
|
+
);
|
|
1739
1673
|
}
|
|
1740
|
-
return
|
|
1674
|
+
return caveats;
|
|
1741
1675
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1676
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
1677
|
+
var DEFAULT_STATUS_TTL_MS = 6e4;
|
|
1678
|
+
var MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
1679
|
+
function normalizePath(path3) {
|
|
1680
|
+
return path3.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
1744
1681
|
}
|
|
1745
|
-
function
|
|
1746
|
-
|
|
1747
|
-
return Math.min(MAX_OUTPUT_CHARS, Math.max(MIN_OUTPUT_CHARS, Math.floor(value)));
|
|
1682
|
+
function normalizeSignature(signature) {
|
|
1683
|
+
return signature.replace(/\s+/g, " ").trim();
|
|
1748
1684
|
}
|
|
1749
|
-
function
|
|
1750
|
-
|
|
1685
|
+
function languageFromPath(path3) {
|
|
1686
|
+
const extension = (0, import_node_path.extname)(path3).toLowerCase();
|
|
1687
|
+
return {
|
|
1688
|
+
".c": "c",
|
|
1689
|
+
".cc": "cpp",
|
|
1690
|
+
".cpp": "cpp",
|
|
1691
|
+
".go": "go",
|
|
1692
|
+
".java": "java",
|
|
1693
|
+
".js": "javascript",
|
|
1694
|
+
".jsx": "javascript",
|
|
1695
|
+
".kt": "kotlin",
|
|
1696
|
+
".py": "python",
|
|
1697
|
+
".rb": "ruby",
|
|
1698
|
+
".rs": "rust",
|
|
1699
|
+
".swift": "swift",
|
|
1700
|
+
".ts": "typescript",
|
|
1701
|
+
".tsx": "typescript"
|
|
1702
|
+
}[extension] ?? "unknown";
|
|
1751
1703
|
}
|
|
1752
|
-
function
|
|
1704
|
+
function asNode(hit) {
|
|
1705
|
+
return hit.node ?? hit;
|
|
1706
|
+
}
|
|
1707
|
+
function toCandidate(node) {
|
|
1708
|
+
if (!node.filePath || !node.kind || !(node.qualifiedName || node.name)) return null;
|
|
1753
1709
|
return {
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
}),
|
|
1762
|
-
integration: {
|
|
1763
|
-
adapter_state: "model_controlled",
|
|
1764
|
-
harness: config.harness ?? "generic",
|
|
1765
|
-
configured_tool_profile: config.profile ?? "general",
|
|
1766
|
-
automatic_capture: false,
|
|
1767
|
-
automatic_delivery: false,
|
|
1768
|
-
delivery_verification: "unsupported",
|
|
1769
|
-
curator_mode: "disabled"
|
|
1770
|
-
},
|
|
1771
|
-
scope: defined({ org_id: config.orgId, agent_id: config.agentId }),
|
|
1772
|
-
capabilities: {
|
|
1773
|
-
status: true,
|
|
1774
|
-
topic_context: true,
|
|
1775
|
-
authoritative_resume_context: true,
|
|
1776
|
-
bounded_rendering: true,
|
|
1777
|
-
structured_content: true,
|
|
1778
|
-
typed_explicit_writes: true,
|
|
1779
|
-
lifecycle_event_ingest: false,
|
|
1780
|
-
automatic_context_injection: false,
|
|
1781
|
-
curator_mutation: false
|
|
1782
|
-
},
|
|
1783
|
-
degradation: [
|
|
1784
|
-
{
|
|
1785
|
-
code: "automatic_capture_unavailable",
|
|
1786
|
-
message: "M0 does not observe host lifecycle events; explicit typed write tools remain available."
|
|
1787
|
-
},
|
|
1788
|
-
{
|
|
1789
|
-
code: "delivery_unverified",
|
|
1790
|
-
message: "A successful MCP read does not prove that the host injected or used the returned context."
|
|
1791
|
-
},
|
|
1792
|
-
{
|
|
1793
|
-
code: "curator_disabled",
|
|
1794
|
-
message: "Curator proposals and autonomous mutation are not enabled in M0."
|
|
1795
|
-
}
|
|
1796
|
-
]
|
|
1710
|
+
path: normalizePath(node.filePath),
|
|
1711
|
+
language: node.language ?? languageFromPath(node.filePath),
|
|
1712
|
+
kind: node.kind,
|
|
1713
|
+
qualifiedName: node.qualifiedName ?? node.name ?? "",
|
|
1714
|
+
signature: node.signature ?? null,
|
|
1715
|
+
startLine: node.startLine ?? 1,
|
|
1716
|
+
endLine: node.endLine ?? node.startLine ?? 1
|
|
1797
1717
|
};
|
|
1798
1718
|
}
|
|
1799
|
-
function
|
|
1800
|
-
const
|
|
1801
|
-
if (
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
const wrapper = obj2(raw);
|
|
1806
|
-
if (!wrapper) return void 0;
|
|
1807
|
-
const value = obj2(wrapper.node) ?? obj2(wrapper.object) ?? wrapper;
|
|
1808
|
-
const uid = text(value.uid) ?? text(value.chunk_uid);
|
|
1809
|
-
if (!uid) return void 0;
|
|
1810
|
-
return defined({
|
|
1811
|
-
uid,
|
|
1812
|
-
type: fallbackType ?? nodeType(value),
|
|
1813
|
-
label: text(value.label) ?? text(value.document_title),
|
|
1814
|
-
truth_status: text(value.truth_status) ?? text(obj2(value.props)?.truth_status),
|
|
1815
|
-
superseded: value.superseded === true ? true : void 0,
|
|
1816
|
-
currently_valid: value.currently_valid === false ? false : void 0,
|
|
1817
|
-
valid_at_time: value.valid_at_time === false ? false : void 0,
|
|
1818
|
-
retrieval_origin: text(value.retrieval_origin),
|
|
1819
|
-
retrieval_depth: typeof value.retrieval_depth === "number" ? value.retrieval_depth : void 0
|
|
1820
|
-
});
|
|
1821
|
-
}
|
|
1822
|
-
function topicCandidates(raw) {
|
|
1823
|
-
const payload = obj2(raw);
|
|
1824
|
-
if (!payload) return [];
|
|
1825
|
-
const graph = obj2(payload.graph);
|
|
1826
|
-
const items = [];
|
|
1827
|
-
for (const article of Array.isArray(payload.articles) ? payload.articles : []) {
|
|
1828
|
-
const item = compactItem(article, "Article");
|
|
1829
|
-
if (item) items.push(item);
|
|
1830
|
-
}
|
|
1831
|
-
const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
|
1832
|
-
const labels = /* @__PURE__ */ new Map();
|
|
1833
|
-
for (const node of rawNodes) {
|
|
1834
|
-
const item = compactItem(node);
|
|
1835
|
-
if (item) {
|
|
1836
|
-
if (HIDDEN_CONTEXT_NODE_TYPES.has(text(item.type) ?? "")) continue;
|
|
1837
|
-
items.push(item);
|
|
1838
|
-
const uid = text(item.uid);
|
|
1839
|
-
const label = text(item.label);
|
|
1840
|
-
if (uid && label) labels.set(uid, label);
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
for (const rawEdge of Array.isArray(graph?.edges) ? graph.edges : []) {
|
|
1844
|
-
const edge = obj2(rawEdge);
|
|
1845
|
-
const fromUid = text(edge?.from_uid);
|
|
1846
|
-
const toUid = text(edge?.to_uid);
|
|
1847
|
-
const edgeType = text(edge?.edge_type) ?? text(obj2(edge?.edge_type)?.Custom);
|
|
1848
|
-
if (!fromUid || !toUid || !edgeType || HIDDEN_CONTEXT_EDGE_TYPES.has(edgeType) || !labels.has(fromUid) || !labels.has(toUid)) {
|
|
1849
|
-
continue;
|
|
1850
|
-
}
|
|
1851
|
-
items.push(
|
|
1852
|
-
defined({
|
|
1853
|
-
type: "Relationship",
|
|
1854
|
-
from_uid: fromUid,
|
|
1855
|
-
to_uid: toUid,
|
|
1856
|
-
edge_type: edgeType,
|
|
1857
|
-
from_label: labels.get(fromUid),
|
|
1858
|
-
to_label: labels.get(toUid)
|
|
1859
|
-
})
|
|
1860
|
-
);
|
|
1861
|
-
}
|
|
1862
|
-
for (const chunk of Array.isArray(payload.chunks) ? payload.chunks : []) {
|
|
1863
|
-
const item = compactItem(chunk, "SourceExcerpt");
|
|
1864
|
-
if (item) items.push(item);
|
|
1865
|
-
}
|
|
1866
|
-
return items;
|
|
1867
|
-
}
|
|
1868
|
-
function resumeCandidates(raw) {
|
|
1869
|
-
const payload = obj2(raw);
|
|
1870
|
-
if (!payload) return [];
|
|
1871
|
-
const items = [];
|
|
1872
|
-
for (const key of ["task", "goal", "project", "plan", "active_execution", "lease"]) {
|
|
1873
|
-
const item = compactItem(payload[key], key);
|
|
1874
|
-
if (item) items.push(item);
|
|
1875
|
-
}
|
|
1876
|
-
for (const key of ["next_steps", "recent_executions", "blockers", "code_targets"]) {
|
|
1877
|
-
for (const value of Array.isArray(payload[key]) ? payload[key] : []) {
|
|
1878
|
-
const item = compactItem(value, key.replace(/s$/, ""));
|
|
1879
|
-
if (item) items.push(item);
|
|
1880
|
-
}
|
|
1719
|
+
function normalizeRemote(remote) {
|
|
1720
|
+
const trimmed = remote.trim();
|
|
1721
|
+
if (!trimmed) return null;
|
|
1722
|
+
const scp = /^(?:[^@]+@)?([^:]+):(.+)$/.exec(trimmed);
|
|
1723
|
+
if (scp && !trimmed.includes("://")) {
|
|
1724
|
+
return `${scp[1].toLowerCase()}/${scp[2].replace(/\.git$/, "")}`;
|
|
1881
1725
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1726
|
+
try {
|
|
1727
|
+
const url = new URL(trimmed);
|
|
1728
|
+
const pathname = url.pathname.replace(/\/$/, "").replace(/\.git$/, "");
|
|
1729
|
+
return `${url.host.toLowerCase()}${pathname}`;
|
|
1730
|
+
} catch {
|
|
1731
|
+
return null;
|
|
1888
1732
|
}
|
|
1889
|
-
return items;
|
|
1890
1733
|
}
|
|
1891
|
-
function
|
|
1892
|
-
const
|
|
1893
|
-
return
|
|
1894
|
-
const uid = text(item.uid);
|
|
1895
|
-
if (uid) {
|
|
1896
|
-
if (seen.has(uid) || !rendered2.includes(`[${uid}]`)) return false;
|
|
1897
|
-
seen.add(uid);
|
|
1898
|
-
return true;
|
|
1899
|
-
}
|
|
1900
|
-
if (item.type === "Relationship") {
|
|
1901
|
-
const from = text(item.from_label);
|
|
1902
|
-
const to = text(item.to_label);
|
|
1903
|
-
const edgeType = text(item.edge_type);
|
|
1904
|
-
if (!from || !to || !edgeType) return false;
|
|
1905
|
-
const key = `${text(item.from_uid)}:${edgeType}:${text(item.to_uid)}`;
|
|
1906
|
-
if (seen.has(key) || !rendered2.includes(`${from} \u2014${edgeType}\u2192 ${to}`)) return false;
|
|
1907
|
-
seen.add(key);
|
|
1908
|
-
return true;
|
|
1909
|
-
}
|
|
1910
|
-
return false;
|
|
1911
|
-
});
|
|
1734
|
+
function pathContains(root, candidate) {
|
|
1735
|
+
const child = (0, import_node_path.relative)(root, candidate);
|
|
1736
|
+
return child === "" || child !== ".." && !child.startsWith(`..${import_node_path.sep}`) && !(0, import_node_path.isAbsolute)(child);
|
|
1912
1737
|
}
|
|
1913
|
-
function
|
|
1914
|
-
let
|
|
1915
|
-
|
|
1916
|
-
if (
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
const record = obj2(current);
|
|
1921
|
-
if (!record) return;
|
|
1922
|
-
for (const [key, item] of Object.entries(record)) {
|
|
1923
|
-
if (["updated_at", "created_at", "completed_at", "started_at", "observed_at"].includes(key) && typeof item === "string" && !Number.isNaN(Date.parse(item)) && (!latest || Date.parse(item) > Date.parse(latest))) {
|
|
1924
|
-
latest = item;
|
|
1738
|
+
function nearestExistingDirectory(candidate) {
|
|
1739
|
+
let current = (0, import_node_path.resolve)(candidate);
|
|
1740
|
+
for (; ; ) {
|
|
1741
|
+
if ((0, import_node_fs.existsSync)(current)) {
|
|
1742
|
+
try {
|
|
1743
|
+
return (0, import_node_fs.statSync)(current).isDirectory() ? current : (0, import_node_path.dirname)(current);
|
|
1744
|
+
} catch {
|
|
1925
1745
|
}
|
|
1926
|
-
visit(item);
|
|
1927
|
-
}
|
|
1928
|
-
};
|
|
1929
|
-
visit(value);
|
|
1930
|
-
return latest;
|
|
1931
|
-
}
|
|
1932
|
-
function contextWarnings(raw, rendered2, available, returned) {
|
|
1933
|
-
const warnings = [
|
|
1934
|
-
{
|
|
1935
|
-
code: "delivery_unverified",
|
|
1936
|
-
message: "The MCP server returned this context but cannot verify that the host injected or used it."
|
|
1937
|
-
}
|
|
1938
|
-
];
|
|
1939
|
-
const serialized = JSON.stringify(raw);
|
|
1940
|
-
if (available > returned || rendered2.includes("item(s) omitted")) {
|
|
1941
|
-
warnings.push({
|
|
1942
|
-
code: "context_bounded",
|
|
1943
|
-
message: `${Math.max(0, available - returned)} structured item(s) were omitted from the rendered context.`
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
if (/"superseded":true/.test(serialized)) {
|
|
1947
|
-
warnings.push({ code: "superseded_items_present", message: "Some retrieved items are superseded." });
|
|
1948
|
-
}
|
|
1949
|
-
if (/"currently_valid":false|"valid_at_time":false/.test(serialized)) {
|
|
1950
|
-
warnings.push({
|
|
1951
|
-
code: "validity_warning",
|
|
1952
|
-
message: "Some retrieved items are outside their current or requested validity window."
|
|
1953
|
-
});
|
|
1954
|
-
}
|
|
1955
|
-
const payload = obj2(raw);
|
|
1956
|
-
if (payload?.truncated === true || obj2(payload?.truncation)) {
|
|
1957
|
-
const truncation = obj2(payload?.truncation);
|
|
1958
|
-
if (payload?.truncated === true || Object.values(truncation ?? {}).some(Boolean)) {
|
|
1959
|
-
warnings.push({ code: "source_truncated", message: "The authoritative source reported truncated fields." });
|
|
1960
1746
|
}
|
|
1747
|
+
const parent = (0, import_node_path.dirname)(current);
|
|
1748
|
+
if (parent === current) return current;
|
|
1749
|
+
current = parent;
|
|
1961
1750
|
}
|
|
1962
|
-
|
|
1963
|
-
|
|
1751
|
+
}
|
|
1752
|
+
var MAX_CHILD_DIRECTORIES = 200;
|
|
1753
|
+
function findChildWorkspaceFiles(start) {
|
|
1754
|
+
let entries;
|
|
1755
|
+
try {
|
|
1756
|
+
entries = (0, import_node_fs.readdirSync)(start, { withFileTypes: true });
|
|
1757
|
+
} catch {
|
|
1758
|
+
return [];
|
|
1964
1759
|
}
|
|
1965
|
-
return
|
|
1760
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).slice(0, MAX_CHILD_DIRECTORIES).map((entry) => (0, import_node_path.join)(start, entry.name, ".mindgraph", "workspace.json")).filter((candidate) => (0, import_node_fs.existsSync)(candidate)).sort();
|
|
1966
1761
|
}
|
|
1967
|
-
function
|
|
1968
|
-
return
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1762
|
+
function findWorkspaceFile(start, explicit) {
|
|
1763
|
+
if (explicit) return (0, import_node_path.resolve)(start, explicit);
|
|
1764
|
+
let current = (0, import_node_path.resolve)(start);
|
|
1765
|
+
for (; ; ) {
|
|
1766
|
+
const candidate = (0, import_node_path.join)(current, ".mindgraph", "workspace.json");
|
|
1767
|
+
if ((0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
1768
|
+
const parent = (0, import_node_path.dirname)(current);
|
|
1769
|
+
if (parent === current) return void 0;
|
|
1770
|
+
current = parent;
|
|
1771
|
+
}
|
|
1972
1772
|
}
|
|
1973
|
-
async function
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
};
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.min(50, Math.max(1, Math.floor(args.limit))) : 8;
|
|
1999
|
-
const request = defined({
|
|
2000
|
-
query,
|
|
2001
|
-
project_uid: text(args.project_uid),
|
|
2002
|
-
node_limit: limit,
|
|
2003
|
-
node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
|
|
2004
|
-
layer: text(args.layer),
|
|
2005
|
-
chunk_limit: args.include_chunks === true ? limit : 0,
|
|
2006
|
-
article_limit: args.include_documents === true ? limit : 0,
|
|
2007
|
-
valid_at: text(args.valid_at),
|
|
2008
|
-
include_graph: true,
|
|
2009
|
-
graph_expansion_limit: typeof args.graph_expansion_limit === "number" ? args.graph_expansion_limit : 3,
|
|
2010
|
-
graph_max_depth: typeof args.graph_max_depth === "number" ? args.graph_max_depth : 2
|
|
2011
|
-
});
|
|
2012
|
-
raw = await client.retrieveContext(request);
|
|
2013
|
-
rendered2 = renderContext(raw, budget) ?? "No results found for this search.";
|
|
2014
|
-
strategy = "retrieve_context";
|
|
2015
|
-
candidates = topicCandidates(raw);
|
|
2016
|
-
} else {
|
|
2017
|
-
const scopeUids = Array.from(
|
|
2018
|
-
/* @__PURE__ */ new Set([
|
|
2019
|
-
...Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : [],
|
|
2020
|
-
...text(args.project_uid) ? [text(args.project_uid)] : []
|
|
2021
|
-
])
|
|
2022
|
-
);
|
|
2023
|
-
raw = await client.plan(
|
|
2024
|
-
defined({
|
|
2025
|
-
action: "resume_work",
|
|
2026
|
-
task_uid: text(args.task_uid),
|
|
2027
|
-
session_uid: text(args.session_uid),
|
|
2028
|
-
scope_uids: scopeUids.length > 0 ? scopeUids : void 0,
|
|
2029
|
-
limit: 1,
|
|
2030
|
-
agent_id: agentId
|
|
2031
|
-
})
|
|
2032
|
-
);
|
|
2033
|
-
rendered2 = renderResumeContext(raw, budget) ?? "No eligible work was found in the requested scope.";
|
|
2034
|
-
strategy = "resume_work";
|
|
2035
|
-
candidates = resumeCandidates(raw);
|
|
2036
|
-
}
|
|
2037
|
-
const items = visibleItems(candidates, rendered2);
|
|
2038
|
-
const renderedHash = sha256(rendered2);
|
|
2039
|
-
const scope = defined({
|
|
2040
|
-
org_id: config.orgId,
|
|
2041
|
-
agent_id: agentId,
|
|
2042
|
-
harness: config.harness ?? "generic",
|
|
2043
|
-
project_uid: text(args.project_uid),
|
|
2044
|
-
task_uid: text(args.task_uid),
|
|
2045
|
-
session_uid: text(args.session_uid),
|
|
2046
|
-
scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0
|
|
2047
|
-
});
|
|
2048
|
-
const contextId = `ctx_${sha256(
|
|
2049
|
-
stableJson({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
|
|
2050
|
-
).slice(0, 24)}`;
|
|
2051
|
-
const bounded = candidates.length > items.length || rendered2.includes("item(s) omitted");
|
|
2052
|
-
const result = {
|
|
2053
|
-
schema_version: RESULT_SCHEMA_VERSION,
|
|
2054
|
-
action: "context",
|
|
2055
|
-
context_id: contextId,
|
|
2056
|
-
mode,
|
|
2057
|
-
scope,
|
|
2058
|
-
rendered_context: rendered2,
|
|
2059
|
-
rendered_hash: renderedHash,
|
|
2060
|
-
items,
|
|
2061
|
-
retrieval_trace: defined({
|
|
2062
|
-
strategy,
|
|
2063
|
-
query: text(args.query),
|
|
2064
|
-
project_uid: text(args.project_uid),
|
|
2065
|
-
task_uid: text(args.task_uid),
|
|
2066
|
-
session_uid: text(args.session_uid),
|
|
2067
|
-
node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
|
|
2068
|
-
graph_expansion_limit: mode === "topic" ? args.graph_expansion_limit ?? 3 : void 0,
|
|
2069
|
-
graph_max_depth: mode === "topic" ? args.graph_max_depth ?? 2 : void 0,
|
|
2070
|
-
available_items: candidates.length,
|
|
2071
|
-
returned_items: items.length,
|
|
2072
|
-
omitted_items: Math.max(0, candidates.length - items.length)
|
|
2073
|
-
}),
|
|
2074
|
-
budget: {
|
|
2075
|
-
unit: "characters",
|
|
2076
|
-
requested_chars: budget,
|
|
2077
|
-
used_chars: rendered2.length,
|
|
2078
|
-
available_items: candidates.length,
|
|
2079
|
-
returned_items: items.length,
|
|
2080
|
-
omitted_items: Math.max(0, candidates.length - items.length),
|
|
2081
|
-
bounded
|
|
2082
|
-
},
|
|
2083
|
-
freshness: defined({
|
|
2084
|
-
valid_at: text(args.valid_at),
|
|
2085
|
-
latest_source_timestamp: latestTimestamp(raw)
|
|
2086
|
-
}),
|
|
2087
|
-
warnings: contextWarnings(raw, rendered2, candidates.length, items.length)
|
|
2088
|
-
};
|
|
2089
|
-
return {
|
|
2090
|
-
content: [{ type: "text", text: rendered2 }],
|
|
2091
|
-
structuredContent: result
|
|
2092
|
-
};
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
// src/resources.ts
|
|
2096
|
-
var RESOURCES = [
|
|
2097
|
-
{
|
|
2098
|
-
uri: "mindgraph://memory/status",
|
|
2099
|
-
name: "Memory Integration Status",
|
|
2100
|
-
description: "Portable memory profile, adapter state, supported capabilities, and explicit limitations",
|
|
2101
|
-
mimeType: "application/json"
|
|
2102
|
-
},
|
|
2103
|
-
{
|
|
2104
|
-
uri: "mindgraph://stats",
|
|
2105
|
-
name: "Graph Statistics",
|
|
2106
|
-
description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
|
|
2107
|
-
mimeType: "application/json"
|
|
2108
|
-
},
|
|
2109
|
-
{
|
|
2110
|
-
uri: "mindgraph://goals",
|
|
2111
|
-
name: "Active Goals",
|
|
2112
|
-
description: "All active goals in the knowledge graph",
|
|
2113
|
-
mimeType: "application/json"
|
|
2114
|
-
},
|
|
2115
|
-
{
|
|
2116
|
-
uri: "mindgraph://questions",
|
|
2117
|
-
name: "Open Questions",
|
|
2118
|
-
description: "All open questions in the knowledge graph",
|
|
2119
|
-
mimeType: "application/json"
|
|
2120
|
-
},
|
|
2121
|
-
{
|
|
2122
|
-
uri: "mindgraph://contradictions",
|
|
2123
|
-
name: "Contradictions",
|
|
2124
|
-
description: "Contradictory claims in the knowledge graph",
|
|
2125
|
-
mimeType: "application/json"
|
|
2126
|
-
},
|
|
2127
|
-
{
|
|
2128
|
-
uri: "mindgraph://decisions",
|
|
2129
|
-
name: "Open Decisions",
|
|
2130
|
-
description: "Unresolved decisions awaiting resolution",
|
|
2131
|
-
mimeType: "application/json"
|
|
2132
|
-
}
|
|
2133
|
-
];
|
|
2134
|
-
var RESOURCE_TEMPLATES = [
|
|
2135
|
-
{
|
|
2136
|
-
uriTemplate: "mindgraph://node/{uid}",
|
|
2137
|
-
name: "Graph Node",
|
|
2138
|
-
description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
|
|
2139
|
-
mimeType: "application/json"
|
|
2140
|
-
},
|
|
2141
|
-
{
|
|
2142
|
-
uriTemplate: "mindgraph://search/{query}",
|
|
2143
|
-
name: "Search Results",
|
|
2144
|
-
description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
|
|
2145
|
-
mimeType: "application/json"
|
|
2146
|
-
},
|
|
2147
|
-
{
|
|
2148
|
-
uriTemplate: "mindgraph://layer/{layer}",
|
|
2149
|
-
name: "Layer Nodes",
|
|
2150
|
-
description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
|
|
2151
|
-
mimeType: "application/json"
|
|
2152
|
-
}
|
|
2153
|
-
];
|
|
2154
|
-
var RESOURCE_GOVERNANCE_TOOL = "mindgraph_retrieve";
|
|
2155
|
-
function json(uri, data) {
|
|
2156
|
-
return {
|
|
2157
|
-
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
|
|
2158
|
-
};
|
|
2159
|
-
}
|
|
2160
|
-
async function readResource(client, uri, memoryConfig = {}) {
|
|
2161
|
-
switch (uri) {
|
|
2162
|
-
case "mindgraph://memory/status":
|
|
2163
|
-
return json(uri, memoryStatus(memoryConfig));
|
|
2164
|
-
case "mindgraph://stats":
|
|
2165
|
-
return json(uri, await client.stats());
|
|
2166
|
-
case "mindgraph://goals":
|
|
2167
|
-
return json(uri, await client.getGoals());
|
|
2168
|
-
case "mindgraph://questions":
|
|
2169
|
-
return json(uri, await client.getOpenQuestions());
|
|
2170
|
-
case "mindgraph://contradictions":
|
|
2171
|
-
return json(uri, await client.getContradictions());
|
|
2172
|
-
case "mindgraph://decisions":
|
|
2173
|
-
return json(uri, await client.getOpenDecisions());
|
|
2174
|
-
}
|
|
2175
|
-
const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
|
|
2176
|
-
if (nodeMatch) {
|
|
2177
|
-
const uid = decodeURIComponent(nodeMatch[1]);
|
|
2178
|
-
const node = await client.getNode(uid);
|
|
2179
|
-
const edges = await client.getEdges({ from_uid: uid });
|
|
2180
|
-
const inEdges = await client.getEdges({ to_uid: uid });
|
|
2181
|
-
return json(uri, { node, outgoing_edges: edges, incoming_edges: inEdges });
|
|
2182
|
-
}
|
|
2183
|
-
const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
|
|
2184
|
-
if (searchMatch) {
|
|
2185
|
-
const query = decodeURIComponent(searchMatch[1]);
|
|
2186
|
-
return json(uri, await client.search(query, { limit: 20 }));
|
|
2187
|
-
}
|
|
2188
|
-
const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
|
|
2189
|
-
if (layerMatch) {
|
|
2190
|
-
const layer = decodeURIComponent(layerMatch[1]);
|
|
2191
|
-
return json(uri, await client.getNodes({ layer, limit: 50 }));
|
|
2192
|
-
}
|
|
2193
|
-
throw new Error(`Unknown resource: ${uri}`);
|
|
2194
|
-
}
|
|
2195
|
-
async function readGovernedResource(client, uri, governanceConfig, memoryConfig = {}) {
|
|
2196
|
-
const toolName = uri === "mindgraph://memory/status" ? "mindgraph_memory" : RESOURCE_GOVERNANCE_TOOL;
|
|
2197
|
-
const toolArgs = uri === "mindgraph://memory/status" ? { action: "status" } : {};
|
|
2198
|
-
const governance = await checkMcpGovernance(
|
|
2199
|
-
toolName,
|
|
2200
|
-
toolArgs,
|
|
2201
|
-
{ ...governanceConfig, context: { resource_uri: uri } }
|
|
2202
|
-
);
|
|
2203
|
-
if (!governance.allowed) {
|
|
2204
|
-
throw new Error(governance.message);
|
|
2205
|
-
}
|
|
2206
|
-
return readResource(client, uri, memoryConfig);
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
// src/tools.ts
|
|
2210
|
-
var import_node_crypto4 = require("crypto");
|
|
2211
|
-
|
|
2212
|
-
// src/codegraph.ts
|
|
2213
|
-
var import_node_child_process = require("child_process");
|
|
2214
|
-
var import_node_fs = require("fs");
|
|
2215
|
-
var import_promises = require("fs/promises");
|
|
2216
|
-
var import_node_os = require("os");
|
|
2217
|
-
var import_node_path = require("path");
|
|
2218
|
-
var import_node_crypto2 = require("crypto");
|
|
2219
|
-
var UnknownRepositoryError = class extends Error {
|
|
2220
|
-
constructor(repo) {
|
|
2221
|
-
super(
|
|
2222
|
-
`unknown repository "${repo}": not declared in .mindgraph/workspace.json, not the current repository, and no such path exists`
|
|
2223
|
-
);
|
|
2224
|
-
this.repo = repo;
|
|
2225
|
-
this.name = "UnknownRepositoryError";
|
|
2226
|
-
}
|
|
2227
|
-
repo;
|
|
2228
|
-
};
|
|
2229
|
-
function invocationCwd(args) {
|
|
2230
|
-
const context = args.invocation_context;
|
|
2231
|
-
if (context && typeof context === "object" && !Array.isArray(context)) {
|
|
2232
|
-
const cwd = context.cwd;
|
|
2233
|
-
if (typeof cwd === "string" && cwd) return cwd;
|
|
2234
|
-
}
|
|
2235
|
-
return process.cwd();
|
|
2236
|
-
}
|
|
2237
|
-
function unavailabilityCaveats(reason, message) {
|
|
2238
|
-
const caveats = [message || "codegraph status failed"];
|
|
2239
|
-
if (reason === "absent") {
|
|
2240
|
-
caveats.push(
|
|
2241
|
-
"Optional: install codegraph (https://github.com/colbymchenry/codegraph) and run `codegraph init` in this repository to enable code anchoring and structural recall. Memory and work tools are unaffected."
|
|
2242
|
-
);
|
|
2243
|
-
}
|
|
2244
|
-
return caveats;
|
|
2245
|
-
}
|
|
2246
|
-
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
2247
|
-
var DEFAULT_STATUS_TTL_MS = 6e4;
|
|
2248
|
-
var MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
2249
|
-
function normalizePath(path2) {
|
|
2250
|
-
return path2.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
2251
|
-
}
|
|
2252
|
-
function normalizeSignature(signature) {
|
|
2253
|
-
return signature.replace(/\s+/g, " ").trim();
|
|
2254
|
-
}
|
|
2255
|
-
function languageFromPath(path2) {
|
|
2256
|
-
const extension = (0, import_node_path.extname)(path2).toLowerCase();
|
|
2257
|
-
return {
|
|
2258
|
-
".c": "c",
|
|
2259
|
-
".cc": "cpp",
|
|
2260
|
-
".cpp": "cpp",
|
|
2261
|
-
".go": "go",
|
|
2262
|
-
".java": "java",
|
|
2263
|
-
".js": "javascript",
|
|
2264
|
-
".jsx": "javascript",
|
|
2265
|
-
".kt": "kotlin",
|
|
2266
|
-
".py": "python",
|
|
2267
|
-
".rb": "ruby",
|
|
2268
|
-
".rs": "rust",
|
|
2269
|
-
".swift": "swift",
|
|
2270
|
-
".ts": "typescript",
|
|
2271
|
-
".tsx": "typescript"
|
|
2272
|
-
}[extension] ?? "unknown";
|
|
2273
|
-
}
|
|
2274
|
-
function asNode(hit) {
|
|
2275
|
-
return hit.node ?? hit;
|
|
2276
|
-
}
|
|
2277
|
-
function toCandidate(node) {
|
|
2278
|
-
if (!node.filePath || !node.kind || !(node.qualifiedName || node.name)) return null;
|
|
2279
|
-
return {
|
|
2280
|
-
path: normalizePath(node.filePath),
|
|
2281
|
-
language: node.language ?? languageFromPath(node.filePath),
|
|
2282
|
-
kind: node.kind,
|
|
2283
|
-
qualifiedName: node.qualifiedName ?? node.name ?? "",
|
|
2284
|
-
signature: node.signature ?? null,
|
|
2285
|
-
startLine: node.startLine ?? 1,
|
|
2286
|
-
endLine: node.endLine ?? node.startLine ?? 1
|
|
2287
|
-
};
|
|
2288
|
-
}
|
|
2289
|
-
function normalizeRemote(remote) {
|
|
2290
|
-
const trimmed = remote.trim();
|
|
2291
|
-
if (!trimmed) return null;
|
|
2292
|
-
const scp = /^(?:[^@]+@)?([^:]+):(.+)$/.exec(trimmed);
|
|
2293
|
-
if (scp && !trimmed.includes("://")) {
|
|
2294
|
-
return `${scp[1].toLowerCase()}/${scp[2].replace(/\.git$/, "")}`;
|
|
2295
|
-
}
|
|
2296
|
-
try {
|
|
2297
|
-
const url = new URL(trimmed);
|
|
2298
|
-
const pathname = url.pathname.replace(/\/$/, "").replace(/\.git$/, "");
|
|
2299
|
-
return `${url.host.toLowerCase()}${pathname}`;
|
|
2300
|
-
} catch {
|
|
2301
|
-
return null;
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
function pathContains(root, candidate) {
|
|
2305
|
-
const child = (0, import_node_path.relative)(root, candidate);
|
|
2306
|
-
return child === "" || child !== ".." && !child.startsWith(`..${import_node_path.sep}`) && !(0, import_node_path.isAbsolute)(child);
|
|
2307
|
-
}
|
|
2308
|
-
function nearestExistingDirectory(candidate) {
|
|
2309
|
-
let current = (0, import_node_path.resolve)(candidate);
|
|
2310
|
-
for (; ; ) {
|
|
2311
|
-
if ((0, import_node_fs.existsSync)(current)) {
|
|
2312
|
-
try {
|
|
2313
|
-
return (0, import_node_fs.statSync)(current).isDirectory() ? current : (0, import_node_path.dirname)(current);
|
|
2314
|
-
} catch {
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
|
-
const parent = (0, import_node_path.dirname)(current);
|
|
2318
|
-
if (parent === current) return current;
|
|
2319
|
-
current = parent;
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
function findWorkspaceFile(start, explicit) {
|
|
2323
|
-
if (explicit) return (0, import_node_path.resolve)(start, explicit);
|
|
2324
|
-
let current = (0, import_node_path.resolve)(start);
|
|
2325
|
-
for (; ; ) {
|
|
2326
|
-
const candidate = (0, import_node_path.join)(current, ".mindgraph", "workspace.json");
|
|
2327
|
-
if ((0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
2328
|
-
const parent = (0, import_node_path.dirname)(current);
|
|
2329
|
-
if (parent === current) return void 0;
|
|
2330
|
-
current = parent;
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2333
|
-
async function runProcess(executable, args, timeoutMs, env, cwd) {
|
|
2334
|
-
return await new Promise((resolvePromise, rejectPromise) => {
|
|
2335
|
-
const child = (0, import_node_child_process.spawn)(executable, args, {
|
|
2336
|
-
cwd,
|
|
2337
|
-
env,
|
|
2338
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2339
|
-
windowsHide: true
|
|
2340
|
-
});
|
|
2341
|
-
const stdout = [];
|
|
2342
|
-
const stderr = [];
|
|
2343
|
-
let outputBytes = 0;
|
|
2344
|
-
let timedOut = false;
|
|
2345
|
-
const timer = setTimeout(() => {
|
|
2346
|
-
timedOut = true;
|
|
2347
|
-
child.kill("SIGTERM");
|
|
2348
|
-
setTimeout(() => child.kill("SIGKILL"), 250).unref();
|
|
2349
|
-
}, timeoutMs);
|
|
2350
|
-
timer.unref();
|
|
2351
|
-
const collect = (target) => (chunk) => {
|
|
2352
|
-
outputBytes += chunk.length;
|
|
2353
|
-
if (outputBytes > MAX_OUTPUT_BYTES) {
|
|
2354
|
-
child.kill("SIGTERM");
|
|
2355
|
-
return;
|
|
2356
|
-
}
|
|
2357
|
-
target.push(chunk);
|
|
1773
|
+
async function runProcess(executable, args, timeoutMs, env, cwd) {
|
|
1774
|
+
return await new Promise((resolvePromise, rejectPromise) => {
|
|
1775
|
+
const child = (0, import_node_child_process.spawn)(executable, args, {
|
|
1776
|
+
cwd,
|
|
1777
|
+
env,
|
|
1778
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1779
|
+
windowsHide: true
|
|
1780
|
+
});
|
|
1781
|
+
const stdout = [];
|
|
1782
|
+
const stderr = [];
|
|
1783
|
+
let outputBytes = 0;
|
|
1784
|
+
let timedOut = false;
|
|
1785
|
+
const timer = setTimeout(() => {
|
|
1786
|
+
timedOut = true;
|
|
1787
|
+
child.kill("SIGTERM");
|
|
1788
|
+
setTimeout(() => child.kill("SIGKILL"), 250).unref();
|
|
1789
|
+
}, timeoutMs);
|
|
1790
|
+
timer.unref();
|
|
1791
|
+
const collect = (target) => (chunk) => {
|
|
1792
|
+
outputBytes += chunk.length;
|
|
1793
|
+
if (outputBytes > MAX_OUTPUT_BYTES) {
|
|
1794
|
+
child.kill("SIGTERM");
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
target.push(chunk);
|
|
2358
1798
|
};
|
|
2359
1799
|
child.stdout.on("data", collect(stdout));
|
|
2360
1800
|
child.stderr.on("data", collect(stderr));
|
|
@@ -2395,6 +1835,7 @@ var CodegraphAdapter = class {
|
|
|
2395
1835
|
env;
|
|
2396
1836
|
cwd;
|
|
2397
1837
|
workspaceFile;
|
|
1838
|
+
childWorkspaceFiles;
|
|
2398
1839
|
now;
|
|
2399
1840
|
identityCache = /* @__PURE__ */ new Map();
|
|
2400
1841
|
statusCache = /* @__PURE__ */ new Map();
|
|
@@ -2407,6 +1848,7 @@ var CodegraphAdapter = class {
|
|
|
2407
1848
|
this.cwd,
|
|
2408
1849
|
options.workspaceFile ?? this.env.MINDGRAPH_WORKSPACE_FILE
|
|
2409
1850
|
);
|
|
1851
|
+
this.childWorkspaceFiles = !this.workspaceFile && options.childWorkspaces ? findChildWorkspaceFiles(this.cwd) : [];
|
|
2410
1852
|
this.now = options.now ?? Date.now;
|
|
2411
1853
|
}
|
|
2412
1854
|
async resolveRepository(repo, fallbackSpaceUid) {
|
|
@@ -2504,7 +1946,7 @@ var CodegraphAdapter = class {
|
|
|
2504
1946
|
} catch {
|
|
2505
1947
|
}
|
|
2506
1948
|
(0, import_node_fs.mkdirSync)(identityDir, { recursive: true, mode: 448 });
|
|
2507
|
-
const generated = (0,
|
|
1949
|
+
const generated = (0, import_node_crypto.randomUUID)();
|
|
2508
1950
|
try {
|
|
2509
1951
|
(0, import_node_fs.writeFileSync)(identityFile, `${generated}
|
|
2510
1952
|
`, {
|
|
@@ -2573,925 +2015,1593 @@ var CodegraphAdapter = class {
|
|
|
2573
2015
|
if ((status.index?.pendingRefs ?? 0) > 0) {
|
|
2574
2016
|
caveats.push(`${status.index?.pendingRefs} references remain pending`);
|
|
2575
2017
|
}
|
|
2576
|
-
if (status.index?.reindexRecommended) caveats.push("codegraph recommends reindexing");
|
|
2577
|
-
availability = {
|
|
2578
|
-
available: true,
|
|
2579
|
-
stale: caveats.length > 0,
|
|
2580
|
-
caveats,
|
|
2581
|
-
lastIndexed: status.lastIndexed
|
|
2018
|
+
if (status.index?.reindexRecommended) caveats.push("codegraph recommends reindexing");
|
|
2019
|
+
availability = {
|
|
2020
|
+
available: true,
|
|
2021
|
+
stale: caveats.length > 0,
|
|
2022
|
+
caveats,
|
|
2023
|
+
lastIndexed: status.lastIndexed
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
this.statusCache.set(repository.repoRoot, {
|
|
2027
|
+
status,
|
|
2028
|
+
availability,
|
|
2029
|
+
expiresAt: this.now() + this.statusTtlMs
|
|
2030
|
+
});
|
|
2031
|
+
return availability;
|
|
2032
|
+
} catch (cause) {
|
|
2033
|
+
const failure = cause;
|
|
2034
|
+
const reason = failure.kind ?? "command_failed";
|
|
2035
|
+
return {
|
|
2036
|
+
available: false,
|
|
2037
|
+
reason,
|
|
2038
|
+
stale: false,
|
|
2039
|
+
caveats: unavailabilityCaveats(reason, failure.message)
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
async resolveRefs(refs, options = {}) {
|
|
2044
|
+
const normalized = refs.map(
|
|
2045
|
+
(ref) => typeof ref === "string" ? { symbol: ref } : ref
|
|
2046
|
+
);
|
|
2047
|
+
const results = await Promise.all(
|
|
2048
|
+
normalized.map(async (ref) => {
|
|
2049
|
+
const repository2 = await this.repositoryForRef(
|
|
2050
|
+
ref,
|
|
2051
|
+
options.repo,
|
|
2052
|
+
options.fallbackSpaceUid
|
|
2053
|
+
);
|
|
2054
|
+
const availability2 = await this.availability(repository2);
|
|
2055
|
+
if (!availability2.available) {
|
|
2056
|
+
return {
|
|
2057
|
+
ref,
|
|
2058
|
+
repository: repository2,
|
|
2059
|
+
availability: availability2,
|
|
2060
|
+
candidates: [],
|
|
2061
|
+
error: availability2.reason
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
2064
|
+
return {
|
|
2065
|
+
...await this.resolveOne(ref, repository2),
|
|
2066
|
+
repository: repository2,
|
|
2067
|
+
availability: availability2
|
|
2068
|
+
};
|
|
2069
|
+
})
|
|
2070
|
+
);
|
|
2071
|
+
const repositories = [
|
|
2072
|
+
...new Map(
|
|
2073
|
+
results.flatMap(
|
|
2074
|
+
(result) => result.repository ? [[result.repository.repoRoot, result.repository]] : []
|
|
2075
|
+
)
|
|
2076
|
+
).values()
|
|
2077
|
+
];
|
|
2078
|
+
const repository = repositories[0] ?? await this.resolveRepository(options.repo, options.fallbackSpaceUid);
|
|
2079
|
+
if (repositories.length === 0) repositories.push(repository);
|
|
2080
|
+
const availabilities = results.flatMap(
|
|
2081
|
+
(result) => result.availability ? [result.availability] : []
|
|
2082
|
+
);
|
|
2083
|
+
const availability = availabilities.length === 0 ? await this.availability(repository) : {
|
|
2084
|
+
available: availabilities.every((item) => item.available),
|
|
2085
|
+
reason: availabilities.find((item) => !item.available)?.reason,
|
|
2086
|
+
stale: availabilities.some((item) => item.stale),
|
|
2087
|
+
caveats: [
|
|
2088
|
+
...new Set(availabilities.flatMap((item) => item.caveats))
|
|
2089
|
+
]
|
|
2090
|
+
};
|
|
2091
|
+
return { repository, repositories, availability, results };
|
|
2092
|
+
}
|
|
2093
|
+
async repositoryForRef(ref, defaultRepo, fallbackSpaceUid) {
|
|
2094
|
+
if (ref.repo || defaultRepo) {
|
|
2095
|
+
return this.resolveRepository(ref.repo || defaultRepo, fallbackSpaceUid);
|
|
2096
|
+
}
|
|
2097
|
+
if (ref.path) {
|
|
2098
|
+
const requested = (0, import_node_path.isAbsolute)(ref.path) ? (0, import_node_path.resolve)(ref.path) : (0, import_node_path.resolve)(this.cwd, ref.path);
|
|
2099
|
+
const absolute = await (0, import_promises.realpath)(requested).catch(() => requested);
|
|
2100
|
+
const configured = await this.resolveRepositories(fallbackSpaceUid);
|
|
2101
|
+
const defining = [...configured].sort((a, b) => b.repoRoot.length - a.repoRoot.length).find((candidate) => pathContains(candidate.repoRoot, absolute));
|
|
2102
|
+
if (defining) {
|
|
2103
|
+
return this.env.MINDGRAPH_REPO_ID && pathContains(defining.repoRoot, this.cwd) ? this.resolveRepositoryIdentity(
|
|
2104
|
+
defining.repoRoot,
|
|
2105
|
+
this.env.MINDGRAPH_REPO_ID,
|
|
2106
|
+
defining.spaceUid
|
|
2107
|
+
) : defining;
|
|
2108
|
+
}
|
|
2109
|
+
const gitRoot = await this.gitRoot(absolute);
|
|
2110
|
+
if (gitRoot) {
|
|
2111
|
+
return this.resolveRepositoryIdentity(
|
|
2112
|
+
gitRoot,
|
|
2113
|
+
this.env.MINDGRAPH_REPO_ID,
|
|
2114
|
+
this.env.MINDGRAPH_CODE_SPACE_UID || fallbackSpaceUid
|
|
2115
|
+
);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
return this.resolveRepository(void 0, fallbackSpaceUid);
|
|
2119
|
+
}
|
|
2120
|
+
async expand(resolved, limit = 20) {
|
|
2121
|
+
const repository = {
|
|
2122
|
+
repoId: resolved.repoId,
|
|
2123
|
+
repoRoot: resolved.repoRoot
|
|
2124
|
+
};
|
|
2125
|
+
const availability = await this.availability(repository);
|
|
2126
|
+
if (!availability.available) {
|
|
2127
|
+
return { availability, callers: [], callees: [], truncated: false };
|
|
2128
|
+
}
|
|
2129
|
+
const cap = Math.min(Math.max(limit, 1), 20);
|
|
2130
|
+
const [callers, callees] = await Promise.all([
|
|
2131
|
+
this.related("callers", resolved, cap + 1),
|
|
2132
|
+
this.related("callees", resolved, cap + 1)
|
|
2133
|
+
]);
|
|
2134
|
+
return {
|
|
2135
|
+
availability,
|
|
2136
|
+
callers: callers.slice(0, cap),
|
|
2137
|
+
callees: callees.slice(0, cap),
|
|
2138
|
+
truncated: callers.length > cap || callees.length > cap
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
async affected(resolved, limit = 50) {
|
|
2142
|
+
const repository = {
|
|
2143
|
+
repoId: resolved.repoId,
|
|
2144
|
+
repoRoot: resolved.repoRoot
|
|
2145
|
+
};
|
|
2146
|
+
const availability = await this.availability(repository);
|
|
2147
|
+
if (!availability.available) {
|
|
2148
|
+
return { availability, affected: [], truncated: false };
|
|
2149
|
+
}
|
|
2150
|
+
const cap = Math.min(Math.max(limit, 1), 50);
|
|
2151
|
+
const raw = await this.runJson([
|
|
2152
|
+
"impact",
|
|
2153
|
+
"-p",
|
|
2154
|
+
resolved.repoRoot,
|
|
2155
|
+
"-j",
|
|
2156
|
+
"-d",
|
|
2157
|
+
"2",
|
|
2158
|
+
resolved.qualifiedName
|
|
2159
|
+
]);
|
|
2160
|
+
const refs = (raw.affected ?? []).map((node) => ({
|
|
2161
|
+
path: node.filePath,
|
|
2162
|
+
line: node.startLine,
|
|
2163
|
+
symbol: node.qualifiedName ?? node.name,
|
|
2164
|
+
kind: node.kind
|
|
2165
|
+
}));
|
|
2166
|
+
const resolvedRefs = await this.resolveRelatedRefs(refs.slice(0, cap + 1), repository);
|
|
2167
|
+
return {
|
|
2168
|
+
availability,
|
|
2169
|
+
affected: resolvedRefs.slice(0, cap),
|
|
2170
|
+
truncated: refs.length > cap
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
configuredRepositories() {
|
|
2174
|
+
const configured = [];
|
|
2175
|
+
const workspaceFiles = this.workspaceFile ? [this.workspaceFile] : this.childWorkspaceFiles;
|
|
2176
|
+
for (const workspaceFile of workspaceFiles) {
|
|
2177
|
+
if (!(0, import_node_fs.existsSync)(workspaceFile)) continue;
|
|
2178
|
+
try {
|
|
2179
|
+
const parsed = JSON.parse(
|
|
2180
|
+
(0, import_node_fs.readFileSync)(workspaceFile, "utf8")
|
|
2181
|
+
);
|
|
2182
|
+
if (parsed.v === 1 && Array.isArray(parsed.repositories)) {
|
|
2183
|
+
const configDirectory = (0, import_node_path.dirname)(workspaceFile);
|
|
2184
|
+
const base = (0, import_node_path.basename)(configDirectory) === ".mindgraph" ? (0, import_node_path.dirname)(configDirectory) : configDirectory;
|
|
2185
|
+
for (const repository of parsed.repositories) {
|
|
2186
|
+
if (!repository || typeof repository.root !== "string") continue;
|
|
2187
|
+
configured.push({
|
|
2188
|
+
repoId: typeof repository.repo_id === "string" ? repository.repo_id : void 0,
|
|
2189
|
+
repoRoot: (0, import_node_path.resolve)(base, repository.root),
|
|
2190
|
+
spaceUid: typeof repository.space_uid === "string" ? repository.space_uid : void 0
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
} catch {
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
const raw = this.env.MINDGRAPH_CODE_REPOS;
|
|
2198
|
+
if (raw) {
|
|
2199
|
+
try {
|
|
2200
|
+
const parsed = JSON.parse(raw);
|
|
2201
|
+
for (const [repoId, config] of Object.entries(parsed)) {
|
|
2202
|
+
configured.push({
|
|
2203
|
+
repoId,
|
|
2204
|
+
repoRoot: (0, import_node_path.resolve)(
|
|
2205
|
+
this.cwd,
|
|
2206
|
+
typeof config === "string" ? config : config.root
|
|
2207
|
+
),
|
|
2208
|
+
spaceUid: typeof config === "string" ? void 0 : config.space_uid
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2211
|
+
} catch {
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
return [
|
|
2215
|
+
...new Map(
|
|
2216
|
+
configured.map((repository) => [repository.repoRoot, repository])
|
|
2217
|
+
).values()
|
|
2218
|
+
];
|
|
2219
|
+
}
|
|
2220
|
+
async resolveOne(ref, repository) {
|
|
2221
|
+
const search = ref.symbol || ref.path;
|
|
2222
|
+
if (!search || ref.line !== void 0 && (!Number.isInteger(ref.line) || ref.line < 1)) {
|
|
2223
|
+
return { ref, candidates: [], error: "invalid_ref" };
|
|
2224
|
+
}
|
|
2225
|
+
try {
|
|
2226
|
+
const args = ["query", "-p", repository.repoRoot, "-j", "-l", "50"];
|
|
2227
|
+
if (ref.kind) args.push("-k", ref.kind);
|
|
2228
|
+
args.push(search);
|
|
2229
|
+
const hits = await this.runJson(args);
|
|
2230
|
+
let candidates = hits.map(asNode).map(toCandidate).filter((item) => item !== null);
|
|
2231
|
+
if (ref.path) {
|
|
2232
|
+
const absolutePath = (0, import_node_path.isAbsolute)(ref.path) ? await (0, import_promises.realpath)(ref.path).catch(() => ref.path) : void 0;
|
|
2233
|
+
const wanted = normalizePath(
|
|
2234
|
+
absolutePath ? (0, import_node_path.relative)(repository.repoRoot, absolutePath) : ref.path
|
|
2235
|
+
);
|
|
2236
|
+
candidates = candidates.filter((candidate) => candidate.path === wanted);
|
|
2237
|
+
}
|
|
2238
|
+
if (ref.language) {
|
|
2239
|
+
candidates = candidates.filter(
|
|
2240
|
+
(candidate) => candidate.language.toLowerCase() === ref.language?.toLowerCase()
|
|
2241
|
+
);
|
|
2242
|
+
}
|
|
2243
|
+
if (ref.kind) {
|
|
2244
|
+
candidates = candidates.filter(
|
|
2245
|
+
(candidate) => candidate.kind.toLowerCase() === ref.kind?.toLowerCase()
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
if (ref.symbol) {
|
|
2249
|
+
const exact = candidates.filter((candidate) => {
|
|
2250
|
+
const tail = candidate.qualifiedName.split(/::|\.|#/).at(-1);
|
|
2251
|
+
return candidate.qualifiedName === ref.symbol || tail === ref.symbol;
|
|
2252
|
+
});
|
|
2253
|
+
if (exact.length > 0) candidates = exact;
|
|
2254
|
+
}
|
|
2255
|
+
if (ref.signature) {
|
|
2256
|
+
const signature = normalizeSignature(ref.signature);
|
|
2257
|
+
candidates = candidates.filter(
|
|
2258
|
+
(candidate) => candidate.signature !== null && normalizeSignature(candidate.signature) === signature
|
|
2259
|
+
);
|
|
2260
|
+
}
|
|
2261
|
+
if (ref.line !== void 0) {
|
|
2262
|
+
candidates = candidates.filter(
|
|
2263
|
+
(candidate) => candidate.startLine <= ref.line && candidate.endLine >= ref.line
|
|
2264
|
+
);
|
|
2265
|
+
if (candidates.length > 1) {
|
|
2266
|
+
const narrowest = Math.min(
|
|
2267
|
+
...candidates.map((candidate) => candidate.endLine - candidate.startLine)
|
|
2268
|
+
);
|
|
2269
|
+
const narrowed = candidates.filter(
|
|
2270
|
+
(candidate) => candidate.endLine - candidate.startLine === narrowest
|
|
2271
|
+
);
|
|
2272
|
+
if (narrowed.length === 1) candidates = narrowed;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
if (candidates.length !== 1) {
|
|
2276
|
+
return {
|
|
2277
|
+
ref,
|
|
2278
|
+
candidates,
|
|
2279
|
+
error: candidates.length === 0 ? "not_found" : "ambiguous"
|
|
2582
2280
|
};
|
|
2583
2281
|
}
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2282
|
+
return {
|
|
2283
|
+
ref,
|
|
2284
|
+
candidates,
|
|
2285
|
+
resolved: {
|
|
2286
|
+
repoId: repository.repoId,
|
|
2287
|
+
repoRoot: repository.repoRoot,
|
|
2288
|
+
...candidates[0]
|
|
2289
|
+
}
|
|
2290
|
+
};
|
|
2590
2291
|
} catch (cause) {
|
|
2591
2292
|
const failure = cause;
|
|
2592
|
-
const reason = failure.kind ?? "command_failed";
|
|
2593
2293
|
return {
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
caveats: unavailabilityCaveats(reason, failure.message)
|
|
2294
|
+
ref,
|
|
2295
|
+
candidates: [],
|
|
2296
|
+
error: failure.kind ?? "command_failed"
|
|
2598
2297
|
};
|
|
2599
2298
|
}
|
|
2600
2299
|
}
|
|
2601
|
-
async
|
|
2602
|
-
const
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2300
|
+
async related(action, resolved, limit) {
|
|
2301
|
+
const raw = await this.runJson([
|
|
2302
|
+
action,
|
|
2303
|
+
"-p",
|
|
2304
|
+
resolved.repoRoot,
|
|
2305
|
+
"-j",
|
|
2306
|
+
"-l",
|
|
2307
|
+
String(limit),
|
|
2308
|
+
resolved.qualifiedName
|
|
2309
|
+
]);
|
|
2310
|
+
const refs = (raw[action] ?? []).map((node) => ({
|
|
2311
|
+
path: node.filePath,
|
|
2312
|
+
line: node.startLine,
|
|
2313
|
+
symbol: node.qualifiedName ?? node.name,
|
|
2314
|
+
kind: node.kind
|
|
2315
|
+
}));
|
|
2316
|
+
return this.resolveRelatedRefs(refs, {
|
|
2317
|
+
repoId: resolved.repoId,
|
|
2318
|
+
repoRoot: resolved.repoRoot
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
async resolveRelatedRefs(refs, repository) {
|
|
2322
|
+
const results = await Promise.all(refs.map((ref) => this.resolveOne(ref, repository)));
|
|
2323
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2324
|
+
return results.flatMap((result) => result.resolved ? [result.resolved] : []).filter((result) => {
|
|
2325
|
+
const key = `${result.path}:${result.qualifiedName}:${result.startLine}`;
|
|
2326
|
+
if (seen.has(key)) return false;
|
|
2327
|
+
seen.add(key);
|
|
2328
|
+
return true;
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
async runJson(args) {
|
|
2332
|
+
const candidates = [
|
|
2333
|
+
this.env.MINDGRAPH_CODEGRAPH_BIN,
|
|
2334
|
+
"codegraph",
|
|
2335
|
+
(0, import_node_path.resolve)((0, import_node_os.homedir)(), ".local", "bin", "codegraph")
|
|
2336
|
+
].filter((candidate, index, all) => {
|
|
2337
|
+
return Boolean(candidate) && all.indexOf(candidate) === index;
|
|
2338
|
+
});
|
|
2339
|
+
let lastFailure;
|
|
2340
|
+
for (const executable of candidates) {
|
|
2341
|
+
try {
|
|
2342
|
+
const stdout = await runProcess(
|
|
2343
|
+
executable,
|
|
2344
|
+
args,
|
|
2345
|
+
this.timeoutMs,
|
|
2346
|
+
this.env,
|
|
2347
|
+
this.cwd
|
|
2611
2348
|
);
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2349
|
+
return JSON.parse(stdout);
|
|
2350
|
+
} catch (cause) {
|
|
2351
|
+
const failure = cause;
|
|
2352
|
+
lastFailure = failure;
|
|
2353
|
+
if (failure.kind !== "absent") throw failure;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
throw lastFailure ?? Object.assign(new Error("codegraph executable not found"), {
|
|
2357
|
+
kind: "absent"
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
function repositoryIdentityKey(repository) {
|
|
2362
|
+
return { v: 1, kind: "repository", repo_id: repository.repoId };
|
|
2363
|
+
}
|
|
2364
|
+
function codeRefIdentityKey(ref) {
|
|
2365
|
+
if (ref.kind === "file") {
|
|
2366
|
+
return {
|
|
2367
|
+
v: 1,
|
|
2368
|
+
kind: "file",
|
|
2369
|
+
repo_id: ref.repoId,
|
|
2370
|
+
path: ref.path
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
return {
|
|
2374
|
+
v: 1,
|
|
2375
|
+
kind: "symbol",
|
|
2376
|
+
repo_id: ref.repoId,
|
|
2377
|
+
language: ref.language,
|
|
2378
|
+
symbol_kind: ref.kind,
|
|
2379
|
+
qualified_name: ref.qualifiedName,
|
|
2380
|
+
disambiguator: {
|
|
2381
|
+
signature: ref.signature ?? "",
|
|
2382
|
+
path: ref.path
|
|
2383
|
+
}
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
// src/code-tool.ts
|
|
2388
|
+
var ok2 = (data) => ({
|
|
2389
|
+
content: [{ type: "text", text: JSON.stringify(data) }]
|
|
2390
|
+
});
|
|
2391
|
+
var err3 = (code, message, details) => ({
|
|
2392
|
+
content: [
|
|
2393
|
+
{
|
|
2394
|
+
type: "text",
|
|
2395
|
+
text: JSON.stringify({ error: message, code, details })
|
|
2396
|
+
}
|
|
2397
|
+
],
|
|
2398
|
+
isError: true
|
|
2399
|
+
});
|
|
2400
|
+
var CODE_TOOL = {
|
|
2401
|
+
name: "mindgraph_code",
|
|
2402
|
+
description: "Resolve typed code references through the local codegraph index and join them to persistent MindGraph memory/work. anchor creates exact repository/code anchors; recall reads attached graph context; expand reads callers/callees; affected reads impact for a code ref or Task/Execution. Ambiguous refs return candidates without writing. Refs with a path route to the repository defining that file (nearest declared root in .mindgraph/workspace.json); bare symbols resolve against the current repository only \u2014 pass path or repo to target a sibling.",
|
|
2403
|
+
inputSchema: {
|
|
2404
|
+
type: "object",
|
|
2405
|
+
properties: {
|
|
2406
|
+
action: {
|
|
2407
|
+
type: "string",
|
|
2408
|
+
enum: ["anchor", "recall", "expand", "affected"]
|
|
2409
|
+
},
|
|
2410
|
+
code_refs: {
|
|
2411
|
+
type: "array",
|
|
2412
|
+
maxItems: 50,
|
|
2413
|
+
items: {
|
|
2414
|
+
oneOf: [
|
|
2415
|
+
{ type: "string" },
|
|
2416
|
+
{
|
|
2417
|
+
type: "object",
|
|
2418
|
+
properties: {
|
|
2419
|
+
repo: { type: "string" },
|
|
2420
|
+
path: { type: "string" },
|
|
2421
|
+
line: { type: "integer", minimum: 1 },
|
|
2422
|
+
symbol: { type: "string" },
|
|
2423
|
+
kind: { type: "string" },
|
|
2424
|
+
language: { type: "string" },
|
|
2425
|
+
signature: { type: "string" }
|
|
2426
|
+
},
|
|
2427
|
+
additionalProperties: false
|
|
2428
|
+
}
|
|
2429
|
+
]
|
|
2430
|
+
}
|
|
2431
|
+
},
|
|
2432
|
+
anchor_uids: {
|
|
2433
|
+
type: "array",
|
|
2434
|
+
maxItems: 50,
|
|
2435
|
+
items: { type: "string" },
|
|
2436
|
+
description: "Already-materialized code anchor UIDs (codegraph is not required for recall)"
|
|
2437
|
+
},
|
|
2438
|
+
repo: {
|
|
2439
|
+
type: "string",
|
|
2440
|
+
description: "Configured repository id or repository root"
|
|
2441
|
+
},
|
|
2442
|
+
repo_space_uid: {
|
|
2443
|
+
type: "string",
|
|
2444
|
+
description: "Writable shared Project/repository Space for new anchors (anchor requires this, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID)"
|
|
2445
|
+
},
|
|
2446
|
+
task_uid: { type: "string" },
|
|
2447
|
+
execution_uid: { type: "string" },
|
|
2448
|
+
limit: { type: "integer", minimum: 1, maximum: 50 },
|
|
2449
|
+
agent_id: { type: "string" }
|
|
2450
|
+
},
|
|
2451
|
+
required: ["action"]
|
|
2452
|
+
},
|
|
2453
|
+
annotations: {
|
|
2454
|
+
readOnlyHint: false,
|
|
2455
|
+
destructiveHint: false,
|
|
2456
|
+
idempotentHint: true,
|
|
2457
|
+
openWorldHint: true
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2460
|
+
async function handleCodeTool(client, args, adapter = new CodegraphAdapter({ cwd: invocationCwd(args) })) {
|
|
2461
|
+
const codeClient = client;
|
|
2462
|
+
const action = args.action;
|
|
2463
|
+
const refs = Array.isArray(args.code_refs) ? args.code_refs : [];
|
|
2464
|
+
const anchorUids = Array.isArray(args.anchor_uids) ? args.anchor_uids : [];
|
|
2465
|
+
const agentId = typeof args.agent_id === "string" ? args.agent_id : "mcp";
|
|
2466
|
+
const fallbackSpaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : void 0;
|
|
2467
|
+
const options = {
|
|
2468
|
+
repo: typeof args.repo === "string" ? args.repo : void 0,
|
|
2469
|
+
fallbackSpaceUid
|
|
2470
|
+
};
|
|
2471
|
+
const limit = typeof args.limit === "number" ? args.limit : void 0;
|
|
2472
|
+
try {
|
|
2473
|
+
switch (action) {
|
|
2474
|
+
case "anchor": {
|
|
2475
|
+
if (refs.length === 0) return err3("missing_code_refs", "code_refs is required for anchor");
|
|
2476
|
+
const resolution = await adapter.resolveRefs(refs, options);
|
|
2477
|
+
const resolved = resolution.results.flatMap(
|
|
2478
|
+
(result) => result.resolved && result.repository ? [{ ref: result.resolved, repository: result.repository }] : []
|
|
2479
|
+
);
|
|
2480
|
+
const unresolved = resolution.results.filter((result) => !result.resolved);
|
|
2481
|
+
if (resolved.length === 0) {
|
|
2482
|
+
return ok2({ ...resolution, anchors: [], wrote: false });
|
|
2483
|
+
}
|
|
2484
|
+
const missingSpace = resolved.find(
|
|
2485
|
+
({ repository }) => typeof args.repo_space_uid !== "string" && !repository.spaceUid
|
|
2486
|
+
);
|
|
2487
|
+
if (missingSpace) {
|
|
2488
|
+
return err3(
|
|
2489
|
+
"missing_repository_space",
|
|
2490
|
+
`repo_space_uid, a workspace space_uid, or MINDGRAPH_CODE_SPACE_UID is required for ${missingSpace.repository.repoId}`
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
const repositoryAnchors = /* @__PURE__ */ new Map();
|
|
2494
|
+
for (const { repository } of resolved) {
|
|
2495
|
+
if (repositoryAnchors.has(repository.repoRoot)) continue;
|
|
2496
|
+
const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
|
|
2497
|
+
const anchored = await anchorRepository(
|
|
2498
|
+
codeClient,
|
|
2499
|
+
repository,
|
|
2500
|
+
spaceUid,
|
|
2501
|
+
agentId
|
|
2502
|
+
);
|
|
2503
|
+
repositoryAnchors.set(repository.repoRoot, {
|
|
2504
|
+
...anchored,
|
|
2505
|
+
repository
|
|
2506
|
+
});
|
|
2507
|
+
}
|
|
2508
|
+
const anchors = [];
|
|
2509
|
+
for (const { ref, repository } of resolved) {
|
|
2510
|
+
const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
|
|
2511
|
+
if (!repositoryAnchor.uid) continue;
|
|
2512
|
+
const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
|
|
2513
|
+
const anchor = await anchorCodeRef(
|
|
2514
|
+
codeClient,
|
|
2615
2515
|
ref,
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2516
|
+
repositoryAnchor.uid,
|
|
2517
|
+
spaceUid,
|
|
2518
|
+
agentId
|
|
2519
|
+
);
|
|
2520
|
+
anchors.push(anchor);
|
|
2621
2521
|
}
|
|
2622
|
-
return {
|
|
2623
|
-
|
|
2624
|
-
repository:
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
...new Map(
|
|
2631
|
-
results.flatMap(
|
|
2632
|
-
(result) => result.repository ? [[result.repository.repoRoot, result.repository]] : []
|
|
2633
|
-
)
|
|
2634
|
-
).values()
|
|
2635
|
-
];
|
|
2636
|
-
const repository = repositories[0] ?? await this.resolveRepository(options.repo, options.fallbackSpaceUid);
|
|
2637
|
-
if (repositories.length === 0) repositories.push(repository);
|
|
2638
|
-
const availabilities = results.flatMap(
|
|
2639
|
-
(result) => result.availability ? [result.availability] : []
|
|
2640
|
-
);
|
|
2641
|
-
const availability = availabilities.length === 0 ? await this.availability(repository) : {
|
|
2642
|
-
available: availabilities.every((item) => item.available),
|
|
2643
|
-
reason: availabilities.find((item) => !item.available)?.reason,
|
|
2644
|
-
stale: availabilities.some((item) => item.stale),
|
|
2645
|
-
caveats: [
|
|
2646
|
-
...new Set(availabilities.flatMap((item) => item.caveats))
|
|
2647
|
-
]
|
|
2648
|
-
};
|
|
2649
|
-
return { repository, repositories, availability, results };
|
|
2650
|
-
}
|
|
2651
|
-
async repositoryForRef(ref, defaultRepo, fallbackSpaceUid) {
|
|
2652
|
-
if (ref.repo || defaultRepo) {
|
|
2653
|
-
return this.resolveRepository(ref.repo || defaultRepo, fallbackSpaceUid);
|
|
2654
|
-
}
|
|
2655
|
-
if (ref.path) {
|
|
2656
|
-
const requested = (0, import_node_path.isAbsolute)(ref.path) ? (0, import_node_path.resolve)(ref.path) : (0, import_node_path.resolve)(this.cwd, ref.path);
|
|
2657
|
-
const absolute = await (0, import_promises.realpath)(requested).catch(() => requested);
|
|
2658
|
-
const configured = await this.resolveRepositories(fallbackSpaceUid);
|
|
2659
|
-
const defining = [...configured].sort((a, b) => b.repoRoot.length - a.repoRoot.length).find((candidate) => pathContains(candidate.repoRoot, absolute));
|
|
2660
|
-
if (defining) {
|
|
2661
|
-
return this.env.MINDGRAPH_REPO_ID && pathContains(defining.repoRoot, this.cwd) ? this.resolveRepositoryIdentity(
|
|
2662
|
-
defining.repoRoot,
|
|
2663
|
-
this.env.MINDGRAPH_REPO_ID,
|
|
2664
|
-
defining.spaceUid
|
|
2665
|
-
) : defining;
|
|
2522
|
+
return ok2({
|
|
2523
|
+
availability: resolution.availability,
|
|
2524
|
+
repository: repositoryAnchors.values().next().value,
|
|
2525
|
+
repositories: [...repositoryAnchors.values()],
|
|
2526
|
+
anchors,
|
|
2527
|
+
unresolved,
|
|
2528
|
+
wrote: anchors.some((anchor) => anchor.created)
|
|
2529
|
+
});
|
|
2666
2530
|
}
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2531
|
+
case "recall": {
|
|
2532
|
+
const materialized = new Set(anchorUids);
|
|
2533
|
+
let resolution;
|
|
2534
|
+
if (refs.length > 0) {
|
|
2535
|
+
resolution = await adapter.resolveRefs(refs, options);
|
|
2536
|
+
for (const result of resolution.results) {
|
|
2537
|
+
if (!result.resolved) continue;
|
|
2538
|
+
const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
|
|
2539
|
+
if (uid) materialized.add(uid);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
if (materialized.size === 0) {
|
|
2543
|
+
return ok2({
|
|
2544
|
+
availability: resolution?.availability,
|
|
2545
|
+
anchors: [],
|
|
2546
|
+
context: [],
|
|
2547
|
+
unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
const context = await Promise.all(
|
|
2551
|
+
[...materialized].map(async (uid) => ({
|
|
2552
|
+
anchor_uid: uid,
|
|
2553
|
+
graph: await codeClient.traverse({
|
|
2554
|
+
action: "neighborhood",
|
|
2555
|
+
start_uid: uid,
|
|
2556
|
+
depth: 2,
|
|
2557
|
+
max_nodes: Math.min(limit ?? 20, 20)
|
|
2558
|
+
})
|
|
2559
|
+
}))
|
|
2673
2560
|
);
|
|
2561
|
+
return ok2({
|
|
2562
|
+
availability: resolution?.availability,
|
|
2563
|
+
anchors: [...materialized],
|
|
2564
|
+
context,
|
|
2565
|
+
unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
|
|
2566
|
+
});
|
|
2674
2567
|
}
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
};
|
|
2683
|
-
const availability = await this.availability(repository);
|
|
2684
|
-
if (!availability.available) {
|
|
2685
|
-
return { availability, callers: [], callees: [], truncated: false };
|
|
2686
|
-
}
|
|
2687
|
-
const cap = Math.min(Math.max(limit, 1), 20);
|
|
2688
|
-
const [callers, callees] = await Promise.all([
|
|
2689
|
-
this.related("callers", resolved, cap + 1),
|
|
2690
|
-
this.related("callees", resolved, cap + 1)
|
|
2691
|
-
]);
|
|
2692
|
-
return {
|
|
2693
|
-
availability,
|
|
2694
|
-
callers: callers.slice(0, cap),
|
|
2695
|
-
callees: callees.slice(0, cap),
|
|
2696
|
-
truncated: callers.length > cap || callees.length > cap
|
|
2697
|
-
};
|
|
2698
|
-
}
|
|
2699
|
-
async affected(resolved, limit = 50) {
|
|
2700
|
-
const repository = {
|
|
2701
|
-
repoId: resolved.repoId,
|
|
2702
|
-
repoRoot: resolved.repoRoot
|
|
2703
|
-
};
|
|
2704
|
-
const availability = await this.availability(repository);
|
|
2705
|
-
if (!availability.available) {
|
|
2706
|
-
return { availability, affected: [], truncated: false };
|
|
2707
|
-
}
|
|
2708
|
-
const cap = Math.min(Math.max(limit, 1), 50);
|
|
2709
|
-
const raw = await this.runJson([
|
|
2710
|
-
"impact",
|
|
2711
|
-
"-p",
|
|
2712
|
-
resolved.repoRoot,
|
|
2713
|
-
"-j",
|
|
2714
|
-
"-d",
|
|
2715
|
-
"2",
|
|
2716
|
-
resolved.qualifiedName
|
|
2717
|
-
]);
|
|
2718
|
-
const refs = (raw.affected ?? []).map((node) => ({
|
|
2719
|
-
path: node.filePath,
|
|
2720
|
-
line: node.startLine,
|
|
2721
|
-
symbol: node.qualifiedName ?? node.name,
|
|
2722
|
-
kind: node.kind
|
|
2723
|
-
}));
|
|
2724
|
-
const resolvedRefs = await this.resolveRelatedRefs(refs.slice(0, cap + 1), repository);
|
|
2725
|
-
return {
|
|
2726
|
-
availability,
|
|
2727
|
-
affected: resolvedRefs.slice(0, cap),
|
|
2728
|
-
truncated: refs.length > cap
|
|
2729
|
-
};
|
|
2730
|
-
}
|
|
2731
|
-
configuredRepositories() {
|
|
2732
|
-
const configured = [];
|
|
2733
|
-
if (this.workspaceFile && (0, import_node_fs.existsSync)(this.workspaceFile)) {
|
|
2734
|
-
try {
|
|
2735
|
-
const parsed = JSON.parse(
|
|
2736
|
-
(0, import_node_fs.readFileSync)(this.workspaceFile, "utf8")
|
|
2568
|
+
case "expand": {
|
|
2569
|
+
const target = await resolveSingleTarget(
|
|
2570
|
+
codeClient,
|
|
2571
|
+
adapter,
|
|
2572
|
+
refs,
|
|
2573
|
+
anchorUids,
|
|
2574
|
+
options
|
|
2737
2575
|
);
|
|
2738
|
-
if (
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
});
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
} catch {
|
|
2576
|
+
if ("error" in target) return target.error;
|
|
2577
|
+
const expanded = await adapter.expand(target.ref, limit);
|
|
2578
|
+
const joined = await joinMaterialized(
|
|
2579
|
+
codeClient,
|
|
2580
|
+
[...expanded.callers, ...expanded.callees],
|
|
2581
|
+
agentId,
|
|
2582
|
+
Math.min(limit ?? 20, 20)
|
|
2583
|
+
);
|
|
2584
|
+
return ok2({ anchor: target.ref, ...expanded, materialized: joined });
|
|
2751
2585
|
}
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2586
|
+
case "affected": {
|
|
2587
|
+
const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
|
|
2588
|
+
let graphContext;
|
|
2589
|
+
const graphRefs = [];
|
|
2590
|
+
let unavailableRepositories = [];
|
|
2591
|
+
if (targetUid) {
|
|
2592
|
+
graphContext = await codeClient.traverse({
|
|
2593
|
+
action: "neighborhood",
|
|
2594
|
+
start_uid: targetUid,
|
|
2595
|
+
depth: 2,
|
|
2596
|
+
max_nodes: 50
|
|
2597
|
+
});
|
|
2598
|
+
const fromGraph = await codeRefsFromGraphContext(
|
|
2599
|
+
codeClient,
|
|
2600
|
+
adapter,
|
|
2601
|
+
graphContext
|
|
2602
|
+
);
|
|
2603
|
+
graphRefs.push(...fromGraph.refs);
|
|
2604
|
+
unavailableRepositories = fromGraph.unavailable;
|
|
2605
|
+
}
|
|
2606
|
+
if (refs.length > 0 || anchorUids.length > 0) {
|
|
2607
|
+
const target = await resolveSingleTarget(
|
|
2608
|
+
codeClient,
|
|
2609
|
+
adapter,
|
|
2610
|
+
refs,
|
|
2611
|
+
anchorUids,
|
|
2612
|
+
options
|
|
2613
|
+
);
|
|
2614
|
+
if ("error" in target) return target.error;
|
|
2615
|
+
graphRefs.unshift(target.ref);
|
|
2616
|
+
}
|
|
2617
|
+
const unique = uniqueRefs(graphRefs);
|
|
2618
|
+
if (unique.length === 0) {
|
|
2619
|
+
return ok2({
|
|
2620
|
+
target_uid: targetUid,
|
|
2621
|
+
graph_context: graphContext,
|
|
2622
|
+
affected: [],
|
|
2623
|
+
degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
|
|
2624
|
+
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
2765
2625
|
});
|
|
2766
2626
|
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
}
|
|
2770
|
-
return [
|
|
2771
|
-
...new Map(
|
|
2772
|
-
configured.map((repository) => [repository.repoRoot, repository])
|
|
2773
|
-
).values()
|
|
2774
|
-
];
|
|
2775
|
-
}
|
|
2776
|
-
async resolveOne(ref, repository) {
|
|
2777
|
-
const search = ref.symbol || ref.path;
|
|
2778
|
-
if (!search || ref.line !== void 0 && (!Number.isInteger(ref.line) || ref.line < 1)) {
|
|
2779
|
-
return { ref, candidates: [], error: "invalid_ref" };
|
|
2780
|
-
}
|
|
2781
|
-
try {
|
|
2782
|
-
const args = ["query", "-p", repository.repoRoot, "-j", "-l", "50"];
|
|
2783
|
-
if (ref.kind) args.push("-k", ref.kind);
|
|
2784
|
-
args.push(search);
|
|
2785
|
-
const hits = await this.runJson(args);
|
|
2786
|
-
let candidates = hits.map(asNode).map(toCandidate).filter((item) => item !== null);
|
|
2787
|
-
if (ref.path) {
|
|
2788
|
-
const absolutePath = (0, import_node_path.isAbsolute)(ref.path) ? await (0, import_promises.realpath)(ref.path).catch(() => ref.path) : void 0;
|
|
2789
|
-
const wanted = normalizePath(
|
|
2790
|
-
absolutePath ? (0, import_node_path.relative)(repository.repoRoot, absolutePath) : ref.path
|
|
2791
|
-
);
|
|
2792
|
-
candidates = candidates.filter((candidate) => candidate.path === wanted);
|
|
2793
|
-
}
|
|
2794
|
-
if (ref.language) {
|
|
2795
|
-
candidates = candidates.filter(
|
|
2796
|
-
(candidate) => candidate.language.toLowerCase() === ref.language?.toLowerCase()
|
|
2627
|
+
const impacts = await Promise.all(
|
|
2628
|
+
unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
|
|
2797
2629
|
);
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2630
|
+
const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
|
|
2631
|
+
const joined = await joinMaterialized(
|
|
2632
|
+
codeClient,
|
|
2633
|
+
structural,
|
|
2634
|
+
agentId,
|
|
2635
|
+
Math.min(limit ?? 50, 50)
|
|
2802
2636
|
);
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2637
|
+
return ok2({
|
|
2638
|
+
target_uid: targetUid,
|
|
2639
|
+
graph_context: graphContext,
|
|
2640
|
+
impacts,
|
|
2641
|
+
materialized: joined,
|
|
2642
|
+
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
2808
2643
|
});
|
|
2809
|
-
if (exact.length > 0) candidates = exact;
|
|
2810
2644
|
}
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2645
|
+
default:
|
|
2646
|
+
return err3("unknown_action", `unknown mindgraph_code action: ${action}`);
|
|
2647
|
+
}
|
|
2648
|
+
} catch (cause) {
|
|
2649
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
2650
|
+
return err3("unknown_repository", cause.message);
|
|
2651
|
+
}
|
|
2652
|
+
return toolError(cause, "code_tool_failed");
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
async function attachCodeRefsToToolResult(client, result, args, adapter) {
|
|
2656
|
+
if (result.isError || !Array.isArray(args.code_refs) || args.code_refs.length === 0) {
|
|
2657
|
+
return result;
|
|
2658
|
+
}
|
|
2659
|
+
const original = JSON.parse(result.content[0]?.text ?? "null");
|
|
2660
|
+
const targetUids = primaryUids(original);
|
|
2661
|
+
if (targetUids.length === 0) {
|
|
2662
|
+
return ok2({
|
|
2663
|
+
result: original,
|
|
2664
|
+
code_anchors: {
|
|
2665
|
+
wrote: false,
|
|
2666
|
+
caveat: "the write response exposed no primary UID to attach"
|
|
2816
2667
|
}
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
const anchorResult = await handleCodeTool(
|
|
2671
|
+
client,
|
|
2672
|
+
{
|
|
2673
|
+
action: "anchor",
|
|
2674
|
+
code_refs: args.code_refs,
|
|
2675
|
+
repo: args.repo,
|
|
2676
|
+
repo_space_uid: args.repo_space_uid,
|
|
2677
|
+
agent_id: args.agent_id,
|
|
2678
|
+
// Keeps the default adapter anchored to the live session directory
|
|
2679
|
+
// rather than wherever the MCP server process was launched.
|
|
2680
|
+
invocation_context: args.invocation_context
|
|
2681
|
+
},
|
|
2682
|
+
adapter
|
|
2683
|
+
);
|
|
2684
|
+
const anchors = JSON.parse(anchorResult.content[0]?.text ?? "null");
|
|
2685
|
+
if (!anchorResult.isError) {
|
|
2686
|
+
const codeClient = client;
|
|
2687
|
+
const anchorUids = (anchors.anchors ?? []).flatMap(
|
|
2688
|
+
(anchor) => anchor.uid ? [anchor.uid] : []
|
|
2689
|
+
);
|
|
2690
|
+
const relateFailures = [];
|
|
2691
|
+
for (const targetUid of targetUids) {
|
|
2692
|
+
for (const anchorUid of anchorUids) {
|
|
2693
|
+
try {
|
|
2694
|
+
await codeClient.entity({
|
|
2695
|
+
action: "relate",
|
|
2696
|
+
source_uid: targetUid,
|
|
2697
|
+
target_uid: anchorUid,
|
|
2698
|
+
edge_type: args.action === "lesson" ? "ABOUT" : "RELEVANT_TO",
|
|
2699
|
+
agent_id: args.agent_id
|
|
2700
|
+
});
|
|
2701
|
+
} catch (cause) {
|
|
2702
|
+
relateFailures.push(
|
|
2703
|
+
`${targetUid} -> ${anchorUid}: ${errorDetail(cause)}`
|
|
2827
2704
|
);
|
|
2828
|
-
if (narrowed.length === 1) candidates = narrowed;
|
|
2829
2705
|
}
|
|
2830
2706
|
}
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
return {
|
|
2839
|
-
ref,
|
|
2840
|
-
candidates,
|
|
2841
|
-
resolved: {
|
|
2842
|
-
repoId: repository.repoId,
|
|
2843
|
-
repoRoot: repository.repoRoot,
|
|
2844
|
-
...candidates[0]
|
|
2707
|
+
}
|
|
2708
|
+
if (relateFailures.length > 0) {
|
|
2709
|
+
return ok2({
|
|
2710
|
+
result: original,
|
|
2711
|
+
code_anchors: {
|
|
2712
|
+
...anchors,
|
|
2713
|
+
caveat: `anchors written, but ${relateFailures.length} relate edge(s) failed: ${relateFailures.join("; ")}`
|
|
2845
2714
|
}
|
|
2846
|
-
};
|
|
2847
|
-
}
|
|
2848
|
-
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
return ok2({ result: original, code_anchors: anchors });
|
|
2719
|
+
}
|
|
2720
|
+
async function anchorRepository(client, repository, spaceUid, agentId) {
|
|
2721
|
+
return client.entity({
|
|
2722
|
+
action: "create",
|
|
2723
|
+
label: repository.repoId,
|
|
2724
|
+
props: {
|
|
2725
|
+
entity_type: "repository",
|
|
2726
|
+
canonical_name: repository.repoId,
|
|
2727
|
+
identifiers: { repo_id: repository.repoId }
|
|
2728
|
+
},
|
|
2729
|
+
identity: {
|
|
2730
|
+
namespace: "external.code",
|
|
2731
|
+
key_version: 1,
|
|
2732
|
+
key: repositoryIdentityKey(repository)
|
|
2733
|
+
},
|
|
2734
|
+
identity_space_uid: spaceUid,
|
|
2735
|
+
agent_id: agentId
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
async function anchorCodeRef(client, ref, repositoryUid, spaceUid, agentId) {
|
|
2739
|
+
const response = await client.entity({
|
|
2740
|
+
action: "create",
|
|
2741
|
+
label: ref.qualifiedName,
|
|
2742
|
+
props: {
|
|
2743
|
+
entity_type: ref.kind === "file" ? "code_file" : "code_symbol",
|
|
2744
|
+
canonical_name: ref.qualifiedName,
|
|
2745
|
+
identifiers: {
|
|
2746
|
+
repo_id: ref.repoId,
|
|
2747
|
+
path: ref.path,
|
|
2748
|
+
qualified_name: ref.qualifiedName
|
|
2749
|
+
},
|
|
2750
|
+
attributes: { code_ref: storedCodeRef(ref) }
|
|
2751
|
+
},
|
|
2752
|
+
identity: {
|
|
2753
|
+
namespace: "external.code",
|
|
2754
|
+
key_version: 1,
|
|
2755
|
+
key: codeRefIdentityKey(ref)
|
|
2756
|
+
},
|
|
2757
|
+
identity_space_uid: spaceUid,
|
|
2758
|
+
agent_id: agentId
|
|
2759
|
+
});
|
|
2760
|
+
if (response.uid && response.created !== false && response.status !== "existing") {
|
|
2761
|
+
await client.entity({
|
|
2762
|
+
action: "relate",
|
|
2763
|
+
source_uid: response.uid,
|
|
2764
|
+
target_uid: repositoryUid,
|
|
2765
|
+
edge_type: "PART_OF",
|
|
2766
|
+
agent_id: agentId
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
return { ...response, ref };
|
|
2770
|
+
}
|
|
2771
|
+
async function resolveMaterializedUid(client, ref, agentId) {
|
|
2772
|
+
const response = await client.entity({
|
|
2773
|
+
action: "resolve_identity",
|
|
2774
|
+
identity: {
|
|
2775
|
+
namespace: "external.code",
|
|
2776
|
+
key_version: 1,
|
|
2777
|
+
key: codeRefIdentityKey(ref)
|
|
2778
|
+
},
|
|
2779
|
+
agent_id: agentId
|
|
2780
|
+
});
|
|
2781
|
+
return response.status === "existing" ? response.uid : void 0;
|
|
2782
|
+
}
|
|
2783
|
+
async function codeRefFromAnchor(client, adapter, uid) {
|
|
2784
|
+
const node = await client.getNode(uid);
|
|
2785
|
+
const attributes = node.props?.attributes;
|
|
2786
|
+
if (!attributes || typeof attributes !== "object") return void 0;
|
|
2787
|
+
const codeRef = attributes.code_ref;
|
|
2788
|
+
if (!isStoredCodeRef(codeRef)) return void 0;
|
|
2789
|
+
try {
|
|
2790
|
+
const repository = await adapter.resolveRepository(codeRef.repoId);
|
|
2791
|
+
return { ref: { ...codeRef, repoRoot: repository.repoRoot } };
|
|
2792
|
+
} catch (cause) {
|
|
2793
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
2794
|
+
return { missingRepo: codeRef.repoId };
|
|
2795
|
+
}
|
|
2796
|
+
throw cause;
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
async function resolveSingleTarget(client, adapter, refs, anchorUids, options) {
|
|
2800
|
+
if (refs.length > 1 || anchorUids.length > 1) {
|
|
2801
|
+
return { error: err3("ambiguous_target", "expand/affected accepts exactly one code target") };
|
|
2802
|
+
}
|
|
2803
|
+
if (anchorUids.length === 1) {
|
|
2804
|
+
const outcome = await codeRefFromAnchor(client, adapter, anchorUids[0]);
|
|
2805
|
+
if (!outcome) {
|
|
2806
|
+
return { error: err3("invalid_anchor", "anchor UID does not contain a typed code_ref") };
|
|
2807
|
+
}
|
|
2808
|
+
if ("missingRepo" in outcome) {
|
|
2849
2809
|
return {
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2810
|
+
error: err3(
|
|
2811
|
+
"repository_unavailable",
|
|
2812
|
+
`repository ${outcome.missingRepo} is not checked out here or declared in .mindgraph/workspace.json; expand/affected need its local index`
|
|
2813
|
+
)
|
|
2853
2814
|
};
|
|
2854
2815
|
}
|
|
2816
|
+
return { ref: outcome.ref };
|
|
2855
2817
|
}
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
action,
|
|
2859
|
-
"-p",
|
|
2860
|
-
resolved.repoRoot,
|
|
2861
|
-
"-j",
|
|
2862
|
-
"-l",
|
|
2863
|
-
String(limit),
|
|
2864
|
-
resolved.qualifiedName
|
|
2865
|
-
]);
|
|
2866
|
-
const refs = (raw[action] ?? []).map((node) => ({
|
|
2867
|
-
path: node.filePath,
|
|
2868
|
-
line: node.startLine,
|
|
2869
|
-
symbol: node.qualifiedName ?? node.name,
|
|
2870
|
-
kind: node.kind
|
|
2871
|
-
}));
|
|
2872
|
-
return this.resolveRelatedRefs(refs, {
|
|
2873
|
-
repoId: resolved.repoId,
|
|
2874
|
-
repoRoot: resolved.repoRoot
|
|
2875
|
-
});
|
|
2818
|
+
if (refs.length !== 1) {
|
|
2819
|
+
return { error: err3("missing_code_ref", "one code_ref or anchor_uid is required") };
|
|
2876
2820
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
return
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2821
|
+
const resolution = await adapter.resolveRefs(refs, options);
|
|
2822
|
+
const result = resolution.results[0];
|
|
2823
|
+
if (!result.resolved) {
|
|
2824
|
+
return {
|
|
2825
|
+
error: ok2({
|
|
2826
|
+
availability: resolution.availability,
|
|
2827
|
+
result,
|
|
2828
|
+
wrote: false
|
|
2829
|
+
})
|
|
2830
|
+
};
|
|
2886
2831
|
}
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2832
|
+
return { ref: result.resolved };
|
|
2833
|
+
}
|
|
2834
|
+
async function joinMaterialized(client, refs, agentId, limit) {
|
|
2835
|
+
const joined = [];
|
|
2836
|
+
for (const ref of uniqueRefs(refs).slice(0, limit)) {
|
|
2837
|
+
const uid = await resolveMaterializedUid(client, ref, agentId);
|
|
2838
|
+
if (!uid) continue;
|
|
2839
|
+
joined.push({
|
|
2840
|
+
ref,
|
|
2841
|
+
anchor_uid: uid,
|
|
2842
|
+
graph: await client.traverse({
|
|
2843
|
+
action: "neighborhood",
|
|
2844
|
+
start_uid: uid,
|
|
2845
|
+
depth: 2,
|
|
2846
|
+
max_nodes: 20
|
|
2847
|
+
})
|
|
2894
2848
|
});
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2849
|
+
}
|
|
2850
|
+
return joined;
|
|
2851
|
+
}
|
|
2852
|
+
async function codeRefsFromGraphContext(client, adapter, context) {
|
|
2853
|
+
const uids = /* @__PURE__ */ new Set();
|
|
2854
|
+
collectUids(context, uids);
|
|
2855
|
+
if (uids.size === 0) return { refs: [], unavailable: [] };
|
|
2856
|
+
const nodes = client.getNodesBatch ? await client.getNodesBatch([...uids]) : await Promise.all([...uids].map((uid) => client.getNode(uid).catch(() => void 0)));
|
|
2857
|
+
const stored = nodes.flatMap((node) => {
|
|
2858
|
+
if (!node) return [];
|
|
2859
|
+
const attributes = node.props?.attributes;
|
|
2860
|
+
if (!attributes || typeof attributes !== "object") return [];
|
|
2861
|
+
const codeRef = attributes.code_ref;
|
|
2862
|
+
return isStoredCodeRef(codeRef) ? [codeRef] : [];
|
|
2863
|
+
});
|
|
2864
|
+
const refs = [];
|
|
2865
|
+
const unavailable = /* @__PURE__ */ new Set();
|
|
2866
|
+
for (const ref of stored) {
|
|
2867
|
+
try {
|
|
2868
|
+
const repository = await adapter.resolveRepository(ref.repoId);
|
|
2869
|
+
refs.push({ ...ref, repoRoot: repository.repoRoot });
|
|
2870
|
+
} catch (cause) {
|
|
2871
|
+
if (cause instanceof UnknownRepositoryError) {
|
|
2872
|
+
unavailable.add(ref.repoId);
|
|
2873
|
+
} else {
|
|
2874
|
+
throw cause;
|
|
2910
2875
|
}
|
|
2911
2876
|
}
|
|
2912
|
-
throw lastFailure ?? Object.assign(new Error("codegraph executable not found"), {
|
|
2913
|
-
kind: "absent"
|
|
2914
|
-
});
|
|
2915
2877
|
}
|
|
2916
|
-
};
|
|
2917
|
-
function repositoryIdentityKey(repository) {
|
|
2918
|
-
return { v: 1, kind: "repository", repo_id: repository.repoId };
|
|
2878
|
+
return { refs, unavailable: [...unavailable].sort() };
|
|
2919
2879
|
}
|
|
2920
|
-
function
|
|
2921
|
-
if (
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
kind: "file",
|
|
2925
|
-
repo_id: ref.repoId,
|
|
2926
|
-
path: ref.path
|
|
2927
|
-
};
|
|
2880
|
+
function collectUids(value, uids) {
|
|
2881
|
+
if (Array.isArray(value)) {
|
|
2882
|
+
value.forEach((item) => collectUids(item, uids));
|
|
2883
|
+
return;
|
|
2928
2884
|
}
|
|
2929
|
-
return
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
qualified_name: ref.qualifiedName,
|
|
2936
|
-
disambiguator: {
|
|
2937
|
-
signature: ref.signature ?? "",
|
|
2938
|
-
path: ref.path
|
|
2885
|
+
if (!value || typeof value !== "object") return;
|
|
2886
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2887
|
+
if (typeof child === "string" && ["uid", "node_uid", "neighbor_uid", "from_uid", "to_uid"].includes(key)) {
|
|
2888
|
+
uids.add(child);
|
|
2889
|
+
} else {
|
|
2890
|
+
collectUids(child, uids);
|
|
2939
2891
|
}
|
|
2940
|
-
}
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
function isStoredCodeRef(value) {
|
|
2895
|
+
if (!value || typeof value !== "object") return false;
|
|
2896
|
+
const ref = value;
|
|
2897
|
+
return typeof ref.repoId === "string" && typeof ref.path === "string" && typeof ref.language === "string" && typeof ref.kind === "string" && typeof ref.qualifiedName === "string" && (typeof ref.signature === "string" || ref.signature === null) && typeof ref.startLine === "number" && typeof ref.endLine === "number";
|
|
2898
|
+
}
|
|
2899
|
+
function storedCodeRef(ref) {
|
|
2900
|
+
const { repoRoot: _localOnly, ...stored } = ref;
|
|
2901
|
+
return stored;
|
|
2902
|
+
}
|
|
2903
|
+
function uniqueRefs(refs) {
|
|
2904
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2905
|
+
return refs.filter((ref) => {
|
|
2906
|
+
const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
|
|
2907
|
+
if (seen.has(key)) return false;
|
|
2908
|
+
seen.add(key);
|
|
2909
|
+
return true;
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2912
|
+
function primaryUids(value) {
|
|
2913
|
+
if (!value || typeof value !== "object") return [];
|
|
2914
|
+
const object = value;
|
|
2915
|
+
const keys = [
|
|
2916
|
+
"uid",
|
|
2917
|
+
"claim_uid",
|
|
2918
|
+
"goal_uid",
|
|
2919
|
+
"project_uid",
|
|
2920
|
+
"milestone_uid",
|
|
2921
|
+
"decision_uid",
|
|
2922
|
+
"task_uid",
|
|
2923
|
+
"plan_uid",
|
|
2924
|
+
"step_uid",
|
|
2925
|
+
"execution_uid",
|
|
2926
|
+
"lesson_uid"
|
|
2927
|
+
];
|
|
2928
|
+
return [
|
|
2929
|
+
...new Set(
|
|
2930
|
+
keys.flatMap((key) => typeof object[key] === "string" ? [object[key]] : [])
|
|
2931
|
+
)
|
|
2932
|
+
];
|
|
2941
2933
|
}
|
|
2942
2934
|
|
|
2943
|
-
// src/
|
|
2944
|
-
var
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
name: "mindgraph_code",
|
|
2958
|
-
description: "Resolve typed code references through the local codegraph index and join them to persistent MindGraph memory/work. anchor creates exact repository/code anchors; recall reads attached graph context; expand reads callers/callees; affected reads impact for a code ref or Task/Execution. Ambiguous refs return candidates without writing. Refs with a path route to the repository defining that file (nearest declared root in .mindgraph/workspace.json); bare symbols resolve against the current repository only \u2014 pass path or repo to target a sibling.",
|
|
2935
|
+
// src/workspace-scope.ts
|
|
2936
|
+
var WORKSPACE_RESUME_LIMIT = 9;
|
|
2937
|
+
var SCOPE_CACHE_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
2938
|
+
|
|
2939
|
+
// src/memory-tool.ts
|
|
2940
|
+
var RESULT_SCHEMA_VERSION = "mindgraph.memory.result.v1";
|
|
2941
|
+
var DEFAULT_OUTPUT_CHARS = 8e3;
|
|
2942
|
+
var MIN_OUTPUT_CHARS = 512;
|
|
2943
|
+
var MAX_OUTPUT_CHARS = 24e3;
|
|
2944
|
+
var HIDDEN_CONTEXT_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
|
|
2945
|
+
var HIDDEN_CONTEXT_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
|
|
2946
|
+
var MEMORY_TOOL = {
|
|
2947
|
+
name: "mindgraph_memory",
|
|
2948
|
+
description: "Read the portable MindGraph memory surface. Use action='context' with mode='resume' or 'continuity' for authoritative task-first work state, and mode='topic' with a short keyword query for prior knowledge relevant to the user's request. Use action='status' to inspect integration capabilities and limitations. This tool never writes; durable capture and correction use the existing typed MindGraph write tools.",
|
|
2959
2949
|
inputSchema: {
|
|
2960
2950
|
type: "object",
|
|
2961
2951
|
properties: {
|
|
2962
2952
|
action: {
|
|
2963
2953
|
type: "string",
|
|
2964
|
-
enum: ["
|
|
2954
|
+
enum: ["status", "context"],
|
|
2955
|
+
description: "Read integration status or bounded memory context"
|
|
2965
2956
|
},
|
|
2966
|
-
|
|
2957
|
+
mode: {
|
|
2958
|
+
type: "string",
|
|
2959
|
+
enum: ["topic", "continuity", "resume"],
|
|
2960
|
+
description: "Context strategy. topic uses query-shaped retrieval; continuity/resume use authoritative task-first resume_work."
|
|
2961
|
+
},
|
|
2962
|
+
query: {
|
|
2963
|
+
type: "string",
|
|
2964
|
+
description: "Required for mode='topic': 1-3 discriminating BM25 keywords, not a full sentence"
|
|
2965
|
+
},
|
|
2966
|
+
project_uid: { type: "string", description: "Optional project scope" },
|
|
2967
|
+
task_uid: { type: "string", description: "Optional task scope for resume context" },
|
|
2968
|
+
session_uid: { type: "string", description: "Optional durable session scope" },
|
|
2969
|
+
scope_uids: {
|
|
2967
2970
|
type: "array",
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
oneOf: [
|
|
2971
|
-
{ type: "string" },
|
|
2972
|
-
{
|
|
2973
|
-
type: "object",
|
|
2974
|
-
properties: {
|
|
2975
|
-
repo: { type: "string" },
|
|
2976
|
-
path: { type: "string" },
|
|
2977
|
-
line: { type: "integer", minimum: 1 },
|
|
2978
|
-
symbol: { type: "string" },
|
|
2979
|
-
kind: { type: "string" },
|
|
2980
|
-
language: { type: "string" },
|
|
2981
|
-
signature: { type: "string" }
|
|
2982
|
-
},
|
|
2983
|
-
additionalProperties: false
|
|
2984
|
-
}
|
|
2985
|
-
]
|
|
2986
|
-
}
|
|
2971
|
+
items: { type: "string" },
|
|
2972
|
+
description: "Optional explicit graph scopes for continuity/resume"
|
|
2987
2973
|
},
|
|
2988
|
-
|
|
2974
|
+
scope: {
|
|
2975
|
+
type: "string",
|
|
2976
|
+
enum: ["workspace"],
|
|
2977
|
+
description: "For continuity/resume on a local server: select work across every repository the working directory's .mindgraph/workspace.json declares, and list the other eligible tasks grouped by repository"
|
|
2978
|
+
},
|
|
2979
|
+
node_types: {
|
|
2989
2980
|
type: "array",
|
|
2990
|
-
maxItems: 50,
|
|
2991
2981
|
items: { type: "string" },
|
|
2992
|
-
description: "
|
|
2982
|
+
description: "Optional node-type filter for topic context"
|
|
2993
2983
|
},
|
|
2994
|
-
|
|
2984
|
+
layer: {
|
|
2995
2985
|
type: "string",
|
|
2996
|
-
|
|
2986
|
+
enum: ["reality", "epistemic", "intent", "action", "memory", "agent"],
|
|
2987
|
+
description: "Optional cognitive-layer filter for topic context"
|
|
2997
2988
|
},
|
|
2998
|
-
|
|
2989
|
+
limit: {
|
|
2990
|
+
type: "integer",
|
|
2991
|
+
minimum: 1,
|
|
2992
|
+
maximum: 50,
|
|
2993
|
+
default: 8,
|
|
2994
|
+
description: "Maximum direct graph items requested before bounded expansion"
|
|
2995
|
+
},
|
|
2996
|
+
include_chunks: {
|
|
2997
|
+
type: "boolean",
|
|
2998
|
+
default: false,
|
|
2999
|
+
description: "Include bounded source excerpts when topic summaries are insufficient"
|
|
3000
|
+
},
|
|
3001
|
+
include_documents: {
|
|
3002
|
+
type: "boolean",
|
|
3003
|
+
default: false,
|
|
3004
|
+
description: "Include bounded knowledge articles for topic context"
|
|
3005
|
+
},
|
|
3006
|
+
valid_at: {
|
|
2999
3007
|
type: "string",
|
|
3000
|
-
description: "
|
|
3008
|
+
description: "Optional ISO timestamp for time-aware topic retrieval"
|
|
3001
3009
|
},
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3010
|
+
graph_expansion_limit: {
|
|
3011
|
+
type: "integer",
|
|
3012
|
+
minimum: 0,
|
|
3013
|
+
maximum: 20,
|
|
3014
|
+
default: 3,
|
|
3015
|
+
description: "Bounded graph neighbors added after direct topic matches"
|
|
3016
|
+
},
|
|
3017
|
+
graph_max_depth: {
|
|
3018
|
+
type: "integer",
|
|
3019
|
+
minimum: 0,
|
|
3020
|
+
maximum: 5,
|
|
3021
|
+
default: 2,
|
|
3022
|
+
description: "Maximum graph-expansion depth for topic context"
|
|
3023
|
+
},
|
|
3024
|
+
max_output_chars: {
|
|
3025
|
+
type: "integer",
|
|
3026
|
+
minimum: MIN_OUTPUT_CHARS,
|
|
3027
|
+
maximum: MAX_OUTPUT_CHARS,
|
|
3028
|
+
default: DEFAULT_OUTPUT_CHARS,
|
|
3029
|
+
description: "Hard character budget for model-ready context; whole items are retained or omitted"
|
|
3030
|
+
},
|
|
3031
|
+
agent_id: { type: "string", description: "Agent identity" }
|
|
3006
3032
|
},
|
|
3007
3033
|
required: ["action"]
|
|
3008
3034
|
},
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
}
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
const
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
for (const { repository } of resolved) {
|
|
3051
|
-
if (repositoryAnchors.has(repository.repoRoot)) continue;
|
|
3052
|
-
const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
|
|
3053
|
-
const anchored = await anchorRepository(
|
|
3054
|
-
codeClient,
|
|
3055
|
-
repository,
|
|
3056
|
-
spaceUid,
|
|
3057
|
-
agentId
|
|
3058
|
-
);
|
|
3059
|
-
repositoryAnchors.set(repository.repoRoot, {
|
|
3060
|
-
...anchored,
|
|
3061
|
-
repository
|
|
3062
|
-
});
|
|
3063
|
-
}
|
|
3064
|
-
const anchors = [];
|
|
3065
|
-
for (const { ref, repository } of resolved) {
|
|
3066
|
-
const repositoryAnchor = repositoryAnchors.get(repository.repoRoot);
|
|
3067
|
-
if (!repositoryAnchor.uid) continue;
|
|
3068
|
-
const spaceUid = typeof args.repo_space_uid === "string" ? args.repo_space_uid : repository.spaceUid;
|
|
3069
|
-
const anchor = await anchorCodeRef(
|
|
3070
|
-
codeClient,
|
|
3071
|
-
ref,
|
|
3072
|
-
repositoryAnchor.uid,
|
|
3073
|
-
spaceUid,
|
|
3074
|
-
agentId
|
|
3075
|
-
);
|
|
3076
|
-
anchors.push(anchor);
|
|
3077
|
-
}
|
|
3078
|
-
return ok2({
|
|
3079
|
-
availability: resolution.availability,
|
|
3080
|
-
repository: repositoryAnchors.values().next().value,
|
|
3081
|
-
repositories: [...repositoryAnchors.values()],
|
|
3082
|
-
anchors,
|
|
3083
|
-
unresolved,
|
|
3084
|
-
wrote: anchors.some((anchor) => anchor.created)
|
|
3085
|
-
});
|
|
3086
|
-
}
|
|
3087
|
-
case "recall": {
|
|
3088
|
-
const materialized = new Set(anchorUids);
|
|
3089
|
-
let resolution;
|
|
3090
|
-
if (refs.length > 0) {
|
|
3091
|
-
resolution = await adapter.resolveRefs(refs, options);
|
|
3092
|
-
for (const result of resolution.results) {
|
|
3093
|
-
if (!result.resolved) continue;
|
|
3094
|
-
const uid = await resolveMaterializedUid(codeClient, result.resolved, agentId);
|
|
3095
|
-
if (uid) materialized.add(uid);
|
|
3096
|
-
}
|
|
3097
|
-
}
|
|
3098
|
-
if (materialized.size === 0) {
|
|
3099
|
-
return ok2({
|
|
3100
|
-
availability: resolution?.availability,
|
|
3101
|
-
anchors: [],
|
|
3102
|
-
context: [],
|
|
3103
|
-
unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
|
|
3104
|
-
});
|
|
3105
|
-
}
|
|
3106
|
-
const context = await Promise.all(
|
|
3107
|
-
[...materialized].map(async (uid) => ({
|
|
3108
|
-
anchor_uid: uid,
|
|
3109
|
-
graph: await codeClient.traverse({
|
|
3110
|
-
action: "neighborhood",
|
|
3111
|
-
start_uid: uid,
|
|
3112
|
-
depth: 2,
|
|
3113
|
-
max_nodes: Math.min(limit ?? 20, 20)
|
|
3114
|
-
})
|
|
3115
|
-
}))
|
|
3116
|
-
);
|
|
3117
|
-
return ok2({
|
|
3118
|
-
availability: resolution?.availability,
|
|
3119
|
-
anchors: [...materialized],
|
|
3120
|
-
context,
|
|
3121
|
-
unresolved: resolution?.results.filter((result) => !result.resolved) ?? []
|
|
3122
|
-
});
|
|
3123
|
-
}
|
|
3124
|
-
case "expand": {
|
|
3125
|
-
const target = await resolveSingleTarget(
|
|
3126
|
-
codeClient,
|
|
3127
|
-
adapter,
|
|
3128
|
-
refs,
|
|
3129
|
-
anchorUids,
|
|
3130
|
-
options
|
|
3131
|
-
);
|
|
3132
|
-
if ("error" in target) return target.error;
|
|
3133
|
-
const expanded = await adapter.expand(target.ref, limit);
|
|
3134
|
-
const joined = await joinMaterialized(
|
|
3135
|
-
codeClient,
|
|
3136
|
-
[...expanded.callers, ...expanded.callees],
|
|
3137
|
-
agentId,
|
|
3138
|
-
Math.min(limit ?? 20, 20)
|
|
3139
|
-
);
|
|
3140
|
-
return ok2({ anchor: target.ref, ...expanded, materialized: joined });
|
|
3141
|
-
}
|
|
3142
|
-
case "affected": {
|
|
3143
|
-
const targetUid = typeof args.task_uid === "string" ? args.task_uid : typeof args.execution_uid === "string" ? args.execution_uid : void 0;
|
|
3144
|
-
let graphContext;
|
|
3145
|
-
const graphRefs = [];
|
|
3146
|
-
let unavailableRepositories = [];
|
|
3147
|
-
if (targetUid) {
|
|
3148
|
-
graphContext = await codeClient.traverse({
|
|
3149
|
-
action: "neighborhood",
|
|
3150
|
-
start_uid: targetUid,
|
|
3151
|
-
depth: 2,
|
|
3152
|
-
max_nodes: 50
|
|
3153
|
-
});
|
|
3154
|
-
const fromGraph = await codeRefsFromGraphContext(
|
|
3155
|
-
codeClient,
|
|
3156
|
-
adapter,
|
|
3157
|
-
graphContext
|
|
3158
|
-
);
|
|
3159
|
-
graphRefs.push(...fromGraph.refs);
|
|
3160
|
-
unavailableRepositories = fromGraph.unavailable;
|
|
3161
|
-
}
|
|
3162
|
-
if (refs.length > 0 || anchorUids.length > 0) {
|
|
3163
|
-
const target = await resolveSingleTarget(
|
|
3164
|
-
codeClient,
|
|
3165
|
-
adapter,
|
|
3166
|
-
refs,
|
|
3167
|
-
anchorUids,
|
|
3168
|
-
options
|
|
3169
|
-
);
|
|
3170
|
-
if ("error" in target) return target.error;
|
|
3171
|
-
graphRefs.unshift(target.ref);
|
|
3172
|
-
}
|
|
3173
|
-
const unique = uniqueRefs(graphRefs);
|
|
3174
|
-
if (unique.length === 0) {
|
|
3175
|
-
return ok2({
|
|
3176
|
-
target_uid: targetUid,
|
|
3177
|
-
graph_context: graphContext,
|
|
3178
|
-
affected: [],
|
|
3179
|
-
degradation: unavailableRepositories.length > 0 ? `attached code anchors reference repositories unavailable here: ${unavailableRepositories.join(", ")}` : "no materialized code anchors were attached",
|
|
3180
|
-
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
3181
|
-
});
|
|
3182
|
-
}
|
|
3183
|
-
const impacts = await Promise.all(
|
|
3184
|
-
unique.map(async (ref) => ({ anchor: ref, ...await adapter.affected(ref, limit) }))
|
|
3185
|
-
);
|
|
3186
|
-
const structural = uniqueRefs(impacts.flatMap((impact) => impact.affected));
|
|
3187
|
-
const joined = await joinMaterialized(
|
|
3188
|
-
codeClient,
|
|
3189
|
-
structural,
|
|
3190
|
-
agentId,
|
|
3191
|
-
Math.min(limit ?? 50, 50)
|
|
3192
|
-
);
|
|
3193
|
-
return ok2({
|
|
3194
|
-
target_uid: targetUid,
|
|
3195
|
-
graph_context: graphContext,
|
|
3196
|
-
impacts,
|
|
3197
|
-
materialized: joined,
|
|
3198
|
-
...unavailableRepositories.length > 0 ? { unavailable_repositories: unavailableRepositories } : {}
|
|
3199
|
-
});
|
|
3035
|
+
outputSchema: {
|
|
3036
|
+
type: "object",
|
|
3037
|
+
properties: {
|
|
3038
|
+
schema_version: { type: "string", const: RESULT_SCHEMA_VERSION },
|
|
3039
|
+
action: { type: "string", enum: ["status", "context"] },
|
|
3040
|
+
profile: { type: "string" },
|
|
3041
|
+
server: { type: "object" },
|
|
3042
|
+
integration: { type: "object" },
|
|
3043
|
+
capabilities: { type: "object" },
|
|
3044
|
+
degradation: { type: "array", items: { type: "object" } },
|
|
3045
|
+
context_id: { type: "string" },
|
|
3046
|
+
mode: { type: "string", enum: ["topic", "continuity", "resume"] },
|
|
3047
|
+
scope: { type: "object" },
|
|
3048
|
+
rendered_context: { type: "string" },
|
|
3049
|
+
rendered_hash: { type: "string" },
|
|
3050
|
+
items: { type: "array", items: { type: "object" } },
|
|
3051
|
+
retrieval_trace: { type: "object" },
|
|
3052
|
+
budget: { type: "object" },
|
|
3053
|
+
freshness: { type: "object" },
|
|
3054
|
+
warnings: { type: "array", items: { type: "object" } }
|
|
3055
|
+
},
|
|
3056
|
+
required: ["schema_version", "action"],
|
|
3057
|
+
oneOf: [
|
|
3058
|
+
{
|
|
3059
|
+
properties: { action: { const: "status" } },
|
|
3060
|
+
required: ["profile", "server", "integration", "scope", "capabilities", "degradation"]
|
|
3061
|
+
},
|
|
3062
|
+
{
|
|
3063
|
+
properties: { action: { const: "context" } },
|
|
3064
|
+
required: [
|
|
3065
|
+
"context_id",
|
|
3066
|
+
"mode",
|
|
3067
|
+
"scope",
|
|
3068
|
+
"rendered_context",
|
|
3069
|
+
"rendered_hash",
|
|
3070
|
+
"items",
|
|
3071
|
+
"retrieval_trace",
|
|
3072
|
+
"budget",
|
|
3073
|
+
"freshness",
|
|
3074
|
+
"warnings"
|
|
3075
|
+
]
|
|
3200
3076
|
}
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
return toolError(cause, "code_tool_failed");
|
|
3077
|
+
]
|
|
3078
|
+
},
|
|
3079
|
+
annotations: {
|
|
3080
|
+
readOnlyHint: true,
|
|
3081
|
+
destructiveHint: false,
|
|
3082
|
+
idempotentHint: true,
|
|
3083
|
+
openWorldHint: false
|
|
3209
3084
|
}
|
|
3085
|
+
};
|
|
3086
|
+
function obj2(value) {
|
|
3087
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3210
3088
|
}
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3089
|
+
function text(value) {
|
|
3090
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3091
|
+
}
|
|
3092
|
+
function stableJson(value) {
|
|
3093
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
3094
|
+
if (value && typeof value === "object") {
|
|
3095
|
+
return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
|
|
3214
3096
|
}
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3097
|
+
return JSON.stringify(value);
|
|
3098
|
+
}
|
|
3099
|
+
function sha256(value) {
|
|
3100
|
+
return (0, import_node_crypto2.createHash)("sha256").update(value).digest("hex");
|
|
3101
|
+
}
|
|
3102
|
+
function outputBudget(value) {
|
|
3103
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OUTPUT_CHARS;
|
|
3104
|
+
return Math.min(MAX_OUTPUT_CHARS, Math.max(MIN_OUTPUT_CHARS, Math.floor(value)));
|
|
3105
|
+
}
|
|
3106
|
+
function defined(values) {
|
|
3107
|
+
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
|
|
3108
|
+
}
|
|
3109
|
+
function memoryStatus(config = {}) {
|
|
3110
|
+
return {
|
|
3111
|
+
schema_version: RESULT_SCHEMA_VERSION,
|
|
3112
|
+
action: "status",
|
|
3113
|
+
profile: "M0",
|
|
3114
|
+
server: defined({
|
|
3115
|
+
name: "mindgraph-mcp",
|
|
3116
|
+
version: config.serverVersion ?? "unknown",
|
|
3117
|
+
base_url: config.baseUrl
|
|
3118
|
+
}),
|
|
3119
|
+
integration: {
|
|
3120
|
+
adapter_state: "model_controlled",
|
|
3121
|
+
harness: config.harness ?? "generic",
|
|
3122
|
+
configured_tool_profile: config.profile ?? "general",
|
|
3123
|
+
automatic_capture: false,
|
|
3124
|
+
automatic_delivery: false,
|
|
3125
|
+
delivery_verification: "unsupported",
|
|
3126
|
+
curator_mode: "disabled"
|
|
3127
|
+
},
|
|
3128
|
+
scope: defined({ org_id: config.orgId, agent_id: config.agentId }),
|
|
3129
|
+
capabilities: {
|
|
3130
|
+
status: true,
|
|
3131
|
+
topic_context: true,
|
|
3132
|
+
authoritative_resume_context: true,
|
|
3133
|
+
bounded_rendering: true,
|
|
3134
|
+
structured_content: true,
|
|
3135
|
+
typed_explicit_writes: true,
|
|
3136
|
+
lifecycle_event_ingest: false,
|
|
3137
|
+
automatic_context_injection: false,
|
|
3138
|
+
curator_mutation: false
|
|
3139
|
+
},
|
|
3140
|
+
degradation: [
|
|
3141
|
+
{
|
|
3142
|
+
code: "automatic_capture_unavailable",
|
|
3143
|
+
message: "M0 does not observe host lifecycle events; explicit typed write tools remain available."
|
|
3144
|
+
},
|
|
3145
|
+
{
|
|
3146
|
+
code: "delivery_unverified",
|
|
3147
|
+
message: "A successful MCP read does not prove that the host injected or used the returned context."
|
|
3148
|
+
},
|
|
3149
|
+
{
|
|
3150
|
+
code: "curator_disabled",
|
|
3151
|
+
message: "Curator proposals and autonomous mutation are not enabled in M0."
|
|
3223
3152
|
}
|
|
3224
|
-
|
|
3153
|
+
]
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
function nodeType(node) {
|
|
3157
|
+
const raw = node.node_type;
|
|
3158
|
+
if (typeof raw === "string") return raw;
|
|
3159
|
+
return text(obj2(raw)?.Custom) ?? "Node";
|
|
3160
|
+
}
|
|
3161
|
+
function compactItem(raw, fallbackType) {
|
|
3162
|
+
const wrapper = obj2(raw);
|
|
3163
|
+
if (!wrapper) return void 0;
|
|
3164
|
+
const value = obj2(wrapper.node) ?? obj2(wrapper.object) ?? wrapper;
|
|
3165
|
+
const uid = text(value.uid) ?? text(value.chunk_uid);
|
|
3166
|
+
if (!uid) return void 0;
|
|
3167
|
+
return defined({
|
|
3168
|
+
uid,
|
|
3169
|
+
type: fallbackType ?? nodeType(value),
|
|
3170
|
+
label: text(value.label) ?? text(value.document_title),
|
|
3171
|
+
truth_status: text(value.truth_status) ?? text(obj2(value.props)?.truth_status),
|
|
3172
|
+
// `true` from /retrieve, an object naming the replacement from /retrieve/context.
|
|
3173
|
+
superseded: value.superseded ? true : void 0,
|
|
3174
|
+
currently_valid: value.currently_valid === false ? false : void 0,
|
|
3175
|
+
valid_at_time: value.valid_at_time === false ? false : void 0,
|
|
3176
|
+
retrieval_origin: text(value.retrieval_origin),
|
|
3177
|
+
retrieval_depth: typeof value.retrieval_depth === "number" ? value.retrieval_depth : void 0
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3180
|
+
function topicCandidates(raw) {
|
|
3181
|
+
const payload = obj2(raw);
|
|
3182
|
+
if (!payload) return [];
|
|
3183
|
+
const graph = obj2(payload.graph);
|
|
3184
|
+
const items = [];
|
|
3185
|
+
for (const article of Array.isArray(payload.articles) ? payload.articles : []) {
|
|
3186
|
+
const item = compactItem(article, "Article");
|
|
3187
|
+
if (item) items.push(item);
|
|
3225
3188
|
}
|
|
3226
|
-
const
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
const
|
|
3243
|
-
|
|
3244
|
-
|
|
3189
|
+
const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
|
3190
|
+
const labels = /* @__PURE__ */ new Map();
|
|
3191
|
+
for (const node of rawNodes) {
|
|
3192
|
+
const item = compactItem(node);
|
|
3193
|
+
if (item) {
|
|
3194
|
+
if (HIDDEN_CONTEXT_NODE_TYPES.has(text(item.type) ?? "")) continue;
|
|
3195
|
+
items.push(item);
|
|
3196
|
+
const uid = text(item.uid);
|
|
3197
|
+
const label = text(item.label);
|
|
3198
|
+
if (uid && label) labels.set(uid, label);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
for (const rawEdge of Array.isArray(graph?.edges) ? graph.edges : []) {
|
|
3202
|
+
const edge = obj2(rawEdge);
|
|
3203
|
+
const fromUid = text(edge?.from_uid);
|
|
3204
|
+
const toUid = text(edge?.to_uid);
|
|
3205
|
+
const edgeType = text(edge?.edge_type) ?? text(obj2(edge?.edge_type)?.Custom);
|
|
3206
|
+
if (!fromUid || !toUid || !edgeType || HIDDEN_CONTEXT_EDGE_TYPES.has(edgeType) || !labels.has(fromUid) || !labels.has(toUid)) {
|
|
3207
|
+
continue;
|
|
3208
|
+
}
|
|
3209
|
+
items.push(
|
|
3210
|
+
defined({
|
|
3211
|
+
type: "Relationship",
|
|
3212
|
+
from_uid: fromUid,
|
|
3213
|
+
to_uid: toUid,
|
|
3214
|
+
edge_type: edgeType,
|
|
3215
|
+
from_label: labels.get(fromUid),
|
|
3216
|
+
to_label: labels.get(toUid)
|
|
3217
|
+
})
|
|
3245
3218
|
);
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3219
|
+
}
|
|
3220
|
+
for (const chunk of Array.isArray(payload.chunks) ? payload.chunks : []) {
|
|
3221
|
+
const item = compactItem(chunk, "SourceExcerpt");
|
|
3222
|
+
if (item) items.push(item);
|
|
3223
|
+
}
|
|
3224
|
+
return items;
|
|
3225
|
+
}
|
|
3226
|
+
function resumeCandidates(raw) {
|
|
3227
|
+
const payload = obj2(raw);
|
|
3228
|
+
if (!payload) return [];
|
|
3229
|
+
const items = [];
|
|
3230
|
+
for (const key of ["task", "goal", "project", "plan", "active_execution", "lease"]) {
|
|
3231
|
+
const item = compactItem(payload[key], key);
|
|
3232
|
+
if (item) items.push(item);
|
|
3233
|
+
}
|
|
3234
|
+
for (const value of Array.isArray(payload.other_eligible) ? payload.other_eligible : []) {
|
|
3235
|
+
const item = compactItem(value, "Task");
|
|
3236
|
+
if (item) items.push(item);
|
|
3237
|
+
}
|
|
3238
|
+
for (const key of ["next_steps", "recent_executions", "blockers", "code_targets"]) {
|
|
3239
|
+
for (const value of Array.isArray(payload[key]) ? payload[key] : []) {
|
|
3240
|
+
const item = compactItem(value, key.replace(/s$/, ""));
|
|
3241
|
+
if (item) items.push(item);
|
|
3263
3242
|
}
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
}
|
|
3271
|
-
});
|
|
3243
|
+
}
|
|
3244
|
+
const knowledge = obj2(payload.knowledge);
|
|
3245
|
+
for (const key of ["lessons", "risks"]) {
|
|
3246
|
+
for (const value of Array.isArray(knowledge?.[key]) ? knowledge[key] : []) {
|
|
3247
|
+
const item = compactItem(value, key.replace(/s$/, ""));
|
|
3248
|
+
if (item) items.push(item);
|
|
3272
3249
|
}
|
|
3273
3250
|
}
|
|
3274
|
-
return
|
|
3251
|
+
return items;
|
|
3275
3252
|
}
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
}
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3253
|
+
function visibleItems(candidates, rendered2) {
|
|
3254
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3255
|
+
return candidates.filter((item) => {
|
|
3256
|
+
const uid = text(item.uid);
|
|
3257
|
+
if (uid) {
|
|
3258
|
+
if (seen.has(uid) || !rendered2.includes(`[${uid}]`)) return false;
|
|
3259
|
+
seen.add(uid);
|
|
3260
|
+
return true;
|
|
3261
|
+
}
|
|
3262
|
+
if (item.type === "Relationship") {
|
|
3263
|
+
const from = text(item.from_label);
|
|
3264
|
+
const to = text(item.to_label);
|
|
3265
|
+
const edgeType = text(item.edge_type);
|
|
3266
|
+
if (!from || !to || !edgeType) return false;
|
|
3267
|
+
const key = `${text(item.from_uid)}:${edgeType}:${text(item.to_uid)}`;
|
|
3268
|
+
if (seen.has(key) || !rendered2.includes(`${from} \u2014${edgeType}\u2192 ${to}`)) return false;
|
|
3269
|
+
seen.add(key);
|
|
3270
|
+
return true;
|
|
3271
|
+
}
|
|
3272
|
+
return false;
|
|
3292
3273
|
});
|
|
3293
3274
|
}
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3275
|
+
function latestTimestamp(value) {
|
|
3276
|
+
let latest;
|
|
3277
|
+
const visit = (current) => {
|
|
3278
|
+
if (Array.isArray(current)) {
|
|
3279
|
+
for (const item of current) visit(item);
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
const record = obj2(current);
|
|
3283
|
+
if (!record) return;
|
|
3284
|
+
for (const [key, item] of Object.entries(record)) {
|
|
3285
|
+
if (["updated_at", "created_at", "completed_at", "started_at", "observed_at"].includes(key) && typeof item === "string" && !Number.isNaN(Date.parse(item)) && (!latest || Date.parse(item) > Date.parse(latest))) {
|
|
3286
|
+
latest = item;
|
|
3287
|
+
}
|
|
3288
|
+
visit(item);
|
|
3289
|
+
}
|
|
3290
|
+
};
|
|
3291
|
+
visit(value);
|
|
3292
|
+
return latest;
|
|
3293
|
+
}
|
|
3294
|
+
function contextWarnings(raw, rendered2, available, returned) {
|
|
3295
|
+
const warnings = [
|
|
3296
|
+
{
|
|
3297
|
+
code: "delivery_unverified",
|
|
3298
|
+
message: "The MCP server returned this context but cannot verify that the host injected or used it."
|
|
3299
|
+
}
|
|
3300
|
+
];
|
|
3301
|
+
const serialized = JSON.stringify(raw);
|
|
3302
|
+
if (available > returned || rendered2.includes("item(s) omitted")) {
|
|
3303
|
+
warnings.push({
|
|
3304
|
+
code: "context_bounded",
|
|
3305
|
+
message: `${Math.max(0, available - returned)} structured item(s) were omitted from the rendered context.`
|
|
3323
3306
|
});
|
|
3324
3307
|
}
|
|
3325
|
-
|
|
3308
|
+
if (/"superseded":(?:true|\{)/.test(serialized)) {
|
|
3309
|
+
warnings.push({ code: "superseded_items_present", message: "Some retrieved items are superseded." });
|
|
3310
|
+
}
|
|
3311
|
+
if (/"currently_valid":false|"valid_at_time":false/.test(serialized)) {
|
|
3312
|
+
warnings.push({
|
|
3313
|
+
code: "validity_warning",
|
|
3314
|
+
message: "Some retrieved items are outside their current or requested validity window."
|
|
3315
|
+
});
|
|
3316
|
+
}
|
|
3317
|
+
const payload = obj2(raw);
|
|
3318
|
+
if (payload?.truncated === true || obj2(payload?.truncation)) {
|
|
3319
|
+
const truncation = obj2(payload?.truncation);
|
|
3320
|
+
if (payload?.truncated === true || Object.values(truncation ?? {}).some(Boolean)) {
|
|
3321
|
+
warnings.push({ code: "source_truncated", message: "The authoritative source reported truncated fields." });
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
if (text(payload?.warning)) {
|
|
3325
|
+
warnings.push({ code: "source_warning", message: text(payload?.warning) });
|
|
3326
|
+
}
|
|
3327
|
+
return warnings;
|
|
3326
3328
|
}
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
key_version: 1,
|
|
3333
|
-
key: codeRefIdentityKey(ref)
|
|
3334
|
-
},
|
|
3335
|
-
agent_id: agentId
|
|
3336
|
-
});
|
|
3337
|
-
return response.status === "existing" ? response.uid : void 0;
|
|
3329
|
+
function error(message) {
|
|
3330
|
+
return {
|
|
3331
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
3332
|
+
isError: true
|
|
3333
|
+
};
|
|
3338
3334
|
}
|
|
3339
|
-
async function
|
|
3340
|
-
const
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3335
|
+
async function handleMemoryTool(client, args, config = {}) {
|
|
3336
|
+
const action = text(args.action);
|
|
3337
|
+
if (action === "status") {
|
|
3338
|
+
const status = memoryStatus({ ...config, agentId: text(args.agent_id) ?? config.agentId });
|
|
3339
|
+
return {
|
|
3340
|
+
content: [{ type: "text", text: JSON.stringify(status) }],
|
|
3341
|
+
structuredContent: status
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
if (action !== "context") {
|
|
3345
|
+
return error("action must be 'status' or 'context'");
|
|
3346
|
+
}
|
|
3347
|
+
const mode = text(args.mode) ?? (text(args.query) ? "topic" : void 0);
|
|
3348
|
+
if (!mode || !["topic", "continuity", "resume"].includes(mode)) {
|
|
3349
|
+
return error("action='context' requires mode='topic', 'continuity', or 'resume'");
|
|
3350
|
+
}
|
|
3351
|
+
const budget = outputBudget(args.max_output_chars);
|
|
3352
|
+
const agentId = text(args.agent_id) ?? config.agentId;
|
|
3353
|
+
let raw;
|
|
3354
|
+
let rendered2;
|
|
3355
|
+
let strategy;
|
|
3356
|
+
let candidates;
|
|
3357
|
+
let workspace;
|
|
3358
|
+
if (mode === "topic") {
|
|
3359
|
+
const query = text(args.query);
|
|
3360
|
+
if (!query) return error("mode='topic' requires a non-empty query of 1-3 discriminating keywords");
|
|
3361
|
+
const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.min(50, Math.max(1, Math.floor(args.limit))) : 8;
|
|
3362
|
+
const request = defined({
|
|
3363
|
+
query,
|
|
3364
|
+
project_uid: text(args.project_uid),
|
|
3365
|
+
node_limit: limit,
|
|
3366
|
+
node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
|
|
3367
|
+
layer: text(args.layer),
|
|
3368
|
+
chunk_limit: args.include_chunks === true ? limit : 0,
|
|
3369
|
+
article_limit: args.include_documents === true ? limit : 0,
|
|
3370
|
+
valid_at: text(args.valid_at),
|
|
3371
|
+
include_graph: true,
|
|
3372
|
+
graph_expansion_limit: typeof args.graph_expansion_limit === "number" ? args.graph_expansion_limit : 3,
|
|
3373
|
+
graph_max_depth: typeof args.graph_max_depth === "number" ? args.graph_max_depth : 2
|
|
3374
|
+
});
|
|
3375
|
+
raw = await client.retrieveContext(request);
|
|
3376
|
+
rendered2 = renderContext(raw, budget) ?? "No results found for this search.";
|
|
3377
|
+
strategy = "retrieve_context";
|
|
3378
|
+
candidates = topicCandidates(raw);
|
|
3379
|
+
} else {
|
|
3380
|
+
if (args.scope !== void 0 && args.scope !== "workspace") {
|
|
3381
|
+
return error("scope must be 'workspace' when given");
|
|
3351
3382
|
}
|
|
3352
|
-
|
|
3383
|
+
if (args.scope === "workspace") {
|
|
3384
|
+
if (!config.resolveWorkspaceScope) {
|
|
3385
|
+
return error(
|
|
3386
|
+
"scope='workspace' needs the local MindGraph MCP server, which reads .mindgraph/workspace.json beside the checkout; this server has no working directory. Pass scope_uids instead."
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
workspace = await config.resolveWorkspaceScope(agentId ?? "mcp");
|
|
3390
|
+
if (workspace.uids.length === 0) {
|
|
3391
|
+
return error(
|
|
3392
|
+
`scope='workspace' resolved no repositories${workspace.failures.length > 0 ? ` (${workspace.failures.join("; ")})` : ""}`
|
|
3393
|
+
);
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
const scopeUids = Array.from(
|
|
3397
|
+
/* @__PURE__ */ new Set([
|
|
3398
|
+
...Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : [],
|
|
3399
|
+
...text(args.project_uid) ? [text(args.project_uid)] : [],
|
|
3400
|
+
...workspace?.uids ?? []
|
|
3401
|
+
])
|
|
3402
|
+
);
|
|
3403
|
+
raw = await client.plan(
|
|
3404
|
+
defined({
|
|
3405
|
+
action: "resume_work",
|
|
3406
|
+
task_uid: text(args.task_uid),
|
|
3407
|
+
session_uid: text(args.session_uid),
|
|
3408
|
+
scope_uids: scopeUids.length > 0 ? scopeUids : void 0,
|
|
3409
|
+
limit: workspace ? WORKSPACE_RESUME_LIMIT : 1,
|
|
3410
|
+
agent_id: agentId
|
|
3411
|
+
})
|
|
3412
|
+
);
|
|
3413
|
+
rendered2 = renderResumeContext(raw, budget, workspace?.names) ?? "No eligible work was found in the requested scope.";
|
|
3414
|
+
strategy = "resume_work";
|
|
3415
|
+
candidates = resumeCandidates(raw);
|
|
3353
3416
|
}
|
|
3417
|
+
const items = visibleItems(candidates, rendered2);
|
|
3418
|
+
const renderedHash = sha256(rendered2);
|
|
3419
|
+
const scope = defined({
|
|
3420
|
+
org_id: config.orgId,
|
|
3421
|
+
agent_id: agentId,
|
|
3422
|
+
harness: config.harness ?? "generic",
|
|
3423
|
+
project_uid: text(args.project_uid),
|
|
3424
|
+
task_uid: text(args.task_uid),
|
|
3425
|
+
session_uid: text(args.session_uid),
|
|
3426
|
+
scope_uids: Array.isArray(args.scope_uids) ? args.scope_uids.map(String) : void 0,
|
|
3427
|
+
workspace_repositories: workspace ? Object.values(workspace.names).sort() : void 0
|
|
3428
|
+
});
|
|
3429
|
+
const contextId = `ctx_${sha256(
|
|
3430
|
+
stableJson({ mode, scope, strategy, query: text(args.query), rendered_hash: renderedHash })
|
|
3431
|
+
).slice(0, 24)}`;
|
|
3432
|
+
const bounded = candidates.length > items.length || rendered2.includes("item(s) omitted");
|
|
3433
|
+
const result = {
|
|
3434
|
+
schema_version: RESULT_SCHEMA_VERSION,
|
|
3435
|
+
action: "context",
|
|
3436
|
+
context_id: contextId,
|
|
3437
|
+
mode,
|
|
3438
|
+
scope,
|
|
3439
|
+
rendered_context: rendered2,
|
|
3440
|
+
rendered_hash: renderedHash,
|
|
3441
|
+
items,
|
|
3442
|
+
retrieval_trace: defined({
|
|
3443
|
+
strategy,
|
|
3444
|
+
query: text(args.query),
|
|
3445
|
+
project_uid: text(args.project_uid),
|
|
3446
|
+
task_uid: text(args.task_uid),
|
|
3447
|
+
session_uid: text(args.session_uid),
|
|
3448
|
+
node_types: Array.isArray(args.node_types) ? args.node_types.map(String) : void 0,
|
|
3449
|
+
graph_expansion_limit: mode === "topic" ? args.graph_expansion_limit ?? 3 : void 0,
|
|
3450
|
+
graph_max_depth: mode === "topic" ? args.graph_max_depth ?? 2 : void 0,
|
|
3451
|
+
available_items: candidates.length,
|
|
3452
|
+
returned_items: items.length,
|
|
3453
|
+
omitted_items: Math.max(0, candidates.length - items.length)
|
|
3454
|
+
}),
|
|
3455
|
+
budget: {
|
|
3456
|
+
unit: "characters",
|
|
3457
|
+
requested_chars: budget,
|
|
3458
|
+
used_chars: rendered2.length,
|
|
3459
|
+
available_items: candidates.length,
|
|
3460
|
+
returned_items: items.length,
|
|
3461
|
+
omitted_items: Math.max(0, candidates.length - items.length),
|
|
3462
|
+
bounded
|
|
3463
|
+
},
|
|
3464
|
+
freshness: defined({
|
|
3465
|
+
valid_at: text(args.valid_at),
|
|
3466
|
+
latest_source_timestamp: latestTimestamp(raw)
|
|
3467
|
+
}),
|
|
3468
|
+
warnings: [
|
|
3469
|
+
...contextWarnings(raw, rendered2, candidates.length, items.length),
|
|
3470
|
+
...workspace && workspace.failures.length > 0 ? [
|
|
3471
|
+
{
|
|
3472
|
+
code: "workspace_scope_partial",
|
|
3473
|
+
message: `${workspace.failures.length} declared repositories did not resolve and were left out: ${workspace.failures.join("; ")}`
|
|
3474
|
+
}
|
|
3475
|
+
] : []
|
|
3476
|
+
]
|
|
3477
|
+
};
|
|
3478
|
+
return {
|
|
3479
|
+
content: [{ type: "text", text: rendered2 }],
|
|
3480
|
+
structuredContent: result
|
|
3481
|
+
};
|
|
3354
3482
|
}
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3483
|
+
|
|
3484
|
+
// src/resources.ts
|
|
3485
|
+
var RESOURCES = [
|
|
3486
|
+
{
|
|
3487
|
+
uri: "mindgraph://memory/status",
|
|
3488
|
+
name: "Memory Integration Status",
|
|
3489
|
+
description: "Portable memory profile, adapter state, supported capabilities, and explicit limitations",
|
|
3490
|
+
mimeType: "application/json"
|
|
3491
|
+
},
|
|
3492
|
+
{
|
|
3493
|
+
uri: "mindgraph://stats",
|
|
3494
|
+
name: "Graph Statistics",
|
|
3495
|
+
description: "Current knowledge graph statistics: node counts, edge counts, and layer distribution",
|
|
3496
|
+
mimeType: "application/json"
|
|
3497
|
+
},
|
|
3498
|
+
{
|
|
3499
|
+
uri: "mindgraph://goals",
|
|
3500
|
+
name: "Active Goals",
|
|
3501
|
+
description: "All active goals in the knowledge graph",
|
|
3502
|
+
mimeType: "application/json"
|
|
3503
|
+
},
|
|
3504
|
+
{
|
|
3505
|
+
uri: "mindgraph://questions",
|
|
3506
|
+
name: "Open Questions",
|
|
3507
|
+
description: "All open questions in the knowledge graph",
|
|
3508
|
+
mimeType: "application/json"
|
|
3509
|
+
},
|
|
3510
|
+
{
|
|
3511
|
+
uri: "mindgraph://contradictions",
|
|
3512
|
+
name: "Contradictions",
|
|
3513
|
+
description: "Contradictory claims in the knowledge graph",
|
|
3514
|
+
mimeType: "application/json"
|
|
3515
|
+
},
|
|
3516
|
+
{
|
|
3517
|
+
uri: "mindgraph://decisions",
|
|
3518
|
+
name: "Open Decisions",
|
|
3519
|
+
description: "Unresolved decisions awaiting resolution",
|
|
3520
|
+
mimeType: "application/json"
|
|
3376
3521
|
}
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
}
|
|
3522
|
+
];
|
|
3523
|
+
var RESOURCE_TEMPLATES = [
|
|
3524
|
+
{
|
|
3525
|
+
uriTemplate: "mindgraph://node/{uid}",
|
|
3526
|
+
name: "Graph Node",
|
|
3527
|
+
description: "Retrieve a specific node by its UID, including all properties, edges, and version history",
|
|
3528
|
+
mimeType: "application/json"
|
|
3529
|
+
},
|
|
3530
|
+
{
|
|
3531
|
+
uriTemplate: "mindgraph://search/{query}",
|
|
3532
|
+
name: "Search Results",
|
|
3533
|
+
description: "BM25 keyword search across all nodes \u2014 pass 1\u20133 discriminating terms (e.g. 'Kissinger NATO'), not natural language sentences",
|
|
3534
|
+
mimeType: "application/json"
|
|
3535
|
+
},
|
|
3536
|
+
{
|
|
3537
|
+
uriTemplate: "mindgraph://layer/{layer}",
|
|
3538
|
+
name: "Layer Nodes",
|
|
3539
|
+
description: "List all nodes in a specific cognitive layer (reality, epistemic, intent, action, memory, agent)",
|
|
3540
|
+
mimeType: "application/json"
|
|
3387
3541
|
}
|
|
3388
|
-
|
|
3542
|
+
];
|
|
3543
|
+
var RESOURCE_GOVERNANCE_TOOL = "mindgraph_retrieve";
|
|
3544
|
+
function json(uri, data) {
|
|
3545
|
+
return {
|
|
3546
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
|
|
3547
|
+
};
|
|
3389
3548
|
}
|
|
3390
|
-
async function
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
});
|
|
3549
|
+
async function readResource(client, uri, memoryConfig = {}) {
|
|
3550
|
+
switch (uri) {
|
|
3551
|
+
case "mindgraph://memory/status":
|
|
3552
|
+
return json(uri, memoryStatus(memoryConfig));
|
|
3553
|
+
case "mindgraph://stats":
|
|
3554
|
+
return json(uri, await client.stats());
|
|
3555
|
+
case "mindgraph://goals":
|
|
3556
|
+
return json(uri, await client.getGoals());
|
|
3557
|
+
case "mindgraph://questions":
|
|
3558
|
+
return json(uri, await client.getOpenQuestions());
|
|
3559
|
+
case "mindgraph://contradictions":
|
|
3560
|
+
return json(uri, await client.getContradictions());
|
|
3561
|
+
case "mindgraph://decisions":
|
|
3562
|
+
return json(uri, await client.getOpenDecisions());
|
|
3405
3563
|
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
const stored = nodes.flatMap((node) => {
|
|
3414
|
-
if (!node) return [];
|
|
3415
|
-
const attributes = node.props?.attributes;
|
|
3416
|
-
if (!attributes || typeof attributes !== "object") return [];
|
|
3417
|
-
const codeRef = attributes.code_ref;
|
|
3418
|
-
return isStoredCodeRef(codeRef) ? [codeRef] : [];
|
|
3419
|
-
});
|
|
3420
|
-
const refs = [];
|
|
3421
|
-
const unavailable = /* @__PURE__ */ new Set();
|
|
3422
|
-
for (const ref of stored) {
|
|
3423
|
-
try {
|
|
3424
|
-
const repository = await adapter.resolveRepository(ref.repoId);
|
|
3425
|
-
refs.push({ ...ref, repoRoot: repository.repoRoot });
|
|
3426
|
-
} catch (cause) {
|
|
3427
|
-
if (cause instanceof UnknownRepositoryError) {
|
|
3428
|
-
unavailable.add(ref.repoId);
|
|
3429
|
-
} else {
|
|
3430
|
-
throw cause;
|
|
3431
|
-
}
|
|
3432
|
-
}
|
|
3564
|
+
const nodeMatch = uri.match(/^mindgraph:\/\/node\/(.+)$/);
|
|
3565
|
+
if (nodeMatch) {
|
|
3566
|
+
const uid = decodeURIComponent(nodeMatch[1]);
|
|
3567
|
+
const node = await client.getNode(uid);
|
|
3568
|
+
const edges = await client.getEdges({ from_uid: uid });
|
|
3569
|
+
const inEdges = await client.getEdges({ to_uid: uid });
|
|
3570
|
+
return json(uri, { node, outgoing_edges: edges, incoming_edges: inEdges });
|
|
3433
3571
|
}
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
value.forEach((item) => collectUids(item, uids));
|
|
3439
|
-
return;
|
|
3572
|
+
const searchMatch = uri.match(/^mindgraph:\/\/search\/(.+)$/);
|
|
3573
|
+
if (searchMatch) {
|
|
3574
|
+
const query = decodeURIComponent(searchMatch[1]);
|
|
3575
|
+
return json(uri, await client.search(query, { limit: 20 }));
|
|
3440
3576
|
}
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
} else {
|
|
3446
|
-
collectUids(child, uids);
|
|
3447
|
-
}
|
|
3577
|
+
const layerMatch = uri.match(/^mindgraph:\/\/layer\/(.+)$/);
|
|
3578
|
+
if (layerMatch) {
|
|
3579
|
+
const layer = decodeURIComponent(layerMatch[1]);
|
|
3580
|
+
return json(uri, await client.getNodes({ layer, limit: 50 }));
|
|
3448
3581
|
}
|
|
3582
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
3449
3583
|
}
|
|
3450
|
-
function
|
|
3451
|
-
|
|
3452
|
-
const
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
return
|
|
3462
|
-
const key = `${ref.repoId}:${ref.path}:${ref.qualifiedName}:${ref.signature ?? ""}`;
|
|
3463
|
-
if (seen.has(key)) return false;
|
|
3464
|
-
seen.add(key);
|
|
3465
|
-
return true;
|
|
3466
|
-
});
|
|
3467
|
-
}
|
|
3468
|
-
function primaryUids(value) {
|
|
3469
|
-
if (!value || typeof value !== "object") return [];
|
|
3470
|
-
const object = value;
|
|
3471
|
-
const keys = [
|
|
3472
|
-
"uid",
|
|
3473
|
-
"claim_uid",
|
|
3474
|
-
"goal_uid",
|
|
3475
|
-
"project_uid",
|
|
3476
|
-
"milestone_uid",
|
|
3477
|
-
"decision_uid",
|
|
3478
|
-
"task_uid",
|
|
3479
|
-
"plan_uid",
|
|
3480
|
-
"step_uid",
|
|
3481
|
-
"execution_uid",
|
|
3482
|
-
"lesson_uid"
|
|
3483
|
-
];
|
|
3484
|
-
return [
|
|
3485
|
-
...new Set(
|
|
3486
|
-
keys.flatMap((key) => typeof object[key] === "string" ? [object[key]] : [])
|
|
3487
|
-
)
|
|
3488
|
-
];
|
|
3584
|
+
async function readGovernedResource(client, uri, governanceConfig, memoryConfig = {}) {
|
|
3585
|
+
const toolName = uri === "mindgraph://memory/status" ? "mindgraph_memory" : RESOURCE_GOVERNANCE_TOOL;
|
|
3586
|
+
const toolArgs = uri === "mindgraph://memory/status" ? { action: "status" } : {};
|
|
3587
|
+
const governance = await checkMcpGovernance(
|
|
3588
|
+
toolName,
|
|
3589
|
+
toolArgs,
|
|
3590
|
+
{ ...governanceConfig, context: { resource_uri: uri } }
|
|
3591
|
+
);
|
|
3592
|
+
if (!governance.allowed) {
|
|
3593
|
+
throw new Error(governance.message);
|
|
3594
|
+
}
|
|
3595
|
+
return readResource(client, uri, memoryConfig);
|
|
3489
3596
|
}
|
|
3490
3597
|
|
|
3598
|
+
// src/tools.ts
|
|
3599
|
+
var import_node_crypto4 = require("crypto");
|
|
3600
|
+
|
|
3491
3601
|
// src/sync-tool.ts
|
|
3492
3602
|
var import_node_crypto3 = __toESM(require("crypto"));
|
|
3493
|
-
var
|
|
3494
|
-
var
|
|
3603
|
+
var import_node_fs3 = __toESM(require("fs"));
|
|
3604
|
+
var import_node_path3 = __toESM(require("path"));
|
|
3495
3605
|
var ok3 = (data) => ({
|
|
3496
3606
|
content: [{ type: "text", text: JSON.stringify(data) }]
|
|
3497
3607
|
});
|
|
@@ -3568,12 +3678,12 @@ var SYNC_TOOL = {
|
|
|
3568
3678
|
}
|
|
3569
3679
|
};
|
|
3570
3680
|
function safePath(root, candidate) {
|
|
3571
|
-
const absolute =
|
|
3572
|
-
const relative2 =
|
|
3573
|
-
if (relative2.startsWith("..") ||
|
|
3681
|
+
const absolute = import_node_path3.default.resolve(root, candidate);
|
|
3682
|
+
const relative2 = import_node_path3.default.relative(root, absolute);
|
|
3683
|
+
if (relative2.startsWith("..") || import_node_path3.default.isAbsolute(relative2)) {
|
|
3574
3684
|
throw new Error(`path escapes repository root: ${candidate}`);
|
|
3575
3685
|
}
|
|
3576
|
-
if (
|
|
3686
|
+
if (import_node_fs3.default.existsSync(absolute)) {
|
|
3577
3687
|
if (!pathContains(realPathOf(root), realPathOf(absolute))) {
|
|
3578
3688
|
throw new Error(`path escapes repository root via symlink: ${candidate}`);
|
|
3579
3689
|
}
|
|
@@ -3582,25 +3692,25 @@ function safePath(root, candidate) {
|
|
|
3582
3692
|
}
|
|
3583
3693
|
function realPathOf(target) {
|
|
3584
3694
|
try {
|
|
3585
|
-
return
|
|
3695
|
+
return import_node_fs3.default.realpathSync(target);
|
|
3586
3696
|
} catch {
|
|
3587
|
-
return
|
|
3697
|
+
return import_node_path3.default.resolve(target);
|
|
3588
3698
|
}
|
|
3589
3699
|
}
|
|
3590
3700
|
function markdownFiles(target, realRoot, visited = /* @__PURE__ */ new Set()) {
|
|
3591
|
-
if (!
|
|
3701
|
+
if (!import_node_fs3.default.existsSync(target)) return [];
|
|
3592
3702
|
const real = realPathOf(target);
|
|
3593
3703
|
if (!pathContains(realRoot, real)) return [];
|
|
3594
|
-
const stat =
|
|
3704
|
+
const stat = import_node_fs3.default.statSync(target);
|
|
3595
3705
|
if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
|
|
3596
3706
|
if (!stat.isDirectory() || visited.has(real)) return [];
|
|
3597
3707
|
visited.add(real);
|
|
3598
|
-
return
|
|
3708
|
+
return import_node_fs3.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path3.default.join(target, entry.name), realRoot, visited));
|
|
3599
3709
|
}
|
|
3600
3710
|
function fileDescriptor(root, absolute) {
|
|
3601
|
-
const content =
|
|
3711
|
+
const content = import_node_fs3.default.readFileSync(absolute, "utf8");
|
|
3602
3712
|
return {
|
|
3603
|
-
logical_path:
|
|
3713
|
+
logical_path: import_node_path3.default.relative(root, absolute).split(import_node_path3.default.sep).join("/"),
|
|
3604
3714
|
content_hash: import_node_crypto3.default.createHash("sha256").update(content).digest("hex"),
|
|
3605
3715
|
bytes: Buffer.byteLength(content),
|
|
3606
3716
|
content
|
|
@@ -3670,7 +3780,7 @@ async function handleSyncTool(client, args) {
|
|
|
3670
3780
|
}
|
|
3671
3781
|
if (action === "status" && request.logical_path && request.file_present !== false) {
|
|
3672
3782
|
const absolute = safePath(root, String(request.logical_path));
|
|
3673
|
-
request.file_present =
|
|
3783
|
+
request.file_present = import_node_fs3.default.existsSync(absolute);
|
|
3674
3784
|
if (request.file_present && !request.content_hash) {
|
|
3675
3785
|
request.content_hash = fileDescriptor(root, absolute).content_hash;
|
|
3676
3786
|
}
|
|
@@ -6209,7 +6319,7 @@ function buildMindGraphServer(wiring) {
|
|
|
6209
6319
|
instructions: wiring.instructions
|
|
6210
6320
|
}
|
|
6211
6321
|
);
|
|
6212
|
-
server.setRequestHandler(
|
|
6322
|
+
server.setRequestHandler("tools/list", async () => {
|
|
6213
6323
|
const generated = await getGeneratedTools(client, wiring.generatedTools);
|
|
6214
6324
|
const tools = [...toolsForProfile(wiring.profile), ...generated.tools];
|
|
6215
6325
|
if (!hosted) return { tools };
|
|
@@ -6220,7 +6330,7 @@ function buildMindGraphServer(wiring) {
|
|
|
6220
6330
|
]
|
|
6221
6331
|
};
|
|
6222
6332
|
});
|
|
6223
|
-
server.setRequestHandler(
|
|
6333
|
+
server.setRequestHandler("tools/call", async (request) => {
|
|
6224
6334
|
const { name, arguments: args } = request.params;
|
|
6225
6335
|
const rawArgs = {
|
|
6226
6336
|
...args || {},
|
|
@@ -6267,20 +6377,20 @@ function buildMindGraphServer(wiring) {
|
|
|
6267
6377
|
}
|
|
6268
6378
|
return handleTool(client, name, toolArgs, wiring.memory);
|
|
6269
6379
|
});
|
|
6270
|
-
server.setRequestHandler(
|
|
6380
|
+
server.setRequestHandler("prompts/list", async () => {
|
|
6271
6381
|
return { prompts: PROMPTS };
|
|
6272
6382
|
});
|
|
6273
|
-
server.setRequestHandler(
|
|
6383
|
+
server.setRequestHandler("prompts/get", async (request) => {
|
|
6274
6384
|
const { name, arguments: args } = request.params;
|
|
6275
6385
|
return getPrompt(name, args);
|
|
6276
6386
|
});
|
|
6277
|
-
server.setRequestHandler(
|
|
6387
|
+
server.setRequestHandler("resources/list", async () => {
|
|
6278
6388
|
return { resources: RESOURCES };
|
|
6279
6389
|
});
|
|
6280
|
-
server.setRequestHandler(
|
|
6390
|
+
server.setRequestHandler("resources/templates/list", async () => {
|
|
6281
6391
|
return { resourceTemplates: RESOURCE_TEMPLATES };
|
|
6282
6392
|
});
|
|
6283
|
-
server.setRequestHandler(
|
|
6393
|
+
server.setRequestHandler("resources/read", async (request) => {
|
|
6284
6394
|
return readGovernedResource(
|
|
6285
6395
|
client,
|
|
6286
6396
|
request.params.uri,
|
|
@@ -6346,8 +6456,21 @@ async function handleHostedRequest(request, opts) {
|
|
|
6346
6456
|
if (Array.isArray(parsedBody)) {
|
|
6347
6457
|
return jsonRpcError(400, -32600, "JSON-RPC batches are not accepted");
|
|
6348
6458
|
}
|
|
6459
|
+
if (!await (0, import_server.isLegacyRequest)(request, parsedBody)) {
|
|
6460
|
+
const modern = (0, import_server.createMcpHandler)(() => createMindGraphServer(opts), {
|
|
6461
|
+
legacy: "reject"
|
|
6462
|
+
// Default response mode: one JSON body unless a handler emits a message
|
|
6463
|
+
// before its result. No tool does today; a tool that starts emitting
|
|
6464
|
+
// progress must also stop this function closing the handler on return.
|
|
6465
|
+
});
|
|
6466
|
+
try {
|
|
6467
|
+
return await modern.fetch(request, { parsedBody });
|
|
6468
|
+
} finally {
|
|
6469
|
+
await modern.close();
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6349
6472
|
const server = createMindGraphServer(opts);
|
|
6350
|
-
const transport = new
|
|
6473
|
+
const transport = new import_server.WebStandardStreamableHTTPServerTransport({
|
|
6351
6474
|
sessionIdGenerator: void 0,
|
|
6352
6475
|
enableJsonResponse: true
|
|
6353
6476
|
});
|