@duckmind/dm-windows-x64 0.63.0 → 0.63.6
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/dm.exe +0 -0
- package/extensions/.dm-extensions.json +200 -4
- package/extensions/dm-context/package.json +1 -1
- package/extensions/dm-context/skills/context-management/SKILL.md +173 -223
- package/extensions/dm-context/skills/context-management/references/development-and-troubleshooting.md +75 -0
- package/extensions/dm-context/skills/context-management/references/interleaved-async-work.md +143 -0
- package/extensions/dm-context/skills/context-management/references/planning-and-execution.md +81 -0
- package/extensions/dm-context/skills/context-management/references/repeated-items-and-batch-work.md +69 -0
- package/extensions/dm-context/skills/context-management/references/retry-branch-and-pivot.md +80 -0
- package/extensions/dm-context/skills/context-management/references/search-research-and-reading.md +103 -0
- package/extensions/dm-context/skills/context-management/references/task-switching-and-cleanup.md +73 -0
- package/extensions/dm-context/src/context.js +3 -2
- package/extensions/dm-context/src/index.js +168 -84
- package/extensions/dm-skills-manager/THIRD_PARTY_NOTICES.md +27 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/components.js +265 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/constants.js +31 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/creation-fallback.js +20 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/creation.js +145 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/dialog.js +738 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/dm-ai-compat.js +15 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/format.js +125 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/glyphs.js +142 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/layout.js +93 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/paths.js +76 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/registry.js +95 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/settings.js +93 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/startup.js +32 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/toggle.js +54 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/types.js +14 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/ui.js +121 -0
- package/extensions/dm-skills-manager/extensions/skills-manager.js +111 -0
- package/extensions/dm-skills-manager/package.json +121 -0
- package/extensions/dm-web-access/SECURITY.md +5 -0
- package/extensions/dm-web-access/activity.js +65 -0
- package/extensions/dm-web-access/anysearch.js +158 -0
- package/extensions/dm-web-access/auth-fetch.js +131 -0
- package/extensions/dm-web-access/bocha.js +214 -0
- package/extensions/dm-web-access/brave.js +196 -0
- package/extensions/dm-web-access/brightdata-unlocker.js +202 -0
- package/extensions/dm-web-access/brightdata.js +334 -0
- package/extensions/dm-web-access/chrome-cookies.js +627 -0
- package/extensions/dm-web-access/content-find.js +114 -0
- package/extensions/dm-web-access/credential-source.js +150 -0
- package/extensions/dm-web-access/curator-page.js +3559 -0
- package/extensions/dm-web-access/curator-server.js +691 -0
- package/extensions/dm-web-access/data-uri-sanitize.js +312 -0
- package/extensions/dm-web-access/datalab-pdf-extract.js +346 -0
- package/extensions/dm-web-access/declared-web-links.js +167 -0
- package/extensions/dm-web-access/dm-web-fetch-demo.mp4 +0 -0
- package/extensions/dm-web-access/duckduckgo.js +118 -0
- package/extensions/dm-web-access/exa.js +401 -0
- package/extensions/dm-web-access/extract.js +1220 -0
- package/extensions/dm-web-access/feature-config.js +24 -0
- package/extensions/dm-web-access/fetch-params.js +81 -0
- package/extensions/dm-web-access/firecrawl.js +378 -0
- package/extensions/dm-web-access/gemini-adc.js +241 -0
- package/extensions/dm-web-access/gemini-api.js +258 -0
- package/extensions/dm-web-access/gemini-pdf-extract.js +74 -0
- package/extensions/dm-web-access/gemini-search.js +889 -0
- package/extensions/dm-web-access/gemini-url-context.js +97 -0
- package/extensions/dm-web-access/gemini-web-config.js +84 -0
- package/extensions/dm-web-access/gemini-web.js +351 -0
- package/extensions/dm-web-access/github-api.js +166 -0
- package/extensions/dm-web-access/github-extract.js +991 -0
- package/extensions/dm-web-access/github-issue-pr.js +750 -0
- package/extensions/dm-web-access/index.js +3117 -0
- package/extensions/dm-web-access/jina-search.js +242 -0
- package/extensions/dm-web-access/kagi.js +255 -0
- package/extensions/dm-web-access/kimi-search.js +214 -0
- package/extensions/dm-web-access/ollama.js +209 -0
- package/extensions/dm-web-access/openai-search.js +510 -0
- package/extensions/dm-web-access/package.json +34 -0
- package/extensions/dm-web-access/page-query.js +124 -0
- package/extensions/dm-web-access/parallel-mcp.js +223 -0
- package/extensions/dm-web-access/parallel.js +345 -0
- package/extensions/dm-web-access/pdf-extract.js +257 -0
- package/extensions/dm-web-access/perplexity.js +151 -0
- package/extensions/dm-web-access/querit.js +327 -0
- package/extensions/dm-web-access/query-rewrite.js +40 -0
- package/extensions/dm-web-access/render-search-error.js +80 -0
- package/extensions/dm-web-access/rsc-extract.js +347 -0
- package/extensions/dm-web-access/search1api.js +245 -0
- package/extensions/dm-web-access/searchinfinity.js +221 -0
- package/extensions/dm-web-access/searxng.js +223 -0
- package/extensions/dm-web-access/serpbase.js +205 -0
- package/extensions/dm-web-access/serpdive.js +238 -0
- package/extensions/dm-web-access/serper.js +200 -0
- package/extensions/dm-web-access/source-check.js +198 -0
- package/extensions/dm-web-access/ssrf-protection.js +436 -0
- package/extensions/dm-web-access/storage.js +451 -0
- package/extensions/dm-web-access/summary-model-scope.js +83 -0
- package/extensions/dm-web-access/summary-review.js +364 -0
- package/extensions/dm-web-access/tavily.js +199 -0
- package/extensions/dm-web-access/tinyfish.js +325 -0
- package/extensions/dm-web-access/utils.js +476 -0
- package/extensions/dm-web-access/valyu.js +189 -0
- package/extensions/dm-web-access/video-extract.js +336 -0
- package/extensions/dm-web-access/xai-search.js +285 -0
- package/extensions/dm-web-access/xcrawl.js +221 -0
- package/extensions/dm-web-access/youtube-extract.js +279 -0
- package/package.json +9 -1
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { closeSync, constants, fchmodSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { getWebSearchConfigDir } from "./utils.js";
|
|
5
|
+
const CACHE_TTL_MS = 60 * 60 * 1000;
|
|
6
|
+
const FETCH_CACHE_DIR = "web-search-cache";
|
|
7
|
+
const FETCH_CACHE_VERSION = 1;
|
|
8
|
+
const CACHE_KEY_PATTERN = /^[A-Za-z0-9_-]+\.json$/;
|
|
9
|
+
const CACHE_TMP_PATTERN = /^[A-Za-z0-9_-]+\.json\.\d+\.\d+(?:\.[a-f0-9]{32})?\.tmp$/;
|
|
10
|
+
const CACHE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
11
|
+
const MAX_METADATA_TEXT = 8192;
|
|
12
|
+
const DEFAULT_CACHE_LIMITS = { maxEntries: 128, maxBytes: 128 * 1024 * 1024 };
|
|
13
|
+
const O_DIRECTORY = process.platform === "win32" ? 0 : constants.O_DIRECTORY ?? 0;
|
|
14
|
+
const O_NOFOLLOW = process.platform === "win32" ? 0 : constants.O_NOFOLLOW ?? 0;
|
|
15
|
+
const storedResults = new Map;
|
|
16
|
+
export function generateId() {
|
|
17
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
18
|
+
}
|
|
19
|
+
export function getFetchCacheDir() {
|
|
20
|
+
return join(getWebSearchConfigDir(), FETCH_CACHE_DIR);
|
|
21
|
+
}
|
|
22
|
+
function fetchCachePath(key) {
|
|
23
|
+
if (!CACHE_KEY_PATTERN.test(key))
|
|
24
|
+
return null;
|
|
25
|
+
return join(getFetchCacheDir(), key);
|
|
26
|
+
}
|
|
27
|
+
function cacheKeyForId(id) {
|
|
28
|
+
if (!CACHE_ID_PATTERN.test(id)) {
|
|
29
|
+
throw new Error(`Invalid fetched content cache id: ${id}`);
|
|
30
|
+
}
|
|
31
|
+
return `${id}.json`;
|
|
32
|
+
}
|
|
33
|
+
function truncateMetadataText(value) {
|
|
34
|
+
if (!value)
|
|
35
|
+
return "";
|
|
36
|
+
return value.length > MAX_METADATA_TEXT ? `${value.slice(0, MAX_METADATA_TEXT)}...` : value;
|
|
37
|
+
}
|
|
38
|
+
function metadataForUrls(urls) {
|
|
39
|
+
return urls.map((url) => ({
|
|
40
|
+
url: truncateMetadataText(url.url),
|
|
41
|
+
title: truncateMetadataText(url.title),
|
|
42
|
+
error: url.error ? truncateMetadataText(url.error) : null,
|
|
43
|
+
contentLength: url.content.length,
|
|
44
|
+
...url.mimeType ? { mimeType: truncateMetadataText(url.mimeType) } : {},
|
|
45
|
+
...typeof url.status === "number" ? { status: url.status } : {},
|
|
46
|
+
...typeof url.duration === "number" ? { duration: url.duration } : {}
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
function isFetchCacheRef(value) {
|
|
50
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
51
|
+
return false;
|
|
52
|
+
const ref = value;
|
|
53
|
+
return ref.version === FETCH_CACHE_VERSION && typeof ref.key === "string" && CACHE_KEY_PATTERN.test(ref.key) && typeof ref.storedAt === "number" && Number.isFinite(ref.storedAt);
|
|
54
|
+
}
|
|
55
|
+
function isStoredFetchUrlMetadata(value) {
|
|
56
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
57
|
+
return false;
|
|
58
|
+
const meta = value;
|
|
59
|
+
return typeof meta.url === "string" && typeof meta.title === "string" && (meta.error === null || typeof meta.error === "string") && typeof meta.contentLength === "number" && Number.isFinite(meta.contentLength) && meta.contentLength >= 0 && (meta.mimeType === undefined || typeof meta.mimeType === "string") && (meta.status === undefined || typeof meta.status === "number") && (meta.duration === undefined || typeof meta.duration === "number");
|
|
60
|
+
}
|
|
61
|
+
function isInlineFetchedUrl(value) {
|
|
62
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
63
|
+
return false;
|
|
64
|
+
const url = value;
|
|
65
|
+
return typeof url.url === "string" && typeof url.title === "string" && typeof url.content === "string" && (url.error === null || typeof url.error === "string");
|
|
66
|
+
}
|
|
67
|
+
function isInlineFetchData(data) {
|
|
68
|
+
return data.type === "fetch" && Array.isArray(data.urls) && data.urls.every(isInlineFetchedUrl);
|
|
69
|
+
}
|
|
70
|
+
function cacheLimits(limits) {
|
|
71
|
+
const resolved = {
|
|
72
|
+
maxEntries: limits?.maxEntries ?? DEFAULT_CACHE_LIMITS.maxEntries,
|
|
73
|
+
maxBytes: limits?.maxBytes ?? DEFAULT_CACHE_LIMITS.maxBytes
|
|
74
|
+
};
|
|
75
|
+
if (!Number.isFinite(resolved.maxEntries) || !Number.isInteger(resolved.maxEntries) || resolved.maxEntries <= 0 || !Number.isFinite(resolved.maxBytes) || !Number.isInteger(resolved.maxBytes) || resolved.maxBytes <= 0) {
|
|
76
|
+
throw new Error("Fetched content cache limits must be finite positive integers");
|
|
77
|
+
}
|
|
78
|
+
return resolved;
|
|
79
|
+
}
|
|
80
|
+
function enforceDirectoryMode(fd) {
|
|
81
|
+
try {
|
|
82
|
+
fchmodSync(fd, 448);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if (process.platform !== "win32")
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function enforceFileMode(fd) {
|
|
89
|
+
try {
|
|
90
|
+
fchmodSync(fd, 384);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (process.platform !== "win32")
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function safeFetchCacheDir(create) {
|
|
97
|
+
const dir = getFetchCacheDir();
|
|
98
|
+
if (create)
|
|
99
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
100
|
+
let before;
|
|
101
|
+
try {
|
|
102
|
+
before = lstatSync(dir);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
if (!create && err.code === "ENOENT")
|
|
105
|
+
return null;
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
if (before.isSymbolicLink() || !before.isDirectory()) {
|
|
109
|
+
throw new Error("Fetched content cache path is not a safe directory");
|
|
110
|
+
}
|
|
111
|
+
if (process.platform === "win32") {
|
|
112
|
+
const after = lstatSync(dir);
|
|
113
|
+
if (after.isSymbolicLink() || !after.isDirectory() || after.dev !== before.dev || after.ino !== before.ino) {
|
|
114
|
+
throw new Error("Fetched content cache directory changed while securing it");
|
|
115
|
+
}
|
|
116
|
+
return dir;
|
|
117
|
+
}
|
|
118
|
+
let fd = null;
|
|
119
|
+
try {
|
|
120
|
+
fd = openSync(dir, constants.O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
|
|
121
|
+
const opened = fstatSync(fd);
|
|
122
|
+
if (!opened.isDirectory() || opened.dev !== before.dev || opened.ino !== before.ino) {
|
|
123
|
+
throw new Error("Fetched content cache directory changed while opening");
|
|
124
|
+
}
|
|
125
|
+
enforceDirectoryMode(fd);
|
|
126
|
+
closeSync(fd);
|
|
127
|
+
fd = null;
|
|
128
|
+
const after = lstatSync(dir);
|
|
129
|
+
if (after.isSymbolicLink() || !after.isDirectory() || after.dev !== before.dev || after.ino !== before.ino) {
|
|
130
|
+
throw new Error("Fetched content cache directory changed while securing it");
|
|
131
|
+
}
|
|
132
|
+
return dir;
|
|
133
|
+
} finally {
|
|
134
|
+
if (fd !== null)
|
|
135
|
+
try {
|
|
136
|
+
closeSync(fd);
|
|
137
|
+
} catch {}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function openRegularFile(path) {
|
|
141
|
+
const before = lstatSync(path);
|
|
142
|
+
if (before.isSymbolicLink() || !before.isFile())
|
|
143
|
+
throw new Error("Fetched content cache entry is not a regular file");
|
|
144
|
+
const fd = openSync(path, constants.O_RDONLY | O_NOFOLLOW);
|
|
145
|
+
try {
|
|
146
|
+
const info = fstatSync(fd);
|
|
147
|
+
if (!info.isFile() || info.dev !== before.dev || info.ino !== before.ino) {
|
|
148
|
+
throw new Error("Fetched content cache entry changed while opening");
|
|
149
|
+
}
|
|
150
|
+
return { fd, info };
|
|
151
|
+
} catch (err) {
|
|
152
|
+
closeSync(fd);
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function unlinkCacheFile(dir, file) {
|
|
157
|
+
try {
|
|
158
|
+
const root = lstatSync(dir);
|
|
159
|
+
if (root.isSymbolicLink() || !root.isDirectory())
|
|
160
|
+
return "changed";
|
|
161
|
+
const path = join(dir, file.name);
|
|
162
|
+
let current;
|
|
163
|
+
try {
|
|
164
|
+
current = lstatSync(path);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
return err.code === "ENOENT" ? "missing" : "error";
|
|
167
|
+
}
|
|
168
|
+
if (current.isSymbolicLink() || !current.isFile() || current.dev !== file.dev || current.ino !== file.ino)
|
|
169
|
+
return "changed";
|
|
170
|
+
try {
|
|
171
|
+
unlinkSync(path);
|
|
172
|
+
return "removed";
|
|
173
|
+
} catch (err) {
|
|
174
|
+
return err.code === "ENOENT" ? "missing" : "error";
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
return "error";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function pruneFetchCache(now, limits, preferredKey, reservation) {
|
|
181
|
+
let dir;
|
|
182
|
+
try {
|
|
183
|
+
dir = safeFetchCacheDir(false);
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
if (!dir)
|
|
188
|
+
return true;
|
|
189
|
+
let entries;
|
|
190
|
+
try {
|
|
191
|
+
entries = readdirSync(dir);
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const files = [];
|
|
196
|
+
for (const entry of entries) {
|
|
197
|
+
if (!CACHE_KEY_PATTERN.test(entry) && !CACHE_TMP_PATTERN.test(entry))
|
|
198
|
+
continue;
|
|
199
|
+
const path = join(dir, entry);
|
|
200
|
+
let opened;
|
|
201
|
+
try {
|
|
202
|
+
opened = openRegularFile(path);
|
|
203
|
+
} catch {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
enforceFileMode(opened.fd);
|
|
208
|
+
} catch {
|
|
209
|
+
closeSync(opened.fd);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
closeSync(opened.fd);
|
|
213
|
+
const file = { name: entry, size: opened.info.size, mtimeMs: opened.info.mtimeMs, dev: opened.info.dev, ino: opened.info.ino };
|
|
214
|
+
if (now - file.mtimeMs >= CACHE_TTL_MS) {
|
|
215
|
+
const removed = unlinkCacheFile(dir, file);
|
|
216
|
+
if (removed !== "removed" && removed !== "missing")
|
|
217
|
+
return false;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (CACHE_KEY_PATTERN.test(entry))
|
|
221
|
+
files.push(file);
|
|
222
|
+
}
|
|
223
|
+
files.sort((a, b) => a.mtimeMs - b.mtimeMs || a.name.localeCompare(b.name));
|
|
224
|
+
const projectedUsage = () => {
|
|
225
|
+
const replaced = reservation ? files.find((file) => file.name === reservation.key) : undefined;
|
|
226
|
+
return {
|
|
227
|
+
entries: files.length + (reservation && !replaced ? 1 : 0),
|
|
228
|
+
bytes: files.reduce((total, file) => total + file.size, 0) + (reservation ? reservation.bytes - (replaced?.size ?? 0) : 0)
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
const attempted = new Set;
|
|
232
|
+
let usage = projectedUsage();
|
|
233
|
+
while (usage.entries > limits.maxEntries || usage.bytes > limits.maxBytes) {
|
|
234
|
+
const index = files.findIndex((file) => file.name !== preferredKey && !attempted.has(file.name));
|
|
235
|
+
if (index < 0)
|
|
236
|
+
break;
|
|
237
|
+
const file = files[index];
|
|
238
|
+
attempted.add(file.name);
|
|
239
|
+
const removed = unlinkCacheFile(dir, file);
|
|
240
|
+
if (removed === "removed" || removed === "missing")
|
|
241
|
+
files.splice(index, 1);
|
|
242
|
+
usage = projectedUsage();
|
|
243
|
+
}
|
|
244
|
+
return usage.entries <= limits.maxEntries && usage.bytes <= limits.maxBytes;
|
|
245
|
+
}
|
|
246
|
+
function writeFetchCache(data) {
|
|
247
|
+
const limits = DEFAULT_CACHE_LIMITS;
|
|
248
|
+
const serialized = JSON.stringify(data);
|
|
249
|
+
const size = Buffer.byteLength(serialized);
|
|
250
|
+
if (size > limits.maxBytes)
|
|
251
|
+
throw new Error(`Fetched content cache entry exceeds ${limits.maxBytes} bytes`);
|
|
252
|
+
const dir = safeFetchCacheDir(true);
|
|
253
|
+
const key = cacheKeyForId(data.id);
|
|
254
|
+
if (!pruneFetchCache(Date.now(), limits, key, { key, bytes: size })) {
|
|
255
|
+
throw new Error("Fetched content cache could not reserve space for a new entry");
|
|
256
|
+
}
|
|
257
|
+
const finalPath = join(dir, key);
|
|
258
|
+
const tmpName = `${key}.${process.pid}.${Date.now()}.${randomBytes(16).toString("hex")}.tmp`;
|
|
259
|
+
const tmpPath = join(dir, tmpName);
|
|
260
|
+
let fd = null;
|
|
261
|
+
let tmpFile = null;
|
|
262
|
+
let renamed = false;
|
|
263
|
+
try {
|
|
264
|
+
fd = openSync(tmpPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | O_NOFOLLOW, 384);
|
|
265
|
+
const tmpInfo = fstatSync(fd);
|
|
266
|
+
tmpFile = { name: tmpName, size: tmpInfo.size, mtimeMs: tmpInfo.mtimeMs, dev: tmpInfo.dev, ino: tmpInfo.ino };
|
|
267
|
+
enforceFileMode(fd);
|
|
268
|
+
writeFileSync(fd, serialized, "utf8");
|
|
269
|
+
fsyncSync(fd);
|
|
270
|
+
closeSync(fd);
|
|
271
|
+
fd = null;
|
|
272
|
+
safeFetchCacheDir(false);
|
|
273
|
+
renameSync(tmpPath, finalPath);
|
|
274
|
+
renamed = true;
|
|
275
|
+
const written = lstatSync(finalPath);
|
|
276
|
+
if (!written.isFile() || written.dev !== tmpFile.dev || written.ino !== tmpFile.ino) {
|
|
277
|
+
throw new Error("Fetched content cache entry changed after writing");
|
|
278
|
+
}
|
|
279
|
+
if (!pruneFetchCache(Date.now(), limits, key)) {
|
|
280
|
+
throw new Error("Fetched content cache could not meet its limits after writing");
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (fd !== null)
|
|
284
|
+
try {
|
|
285
|
+
closeSync(fd);
|
|
286
|
+
} catch {}
|
|
287
|
+
if (tmpFile)
|
|
288
|
+
unlinkCacheFile(dir, { ...tmpFile, name: renamed ? key : tmpName });
|
|
289
|
+
throw err;
|
|
290
|
+
}
|
|
291
|
+
return { version: FETCH_CACHE_VERSION, key, storedAt: Date.now() };
|
|
292
|
+
}
|
|
293
|
+
function cacheWriteError(err) {
|
|
294
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
295
|
+
return `Failed to write fetched content cache: ${message}`;
|
|
296
|
+
}
|
|
297
|
+
function createFetchSessionData(data, ref, cacheError) {
|
|
298
|
+
return {
|
|
299
|
+
id: data.id,
|
|
300
|
+
type: "fetch",
|
|
301
|
+
timestamp: data.timestamp,
|
|
302
|
+
urlMetadata: metadataForUrls(data.urls),
|
|
303
|
+
...ref ? { fetchCache: ref } : {},
|
|
304
|
+
...cacheError ? { fetchCacheError: truncateMetadataText(cacheError) } : {}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function fetchUrlMetadata(data) {
|
|
308
|
+
if (data.urlMetadata)
|
|
309
|
+
return data.urlMetadata;
|
|
310
|
+
return isInlineFetchData(data) ? metadataForUrls(data.urls) : [];
|
|
311
|
+
}
|
|
312
|
+
function unavailableFetchData(data, reason) {
|
|
313
|
+
return {
|
|
314
|
+
...data,
|
|
315
|
+
urls: fetchUrlMetadata(data).map((meta) => ({
|
|
316
|
+
url: meta.url,
|
|
317
|
+
title: meta.title,
|
|
318
|
+
content: "",
|
|
319
|
+
error: reason,
|
|
320
|
+
...meta.mimeType ? { mimeType: meta.mimeType } : {},
|
|
321
|
+
...typeof meta.status === "number" ? { status: meta.status } : {},
|
|
322
|
+
...typeof meta.duration === "number" ? { duration: meta.duration } : {}
|
|
323
|
+
}))
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function readCachedFetchData(data, now = Date.now()) {
|
|
327
|
+
if (data.type !== "fetch")
|
|
328
|
+
return data;
|
|
329
|
+
if (now - data.timestamp >= CACHE_TTL_MS) {
|
|
330
|
+
return unavailableFetchData(data, "Cached fetched content is missing or expired");
|
|
331
|
+
}
|
|
332
|
+
if (isInlineFetchData(data))
|
|
333
|
+
return data;
|
|
334
|
+
if (!data.fetchCache) {
|
|
335
|
+
return unavailableFetchData(data, data.fetchCacheError ?? "Cached fetched content is unavailable");
|
|
336
|
+
}
|
|
337
|
+
const path = fetchCachePath(data.fetchCache.key);
|
|
338
|
+
if (!path)
|
|
339
|
+
return unavailableFetchData(data, "Cached fetched content is missing or expired");
|
|
340
|
+
let fd = null;
|
|
341
|
+
try {
|
|
342
|
+
if (!safeFetchCacheDir(false))
|
|
343
|
+
return unavailableFetchData(data, "Cached fetched content is missing or expired");
|
|
344
|
+
const opened = openRegularFile(path);
|
|
345
|
+
fd = opened.fd;
|
|
346
|
+
enforceFileMode(fd);
|
|
347
|
+
const parsed = JSON.parse(readFileSync(fd, "utf8"));
|
|
348
|
+
if (!isValidStoredData(parsed) || parsed.type !== "fetch" || parsed.id !== data.id || !isInlineFetchData(parsed)) {
|
|
349
|
+
return unavailableFetchData(data, "Cached fetched content is invalid");
|
|
350
|
+
}
|
|
351
|
+
return { ...parsed, fetchCache: data.fetchCache, urlMetadata: data.urlMetadata };
|
|
352
|
+
} catch (err) {
|
|
353
|
+
if (err.code === "ENOENT") {
|
|
354
|
+
return unavailableFetchData(data, "Cached fetched content is missing or expired");
|
|
355
|
+
}
|
|
356
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
357
|
+
return unavailableFetchData(data, `Cached fetched content could not be read: ${message}`);
|
|
358
|
+
} finally {
|
|
359
|
+
if (fd !== null)
|
|
360
|
+
try {
|
|
361
|
+
closeSync(fd);
|
|
362
|
+
} catch {}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
export function pruneExpiredFetchCache(now = Date.now(), requestedLimits) {
|
|
366
|
+
const limits = cacheLimits(requestedLimits);
|
|
367
|
+
try {
|
|
368
|
+
pruneFetchCache(now, limits);
|
|
369
|
+
} catch {}
|
|
370
|
+
}
|
|
371
|
+
export function storeResult(id, data) {
|
|
372
|
+
storedResults.set(id, data);
|
|
373
|
+
}
|
|
374
|
+
export function storeFetchedContentResult(id, data) {
|
|
375
|
+
let ref = null;
|
|
376
|
+
let cacheError;
|
|
377
|
+
try {
|
|
378
|
+
ref = writeFetchCache(data);
|
|
379
|
+
} catch (err) {
|
|
380
|
+
cacheError = cacheWriteError(err);
|
|
381
|
+
}
|
|
382
|
+
storedResults.set(id, ref ? { ...data, fetchCache: ref, urlMetadata: metadataForUrls(data.urls) } : { ...data, fetchCacheError: cacheError });
|
|
383
|
+
return createFetchSessionData(data, ref, cacheError);
|
|
384
|
+
}
|
|
385
|
+
export function getResult(id) {
|
|
386
|
+
const data = storedResults.get(id);
|
|
387
|
+
if (!data)
|
|
388
|
+
return null;
|
|
389
|
+
const loaded = readCachedFetchData(data);
|
|
390
|
+
if (loaded !== data)
|
|
391
|
+
storedResults.set(id, loaded);
|
|
392
|
+
return loaded;
|
|
393
|
+
}
|
|
394
|
+
export function getAllResults() {
|
|
395
|
+
return Array.from(storedResults.values());
|
|
396
|
+
}
|
|
397
|
+
export function deleteResult(id) {
|
|
398
|
+
const data = storedResults.get(id);
|
|
399
|
+
if (data?.fetchCache) {
|
|
400
|
+
try {
|
|
401
|
+
const dir = safeFetchCacheDir(false);
|
|
402
|
+
const path = fetchCachePath(data.fetchCache.key);
|
|
403
|
+
if (dir && path) {
|
|
404
|
+
const info = lstatSync(path);
|
|
405
|
+
if (!info.isSymbolicLink() && info.isFile()) {
|
|
406
|
+
unlinkCacheFile(dir, { name: data.fetchCache.key, size: info.size, mtimeMs: info.mtimeMs, dev: info.dev, ino: info.ino });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
} catch {}
|
|
410
|
+
}
|
|
411
|
+
return storedResults.delete(id);
|
|
412
|
+
}
|
|
413
|
+
export function clearResults() {
|
|
414
|
+
storedResults.clear();
|
|
415
|
+
}
|
|
416
|
+
function isValidStoredData(data) {
|
|
417
|
+
if (!data || typeof data !== "object")
|
|
418
|
+
return false;
|
|
419
|
+
const d = data;
|
|
420
|
+
if (typeof d.id !== "string" || !d.id)
|
|
421
|
+
return false;
|
|
422
|
+
if (d.type !== "search" && d.type !== "fetch" && d.type !== "research")
|
|
423
|
+
return false;
|
|
424
|
+
if (typeof d.timestamp !== "number")
|
|
425
|
+
return false;
|
|
426
|
+
if (d.type === "search" && !Array.isArray(d.queries))
|
|
427
|
+
return false;
|
|
428
|
+
if (d.type === "fetch") {
|
|
429
|
+
if (Array.isArray(d.urls))
|
|
430
|
+
return d.urls.every(isInlineFetchedUrl);
|
|
431
|
+
if (!Array.isArray(d.urlMetadata) || !d.urlMetadata.every(isStoredFetchUrlMetadata))
|
|
432
|
+
return false;
|
|
433
|
+
return d.fetchCache === undefined ? d.fetchCacheError === undefined || typeof d.fetchCacheError === "string" : isFetchCacheRef(d.fetchCache);
|
|
434
|
+
}
|
|
435
|
+
if (d.type === "research" && (!d.artifact || typeof d.artifact !== "object"))
|
|
436
|
+
return false;
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
export function restoreFromSession(ctx) {
|
|
440
|
+
storedResults.clear();
|
|
441
|
+
const now = Date.now();
|
|
442
|
+
pruneExpiredFetchCache(now);
|
|
443
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
444
|
+
if (entry.type === "custom" && entry.customType === "web-search-results") {
|
|
445
|
+
const data = entry.data;
|
|
446
|
+
if (isValidStoredData(data) && now - data.timestamp < CACHE_TTL_MS) {
|
|
447
|
+
storedResults.set(data.id, data);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
5
|
+
export function findModelWithProviderRouting(registry, provider, id) {
|
|
6
|
+
const available = registry.getAvailable();
|
|
7
|
+
const direct = available.find((model) => model.provider === provider && model.id === id);
|
|
8
|
+
if (direct)
|
|
9
|
+
return direct;
|
|
10
|
+
const routedId = `${provider}/${id}`;
|
|
11
|
+
const routed = available.find((model) => model.id === routedId);
|
|
12
|
+
return routed ?? registry.find(provider, id);
|
|
13
|
+
}
|
|
14
|
+
function getAgentDir() {
|
|
15
|
+
return process.env.DM_CODING_AGENT_DIR || join(homedir(), ".dm", "agent");
|
|
16
|
+
}
|
|
17
|
+
function readSettings(path) {
|
|
18
|
+
if (!existsSync(path))
|
|
19
|
+
return {};
|
|
20
|
+
const raw = readFileSync(path, "utf8");
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(raw);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25
|
+
throw new Error(`Failed to parse ${path}: ${message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function loadEnabledModelPatterns(ctx) {
|
|
29
|
+
const globalSettings = readSettings(join(getAgentDir(), "settings.json"));
|
|
30
|
+
const projectSettings = ctx.isProjectTrusted() ? readSettings(join(ctx.cwd, ".dm", "settings.json")) : {};
|
|
31
|
+
const value = Object.hasOwn(projectSettings, "enabledModels") ? projectSettings.enabledModels : globalSettings.enabledModels;
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
return null;
|
|
34
|
+
if (!Array.isArray(value))
|
|
35
|
+
throw new Error("enabledModels must be an array");
|
|
36
|
+
return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
export function summaryModelValue(model) {
|
|
39
|
+
return `${model.provider}/${model.id}`;
|
|
40
|
+
}
|
|
41
|
+
export function splitThinkingSuffix(value) {
|
|
42
|
+
const index = value.lastIndexOf(":");
|
|
43
|
+
if (index < 0)
|
|
44
|
+
return { value };
|
|
45
|
+
const suffix = value.slice(index + 1);
|
|
46
|
+
return THINKING_LEVELS.has(suffix) ? { value: value.slice(0, index), thinkingLevel: suffix } : { value };
|
|
47
|
+
}
|
|
48
|
+
function stripThinkingSuffix(pattern) {
|
|
49
|
+
return splitThinkingSuffix(pattern).value;
|
|
50
|
+
}
|
|
51
|
+
function globToRegExp(pattern) {
|
|
52
|
+
let source = "^";
|
|
53
|
+
for (const char of pattern) {
|
|
54
|
+
if (char === "*") {
|
|
55
|
+
source += ".*";
|
|
56
|
+
} else if (char === "?") {
|
|
57
|
+
source += ".";
|
|
58
|
+
} else {
|
|
59
|
+
source += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return new RegExp(`${source}$`, "i");
|
|
63
|
+
}
|
|
64
|
+
export function modelMatchesEnabledPatterns(model, patterns) {
|
|
65
|
+
if (patterns === null)
|
|
66
|
+
return true;
|
|
67
|
+
const value = summaryModelValue(model).toLowerCase();
|
|
68
|
+
const id = model.id.toLowerCase();
|
|
69
|
+
for (const rawPattern of patterns) {
|
|
70
|
+
const pattern = stripThinkingSuffix(rawPattern.trim()).toLowerCase();
|
|
71
|
+
if (!pattern)
|
|
72
|
+
continue;
|
|
73
|
+
if (pattern.includes("*") || pattern.includes("?")) {
|
|
74
|
+
const regex = globToRegExp(pattern);
|
|
75
|
+
if (regex.test(value) || regex.test(id))
|
|
76
|
+
return true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (pattern === value || pattern === id)
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|