@aexol/spectral 0.9.58 → 0.9.60
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/CHANGELOG.md +6 -0
- package/README.md +43 -0
- package/dist/agent/index.d.ts +6 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +133 -14
- package/dist/extensions/kanban-bridge.d.ts.map +1 -1
- package/dist/extensions/kanban-bridge.js +21 -1
- package/dist/extensions/web/index.d.ts +10 -0
- package/dist/extensions/web/index.d.ts.map +1 -0
- package/dist/extensions/web/index.js +11 -0
- package/dist/extensions/web/tools/fetch.d.ts +3 -0
- package/dist/extensions/web/tools/fetch.d.ts.map +1 -0
- package/dist/extensions/web/tools/fetch.js +80 -0
- package/dist/extensions/web/utils/fetcher.d.ts +34 -0
- package/dist/extensions/web/utils/fetcher.d.ts.map +1 -0
- package/dist/extensions/web/utils/fetcher.js +289 -0
- package/dist/extensions/web/utils/parser.d.ts +15 -0
- package/dist/extensions/web/utils/parser.d.ts.map +1 -0
- package/dist/extensions/web/utils/parser.js +205 -0
- package/dist/memory/branch.d.ts.map +1 -1
- package/dist/memory/branch.js +16 -8
- package/dist/memory/commands/status.d.ts.map +1 -1
- package/dist/memory/commands/status.js +3 -2
- package/dist/memory/config.d.ts +12 -0
- package/dist/memory/config.d.ts.map +1 -1
- package/dist/memory/config.js +21 -2
- package/dist/memory/hooks/compaction-trigger.d.ts.map +1 -1
- package/dist/memory/hooks/compaction-trigger.js +19 -3
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +2 -0
- package/dist/memory/testing/random-text.js +1 -1
- package/dist/memory/tool-output-compressor.d.ts +14 -20
- package/dist/memory/tool-output-compressor.d.ts.map +1 -1
- package/dist/memory/tool-output-compressor.js +391 -137
- package/dist/memory/tools/compact-context.d.ts +5 -0
- package/dist/memory/tools/compact-context.d.ts.map +1 -0
- package/dist/memory/tools/compact-context.js +772 -0
- package/dist/relay/dispatcher.d.ts +1 -1
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +61 -1
- package/dist/sdk/coding-agent/core/agent-session.d.ts +56 -3
- package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/agent-session.js +408 -210
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts +57 -7
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/compaction/compaction.js +146 -27
- package/dist/sdk/coding-agent/core/compaction/dcp-lite.d.ts +24 -0
- package/dist/sdk/coding-agent/core/compaction/dcp-lite.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/dcp-lite.js +411 -0
- package/dist/sdk/coding-agent/core/compaction/index.d.ts +2 -0
- package/dist/sdk/coding-agent/core/compaction/index.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/compaction/index.js +2 -0
- package/dist/sdk/coding-agent/core/compaction/policy.d.ts +71 -0
- package/dist/sdk/coding-agent/core/compaction/policy.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/policy.js +147 -0
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/native-extensions.js +9 -0
- package/dist/sdk/coding-agent/core/extensions/types.d.ts +17 -0
- package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/settings-manager.d.ts +4 -10
- package/dist/sdk/coding-agent/core/settings-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/settings-manager.js +17 -17
- package/dist/sdk/coding-agent/index.d.ts +1 -1
- package/dist/sdk/coding-agent/index.d.ts.map +1 -1
- package/dist/server/agent-bridge.d.ts +12 -5
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +65 -17
- package/dist/server/handlers/paths-home.d.ts +11 -0
- package/dist/server/handlers/paths-home.d.ts.map +1 -0
- package/dist/server/handlers/paths-home.js +10 -0
- package/dist/server/handlers/paths-list.d.ts +24 -0
- package/dist/server/handlers/paths-list.d.ts.map +1 -0
- package/dist/server/handlers/paths-list.js +56 -0
- package/dist/server/handlers/paths-pick-directory.d.ts.map +1 -1
- package/dist/server/handlers/paths-pick-directory.js +2 -1
- package/dist/server/handlers/sessions.d.ts +11 -1
- package/dist/server/handlers/sessions.d.ts.map +1 -1
- package/dist/server/handlers/sessions.js +22 -2
- package/dist/server/handlers/settings.d.ts +18 -1
- package/dist/server/handlers/settings.d.ts.map +1 -1
- package/dist/server/handlers/settings.js +63 -13
- package/dist/server/session-stream.d.ts +65 -37
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +525 -119
- package/dist/server/storage.d.ts +3 -0
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +25 -0
- package/dist/server/wire.d.ts +127 -2
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP fetch wrapper for the web_fetch tool.
|
|
3
|
+
*
|
|
4
|
+
* Handles URL validation, SSRF protection, redirects, timeouts, abort signals,
|
|
5
|
+
* response size limits, and content-type filtering.
|
|
6
|
+
*/
|
|
7
|
+
import { lookup } from "node:dns/promises";
|
|
8
|
+
import { isIP } from "node:net";
|
|
9
|
+
export class WebFetchError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "WebFetchError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36";
|
|
16
|
+
const MAX_BODY_SIZE_BYTES = 5 * 1024 * 1024; // 5 MB
|
|
17
|
+
const MAX_REDIRECTS = 10;
|
|
18
|
+
/** Private/loopback IPv4 CIDR prefixes. */
|
|
19
|
+
const PRIVATE_IPV4_PREFIXES = [
|
|
20
|
+
"10.",
|
|
21
|
+
...Array.from({ length: 16 }, (_, i) => `172.${16 + i}.`),
|
|
22
|
+
"192.168.",
|
|
23
|
+
"169.254.",
|
|
24
|
+
"127.",
|
|
25
|
+
"0.",
|
|
26
|
+
...Array.from({ length: 64 }, (_, i) => `100.${64 + i}.`),
|
|
27
|
+
];
|
|
28
|
+
const BLOCKED_HOSTNAMES = new Set([
|
|
29
|
+
"localhost",
|
|
30
|
+
"metadata.google.internal",
|
|
31
|
+
"metadata",
|
|
32
|
+
"169.254.169.254",
|
|
33
|
+
"fd00.local",
|
|
34
|
+
"metadata.aws.internal",
|
|
35
|
+
"metadata.azure.internal",
|
|
36
|
+
]);
|
|
37
|
+
function isPrivateIPv4(ip) {
|
|
38
|
+
for (const prefix of PRIVATE_IPV4_PREFIXES) {
|
|
39
|
+
if (ip.startsWith(prefix))
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
function isPrivateIPv6(ip) {
|
|
45
|
+
const lower = ip.toLowerCase();
|
|
46
|
+
return (lower === "::1" ||
|
|
47
|
+
lower.startsWith("fe80:") ||
|
|
48
|
+
lower.startsWith("fc00:") ||
|
|
49
|
+
lower.startsWith("fd00:") ||
|
|
50
|
+
lower.startsWith("fd01:") ||
|
|
51
|
+
(lower.startsWith("::ffff:") && isPrivateIPv4(lower.replace("::ffff:", ""))));
|
|
52
|
+
}
|
|
53
|
+
function isPrivateIp(ip) {
|
|
54
|
+
const family = isIP(ip);
|
|
55
|
+
if (family === 4)
|
|
56
|
+
return isPrivateIPv4(ip);
|
|
57
|
+
if (family === 6)
|
|
58
|
+
return isPrivateIPv6(ip);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validate a URL with SSRF protection.
|
|
63
|
+
*
|
|
64
|
+
* Throws WebFetchError for invalid URLs, non-http(s) protocols, blocked
|
|
65
|
+
* hostnames, or hostnames that resolve to private IP addresses.
|
|
66
|
+
*/
|
|
67
|
+
export async function validateUrl(urlStr, allowPrivateHosts = false) {
|
|
68
|
+
if (!urlStr || typeof urlStr !== "string") {
|
|
69
|
+
throw new WebFetchError("URL is required.");
|
|
70
|
+
}
|
|
71
|
+
const trimmed = urlStr.trim();
|
|
72
|
+
if (/^(file|ftp|gopher|dict|ldap|data|blob|javascript):/i.test(trimmed)) {
|
|
73
|
+
throw new WebFetchError(`Protocol not allowed: ${trimmed}`);
|
|
74
|
+
}
|
|
75
|
+
let parsed;
|
|
76
|
+
try {
|
|
77
|
+
parsed = new URL(trimmed);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
throw new WebFetchError(`Invalid URL: ${urlStr}`);
|
|
81
|
+
}
|
|
82
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
83
|
+
throw new WebFetchError(`Only http:// and https:// URLs are supported, got: ${parsed.protocol}`);
|
|
84
|
+
}
|
|
85
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
86
|
+
if (!allowPrivateHosts && BLOCKED_HOSTNAMES.has(hostname)) {
|
|
87
|
+
throw new WebFetchError(`Blocked hostname: ${hostname}`);
|
|
88
|
+
}
|
|
89
|
+
const ipLiteral = hostname.replace(/^\[|\]$/g, "");
|
|
90
|
+
const ipFamily = isIP(ipLiteral);
|
|
91
|
+
if (!allowPrivateHosts && ipFamily && isPrivateIp(ipLiteral)) {
|
|
92
|
+
throw new WebFetchError(`URL points to a private IP address: ${hostname}`);
|
|
93
|
+
}
|
|
94
|
+
if (!allowPrivateHosts && !ipFamily) {
|
|
95
|
+
try {
|
|
96
|
+
const records = await lookup(hostname, { all: true });
|
|
97
|
+
for (const record of records) {
|
|
98
|
+
if (isPrivateIp(record.address)) {
|
|
99
|
+
throw new WebFetchError(`Hostname ${hostname} resolves to private IP ${record.address}. Blocked by SSRF protection.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
if (err instanceof WebFetchError)
|
|
105
|
+
throw err;
|
|
106
|
+
// DNS failures are allowed to pass through; the fetch itself will fail.
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
function detectCharset(contentType) {
|
|
112
|
+
const match = /charset=([^\s;]+)/i.exec(contentType);
|
|
113
|
+
if (match) {
|
|
114
|
+
const charset = match[1].toLowerCase().trim().replace(/^["']|["']$/g, "");
|
|
115
|
+
// Only return charsets TextDecoder is likely to support.
|
|
116
|
+
if (["utf-8", "utf8", "iso-8859-1", "windows-1252", "ascii", "us-ascii"].includes(charset)) {
|
|
117
|
+
return charset === "utf8" ? "utf-8" : charset;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
/** Validate that the Content-Type is readable text, not binary media. */
|
|
123
|
+
export function validateContentType(contentType) {
|
|
124
|
+
const lower = contentType.toLowerCase().split(";")[0].trim();
|
|
125
|
+
const allowedPrefixes = [
|
|
126
|
+
"text/",
|
|
127
|
+
"application/json",
|
|
128
|
+
"application/xml",
|
|
129
|
+
"application/xhtml+xml",
|
|
130
|
+
"application/ld+json",
|
|
131
|
+
"application/rss+xml",
|
|
132
|
+
"application/atom+xml",
|
|
133
|
+
];
|
|
134
|
+
const blockedPrefixes = [
|
|
135
|
+
"image/",
|
|
136
|
+
"video/",
|
|
137
|
+
"audio/",
|
|
138
|
+
"application/octet-stream",
|
|
139
|
+
"application/zip",
|
|
140
|
+
"application/gzip",
|
|
141
|
+
"application/x-gzip",
|
|
142
|
+
"application/x-tar",
|
|
143
|
+
"application/x-bzip",
|
|
144
|
+
"application/x-bzip2",
|
|
145
|
+
"application/x-rar-compressed",
|
|
146
|
+
"application/pdf",
|
|
147
|
+
"application/msword",
|
|
148
|
+
"application/vnd.ms-",
|
|
149
|
+
"application/vnd.openxmlformats-",
|
|
150
|
+
"font/",
|
|
151
|
+
];
|
|
152
|
+
for (const prefix of blockedPrefixes) {
|
|
153
|
+
if (lower.startsWith(prefix)) {
|
|
154
|
+
throw new WebFetchError(`Content-Type not readable: ${contentType}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const isJsonSubtype = lower.endsWith("+json");
|
|
158
|
+
if (!isJsonSubtype && !allowedPrefixes.some((prefix) => lower.startsWith(prefix))) {
|
|
159
|
+
// Reject unknown content types rather than guessing; this prevents returning
|
|
160
|
+
// binary or otherwise unreadable data to the model.
|
|
161
|
+
throw new WebFetchError(`Content-Type not allowed: ${contentType}. Only text-based types (text/*, application/json, application/*+json, application/xml, RSS/Atom) are supported.`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Fetch a public URL and return its decoded text content.
|
|
166
|
+
*/
|
|
167
|
+
export async function fetchWebPage(url, opts = {}) {
|
|
168
|
+
const parsed = await validateUrl(url, opts.allowPrivateHosts);
|
|
169
|
+
const userAgent = opts.userAgent ?? DEFAULT_USER_AGENT;
|
|
170
|
+
const timeoutSeconds = clampTimeout(opts.timeoutSeconds ?? 30);
|
|
171
|
+
let currentUrl = parsed.href;
|
|
172
|
+
let lastResponse = null;
|
|
173
|
+
let lastError;
|
|
174
|
+
// Single global timeout for the entire fetch operation, including redirects.
|
|
175
|
+
const globalController = new AbortController();
|
|
176
|
+
const globalTimeoutId = setTimeout(() => globalController.abort(new WebFetchError("Global fetch timeout exceeded.")), timeoutSeconds * 1000);
|
|
177
|
+
// Link the caller's signal so external cancellation also aborts the fetch.
|
|
178
|
+
const callerSignal = opts.signal;
|
|
179
|
+
const onCallerAbort = () => globalController.abort();
|
|
180
|
+
if (callerSignal) {
|
|
181
|
+
if (callerSignal.aborted) {
|
|
182
|
+
clearTimeout(globalTimeoutId);
|
|
183
|
+
throw new WebFetchError("Request was cancelled.");
|
|
184
|
+
}
|
|
185
|
+
callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
for (let i = 0; i <= MAX_REDIRECTS; i++) {
|
|
189
|
+
try {
|
|
190
|
+
const res = await fetch(currentUrl, {
|
|
191
|
+
redirect: "manual",
|
|
192
|
+
signal: globalController.signal,
|
|
193
|
+
headers: {
|
|
194
|
+
"User-Agent": userAgent,
|
|
195
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,application/json;q=0.8,*/*;q=0.7",
|
|
196
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
if (res.status >= 300 && res.status < 400) {
|
|
200
|
+
const location = res.headers.get("location");
|
|
201
|
+
if (location) {
|
|
202
|
+
currentUrl = new URL(location, currentUrl).href;
|
|
203
|
+
await validateUrl(currentUrl, opts.allowPrivateHosts);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
lastResponse = res;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
212
|
+
if (err instanceof WebFetchError)
|
|
213
|
+
throw err;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
clearTimeout(globalTimeoutId);
|
|
220
|
+
if (callerSignal) {
|
|
221
|
+
callerSignal.removeEventListener("abort", onCallerAbort);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!lastResponse) {
|
|
225
|
+
if (!lastError) {
|
|
226
|
+
throw new WebFetchError(`Too many redirects: the server redirected more than ${MAX_REDIRECTS} times. Last URL: ${currentUrl}`);
|
|
227
|
+
}
|
|
228
|
+
throw new WebFetchError(lastError);
|
|
229
|
+
}
|
|
230
|
+
if (!lastResponse.ok) {
|
|
231
|
+
throw new WebFetchError(`HTTP ${lastResponse.status} ${lastResponse.statusText}`.trim());
|
|
232
|
+
}
|
|
233
|
+
const contentTypeHeader = lastResponse.headers.get("content-type") ?? "";
|
|
234
|
+
validateContentType(contentTypeHeader);
|
|
235
|
+
const contentLengthHeader = lastResponse.headers.get("content-length");
|
|
236
|
+
if (contentLengthHeader) {
|
|
237
|
+
const contentLength = Number.parseInt(contentLengthHeader, 10);
|
|
238
|
+
if (!Number.isNaN(contentLength) && contentLength > MAX_BODY_SIZE_BYTES) {
|
|
239
|
+
throw new WebFetchError(`Response body too large: ${contentLength} bytes (max ${MAX_BODY_SIZE_BYTES} bytes)`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Stream the body so we can abort early if it exceeds the limit.
|
|
243
|
+
const reader = lastResponse.body?.getReader();
|
|
244
|
+
if (!reader) {
|
|
245
|
+
throw new WebFetchError("Response body is not readable.");
|
|
246
|
+
}
|
|
247
|
+
const chunks = [];
|
|
248
|
+
let received = 0;
|
|
249
|
+
try {
|
|
250
|
+
while (true) {
|
|
251
|
+
const { done, value } = await reader.read();
|
|
252
|
+
if (done)
|
|
253
|
+
break;
|
|
254
|
+
if (value) {
|
|
255
|
+
received += value.length;
|
|
256
|
+
if (received > MAX_BODY_SIZE_BYTES) {
|
|
257
|
+
throw new WebFetchError(`Response body exceeds maximum size of ${MAX_BODY_SIZE_BYTES} bytes`);
|
|
258
|
+
}
|
|
259
|
+
chunks.push(value);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
reader.releaseLock();
|
|
265
|
+
}
|
|
266
|
+
const buffer = Buffer.concat(chunks);
|
|
267
|
+
const charset = detectCharset(contentTypeHeader) ?? "utf-8";
|
|
268
|
+
let content;
|
|
269
|
+
try {
|
|
270
|
+
content = new TextDecoder(charset, { fatal: false }).decode(buffer);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
content = new TextDecoder("utf-8", { fatal: false }).decode(buffer);
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
url: parsed.href,
|
|
277
|
+
finalUrl: currentUrl,
|
|
278
|
+
contentType: contentTypeHeader,
|
|
279
|
+
content,
|
|
280
|
+
contentLengthBytes: buffer.length,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function clampTimeout(seconds) {
|
|
284
|
+
const min = 1;
|
|
285
|
+
const max = 120;
|
|
286
|
+
if (!Number.isFinite(seconds))
|
|
287
|
+
return 30;
|
|
288
|
+
return Math.max(min, Math.min(max, seconds));
|
|
289
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type OutputFormat = "text" | "markdown" | "html" | "json";
|
|
2
|
+
export declare class ContentConversionError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Convert fetched content into the requested output format.
|
|
7
|
+
*
|
|
8
|
+
* - html: returns the raw body as-is.
|
|
9
|
+
* - json: parses JSON and returns stable two-space pretty-printed JSON.
|
|
10
|
+
* - text: strips scripts, styles, iframes, etc. and returns plain visible text.
|
|
11
|
+
* - markdown: converts HTML to Markdown using turndown when available,
|
|
12
|
+
* with a cheerio-based fallback for headings, lists, links, and code.
|
|
13
|
+
*/
|
|
14
|
+
export declare function convertContent(content: string, format: OutputFormat): Promise<string>;
|
|
15
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../../src/extensions/web/utils/parser.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEjE,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,MAAM,CAAC,CAejB"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML/JSON → text / markdown / raw HTML / pretty JSON conversion for the
|
|
3
|
+
* web_fetch tool.
|
|
4
|
+
*/
|
|
5
|
+
import * as cheerio from "cheerio";
|
|
6
|
+
export class ContentConversionError extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "ContentConversionError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Convert fetched content into the requested output format.
|
|
14
|
+
*
|
|
15
|
+
* - html: returns the raw body as-is.
|
|
16
|
+
* - json: parses JSON and returns stable two-space pretty-printed JSON.
|
|
17
|
+
* - text: strips scripts, styles, iframes, etc. and returns plain visible text.
|
|
18
|
+
* - markdown: converts HTML to Markdown using turndown when available,
|
|
19
|
+
* with a cheerio-based fallback for headings, lists, links, and code.
|
|
20
|
+
*/
|
|
21
|
+
export async function convertContent(content, format) {
|
|
22
|
+
if (format === "html") {
|
|
23
|
+
return content;
|
|
24
|
+
}
|
|
25
|
+
if (format === "json") {
|
|
26
|
+
return formatJson(content);
|
|
27
|
+
}
|
|
28
|
+
if (format === "text") {
|
|
29
|
+
return extractText(content);
|
|
30
|
+
}
|
|
31
|
+
// format === "markdown"
|
|
32
|
+
return convertToMarkdown(content);
|
|
33
|
+
}
|
|
34
|
+
function formatJson(content) {
|
|
35
|
+
const source = content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(source);
|
|
38
|
+
const pretty = JSON.stringify(parsed, null, 2);
|
|
39
|
+
if (pretty === undefined) {
|
|
40
|
+
throw new ContentConversionError("JSON value cannot be rendered.");
|
|
41
|
+
}
|
|
42
|
+
return pretty;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (err instanceof ContentConversionError)
|
|
46
|
+
throw err;
|
|
47
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
48
|
+
throw new ContentConversionError(`Invalid JSON content: ${detail}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function extractText(html) {
|
|
52
|
+
const $ = cheerio.load(html);
|
|
53
|
+
$("script, style, noscript, iframe, canvas, svg, template").remove();
|
|
54
|
+
$("[aria-hidden='true']").remove();
|
|
55
|
+
$("[hidden]").remove();
|
|
56
|
+
return $("body")
|
|
57
|
+
.text()
|
|
58
|
+
.replace(/\s+/g, " ")
|
|
59
|
+
.trim();
|
|
60
|
+
}
|
|
61
|
+
async function convertToMarkdown(html) {
|
|
62
|
+
// Try turndown first; fall back to a simple cheerio converter if unavailable.
|
|
63
|
+
try {
|
|
64
|
+
const TurndownService = (await import("turndown")).default;
|
|
65
|
+
const service = new TurndownService({
|
|
66
|
+
headingStyle: "atx",
|
|
67
|
+
codeBlockStyle: "fenced",
|
|
68
|
+
bulletListMarker: "-",
|
|
69
|
+
});
|
|
70
|
+
service.addRule("strikethrough", {
|
|
71
|
+
filter: ["del", "s", "strike"],
|
|
72
|
+
replacement: (content) => `~~${content}~~`,
|
|
73
|
+
});
|
|
74
|
+
service.addRule("removeScripts", {
|
|
75
|
+
filter: ["script", "style", "noscript", "iframe", "canvas", "svg", "template"],
|
|
76
|
+
replacement: () => "",
|
|
77
|
+
});
|
|
78
|
+
return service.turndown(html).trim();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return cheerioToMarkdown(html);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Simple cheerio-based Markdown converter used when turndown is unavailable. */
|
|
85
|
+
function cheerioToMarkdown(html) {
|
|
86
|
+
const $ = cheerio.load(html);
|
|
87
|
+
$("script, style, noscript, iframe, canvas, svg, template").remove();
|
|
88
|
+
const lines = [];
|
|
89
|
+
function pushLine(text) {
|
|
90
|
+
const trimmed = text.replace(/\s+/g, " ").trim();
|
|
91
|
+
if (!trimmed)
|
|
92
|
+
return;
|
|
93
|
+
lines.push(trimmed);
|
|
94
|
+
}
|
|
95
|
+
function walkNodes(nodes, depth = 0) {
|
|
96
|
+
nodes.each((_, el) => {
|
|
97
|
+
if (el.type === "text") {
|
|
98
|
+
const text = el.data ?? "";
|
|
99
|
+
if (text.trim()) {
|
|
100
|
+
lines.push(text.trim());
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (el.type !== "tag")
|
|
105
|
+
return;
|
|
106
|
+
const $el = $(el);
|
|
107
|
+
const tag = el.tagName.toLowerCase();
|
|
108
|
+
const text = $el.text().trim();
|
|
109
|
+
switch (tag) {
|
|
110
|
+
case "h1":
|
|
111
|
+
pushLine(`# ${text}`);
|
|
112
|
+
break;
|
|
113
|
+
case "h2":
|
|
114
|
+
pushLine(`## ${text}`);
|
|
115
|
+
break;
|
|
116
|
+
case "h3":
|
|
117
|
+
pushLine(`### ${text}`);
|
|
118
|
+
break;
|
|
119
|
+
case "h4":
|
|
120
|
+
pushLine(`#### ${text}`);
|
|
121
|
+
break;
|
|
122
|
+
case "h5":
|
|
123
|
+
pushLine(`##### ${text}`);
|
|
124
|
+
break;
|
|
125
|
+
case "h6":
|
|
126
|
+
pushLine(`###### ${text}`);
|
|
127
|
+
break;
|
|
128
|
+
case "p":
|
|
129
|
+
// Walk children instead of emitting the full container text; this
|
|
130
|
+
// avoids duplicating inline elements (links, code, emphasis) while
|
|
131
|
+
// still emitting plain text nodes.
|
|
132
|
+
walkNodes($el.children(), depth);
|
|
133
|
+
break;
|
|
134
|
+
case "br":
|
|
135
|
+
lines.push("");
|
|
136
|
+
break;
|
|
137
|
+
case "a": {
|
|
138
|
+
const href = $el.attr("href") ?? "";
|
|
139
|
+
if (href) {
|
|
140
|
+
pushLine(`[${text}](${href})`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
pushLine(text);
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case "code":
|
|
148
|
+
pushLine(`\`${text}\``);
|
|
149
|
+
break;
|
|
150
|
+
case "pre": {
|
|
151
|
+
const code = $el.find("code").text().trim() || text;
|
|
152
|
+
pushLine(`\`\`\`\n${code}\n\`\`\``);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "li": {
|
|
156
|
+
const prefix = " ".repeat(Math.max(0, depth)) + "- ";
|
|
157
|
+
const nested = $el.children("ul, ol");
|
|
158
|
+
const ownText = $el.clone().children("ul, ol").remove().end().text().trim();
|
|
159
|
+
if (ownText)
|
|
160
|
+
pushLine(`${prefix}${ownText}`);
|
|
161
|
+
if (nested.length) {
|
|
162
|
+
walkNodes(nested.children(), depth + 1);
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case "blockquote":
|
|
167
|
+
pushLine(text
|
|
168
|
+
.split("\n")
|
|
169
|
+
.map((line) => `> ${line}`)
|
|
170
|
+
.join("\n"));
|
|
171
|
+
break;
|
|
172
|
+
case "div":
|
|
173
|
+
case "section":
|
|
174
|
+
case "article":
|
|
175
|
+
case "main":
|
|
176
|
+
case "header":
|
|
177
|
+
case "footer":
|
|
178
|
+
case "aside":
|
|
179
|
+
case "nav":
|
|
180
|
+
walkNodes($el.children(), depth);
|
|
181
|
+
break;
|
|
182
|
+
case "span":
|
|
183
|
+
case "strong":
|
|
184
|
+
case "b":
|
|
185
|
+
case "em":
|
|
186
|
+
case "i":
|
|
187
|
+
default:
|
|
188
|
+
if (text) {
|
|
189
|
+
lines.push(text);
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
walkNodes($("body").children());
|
|
196
|
+
// Collapse whitespace and join lines into readable markdown.
|
|
197
|
+
const result = [];
|
|
198
|
+
for (const line of lines) {
|
|
199
|
+
const normalized = line.replace(/\s+/g, " ").trim();
|
|
200
|
+
if (!normalized)
|
|
201
|
+
continue;
|
|
202
|
+
result.push(normalized);
|
|
203
|
+
}
|
|
204
|
+
return result.join("\n\n").trim();
|
|
205
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/memory/branch.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/memory/branch.ts"],"names":[],"mappings":"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"}
|
package/dist/memory/branch.js
CHANGED
|
@@ -292,13 +292,21 @@ export function recallObservationSources(entries, observationId) {
|
|
|
292
292
|
return { status: "found", observationId, matches, collision: matches.length > 1 };
|
|
293
293
|
}
|
|
294
294
|
function getPriorMemoryDetails(entries) {
|
|
295
|
-
const
|
|
296
|
-
if (
|
|
297
|
-
|
|
295
|
+
const compactionMemory = findLastSupportedMemoryCompaction(entries);
|
|
296
|
+
if (compactionMemory)
|
|
297
|
+
return compactionMemory.details;
|
|
298
|
+
return getLatestRestoredSnapshot(entries)?.details;
|
|
299
|
+
}
|
|
300
|
+
function findLastSupportedMemoryCompaction(entries) {
|
|
301
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
302
|
+
const entry = entries[i];
|
|
303
|
+
if (entry.type !== "compaction")
|
|
304
|
+
continue;
|
|
305
|
+
const details = entry.details;
|
|
298
306
|
if (isSupportedMemoryDetails(details))
|
|
299
|
-
return details;
|
|
307
|
+
return { index: i, details };
|
|
300
308
|
}
|
|
301
|
-
return
|
|
309
|
+
return undefined;
|
|
302
310
|
}
|
|
303
311
|
export function recallMemorySources(entries, memoryId) {
|
|
304
312
|
const indexedObservations = collectIndexedObservations(entries);
|
|
@@ -422,14 +430,14 @@ function collectObservationsPendingNextCompaction(entries) {
|
|
|
422
430
|
const idToIdx = new Map();
|
|
423
431
|
for (let i = 0; i < entries.length; i++)
|
|
424
432
|
idToIdx.set(entries[i].id, i);
|
|
425
|
-
const
|
|
433
|
+
const priorMemoryCompaction = findLastSupportedMemoryCompaction(entries);
|
|
426
434
|
const restoredSnapshot = getLatestRestoredSnapshot(entries);
|
|
427
435
|
let thresholdStartIdx;
|
|
428
|
-
if (
|
|
436
|
+
if (!priorMemoryCompaction) {
|
|
429
437
|
thresholdStartIdx = branchIndexAfterCoveredSourceCount(entries, restoredSnapshot?.coveredSourceCount ?? 0);
|
|
430
438
|
}
|
|
431
439
|
else {
|
|
432
|
-
const priorFirstKept = entries[
|
|
440
|
+
const priorFirstKept = entries[priorMemoryCompaction.index].firstKeptEntryId;
|
|
433
441
|
if (!priorFirstKept)
|
|
434
442
|
throw new Error("prior compaction entry missing firstKeptEntryId");
|
|
435
443
|
const idx = idToIdx.get(priorFirstKept);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/memory/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AASpE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/memory/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AASpE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CA8F/E"}
|
|
@@ -21,9 +21,10 @@ export function registerStatusCommand(ext, runtime) {
|
|
|
21
21
|
const pendingObsTokens = estimateObservationPoolTokens(pendingObs);
|
|
22
22
|
const pendingObsCount = pendingObs.length;
|
|
23
23
|
const relevanceHistogram = countByRelevance([...committedObs, ...pendingObs]);
|
|
24
|
-
const
|
|
24
|
+
const compactionPolicy = SettingsManager.create(ctx.cwd).getCompactionPolicy();
|
|
25
|
+
const keepRecentTokens = compactionPolicy.conversation.keepRecentTokens;
|
|
25
26
|
const obsThreshold = runtime.config.observationThresholdTokens;
|
|
26
|
-
const compThreshold = runtime.config.compactionThresholdTokens;
|
|
27
|
+
const compThreshold = Math.max(runtime.config.compactionThresholdTokens, compactionPolicy.autoOlderHistory.minRawTokens);
|
|
27
28
|
const refThreshold = runtime.config.reflectionThresholdTokens;
|
|
28
29
|
// Approximation: the real compaction gate excludes pending obs whose coversFromId falls inside
|
|
29
30
|
// the new keep-recent tail (deferred to next cycle) and adds any sync-catch-up gap obs produced
|
package/dist/memory/config.d.ts
CHANGED
|
@@ -23,6 +23,18 @@ export interface Config {
|
|
|
23
23
|
observerMaxChunkTokens?: number;
|
|
24
24
|
reflectorMaxTurnsPerPass?: number;
|
|
25
25
|
prunerMaxTurnsPerPass?: number;
|
|
26
|
+
/** Raw source tokens since last compaction before adding DCP-lite nudges. */
|
|
27
|
+
dcpNudgeMinRawTokens: number;
|
|
28
|
+
/** Absolute context tokens before adding DCP-lite nudges. Mirrors DCP compress.minContextLimit. */
|
|
29
|
+
dcpNudgeMinContextTokens: number;
|
|
30
|
+
/** Context-window percent before adding DCP-lite nudges. */
|
|
31
|
+
dcpNudgeContextPercent: number;
|
|
32
|
+
/** Minimum turns between repeated DCP-lite nudges. */
|
|
33
|
+
dcpNudgeTurnInterval: number;
|
|
34
|
+
/** Provider requests after a user prompt before adding an in-run DCP-lite nudge. */
|
|
35
|
+
dcpInRunNudgeIterationThreshold: number;
|
|
36
|
+
/** Minimum provider requests between repeated in-run DCP-lite nudges. */
|
|
37
|
+
dcpInRunNudgeFrequency: number;
|
|
26
38
|
/** @deprecated Use reflectorMaxTurnsPerPass and prunerMaxTurnsPerPass. */
|
|
27
39
|
compactionMaxToolCalls?: number;
|
|
28
40
|
interAgent?: InterAgentConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/memory/config.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAChC,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,4DAA4D;IAC5D,qBAAqB,CAAC,EAAE,SAAS,CAAC;IAClC,wEAAwE;IACxE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,MAAM;IACtB,0BAA0B,EAAE,MAAM,CAAC;IACnC,yBAAyB,EAAE,MAAM,CAAC;IAClC,yBAAyB,EAAE,MAAM,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,0EAA0E;IAC1E,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IACnC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,wBAAwB,EAAE,MAAM,CAAC;IACjC,qBAAqB,EAAE,MAAM,CAAC;CAC9B;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/memory/config.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAChC,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,4DAA4D;IAC5D,qBAAqB,CAAC,EAAE,SAAS,CAAC;IAClC,wEAAwE;IACxE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,MAAM;IACtB,0BAA0B,EAAE,MAAM,CAAC;IACnC,yBAAyB,EAAE,MAAM,CAAC;IAClC,yBAAyB,EAAE,MAAM,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,6EAA6E;IAC7E,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mGAAmG;IACnG,wBAAwB,EAAE,MAAM,CAAC;IACjC,4DAA4D;IAC5D,sBAAsB,EAAE,MAAM,CAAC;IAC/B,sDAAsD;IACtD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oFAAoF;IACpF,+BAA+B,EAAE,MAAM,CAAC;IACxC,yEAAyE;IACzE,sBAAsB,EAAE,MAAM,CAAC;IAC/B,0EAA0E;IAC1E,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IACnC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,wBAAwB,EAAE,MAAM,CAAC;IACjC,qBAAqB,EAAE,MAAM,CAAC;CAC9B;AAcD,eAAO,MAAM,QAAQ,EAAE,MAatB,CAAC;AAsEF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAMrE;AAED,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAOnF;AAaD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAUpF"}
|