codemini-cli 0.3.8 → 0.4.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 +121 -1
- package/deployment.md +6 -6
- package/package.json +6 -1
- package/skills/brainstorm/SKILL.md +49 -29
- package/skills/superpowers-lite/SKILL.md +82 -90
- package/skills/writing-plans/SKILL.md +67 -0
- package/src/commands/chat.js +51 -47
- package/src/commands/doctor.js +27 -7
- package/src/commands/run.js +36 -28
- package/src/core/agent-loop.js +191 -10
- package/src/core/chat-runtime.js +170 -11
- package/src/core/command-evaluator.js +66 -0
- package/src/core/command-policy.js +16 -0
- package/src/core/command-risk.js +148 -0
- package/src/core/config-store.js +7 -0
- package/src/core/constants.js +0 -1
- package/src/core/default-system-prompt.js +27 -0
- package/src/core/dream-audit.js +93 -0
- package/src/core/dream-consolidate.js +157 -0
- package/src/core/dream-evaluator.js +99 -0
- package/src/core/fff-adapter.js +386 -0
- package/src/core/memory-prompt.js +23 -0
- package/src/core/memory-store.js +228 -1
- package/src/core/paths.js +13 -1
- package/src/core/project-index.js +2 -2
- package/src/core/shell-profile.js +5 -1
- package/src/core/tool-output.js +184 -0
- package/src/core/tools.js +425 -110
- package/src/tui/chat-app.js +376 -47
- package/src/tui/tool-activity/presenters/system.js +1 -1
package/src/core/tools.js
CHANGED
|
@@ -18,9 +18,17 @@ import { initializeProjectIndex, queryProjectIndex, refreshIndexedFile } from '.
|
|
|
18
18
|
import { checkReadDedup } from './agent-loop.js';
|
|
19
19
|
import { TOOL_SKIP_DIRS as SKIP_DIRS, TEXT_EXTENSIONS, CODE_WRITE_GUARD_EXTENSIONS, LANGUAGE_FILE_TYPES } from './constants.js';
|
|
20
20
|
import { sha256Prefixed as sha256, sha256 as sha256Hash } from './crypto-utils.js';
|
|
21
|
-
import { forgetMemory, listMemories, rememberMemory, searchMemories } from './memory-store.js';
|
|
21
|
+
import { forgetMemory, listMemories, rememberMemory, searchMemories, captureToInbox } from './memory-store.js';
|
|
22
|
+
import { runDreamConsolidation } from './dream-consolidate.js';
|
|
22
23
|
import { normalizePlanState } from './plan-state.js';
|
|
23
24
|
import { normalizeTodos } from './todo-state.js';
|
|
25
|
+
import { createFffAdapter } from './fff-adapter.js';
|
|
26
|
+
import {
|
|
27
|
+
getToolOutputSanitizeOptions,
|
|
28
|
+
sanitizePreviewLines,
|
|
29
|
+
sanitizeTextForModel,
|
|
30
|
+
summarizeRunOutput
|
|
31
|
+
} from './tool-output.js';
|
|
24
32
|
const BACKGROUND_TASK_RECENT_OUTPUT_LIMIT = 80;
|
|
25
33
|
const BACKGROUND_TASK_POLL_MS = 150;
|
|
26
34
|
const MAX_AST_ENCLOSING_BYTES = 300_000;
|
|
@@ -197,6 +205,199 @@ function normalizeWriteArgs(rawArgs) {
|
|
|
197
205
|
return normalized;
|
|
198
206
|
}
|
|
199
207
|
|
|
208
|
+
function normalizeWebFetchArgs(rawArgs) {
|
|
209
|
+
const source =
|
|
210
|
+
rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)
|
|
211
|
+
? { ...rawArgs }
|
|
212
|
+
: { url: typeof rawArgs === 'string' ? rawArgs : '' };
|
|
213
|
+
const normalized = { ...source };
|
|
214
|
+
const url = String(source.url || source.href || source.link || source.target || '').trim();
|
|
215
|
+
if (url) normalized.url = url;
|
|
216
|
+
return normalized;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function normalizeWebSearchArgs(rawArgs) {
|
|
220
|
+
const source =
|
|
221
|
+
rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)
|
|
222
|
+
? { ...rawArgs }
|
|
223
|
+
: { query: typeof rawArgs === 'string' ? rawArgs : '' };
|
|
224
|
+
const normalized = { ...source };
|
|
225
|
+
const query = String(source.query || source.q || source.keyword || '').trim();
|
|
226
|
+
if (query) normalized.query = query;
|
|
227
|
+
return normalized;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function clampNumber(value, min, max, fallback) {
|
|
231
|
+
const num = Number(value);
|
|
232
|
+
if (!Number.isFinite(num)) return fallback;
|
|
233
|
+
return Math.min(max, Math.max(min, num));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function normalizeWhitespace(value) {
|
|
237
|
+
return String(value || '')
|
|
238
|
+
.replace(/\s+/g, ' ')
|
|
239
|
+
.trim();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function trimPreview(value, maxLen = 300) {
|
|
243
|
+
const text = normalizeWhitespace(value);
|
|
244
|
+
if (text.length <= maxLen) return text;
|
|
245
|
+
return `${text.slice(0, Math.max(0, maxLen - 3))}...`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function normalizeWebUrl(value) {
|
|
249
|
+
const text = String(value || '').trim();
|
|
250
|
+
if (!text) return '';
|
|
251
|
+
let parsed;
|
|
252
|
+
try {
|
|
253
|
+
parsed = new URL(text);
|
|
254
|
+
} catch {
|
|
255
|
+
throw new Error(`Invalid URL: ${text}`);
|
|
256
|
+
}
|
|
257
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
258
|
+
throw new Error(`Unsupported URL protocol: ${parsed.protocol}`);
|
|
259
|
+
}
|
|
260
|
+
return parsed.toString();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function extractHtmlMeta($, name, attribute = 'content') {
|
|
264
|
+
return String(
|
|
265
|
+
$(`meta[name="${name}"]`).attr(attribute) ||
|
|
266
|
+
$(`meta[property="${name}"]`).attr(attribute) ||
|
|
267
|
+
''
|
|
268
|
+
).trim();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function collectPageLinks($, pageUrl, maxLinks = 20) {
|
|
272
|
+
const links = [];
|
|
273
|
+
const seen = new Set();
|
|
274
|
+
$('a[href]').each((_, element) => {
|
|
275
|
+
if (links.length >= maxLinks) return false;
|
|
276
|
+
const hrefRaw = String($(element).attr('href') || '').trim();
|
|
277
|
+
if (!hrefRaw) return undefined;
|
|
278
|
+
try {
|
|
279
|
+
const href = new URL(hrefRaw, pageUrl).toString();
|
|
280
|
+
if (seen.has(href)) return undefined;
|
|
281
|
+
seen.add(href);
|
|
282
|
+
links.push({
|
|
283
|
+
href,
|
|
284
|
+
text: trimPreview($(element).text(), 160)
|
|
285
|
+
});
|
|
286
|
+
} catch {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
return undefined;
|
|
290
|
+
});
|
|
291
|
+
return links;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function buildPlaywrightLaunchEnv() {
|
|
295
|
+
const localLibDir = path.join(
|
|
296
|
+
process.env.HOME || '',
|
|
297
|
+
'.cache',
|
|
298
|
+
'codemini',
|
|
299
|
+
'playwright-libs',
|
|
300
|
+
'usr',
|
|
301
|
+
'lib',
|
|
302
|
+
'x86_64-linux-gnu'
|
|
303
|
+
);
|
|
304
|
+
try {
|
|
305
|
+
await fs.access(localLibDir);
|
|
306
|
+
} catch {
|
|
307
|
+
return process.env;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const existing = String(process.env.LD_LIBRARY_PATH || '').trim();
|
|
311
|
+
return {
|
|
312
|
+
...process.env,
|
|
313
|
+
LD_LIBRARY_PATH: existing ? `${localLibDir}:${existing}` : localLibDir
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function webFetchPage(args = {}) {
|
|
318
|
+
const normalizedArgs = normalizeWebFetchArgs(args);
|
|
319
|
+
const url = normalizeWebUrl(normalizedArgs.url);
|
|
320
|
+
const timeoutMs = clampNumber(normalizedArgs.timeout_ms, 1_000, 120_000, 20_000);
|
|
321
|
+
const maxLinks = clampNumber(normalizedArgs.max_links, 0, 100, 20);
|
|
322
|
+
const waitUntil = ['domcontentloaded', 'load', 'networkidle'].includes(String(normalizedArgs.wait_until || '').trim())
|
|
323
|
+
? String(normalizedArgs.wait_until).trim()
|
|
324
|
+
: 'domcontentloaded';
|
|
325
|
+
|
|
326
|
+
const [{ chromium }, cheerio] = await Promise.all([import('playwright'), import('cheerio')]);
|
|
327
|
+
|
|
328
|
+
// Crawlee is intentionally disabled for now so single-page reads stay lightweight.
|
|
329
|
+
// If we later need multi-URL crawl orchestration, retries, or request queues, we can re-enable it here.
|
|
330
|
+
|
|
331
|
+
const browser = await chromium.launch({
|
|
332
|
+
headless: true,
|
|
333
|
+
env: await buildPlaywrightLaunchEnv()
|
|
334
|
+
});
|
|
335
|
+
try {
|
|
336
|
+
const page = await browser.newPage();
|
|
337
|
+
const response = await page.goto(url, { waitUntil, timeout: timeoutMs });
|
|
338
|
+
const finalUrl = page.url();
|
|
339
|
+
const html = await page.content();
|
|
340
|
+
const $ = cheerio.load(html);
|
|
341
|
+
const bodyText = $('body').text() || $.root().text();
|
|
342
|
+
const text = String(bodyText || '').replace(/[^\S\n]+/g, ' ').replace(/\n{3,}/g, '\n\n').trim();
|
|
343
|
+
const title = trimPreview($('title').first().text() || (await page.title()), 240);
|
|
344
|
+
const description = extractHtmlMeta($, 'description') || extractHtmlMeta($, 'og:description');
|
|
345
|
+
const links = collectPageLinks($, finalUrl, maxLinks);
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
url,
|
|
349
|
+
final_url: finalUrl,
|
|
350
|
+
title,
|
|
351
|
+
description,
|
|
352
|
+
text,
|
|
353
|
+
links,
|
|
354
|
+
metadata: {
|
|
355
|
+
status: response?.status?.() ?? null,
|
|
356
|
+
fetched_at: new Date().toISOString(),
|
|
357
|
+
content_type: response?.headers?.()['content-type'] || '',
|
|
358
|
+
wait_until: waitUntil,
|
|
359
|
+
lang: String($('html').attr('lang') || '').trim()
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
} finally {
|
|
363
|
+
await browser.close();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function webSearchQuery(config, args = {}) {
|
|
368
|
+
if (config?.web?.search_enabled === false) {
|
|
369
|
+
throw new Error('web_search is disabled by config. Set web.search_enabled=true to enable network search.');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const normalizedArgs = normalizeWebSearchArgs(args);
|
|
373
|
+
const query = String(normalizedArgs.query || '').trim();
|
|
374
|
+
if (!query) throw new Error('web_search requires query');
|
|
375
|
+
|
|
376
|
+
const maxResults = clampNumber(normalizedArgs.max_results, 1, 20, 8);
|
|
377
|
+
const [{ search, SafeSearchType }] = await Promise.all([import('duck-duck-scrape')]);
|
|
378
|
+
const response = await search(query, {
|
|
379
|
+
safeSearch: SafeSearchType.MODERATE,
|
|
380
|
+
locale: String(normalizedArgs.locale || 'en-us').trim() || 'en-us',
|
|
381
|
+
region: String(normalizedArgs.region || 'wt-wt').trim() || 'wt-wt'
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
query,
|
|
386
|
+
no_results: response?.noResults === true,
|
|
387
|
+
results: Array.isArray(response?.results)
|
|
388
|
+
? response.results.slice(0, maxResults).map((item) => ({
|
|
389
|
+
title: String(item?.title || '').trim(),
|
|
390
|
+
url: String(item?.url || '').trim(),
|
|
391
|
+
description: normalizeWhitespace(item?.description || item?.rawDescription || ''),
|
|
392
|
+
hostname: String(item?.hostname || '').trim()
|
|
393
|
+
}))
|
|
394
|
+
: [],
|
|
395
|
+
related: Array.isArray(response?.related)
|
|
396
|
+
? response.related.slice(0, 8).map((item) => String(item?.text || item?.raw || '').trim()).filter(Boolean)
|
|
397
|
+
: []
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
200
401
|
function findUniqueLineBlock(lines, blockContent) {
|
|
201
402
|
const probeLines = splitLines(blockContent);
|
|
202
403
|
if (probeLines.length === 0 || (probeLines.length === 1 && probeLines[0] === '')) return null;
|
|
@@ -550,40 +751,6 @@ function findEnclosingSymbolLine(lines, anchorLine) {
|
|
|
550
751
|
return 0;
|
|
551
752
|
}
|
|
552
753
|
|
|
553
|
-
function buildUnifiedDiff(oldContent, newContent, filePath = 'file') {
|
|
554
|
-
const oldLines = splitLines(oldContent);
|
|
555
|
-
const newLines = splitLines(newContent);
|
|
556
|
-
let prefix = 0;
|
|
557
|
-
while (prefix < oldLines.length && prefix < newLines.length && oldLines[prefix] === newLines[prefix]) {
|
|
558
|
-
prefix += 1;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
let suffix = 0;
|
|
562
|
-
while (
|
|
563
|
-
suffix < oldLines.length - prefix &&
|
|
564
|
-
suffix < newLines.length - prefix &&
|
|
565
|
-
oldLines[oldLines.length - 1 - suffix] === newLines[newLines.length - 1 - suffix]
|
|
566
|
-
) {
|
|
567
|
-
suffix += 1;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const oldChanged = oldLines.slice(prefix, oldLines.length - suffix);
|
|
571
|
-
const newChanged = newLines.slice(prefix, newLines.length - suffix);
|
|
572
|
-
const oldStart = prefix + 1;
|
|
573
|
-
const newStart = prefix + 1;
|
|
574
|
-
const oldCount = Math.max(1, oldChanged.length);
|
|
575
|
-
const newCount = Math.max(1, newChanged.length);
|
|
576
|
-
|
|
577
|
-
const body = [
|
|
578
|
-
`--- ${filePath}`,
|
|
579
|
-
`+++ ${filePath}`,
|
|
580
|
-
`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`,
|
|
581
|
-
...oldChanged.map((line) => `-${line}`),
|
|
582
|
-
...newChanged.map((line) => `+${line}`)
|
|
583
|
-
];
|
|
584
|
-
return body.join('\n');
|
|
585
|
-
}
|
|
586
|
-
|
|
587
754
|
async function getFileState(root, relativePath) {
|
|
588
755
|
const target = await resolveInWorkspace(root, relativePath);
|
|
589
756
|
const stat = await fs.stat(target);
|
|
@@ -727,12 +894,15 @@ async function writeFile(root, args) {
|
|
|
727
894
|
}
|
|
728
895
|
const previewStart = Math.max(0, (changeLine || 1) - 1);
|
|
729
896
|
const previewLines = afterLines.slice(previewStart, previewStart + 6);
|
|
897
|
+
const changed = countChangedLines(before, after);
|
|
730
898
|
return {
|
|
731
899
|
ok: true,
|
|
732
900
|
path: rawPath,
|
|
733
901
|
action: normalizedArgs?.append ? 'append' : existed ? 'overwrite' : 'create',
|
|
734
902
|
changed_line: changeLine || Math.max(1, afterLines.length),
|
|
735
|
-
diff_preview: previewLines.map((line, idx) => `${previewStart + idx + 1}| ${line}`).join('\n')
|
|
903
|
+
diff_preview: previewLines.map((line, idx) => `${previewStart + idx + 1}| ${line}`).join('\n'),
|
|
904
|
+
lines_added: changed.added,
|
|
905
|
+
lines_removed: changed.removed
|
|
736
906
|
};
|
|
737
907
|
}
|
|
738
908
|
|
|
@@ -848,10 +1018,9 @@ function shellCommandForBackgroundTask(command, shellSpec) {
|
|
|
848
1018
|
}
|
|
849
1019
|
|
|
850
1020
|
function appendRecentOutput(task, chunk) {
|
|
851
|
-
const lines =
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
.filter(Boolean);
|
|
1021
|
+
const lines = sanitizePreviewLines(chunk, { maxLineLength: 220 }).map((line) =>
|
|
1022
|
+
trimLinePreview(line, 220)
|
|
1023
|
+
);
|
|
855
1024
|
if (lines.length === 0) return;
|
|
856
1025
|
for (const line of lines) {
|
|
857
1026
|
backgroundTaskLogCursorCounter += 1;
|
|
@@ -1133,7 +1302,7 @@ async function stopBackgroundTask(_root, args) {
|
|
|
1133
1302
|
return { ...snapshotBackgroundTask(task), stopped: true };
|
|
1134
1303
|
}
|
|
1135
1304
|
|
|
1136
|
-
async function
|
|
1305
|
+
async function builtinGrep(root, args) {
|
|
1137
1306
|
const normalizedArgs = normalizePatternArgs(args, ['query', 'symbol', 'q'], ['directory', 'dir', 'cwd']);
|
|
1138
1307
|
const pattern = String(normalizedArgs?.pattern || '').trim();
|
|
1139
1308
|
if (!pattern) throw new Error('grep requires pattern');
|
|
@@ -1168,7 +1337,7 @@ async function grep(root, args) {
|
|
|
1168
1337
|
return { pattern, matches, truncated: false };
|
|
1169
1338
|
}
|
|
1170
1339
|
|
|
1171
|
-
async function
|
|
1340
|
+
async function builtinGlob(root, args) {
|
|
1172
1341
|
const normalizedArgs = normalizePatternArgs(args, ['glob', 'query'], ['directory', 'dir', 'cwd']);
|
|
1173
1342
|
const pattern = String(normalizedArgs?.pattern || '').trim();
|
|
1174
1343
|
if (!pattern) throw new Error('glob requires pattern');
|
|
@@ -1188,7 +1357,7 @@ async function glob(root, args) {
|
|
|
1188
1357
|
};
|
|
1189
1358
|
}
|
|
1190
1359
|
|
|
1191
|
-
async function
|
|
1360
|
+
async function builtinList(root, args) {
|
|
1192
1361
|
const normalizedArgs = normalizePathArgs(args, ['dir', 'directory', 'target']);
|
|
1193
1362
|
const relativePath = String(normalizedArgs?.path || '.').trim() || '.';
|
|
1194
1363
|
const target = await resolveInWorkspace(root, relativePath);
|
|
@@ -1302,18 +1471,47 @@ async function validateEdit(root, args) {
|
|
|
1302
1471
|
throw new Error(`validate_edit does not support kind: ${kind}`);
|
|
1303
1472
|
}
|
|
1304
1473
|
|
|
1474
|
+
function countChangedLines(beforeContent, afterContent) {
|
|
1475
|
+
const before = splitLines(beforeContent);
|
|
1476
|
+
const after = splitLines(afterContent);
|
|
1477
|
+
const m = before.length;
|
|
1478
|
+
const n = after.length;
|
|
1479
|
+
// LCS via rolling DP — O(m*n) time, O(min(m,n)) space
|
|
1480
|
+
const short = m <= n ? before : after;
|
|
1481
|
+
const long = m <= n ? after : before;
|
|
1482
|
+
const shortLen = short.length;
|
|
1483
|
+
const longLen = long.length;
|
|
1484
|
+
let prev = new Array(longLen + 1).fill(0);
|
|
1485
|
+
let curr = new Array(longLen + 1).fill(0);
|
|
1486
|
+
for (let i = 1; i <= shortLen; i++) {
|
|
1487
|
+
for (let j = 1; j <= longLen; j++) {
|
|
1488
|
+
if (short[i - 1] === long[j - 1]) {
|
|
1489
|
+
curr[j] = prev[j - 1] + 1;
|
|
1490
|
+
} else {
|
|
1491
|
+
curr[j] = Math.max(prev[j], curr[j - 1]);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
[prev, curr] = [curr, prev];
|
|
1495
|
+
curr.fill(0);
|
|
1496
|
+
}
|
|
1497
|
+
const lcsLen = prev[longLen];
|
|
1498
|
+
return { added: n - lcsLen, removed: m - lcsLen };
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1305
1501
|
function editResult(pathText, action, beforeContent, afterContent, changedLine = 1) {
|
|
1306
1502
|
const afterLines = splitLines(afterContent);
|
|
1307
1503
|
const previewStart = Math.max(0, changedLine - 1);
|
|
1308
1504
|
const diffPreview = afterLines.slice(previewStart, previewStart + 6).map((line, idx) => `${previewStart + idx + 1}| ${line}`).join('\n');
|
|
1505
|
+
const changed = countChangedLines(beforeContent, afterContent);
|
|
1309
1506
|
return {
|
|
1310
1507
|
ok: true,
|
|
1311
1508
|
path: pathText,
|
|
1312
1509
|
action,
|
|
1313
1510
|
changed_line: changedLine,
|
|
1314
1511
|
diff_preview: diffPreview,
|
|
1315
|
-
|
|
1316
|
-
|
|
1512
|
+
new_hash: sha256(afterContent),
|
|
1513
|
+
lines_added: changed.added,
|
|
1514
|
+
lines_removed: changed.removed
|
|
1317
1515
|
};
|
|
1318
1516
|
}
|
|
1319
1517
|
|
|
@@ -1535,7 +1733,7 @@ async function editTarget(root, args) {
|
|
|
1535
1733
|
throw new Error(`edit does not support kind: ${kind}`);
|
|
1536
1734
|
}
|
|
1537
1735
|
|
|
1538
|
-
export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSystemEvent, getTodos, onTodosUpdate, getPlanState, onPlanStateUpdate }) {
|
|
1736
|
+
export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSystemEvent, getTodos, onTodosUpdate, getPlanState, onPlanStateUpdate, fffAdapter }) {
|
|
1539
1737
|
const emitSystemTool = (event) => {
|
|
1540
1738
|
if (typeof onSystemEvent === 'function' && event) onSystemEvent(event);
|
|
1541
1739
|
};
|
|
@@ -1570,7 +1768,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1570
1768
|
};
|
|
1571
1769
|
const ensureProjectIndex = async () => {
|
|
1572
1770
|
const eventId = `project-index:${Date.now()}`;
|
|
1573
|
-
const name = 'project_index(.codemini
|
|
1771
|
+
const name = 'project_index(.codemini/project-map.json,.codemini/file-index.json)';
|
|
1574
1772
|
try {
|
|
1575
1773
|
const result = await initializeProjectIndex(workspaceRoot);
|
|
1576
1774
|
if (result?.skipped || !result?.summary) {
|
|
@@ -1602,7 +1800,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1602
1800
|
type: 'system_tool:end',
|
|
1603
1801
|
id: eventId,
|
|
1604
1802
|
name,
|
|
1605
|
-
summary: result?.summary || `updated .codemini
|
|
1803
|
+
summary: result?.summary || `updated .codemini for ${relativePath}`
|
|
1606
1804
|
});
|
|
1607
1805
|
return result;
|
|
1608
1806
|
} catch (error) {
|
|
@@ -1975,52 +2173,61 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1975
2173
|
}
|
|
1976
2174
|
}
|
|
1977
2175
|
},
|
|
1978
|
-
|
|
2176
|
+
web_fetch: {
|
|
1979
2177
|
type: 'function',
|
|
1980
2178
|
function: {
|
|
1981
|
-
name: '
|
|
1982
|
-
description:
|
|
2179
|
+
name: 'web_fetch',
|
|
2180
|
+
description:
|
|
2181
|
+
'Fetch and read a live web page. Uses Playwright to render the page, Cheerio to extract structured content, and Crawlee request handling to normalize the fetch flow. Use this for direct URL reads, not for keyword search.',
|
|
1983
2182
|
parameters: {
|
|
1984
2183
|
type: 'object',
|
|
1985
2184
|
properties: {
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
2185
|
+
url: { type: 'string', description: 'Absolute http or https URL to fetch' },
|
|
2186
|
+
href: { type: 'string', description: 'Alias for url' },
|
|
2187
|
+
timeout_ms: { type: 'number', description: 'Navigation timeout in milliseconds' },
|
|
2188
|
+
wait_until: { type: 'string', description: 'domcontentloaded, load, or networkidle' },
|
|
2189
|
+
max_links: { type: 'number', description: 'Max number of links to extract from the page' }
|
|
1990
2190
|
},
|
|
1991
|
-
required: ['
|
|
2191
|
+
required: ['url']
|
|
1992
2192
|
}
|
|
1993
2193
|
}
|
|
1994
2194
|
},
|
|
1995
|
-
|
|
2195
|
+
web_search: {
|
|
1996
2196
|
type: 'function',
|
|
1997
2197
|
function: {
|
|
1998
|
-
name: '
|
|
1999
|
-
description:
|
|
2198
|
+
name: 'web_search',
|
|
2199
|
+
description:
|
|
2200
|
+
'Run a live web search through DuckDuckGo. Use this for keyword-based internet search. This tool respects config.web.search_enabled and will fail when network search is disabled.',
|
|
2000
2201
|
parameters: {
|
|
2001
2202
|
type: 'object',
|
|
2002
2203
|
properties: {
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2204
|
+
query: { type: 'string', description: 'Search query' },
|
|
2205
|
+
q: { type: 'string', description: 'Alias for query' },
|
|
2206
|
+
max_results: { type: 'number', description: 'Max results to return' },
|
|
2207
|
+
locale: { type: 'string', description: 'DuckDuckGo locale such as en-us' },
|
|
2208
|
+
region: { type: 'string', description: 'DuckDuckGo region such as wt-wt' }
|
|
2007
2209
|
},
|
|
2008
|
-
required: ['
|
|
2210
|
+
required: ['query']
|
|
2009
2211
|
}
|
|
2010
2212
|
}
|
|
2011
2213
|
},
|
|
2012
|
-
|
|
2214
|
+
save_memory: {
|
|
2013
2215
|
type: 'function',
|
|
2014
2216
|
function: {
|
|
2015
|
-
name: '
|
|
2016
|
-
description:
|
|
2217
|
+
name: 'save_memory',
|
|
2218
|
+
description:
|
|
2219
|
+
'Save a durable observation or knowledge to persistent memory. Use this when you notice a reusable pattern, a user correction, a stable preference, a project convention, or a workflow insight. Do NOT use for casual chatter, trivial typos, one-off noise, or secrets. The memory is saved immediately and available in future sessions.',
|
|
2017
2220
|
parameters: {
|
|
2018
2221
|
type: 'object',
|
|
2019
2222
|
properties: {
|
|
2020
|
-
content: { type: 'string' },
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2223
|
+
content: { type: 'string', description: 'The knowledge or observation to remember' },
|
|
2224
|
+
summary: { type: 'string', description: 'Short summary for the memory index (under 80 chars)' },
|
|
2225
|
+
scope: {
|
|
2226
|
+
type: 'string',
|
|
2227
|
+
description: 'Where to store this memory. "user" = personal preferences (language, style, interaction habits). "global" = cross-project knowledge useful in ANY repository (environment quirks, general workflows, tool tips). "project" = specific to THIS repository only (architecture conventions, local config, test commands, file locations). Default: "global".'
|
|
2228
|
+
},
|
|
2229
|
+
kind: { type: 'string', description: 'Memory kind: preference, pattern, correction, observation, decision, failure, win, gap, convention. Default: observation' },
|
|
2230
|
+
replace_similar: { type: 'boolean', description: 'Replace an existing similar memory when true. Default: true.' }
|
|
2024
2231
|
},
|
|
2025
2232
|
required: ['content']
|
|
2026
2233
|
}
|
|
@@ -2070,6 +2277,21 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2070
2277
|
}
|
|
2071
2278
|
}
|
|
2072
2279
|
},
|
|
2280
|
+
dream_consolidate: {
|
|
2281
|
+
type: 'function',
|
|
2282
|
+
function: {
|
|
2283
|
+
name: 'dream_consolidate',
|
|
2284
|
+
description:
|
|
2285
|
+
'Run a dream loop consolidation pass over inbox entries. Reads recent inbox items, deduplicates, evaluates lifecycle progression (observed → candidate → operational/longterm), promotes stable patterns into persistent memory, archives expired items, and writes an audit report. Use during off-hours or explicit maintenance.',
|
|
2286
|
+
parameters: {
|
|
2287
|
+
type: 'object',
|
|
2288
|
+
properties: {
|
|
2289
|
+
scope: { type: 'string', description: 'Optional scope filter: global, repo, or thread' },
|
|
2290
|
+
dry_run: { type: 'boolean', description: 'If true, only preview what would change without making changes' }
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
},
|
|
2073
2295
|
list_background_tasks: {
|
|
2074
2296
|
type: 'function',
|
|
2075
2297
|
function: {
|
|
@@ -2113,6 +2335,47 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2113
2335
|
};
|
|
2114
2336
|
|
|
2115
2337
|
const definitions = [...primaryDefinitions];
|
|
2338
|
+
const activeFffAdapter = fffAdapter || createFffAdapter({ workspaceRoot, config });
|
|
2339
|
+
let fffConnected = false;
|
|
2340
|
+
|
|
2341
|
+
async function ensureFffConnected() {
|
|
2342
|
+
if (!activeFffAdapter?.connect || fffConnected) return;
|
|
2343
|
+
await activeFffAdapter.connect();
|
|
2344
|
+
fffConnected = true;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
async function grep(args) {
|
|
2348
|
+
if (activeFffAdapter?.grep) {
|
|
2349
|
+
try {
|
|
2350
|
+
await ensureFffConnected();
|
|
2351
|
+
const result = await activeFffAdapter.grep(args);
|
|
2352
|
+
if (result && Array.isArray(result.matches)) return result;
|
|
2353
|
+
} catch {}
|
|
2354
|
+
}
|
|
2355
|
+
return builtinGrep(workspaceRoot, args);
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
async function glob(args) {
|
|
2359
|
+
if (activeFffAdapter?.glob) {
|
|
2360
|
+
try {
|
|
2361
|
+
await ensureFffConnected();
|
|
2362
|
+
const result = await activeFffAdapter.glob(args);
|
|
2363
|
+
if (result && Array.isArray(result.matches)) return result;
|
|
2364
|
+
} catch {}
|
|
2365
|
+
}
|
|
2366
|
+
return builtinGlob(workspaceRoot, args);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
async function list(args) {
|
|
2370
|
+
if (activeFffAdapter?.list) {
|
|
2371
|
+
try {
|
|
2372
|
+
await ensureFffConnected();
|
|
2373
|
+
const result = await activeFffAdapter.list(args);
|
|
2374
|
+
if (result && Array.isArray(result.items)) return result;
|
|
2375
|
+
} catch {}
|
|
2376
|
+
}
|
|
2377
|
+
return builtinList(workspaceRoot, args);
|
|
2378
|
+
}
|
|
2116
2379
|
|
|
2117
2380
|
const handlers = {
|
|
2118
2381
|
read: async (args) => {
|
|
@@ -2176,9 +2439,9 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2176
2439
|
await ensureProjectIndex();
|
|
2177
2440
|
return queryProjectIndex(workspaceRoot, args);
|
|
2178
2441
|
},
|
|
2179
|
-
grep
|
|
2180
|
-
glob
|
|
2181
|
-
list
|
|
2442
|
+
grep,
|
|
2443
|
+
glob,
|
|
2444
|
+
list,
|
|
2182
2445
|
ast_query: async (args) => {
|
|
2183
2446
|
const result = await queryAst(workspaceRoot, args);
|
|
2184
2447
|
const firstTarget = result?.matches?.[0]?.ast_target;
|
|
@@ -2191,6 +2454,8 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2191
2454
|
if (astTarget.path) rememberAstSelection(astTarget.path, astTarget);
|
|
2192
2455
|
return readAstNode(workspaceRoot, { ...args, ast_target: astTarget });
|
|
2193
2456
|
},
|
|
2457
|
+
web_fetch: (args) => webFetchPage(args),
|
|
2458
|
+
web_search: (args) => webSearchQuery(config, args),
|
|
2194
2459
|
edit: async (args) => {
|
|
2195
2460
|
await ensureProjectIndex();
|
|
2196
2461
|
const normalizedKind = String(args?.edit?.kind || args?.kind || '').trim();
|
|
@@ -2271,42 +2536,32 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2271
2536
|
hasPendingApproval: nextPlan?.status === 'pending_approval'
|
|
2272
2537
|
};
|
|
2273
2538
|
},
|
|
2274
|
-
run: (
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
scope: 'global',
|
|
2290
|
-
content: args.content,
|
|
2291
|
-
kind: args.kind,
|
|
2292
|
-
summary: args.summary,
|
|
2293
|
-
replaceSimilar: args.replace_similar !== false,
|
|
2294
|
-
workspaceRoot,
|
|
2295
|
-
config
|
|
2296
|
-
});
|
|
2297
|
-
return { ok: true, scope: 'global', memory: saved };
|
|
2298
|
-
},
|
|
2299
|
-
remember_project: async (args = {}) => {
|
|
2539
|
+
run: Object.assign(
|
|
2540
|
+
(args) => runCommand(workspaceRoot, config, args),
|
|
2541
|
+
{
|
|
2542
|
+
prepareApproval: async (args) => ({
|
|
2543
|
+
command: args?.command || '',
|
|
2544
|
+
risk: args?._risk || 'high',
|
|
2545
|
+
evaluation: args?._evaluation || null
|
|
2546
|
+
})
|
|
2547
|
+
}
|
|
2548
|
+
),
|
|
2549
|
+
save_memory: async (args = {}) => {
|
|
2550
|
+
const rawScope = String(args.scope || 'global').toLowerCase();
|
|
2551
|
+
const memoryScope = rawScope === 'repo' || rawScope === 'project' ? 'project'
|
|
2552
|
+
: rawScope === 'user' ? 'user'
|
|
2553
|
+
: 'global';
|
|
2300
2554
|
const saved = await rememberMemory({
|
|
2301
|
-
scope:
|
|
2555
|
+
scope: memoryScope,
|
|
2302
2556
|
content: args.content,
|
|
2303
|
-
kind: args.kind,
|
|
2304
|
-
summary: args.summary,
|
|
2557
|
+
kind: args.kind || 'observation',
|
|
2558
|
+
summary: args.summary || String(args.content || '').slice(0, 80),
|
|
2559
|
+
source: 'tool',
|
|
2305
2560
|
replaceSimilar: args.replace_similar !== false,
|
|
2306
2561
|
workspaceRoot,
|
|
2307
2562
|
config
|
|
2308
2563
|
});
|
|
2309
|
-
return { ok: true, scope:
|
|
2564
|
+
return { ok: true, scope: memoryScope, memory: saved };
|
|
2310
2565
|
},
|
|
2311
2566
|
list_memory: async (args = {}) => ({
|
|
2312
2567
|
scope: String(args.scope || ''),
|
|
@@ -2321,6 +2576,15 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2321
2576
|
ok: true,
|
|
2322
2577
|
...(await forgetMemory({ scope: args.scope, id: args.id, workspaceRoot }))
|
|
2323
2578
|
}),
|
|
2579
|
+
dream_consolidate: async (args = {}) => {
|
|
2580
|
+
return runDreamConsolidation({
|
|
2581
|
+
dryRun: args.dry_run === true,
|
|
2582
|
+
scope: args.scope || null,
|
|
2583
|
+
workspaceRoot,
|
|
2584
|
+
config,
|
|
2585
|
+
writeAudit: true
|
|
2586
|
+
});
|
|
2587
|
+
},
|
|
2324
2588
|
list_background_tasks: () => listBackgroundTasks(workspaceRoot),
|
|
2325
2589
|
get_background_task: (args) => getBackgroundTask(workspaceRoot, args),
|
|
2326
2590
|
stop_background_task: (args) => stopBackgroundTask(workspaceRoot, args),
|
|
@@ -2347,7 +2611,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2347
2611
|
}
|
|
2348
2612
|
};
|
|
2349
2613
|
|
|
2350
|
-
const
|
|
2614
|
+
const rawFormatters = {
|
|
2351
2615
|
read(result) {
|
|
2352
2616
|
if (typeof result === 'string') return result;
|
|
2353
2617
|
if (!result || typeof result !== 'object') return String(result);
|
|
@@ -2546,9 +2810,11 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2546
2810
|
}
|
|
2547
2811
|
return parts.join('\n');
|
|
2548
2812
|
}
|
|
2813
|
+
const runSummary = summarizeRunOutput(result);
|
|
2814
|
+
if (runSummary) return runSummary;
|
|
2549
2815
|
const command = String(result.command || '').slice(0, 200);
|
|
2550
|
-
const stdout = String(result.stdout || '')
|
|
2551
|
-
const stderr = String(result.stderr || '')
|
|
2816
|
+
const stdout = String(result.stdout || '');
|
|
2817
|
+
const stderr = String(result.stderr || '');
|
|
2552
2818
|
const code = result.code ?? 0;
|
|
2553
2819
|
const parts = [`[exit: ${code}]`];
|
|
2554
2820
|
if (command) parts.push(`command: ${command}`);
|
|
@@ -2569,6 +2835,11 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2569
2835
|
return result?.memory?.content ? `stored project memory: ${result.memory.content}` : JSON.stringify(result);
|
|
2570
2836
|
},
|
|
2571
2837
|
|
|
2838
|
+
save_memory(result) {
|
|
2839
|
+
const scope = result?.scope || 'global';
|
|
2840
|
+
return result?.memory?.content ? `stored ${scope} memory: ${result.memory.content}` : JSON.stringify(result);
|
|
2841
|
+
},
|
|
2842
|
+
|
|
2572
2843
|
list_memory(result) {
|
|
2573
2844
|
if (!result || typeof result !== 'object' || !Array.isArray(result.items)) return JSON.stringify(result);
|
|
2574
2845
|
if (result.items.length === 0) return `No ${result.scope || ''} memories found.`;
|
|
@@ -2604,8 +2875,37 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2604
2875
|
const kind = result.kind || '';
|
|
2605
2876
|
const content = result.content || result.source || '';
|
|
2606
2877
|
const header = `${kind} ${name}`;
|
|
2607
|
-
|
|
2608
|
-
|
|
2878
|
+
return `${header}\n${content}`;
|
|
2879
|
+
},
|
|
2880
|
+
|
|
2881
|
+
web_fetch(result) {
|
|
2882
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2883
|
+
const lines = [`[web_fetch: ${result.final_url || result.url || '?'}]`];
|
|
2884
|
+
if (result.title) lines.push(`title: ${result.title}`);
|
|
2885
|
+
if (result.description) lines.push(`description: ${trimPreview(result.description, 200)}`);
|
|
2886
|
+
if (result.metadata?.status) lines.push(`status: ${result.metadata.status}`);
|
|
2887
|
+
if (Array.isArray(result.links) && result.links.length > 0) {
|
|
2888
|
+
lines.push(`links: ${result.links.slice(0, 5).map((item) => item.href).join(', ')}`);
|
|
2889
|
+
}
|
|
2890
|
+
if (result.text) {
|
|
2891
|
+
lines.push(result.text);
|
|
2892
|
+
}
|
|
2893
|
+
return lines.join('\n');
|
|
2894
|
+
},
|
|
2895
|
+
|
|
2896
|
+
web_search(result) {
|
|
2897
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2898
|
+
const lines = [result.query ? `[web_search: "${result.query}"]` : '[web_search]'];
|
|
2899
|
+
if (!Array.isArray(result.results) || result.results.length === 0) {
|
|
2900
|
+
lines.push(result.no_results ? 'No results found.' : 'No search results returned.');
|
|
2901
|
+
return lines.join('\n');
|
|
2902
|
+
}
|
|
2903
|
+
for (const item of result.results.slice(0, 8)) {
|
|
2904
|
+
lines.push(`- ${item.title || item.url}`);
|
|
2905
|
+
if (item.url) lines.push(` ${item.url}`);
|
|
2906
|
+
if (item.description) lines.push(` ${trimPreview(item.description, 180)}`);
|
|
2907
|
+
}
|
|
2908
|
+
return lines.join('\n');
|
|
2609
2909
|
},
|
|
2610
2910
|
|
|
2611
2911
|
list_background_tasks(result) {
|
|
@@ -2630,5 +2930,20 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2630
2930
|
}
|
|
2631
2931
|
};
|
|
2632
2932
|
|
|
2633
|
-
|
|
2933
|
+
const formatters = Object.fromEntries(
|
|
2934
|
+
Object.entries(rawFormatters).map(([name, formatter]) => [
|
|
2935
|
+
name,
|
|
2936
|
+
(result, args) => sanitizeTextForModel(formatter(result, args), getToolOutputSanitizeOptions(name))
|
|
2937
|
+
])
|
|
2938
|
+
);
|
|
2939
|
+
|
|
2940
|
+
async function dispose() {
|
|
2941
|
+
if (activeFffAdapter?.dispose) {
|
|
2942
|
+
try {
|
|
2943
|
+
await activeFffAdapter.dispose();
|
|
2944
|
+
} catch {}
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
return { definitions, handlers, formatters, deferredDefinitions, dispose };
|
|
2634
2949
|
}
|