poe-code 3.0.311 → 3.0.313
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/dist/cli/commands/memory-mcp.js +8 -1
- package/dist/cli/commands/memory-mcp.js.map +1 -1
- package/dist/cli/commands/memory.js +42 -12
- package/dist/cli/commands/memory.js.map +1 -1
- package/dist/index.js +235 -101
- package/dist/index.js.map +4 -4
- package/dist/metafile.json +1 -1
- package/package.json +1 -1
- package/packages/markdown-reader/dist/core/document.js +6 -0
- package/packages/markdown-reader/dist/core/read-section.js +4 -0
- package/packages/markdown-reader/dist/core/resolve.js +6 -3
- package/packages/markdown-reader/dist/core/scan.js +28 -1
- package/packages/markdown-reader/dist/mcp/tools.js +11 -4
- package/packages/memory/dist/agent-response.d.ts +2 -0
- package/packages/memory/dist/agent-response.js +38 -0
- package/packages/memory/dist/cache.js +13 -9
- package/packages/memory/dist/explain.js +2 -25
- package/packages/memory/dist/frontmatter.js +5 -1
- package/packages/memory/dist/index.js +92 -55
- package/packages/memory/dist/index.js.map +4 -4
- package/packages/memory/dist/install.js +12 -4
- package/packages/memory/dist/paths.js +15 -0
- package/packages/memory/dist/query.js +2 -25
- package/packages/memory/dist/search.js +2 -2
- package/packages/memory/dist/write.js +14 -1
package/package.json
CHANGED
|
@@ -20,6 +20,9 @@ export async function loadMarkdownDocument(file, dependencies = {}) {
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
export function resolveMarkdownPath(file, cwd = process.cwd()) {
|
|
23
|
+
if (file.trim().length === 0) {
|
|
24
|
+
throw new UserError("invalid file: expected a non-empty path");
|
|
25
|
+
}
|
|
23
26
|
return path.isAbsolute(file) ? file : path.resolve(cwd, file);
|
|
24
27
|
}
|
|
25
28
|
export function sliceMarkdownBytes(source, start, end) {
|
|
@@ -58,6 +61,9 @@ function hasYamlLikeLeadingFrontmatter(source) {
|
|
|
58
61
|
if (lines[0] !== "---") {
|
|
59
62
|
return false;
|
|
60
63
|
}
|
|
64
|
+
if (lines[1]?.trim().length === 0) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
61
67
|
for (const line of lines.slice(1)) {
|
|
62
68
|
const trimmed = line.trim();
|
|
63
69
|
if (trimmed.length === 0) {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { loadMarkdownDocument, sliceMarkdownBytes } from "./document.js";
|
|
2
2
|
import { resolveSection } from "./resolve.js";
|
|
3
|
+
import { UserError } from "toolcraft";
|
|
3
4
|
export function createReadSection(dependencies = {}) {
|
|
4
5
|
return async function readSection(params) {
|
|
6
|
+
if (params.section.trim().length === 0) {
|
|
7
|
+
throw new UserError("invalid section: expected a non-empty section id");
|
|
8
|
+
}
|
|
5
9
|
const { source, sections } = await loadMarkdownDocument(params.file, dependencies);
|
|
6
10
|
const section = resolveSection(sections, params.section);
|
|
7
11
|
const end = params.includeChildren === false ? section.bodyEndNoChildren : section.bodyEnd;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { UserError } from "toolcraft";
|
|
2
2
|
export function resolveSection(sections, id) {
|
|
3
3
|
const trimmedId = id.trim();
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return unnumberedTitleMatch;
|
|
4
|
+
if (trimmedId.length === 0) {
|
|
5
|
+
throw new UserError("invalid section: expected a non-empty section id");
|
|
7
6
|
}
|
|
8
7
|
const sectionByNumber = sections.find((section) => section.number === trimmedId);
|
|
9
8
|
if (sectionByNumber !== undefined) {
|
|
10
9
|
return sectionByNumber;
|
|
11
10
|
}
|
|
11
|
+
const unnumberedTitleMatch = sections.find((section) => section.number === null && section.title === trimmedId);
|
|
12
|
+
if (unnumberedTitleMatch !== undefined) {
|
|
13
|
+
return unnumberedTitleMatch;
|
|
14
|
+
}
|
|
12
15
|
const titleMatches = sections.filter((section) => section.title === trimmedId);
|
|
13
16
|
if (titleMatches.length === 1) {
|
|
14
17
|
return titleMatches[0];
|
|
@@ -4,7 +4,10 @@ export function scanMarkdown(source) {
|
|
|
4
4
|
if (ast.type !== "root") {
|
|
5
5
|
return [];
|
|
6
6
|
}
|
|
7
|
-
const
|
|
7
|
+
const htmlCommentRanges = collectHtmlCommentRanges(source);
|
|
8
|
+
const headings = ast.children
|
|
9
|
+
.filter(isHeadingNode)
|
|
10
|
+
.filter((heading) => !isInsideHtmlComment(getRequiredRange(heading).start, htmlCommentRanges));
|
|
8
11
|
if (headings.length === 0) {
|
|
9
12
|
return [];
|
|
10
13
|
}
|
|
@@ -25,6 +28,30 @@ export function scanMarkdown(source) {
|
|
|
25
28
|
applyNumbers(sections, baselineDepth);
|
|
26
29
|
return sections;
|
|
27
30
|
}
|
|
31
|
+
function collectHtmlCommentRanges(source) {
|
|
32
|
+
const ranges = [];
|
|
33
|
+
let searchStart = 0;
|
|
34
|
+
while (searchStart < source.length) {
|
|
35
|
+
const commentStart = source.indexOf("<!--", searchStart);
|
|
36
|
+
if (commentStart === -1) {
|
|
37
|
+
return ranges;
|
|
38
|
+
}
|
|
39
|
+
const commentEndMarker = source.indexOf("-->", commentStart + "<!--".length);
|
|
40
|
+
const commentEnd = commentEndMarker === -1 ? source.length : commentEndMarker + "-->".length;
|
|
41
|
+
ranges.push({
|
|
42
|
+
start: byteOffset(source, commentStart),
|
|
43
|
+
end: byteOffset(source, commentEnd)
|
|
44
|
+
});
|
|
45
|
+
searchStart = commentEnd;
|
|
46
|
+
}
|
|
47
|
+
return ranges;
|
|
48
|
+
}
|
|
49
|
+
function isInsideHtmlComment(offset, ranges) {
|
|
50
|
+
return ranges.some((range) => offset >= range.start && offset < range.end);
|
|
51
|
+
}
|
|
52
|
+
function byteOffset(source, index) {
|
|
53
|
+
return Buffer.byteLength(source.slice(0, index), "utf8");
|
|
54
|
+
}
|
|
28
55
|
function isHeadingNode(node) {
|
|
29
56
|
return node.type === "heading";
|
|
30
57
|
}
|
|
@@ -3,8 +3,12 @@ import { S } from "toolcraft-schema";
|
|
|
3
3
|
import { readMarkdown } from "../core/read-markdown.js";
|
|
4
4
|
import { readSection } from "../core/read-section.js";
|
|
5
5
|
const readParams = S.Object({
|
|
6
|
-
file: S.String({ description: "Path to the markdown file" }),
|
|
7
|
-
depth: S.Optional(S.Number({
|
|
6
|
+
file: S.String({ description: "Path to the markdown file", minLength: 1 }),
|
|
7
|
+
depth: S.Optional(S.Number({
|
|
8
|
+
description: "Limit TOC to headings at depth <= n",
|
|
9
|
+
jsonType: "integer",
|
|
10
|
+
minimum: 0
|
|
11
|
+
}))
|
|
8
12
|
});
|
|
9
13
|
const tocEntryResult = S.Object({
|
|
10
14
|
depth: S.Number(),
|
|
@@ -24,8 +28,11 @@ export const readTool = defineCommand({
|
|
|
24
28
|
handler: async ({ params }) => readMarkdown(params)
|
|
25
29
|
});
|
|
26
30
|
const readSectionParams = S.Object({
|
|
27
|
-
file: S.String({ description: "Path to the markdown file" }),
|
|
28
|
-
section: S.String({
|
|
31
|
+
file: S.String({ description: "Path to the markdown file", minLength: 1 }),
|
|
32
|
+
section: S.String({
|
|
33
|
+
description: "Numeric path or exact heading text to read",
|
|
34
|
+
minLength: 1
|
|
35
|
+
}),
|
|
29
36
|
includeChildren: S.Optional(S.Boolean({ description: "Include nested child sections" }))
|
|
30
37
|
});
|
|
31
38
|
export const readSectionTool = defineCommand({
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const validCitationConfidences = new Set(["extracted", "inferred", "ambiguous"]);
|
|
2
|
+
export function parseMemoryAgentResponse(stdout) {
|
|
3
|
+
let value;
|
|
4
|
+
try {
|
|
5
|
+
value = JSON.parse(stdout);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
throw new Error("Memory agent returned invalid JSON output.");
|
|
9
|
+
}
|
|
10
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
11
|
+
throw new Error("Memory agent returned an invalid result payload.");
|
|
12
|
+
}
|
|
13
|
+
const result = value;
|
|
14
|
+
if (typeof result.answer !== "string" ||
|
|
15
|
+
!Array.isArray(result.citations) ||
|
|
16
|
+
!isNonNegativeInteger(result.tokensUsed) ||
|
|
17
|
+
!result.citations.every(isQueryCitation)) {
|
|
18
|
+
throw new Error("Memory agent returned an invalid result payload.");
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
answer: result.answer,
|
|
22
|
+
citations: result.citations,
|
|
23
|
+
tokensUsed: result.tokensUsed
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function isQueryCitation(value) {
|
|
27
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const citation = value;
|
|
31
|
+
return (typeof citation.relPath === "string" &&
|
|
32
|
+
(citation.section === undefined || typeof citation.section === "string") &&
|
|
33
|
+
typeof citation.confidence === "string" &&
|
|
34
|
+
validCitationConfidences.has(citation.confidence));
|
|
35
|
+
}
|
|
36
|
+
function isNonNegativeInteger(value) {
|
|
37
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
38
|
+
}
|
|
@@ -97,17 +97,21 @@ export async function clearCache(root, opts = {}) {
|
|
|
97
97
|
async function assertIngestCachePathIsNotSymlink(root) {
|
|
98
98
|
await assertNoSymlinkSegments(root, MEMORY_INGEST_CACHE_DIR_RELPATH);
|
|
99
99
|
}
|
|
100
|
-
function parseCacheEntry(value,
|
|
100
|
+
function parseCacheEntry(value, requestedKey) {
|
|
101
101
|
const object = expectRecord(value);
|
|
102
|
+
const key = expectString(getOwnEntry(object, "key"), "key");
|
|
103
|
+
if (key !== requestedKey) {
|
|
104
|
+
throw new Error(`Cache entry key "${key}" does not match requested key "${requestedKey}".`);
|
|
105
|
+
}
|
|
102
106
|
return {
|
|
103
|
-
key
|
|
107
|
+
key,
|
|
104
108
|
ingestedAt: expectString(getOwnEntry(object, "ingestedAt"), "ingestedAt"),
|
|
105
109
|
sourceLabel: expectString(getOwnEntry(object, "sourceLabel"), "sourceLabel"),
|
|
106
110
|
diff: parseMemoryDiff(getOwnEntry(object, "diff")),
|
|
107
|
-
exitCode:
|
|
108
|
-
durationMs:
|
|
109
|
-
memoryTokens:
|
|
110
|
-
sourceTokens:
|
|
111
|
+
exitCode: expectNonNegativeInteger(getOwnEntry(object, "exitCode"), "exitCode"),
|
|
112
|
+
durationMs: expectNonNegativeInteger(getOwnEntry(object, "durationMs"), "durationMs"),
|
|
113
|
+
memoryTokens: expectNonNegativeInteger(getOwnEntry(object, "memoryTokens"), "memoryTokens"),
|
|
114
|
+
sourceTokens: expectNonNegativeInteger(getOwnEntry(object, "sourceTokens"), "sourceTokens"),
|
|
111
115
|
promptTemplateVersion: expectString(getOwnEntry(object, "promptTemplateVersion"), "promptTemplateVersion"),
|
|
112
116
|
agentId: expectString(getOwnEntry(object, "agentId"), "agentId")
|
|
113
117
|
};
|
|
@@ -132,9 +136,9 @@ function expectString(value, field) {
|
|
|
132
136
|
}
|
|
133
137
|
return value;
|
|
134
138
|
}
|
|
135
|
-
function
|
|
136
|
-
if (typeof value !== "number" || Number.
|
|
137
|
-
throw new Error(`Expected
|
|
139
|
+
function expectNonNegativeInteger(value, field) {
|
|
140
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
141
|
+
throw new Error(`Expected non-negative integer at "${field}".`);
|
|
138
142
|
}
|
|
139
143
|
return value;
|
|
140
144
|
}
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { countTokens } from "tokenfill";
|
|
4
4
|
import { spawn } from "@poe-code/agent-spawn";
|
|
5
5
|
import { resolveAgent } from "@poe-code/poe-code-config";
|
|
6
|
+
import { parseMemoryAgentResponse } from "./agent-response.js";
|
|
6
7
|
import { hasOwnErrorCode } from "./errors.js";
|
|
7
8
|
import { readPage } from "./pages.js";
|
|
8
9
|
import { selectQueryContext } from "./query.js";
|
|
@@ -33,7 +34,7 @@ export async function explainPage(root, options) {
|
|
|
33
34
|
};
|
|
34
35
|
const agentId = (await resolveAgent(configOptions, options.agent ?? null)) ?? options.agent ?? "claude-code";
|
|
35
36
|
const spawned = await spawn(agentId, { prompt });
|
|
36
|
-
const response =
|
|
37
|
+
const response = parseMemoryAgentResponse(spawned.stdout);
|
|
37
38
|
return {
|
|
38
39
|
answer: response.answer,
|
|
39
40
|
citations: response.citations,
|
|
@@ -47,30 +48,6 @@ export async function explainPage(root, options) {
|
|
|
47
48
|
outboundSources: targetPage.frontmatter.sources ?? []
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
|
-
function parseExplainResponse(stdout) {
|
|
51
|
-
let value;
|
|
52
|
-
try {
|
|
53
|
-
value = JSON.parse(stdout);
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
throw new Error("Memory agent returned invalid JSON output.");
|
|
57
|
-
}
|
|
58
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
59
|
-
throw new Error("Memory agent returned an invalid result payload.");
|
|
60
|
-
}
|
|
61
|
-
const response = value;
|
|
62
|
-
if (typeof response.answer !== "string" ||
|
|
63
|
-
!Array.isArray(response.citations) ||
|
|
64
|
-
typeof response.tokensUsed !== "number" ||
|
|
65
|
-
!Number.isFinite(response.tokensUsed)) {
|
|
66
|
-
throw new Error("Memory agent returned an invalid result payload.");
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
answer: response.answer,
|
|
70
|
-
citations: response.citations,
|
|
71
|
-
tokensUsed: response.tokensUsed
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
51
|
function collectRelatedPages(pages, targetRelPath, outboundSources) {
|
|
75
52
|
const memorySourcePaths = new Set(outboundSources.map((source) => source.path));
|
|
76
53
|
return pages.filter((page) => {
|
|
@@ -23,7 +23,11 @@ export function serializeFrontmatter(frontmatter, body) {
|
|
|
23
23
|
return stringifyFrontmatter(serialized, body);
|
|
24
24
|
}
|
|
25
25
|
export function parseSourceRef(serialized) {
|
|
26
|
-
const
|
|
26
|
+
const parts = serialized.split("#");
|
|
27
|
+
if (parts.length > 2) {
|
|
28
|
+
throw new Error(`Invalid source ref "${serialized}".`);
|
|
29
|
+
}
|
|
30
|
+
const [rawPath, rawAnchor] = parts;
|
|
27
31
|
const normalizedPath = rawPath?.trim();
|
|
28
32
|
if (normalizedPath === undefined || normalizedPath.length === 0) {
|
|
29
33
|
throw new Error(`Invalid source ref "${serialized}".`);
|
|
@@ -68,6 +68,9 @@ async function assertMemoryRootIsNotSymlink(root) {
|
|
|
68
68
|
try {
|
|
69
69
|
const stat7 = await fs.lstat(currentPath);
|
|
70
70
|
if (stat7.isSymbolicLink()) {
|
|
71
|
+
if (await isAllowedMacSystemAlias(currentPath)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
71
74
|
throw new MemoryPathError(`Memory root "${root}" cannot be a symbolic link.`);
|
|
72
75
|
}
|
|
73
76
|
} catch (error2) {
|
|
@@ -78,6 +81,17 @@ async function assertMemoryRootIsNotSymlink(root) {
|
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
}
|
|
84
|
+
async function isAllowedMacSystemAlias(currentPath) {
|
|
85
|
+
if (currentPath !== "/var") {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const target = await fs.readlink(currentPath);
|
|
90
|
+
return target === "/private/var" || target === "private/var";
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
81
95
|
function isMissing(error2) {
|
|
82
96
|
return hasOwnErrorCode(error2, "ENOENT");
|
|
83
97
|
}
|
|
@@ -5388,7 +5402,11 @@ function serializeFrontmatter(frontmatter, body) {
|
|
|
5388
5402
|
return stringifyFrontmatter(serialized, body);
|
|
5389
5403
|
}
|
|
5390
5404
|
function parseSourceRef(serialized) {
|
|
5391
|
-
const
|
|
5405
|
+
const parts = serialized.split("#");
|
|
5406
|
+
if (parts.length > 2) {
|
|
5407
|
+
throw new Error(`Invalid source ref "${serialized}".`);
|
|
5408
|
+
}
|
|
5409
|
+
const [rawPath, rawAnchor] = parts;
|
|
5392
5410
|
const normalizedPath = rawPath?.trim();
|
|
5393
5411
|
if (normalizedPath === void 0 || normalizedPath.length === 0) {
|
|
5394
5412
|
throw new Error(`Invalid source ref "${serialized}".`);
|
|
@@ -5626,7 +5644,7 @@ async function searchMemory(root, query) {
|
|
|
5626
5644
|
if (normalizedQuery.length === 0) {
|
|
5627
5645
|
throw new Error("Search query cannot be empty.");
|
|
5628
5646
|
}
|
|
5629
|
-
const relPaths = await collectMarkdownRelPaths(root);
|
|
5647
|
+
const relPaths = await collectMarkdownRelPaths(root, MEMORY_PAGES_DIR_RELPATH);
|
|
5630
5648
|
const hits = [];
|
|
5631
5649
|
for (const relPath of relPaths) {
|
|
5632
5650
|
await assertNoSymlinkSegments(root, relPath);
|
|
@@ -6149,7 +6167,7 @@ async function appendToPage(root, relPath, content, opts) {
|
|
|
6149
6167
|
const before = await snapshot(root);
|
|
6150
6168
|
await fs8.mkdir(path22.dirname(pagePath), { recursive: true });
|
|
6151
6169
|
await assertNoSymlinkSegments(root, pageRelPath);
|
|
6152
|
-
const parsed = originalPage
|
|
6170
|
+
const parsed = parseAppendTarget(originalPage, pageRelPath);
|
|
6153
6171
|
try {
|
|
6154
6172
|
await writeFileAtomically2(
|
|
6155
6173
|
pagePath,
|
|
@@ -6161,6 +6179,18 @@ async function appendToPage(root, relPath, content, opts) {
|
|
|
6161
6179
|
throw error2;
|
|
6162
6180
|
}
|
|
6163
6181
|
}
|
|
6182
|
+
function parseAppendTarget(originalPage, relPath) {
|
|
6183
|
+
if (originalPage === void 0) {
|
|
6184
|
+
return { frontmatter: {}, body: "" };
|
|
6185
|
+
}
|
|
6186
|
+
try {
|
|
6187
|
+
return parseFrontmatter2(originalPage);
|
|
6188
|
+
} catch (error2) {
|
|
6189
|
+
const message2 = error2 instanceof Error ? error2.message : String(error2);
|
|
6190
|
+
console.warn(`Failed to parse frontmatter for "${relPath}": ${message2}`);
|
|
6191
|
+
return { frontmatter: {}, body: originalPage };
|
|
6192
|
+
}
|
|
6193
|
+
}
|
|
6164
6194
|
async function clearMemory(root) {
|
|
6165
6195
|
await assertMemoryRootIsNotSymlink(root);
|
|
6166
6196
|
const stagedRoot = `${root}.clear-${randomUUID7()}`;
|
|
@@ -6494,17 +6524,21 @@ async function clearCache(root, opts = {}) {
|
|
|
6494
6524
|
async function assertIngestCachePathIsNotSymlink(root) {
|
|
6495
6525
|
await assertNoSymlinkSegments(root, MEMORY_INGEST_CACHE_DIR_RELPATH);
|
|
6496
6526
|
}
|
|
6497
|
-
function parseCacheEntry(value,
|
|
6527
|
+
function parseCacheEntry(value, requestedKey) {
|
|
6498
6528
|
const object = expectRecord(value);
|
|
6529
|
+
const key = expectString(getOwnEntry8(object, "key"), "key");
|
|
6530
|
+
if (key !== requestedKey) {
|
|
6531
|
+
throw new Error(`Cache entry key "${key}" does not match requested key "${requestedKey}".`);
|
|
6532
|
+
}
|
|
6499
6533
|
return {
|
|
6500
|
-
key
|
|
6534
|
+
key,
|
|
6501
6535
|
ingestedAt: expectString(getOwnEntry8(object, "ingestedAt"), "ingestedAt"),
|
|
6502
6536
|
sourceLabel: expectString(getOwnEntry8(object, "sourceLabel"), "sourceLabel"),
|
|
6503
6537
|
diff: parseMemoryDiff(getOwnEntry8(object, "diff")),
|
|
6504
|
-
exitCode:
|
|
6505
|
-
durationMs:
|
|
6506
|
-
memoryTokens:
|
|
6507
|
-
sourceTokens:
|
|
6538
|
+
exitCode: expectNonNegativeInteger(getOwnEntry8(object, "exitCode"), "exitCode"),
|
|
6539
|
+
durationMs: expectNonNegativeInteger(getOwnEntry8(object, "durationMs"), "durationMs"),
|
|
6540
|
+
memoryTokens: expectNonNegativeInteger(getOwnEntry8(object, "memoryTokens"), "memoryTokens"),
|
|
6541
|
+
sourceTokens: expectNonNegativeInteger(getOwnEntry8(object, "sourceTokens"), "sourceTokens"),
|
|
6508
6542
|
promptTemplateVersion: expectString(
|
|
6509
6543
|
getOwnEntry8(object, "promptTemplateVersion"),
|
|
6510
6544
|
"promptTemplateVersion"
|
|
@@ -6532,9 +6566,9 @@ function expectString(value, field) {
|
|
|
6532
6566
|
}
|
|
6533
6567
|
return value;
|
|
6534
6568
|
}
|
|
6535
|
-
function
|
|
6536
|
-
if (typeof value !== "number" || Number.
|
|
6537
|
-
throw new Error(`Expected
|
|
6569
|
+
function expectNonNegativeInteger(value, field) {
|
|
6570
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
6571
|
+
throw new Error(`Expected non-negative integer at "${field}".`);
|
|
6538
6572
|
}
|
|
6539
6573
|
return value;
|
|
6540
6574
|
}
|
|
@@ -16586,7 +16620,7 @@ async function installMemory(options) {
|
|
|
16586
16620
|
}
|
|
16587
16621
|
throw error2;
|
|
16588
16622
|
}
|
|
16589
|
-
mcpConfigPath = options.agent
|
|
16623
|
+
mcpConfigPath = resolveMcpConfigPath(options.agent, options.homeDir, options.platform);
|
|
16590
16624
|
}
|
|
16591
16625
|
return {
|
|
16592
16626
|
skillInstalled: !options.mcpOnly,
|
|
@@ -16595,6 +16629,14 @@ async function installMemory(options) {
|
|
|
16595
16629
|
mcpConfigPath
|
|
16596
16630
|
};
|
|
16597
16631
|
}
|
|
16632
|
+
function resolveMcpConfigPath(agent, homeDir, platform) {
|
|
16633
|
+
const support = resolveAgentSupport3(agent);
|
|
16634
|
+
if (support.status !== "supported" || support.config === void 0) {
|
|
16635
|
+
throw new Error(`Unsupported agent: ${agent}`);
|
|
16636
|
+
}
|
|
16637
|
+
const configFile = typeof support.config.configFile === "function" ? support.config.configFile(platform) : support.config.configFile;
|
|
16638
|
+
return configFile.startsWith("~/") ? path61.join(homeDir, configFile.slice(2)) : configFile;
|
|
16639
|
+
}
|
|
16598
16640
|
async function removeInstalledSkill(options, skillPath) {
|
|
16599
16641
|
const baseDir = options.scope === "global" ? options.homeDir : options.cwd;
|
|
16600
16642
|
const displayPath = skillPath.startsWith("~/") ? skillPath.slice(2) : skillPath;
|
|
@@ -16609,6 +16651,41 @@ async function removeInstalledSkill(options, skillPath) {
|
|
|
16609
16651
|
// packages/memory/src/query.ts
|
|
16610
16652
|
import * as fs18 from "node:fs/promises";
|
|
16611
16653
|
import path62 from "node:path";
|
|
16654
|
+
|
|
16655
|
+
// packages/memory/src/agent-response.ts
|
|
16656
|
+
var validCitationConfidences = /* @__PURE__ */ new Set(["extracted", "inferred", "ambiguous"]);
|
|
16657
|
+
function parseMemoryAgentResponse(stdout) {
|
|
16658
|
+
let value;
|
|
16659
|
+
try {
|
|
16660
|
+
value = JSON.parse(stdout);
|
|
16661
|
+
} catch {
|
|
16662
|
+
throw new Error("Memory agent returned invalid JSON output.");
|
|
16663
|
+
}
|
|
16664
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
16665
|
+
throw new Error("Memory agent returned an invalid result payload.");
|
|
16666
|
+
}
|
|
16667
|
+
const result = value;
|
|
16668
|
+
if (typeof result.answer !== "string" || !Array.isArray(result.citations) || !isNonNegativeInteger(result.tokensUsed) || !result.citations.every(isQueryCitation)) {
|
|
16669
|
+
throw new Error("Memory agent returned an invalid result payload.");
|
|
16670
|
+
}
|
|
16671
|
+
return {
|
|
16672
|
+
answer: result.answer,
|
|
16673
|
+
citations: result.citations,
|
|
16674
|
+
tokensUsed: result.tokensUsed
|
|
16675
|
+
};
|
|
16676
|
+
}
|
|
16677
|
+
function isQueryCitation(value) {
|
|
16678
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
16679
|
+
return false;
|
|
16680
|
+
}
|
|
16681
|
+
const citation = value;
|
|
16682
|
+
return typeof citation.relPath === "string" && (citation.section === void 0 || typeof citation.section === "string") && typeof citation.confidence === "string" && validCitationConfidences.has(citation.confidence);
|
|
16683
|
+
}
|
|
16684
|
+
function isNonNegativeInteger(value) {
|
|
16685
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
16686
|
+
}
|
|
16687
|
+
|
|
16688
|
+
// packages/memory/src/query.ts
|
|
16612
16689
|
async function queryMemory(root, options) {
|
|
16613
16690
|
const pages = await listPages(root);
|
|
16614
16691
|
if (pages.length === 0) {
|
|
@@ -16628,7 +16705,7 @@ async function queryMemory(root, options) {
|
|
|
16628
16705
|
const agentId = await resolveAgent(configOptions, options.agent ?? null) ?? options.agent ?? "claude-code";
|
|
16629
16706
|
const context = await selectQueryContext(root, options.question, options.budget);
|
|
16630
16707
|
const spawned = await spawn4(agentId, { prompt: context.prompt });
|
|
16631
|
-
const result =
|
|
16708
|
+
const result = parseMemoryAgentResponse(spawned.stdout);
|
|
16632
16709
|
return {
|
|
16633
16710
|
answer: result.answer,
|
|
16634
16711
|
citations: result.citations,
|
|
@@ -16637,26 +16714,6 @@ async function queryMemory(root, options) {
|
|
|
16637
16714
|
exitCode: spawned.exitCode
|
|
16638
16715
|
};
|
|
16639
16716
|
}
|
|
16640
|
-
function parseQueryResponse(stdout) {
|
|
16641
|
-
let value;
|
|
16642
|
-
try {
|
|
16643
|
-
value = JSON.parse(stdout);
|
|
16644
|
-
} catch {
|
|
16645
|
-
throw new Error("Memory agent returned invalid JSON output.");
|
|
16646
|
-
}
|
|
16647
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
16648
|
-
throw new Error("Memory agent returned an invalid result payload.");
|
|
16649
|
-
}
|
|
16650
|
-
const result = value;
|
|
16651
|
-
if (typeof result.answer !== "string" || !Array.isArray(result.citations) || typeof result.tokensUsed !== "number" || !Number.isFinite(result.tokensUsed)) {
|
|
16652
|
-
throw new Error("Memory agent returned an invalid result payload.");
|
|
16653
|
-
}
|
|
16654
|
-
return {
|
|
16655
|
-
answer: result.answer,
|
|
16656
|
-
citations: result.citations,
|
|
16657
|
-
tokensUsed: result.tokensUsed
|
|
16658
|
-
};
|
|
16659
|
-
}
|
|
16660
16717
|
async function selectQueryContext(root, question, budget) {
|
|
16661
16718
|
if (!Number.isFinite(budget) || budget < 0) {
|
|
16662
16719
|
throw new Error("budget must be a finite non-negative number");
|
|
@@ -16774,7 +16831,7 @@ async function explainPage(root, options) {
|
|
|
16774
16831
|
};
|
|
16775
16832
|
const agentId = await resolveAgent(configOptions, options.agent ?? null) ?? options.agent ?? "claude-code";
|
|
16776
16833
|
const spawned = await spawn4(agentId, { prompt });
|
|
16777
|
-
const response =
|
|
16834
|
+
const response = parseMemoryAgentResponse(spawned.stdout);
|
|
16778
16835
|
return {
|
|
16779
16836
|
answer: response.answer,
|
|
16780
16837
|
citations: response.citations,
|
|
@@ -16785,26 +16842,6 @@ async function explainPage(root, options) {
|
|
|
16785
16842
|
outboundSources: targetPage.frontmatter.sources ?? []
|
|
16786
16843
|
};
|
|
16787
16844
|
}
|
|
16788
|
-
function parseExplainResponse(stdout) {
|
|
16789
|
-
let value;
|
|
16790
|
-
try {
|
|
16791
|
-
value = JSON.parse(stdout);
|
|
16792
|
-
} catch {
|
|
16793
|
-
throw new Error("Memory agent returned invalid JSON output.");
|
|
16794
|
-
}
|
|
16795
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
16796
|
-
throw new Error("Memory agent returned an invalid result payload.");
|
|
16797
|
-
}
|
|
16798
|
-
const response = value;
|
|
16799
|
-
if (typeof response.answer !== "string" || !Array.isArray(response.citations) || typeof response.tokensUsed !== "number" || !Number.isFinite(response.tokensUsed)) {
|
|
16800
|
-
throw new Error("Memory agent returned an invalid result payload.");
|
|
16801
|
-
}
|
|
16802
|
-
return {
|
|
16803
|
-
answer: response.answer,
|
|
16804
|
-
citations: response.citations,
|
|
16805
|
-
tokensUsed: response.tokensUsed
|
|
16806
|
-
};
|
|
16807
|
-
}
|
|
16808
16845
|
function collectRelatedPages(pages, targetRelPath, outboundSources) {
|
|
16809
16846
|
const memorySourcePaths = new Set(outboundSources.map((source) => source.path));
|
|
16810
16847
|
return pages.filter((page) => {
|