codemini-cli 0.3.9 → 0.4.1
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 +50 -6
- package/deployment.md +6 -6
- package/package.json +3 -1
- package/src/core/agent-loop.js +103 -115
- package/src/core/chat-runtime.js +134 -6
- 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 +2 -0
- package/src/core/constants.js +0 -1
- package/src/core/context-compact.js +32 -8
- package/src/core/default-system-prompt.js +15 -8
- package/src/core/dream-consolidate.js +54 -14
- package/src/core/dream-evaluator.js +99 -0
- package/src/core/fff-adapter.js +1 -1
- package/src/core/memory-store.js +3 -2
- package/src/core/paths.js +1 -1
- package/src/core/project-index.js +2 -2
- package/src/core/provider/openai-compatible.js +40 -5
- package/src/core/shell-profile.js +13 -9
- package/src/core/tool-args.js +181 -0
- package/src/core/tool-output.js +184 -0
- package/src/core/tools.js +118 -315
- package/src/tui/chat-app.js +362 -45
- package/src/tui/tool-activity/presenters/misc.js +14 -0
- package/src/tui/tool-activity/presenters/system.js +1 -1
package/src/core/tools.js
CHANGED
|
@@ -23,6 +23,20 @@ import { runDreamConsolidation } from './dream-consolidate.js';
|
|
|
23
23
|
import { normalizePlanState } from './plan-state.js';
|
|
24
24
|
import { normalizeTodos } from './todo-state.js';
|
|
25
25
|
import { createFffAdapter } from './fff-adapter.js';
|
|
26
|
+
import {
|
|
27
|
+
getToolOutputSanitizeOptions,
|
|
28
|
+
sanitizePreviewLines,
|
|
29
|
+
sanitizeTextForModel,
|
|
30
|
+
summarizeRunOutput
|
|
31
|
+
} from './tool-output.js';
|
|
32
|
+
import {
|
|
33
|
+
normalizePathArgs,
|
|
34
|
+
normalizePatternArgs,
|
|
35
|
+
normalizeReadArgs,
|
|
36
|
+
normalizeWebFetchArgs,
|
|
37
|
+
normalizeWebSearchArgs,
|
|
38
|
+
normalizeWriteArgs
|
|
39
|
+
} from './tool-args.js';
|
|
26
40
|
const BACKGROUND_TASK_RECENT_OUTPUT_LIMIT = 80;
|
|
27
41
|
const BACKGROUND_TASK_POLL_MS = 150;
|
|
28
42
|
const MAX_AST_ENCLOSING_BYTES = 300_000;
|
|
@@ -94,133 +108,6 @@ function splitLines(text) {
|
|
|
94
108
|
return String(text || '').split('\n');
|
|
95
109
|
}
|
|
96
110
|
|
|
97
|
-
function parseInlineReadRange(value) {
|
|
98
|
-
const text = String(value || '').trim();
|
|
99
|
-
if (!text) return null;
|
|
100
|
-
const match = text.match(/^(.*?):(\d+)(?:-(\d+))?$/);
|
|
101
|
-
if (!match) return null;
|
|
102
|
-
const [, maybePath, startRaw, endRaw] = match;
|
|
103
|
-
if (!maybePath || /^(?:[A-Za-z])$/.test(maybePath)) return null;
|
|
104
|
-
const startLine = Number(startRaw);
|
|
105
|
-
const endLine = Number(endRaw || startRaw);
|
|
106
|
-
if (!Number.isFinite(startLine) || startLine <= 0) return null;
|
|
107
|
-
if (!Number.isFinite(endLine) || endLine < startLine) return null;
|
|
108
|
-
return {
|
|
109
|
-
path: maybePath,
|
|
110
|
-
start_line: startLine,
|
|
111
|
-
end_line: endLine
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function normalizeReadArgs(rawArgs) {
|
|
116
|
-
const source =
|
|
117
|
-
rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)
|
|
118
|
-
? { ...rawArgs }
|
|
119
|
-
: { path: typeof rawArgs === 'string' ? rawArgs : '' };
|
|
120
|
-
|
|
121
|
-
const normalized = { ...source };
|
|
122
|
-
const aliasPath = String(source.path || source.file_path || source.file || source.target || '').trim();
|
|
123
|
-
if (aliasPath) normalized.path = aliasPath;
|
|
124
|
-
|
|
125
|
-
if (!Number.isFinite(Number(normalized.start_line)) && Number.isFinite(Number(source.offset))) {
|
|
126
|
-
normalized.start_line = Number(source.offset);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (!Number.isFinite(Number(normalized.end_line)) && Number.isFinite(Number(source.limit))) {
|
|
130
|
-
const startLine = Number(normalized.start_line);
|
|
131
|
-
const limit = Number(source.limit);
|
|
132
|
-
if (startLine > 0 && limit > 0) {
|
|
133
|
-
normalized.end_line = startLine + limit - 1;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const inlineRange = parseInlineReadRange(normalized.path);
|
|
138
|
-
if (inlineRange) {
|
|
139
|
-
normalized.path = inlineRange.path;
|
|
140
|
-
if (!Number.isFinite(Number(normalized.start_line))) normalized.start_line = inlineRange.start_line;
|
|
141
|
-
if (!Number.isFinite(Number(normalized.end_line))) normalized.end_line = inlineRange.end_line;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return normalized;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function normalizePathArgs(rawArgs, aliases = []) {
|
|
148
|
-
const source =
|
|
149
|
-
rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)
|
|
150
|
-
? { ...rawArgs }
|
|
151
|
-
: { path: typeof rawArgs === 'string' ? rawArgs : '' };
|
|
152
|
-
const normalized = { ...source };
|
|
153
|
-
const keys = ['path', ...aliases];
|
|
154
|
-
for (const key of keys) {
|
|
155
|
-
const value = String(source?.[key] || '').trim();
|
|
156
|
-
if (value) {
|
|
157
|
-
normalized.path = value;
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return normalized;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function normalizePatternArgs(rawArgs, aliases = [], defaultPathAliases = []) {
|
|
165
|
-
const source =
|
|
166
|
-
rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)
|
|
167
|
-
? { ...rawArgs }
|
|
168
|
-
: { pattern: typeof rawArgs === 'string' ? rawArgs : '' };
|
|
169
|
-
const normalized = { ...source };
|
|
170
|
-
for (const key of ['pattern', ...aliases]) {
|
|
171
|
-
const value = String(source?.[key] || '').trim();
|
|
172
|
-
if (value) {
|
|
173
|
-
normalized.pattern = value;
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
for (const key of ['path', ...defaultPathAliases]) {
|
|
178
|
-
const value = String(source?.[key] || '').trim();
|
|
179
|
-
if (value) {
|
|
180
|
-
normalized.path = value;
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return normalized;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function normalizeWriteArgs(rawArgs) {
|
|
188
|
-
const source =
|
|
189
|
-
rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)
|
|
190
|
-
? { ...rawArgs }
|
|
191
|
-
: { path: typeof rawArgs === 'string' ? rawArgs : '' };
|
|
192
|
-
const normalized = { ...source };
|
|
193
|
-
const filePath = String(source.path || source.file_path || source.file || '').trim();
|
|
194
|
-
if (filePath) normalized.path = filePath;
|
|
195
|
-
if (normalized.content == null) {
|
|
196
|
-
if (source.text != null) normalized.content = source.text;
|
|
197
|
-
if (source.new_content != null) normalized.content = source.new_content;
|
|
198
|
-
}
|
|
199
|
-
return normalized;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function normalizeWebFetchArgs(rawArgs) {
|
|
203
|
-
const source =
|
|
204
|
-
rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)
|
|
205
|
-
? { ...rawArgs }
|
|
206
|
-
: { url: typeof rawArgs === 'string' ? rawArgs : '' };
|
|
207
|
-
const normalized = { ...source };
|
|
208
|
-
const url = String(source.url || source.href || source.link || source.target || '').trim();
|
|
209
|
-
if (url) normalized.url = url;
|
|
210
|
-
return normalized;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function normalizeWebSearchArgs(rawArgs) {
|
|
214
|
-
const source =
|
|
215
|
-
rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)
|
|
216
|
-
? { ...rawArgs }
|
|
217
|
-
: { query: typeof rawArgs === 'string' ? rawArgs : '' };
|
|
218
|
-
const normalized = { ...source };
|
|
219
|
-
const query = String(source.query || source.q || source.keyword || '').trim();
|
|
220
|
-
if (query) normalized.query = query;
|
|
221
|
-
return normalized;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
111
|
function clampNumber(value, min, max, fallback) {
|
|
225
112
|
const num = Number(value);
|
|
226
113
|
if (!Number.isFinite(num)) return fallback;
|
|
@@ -333,11 +220,10 @@ async function webFetchPage(args = {}) {
|
|
|
333
220
|
const html = await page.content();
|
|
334
221
|
const $ = cheerio.load(html);
|
|
335
222
|
const bodyText = $('body').text() || $.root().text();
|
|
336
|
-
const text =
|
|
223
|
+
const text = String(bodyText || '').replace(/[^\S\n]+/g, ' ').replace(/\n{3,}/g, '\n\n').trim();
|
|
337
224
|
const title = trimPreview($('title').first().text() || (await page.title()), 240);
|
|
338
225
|
const description = extractHtmlMeta($, 'description') || extractHtmlMeta($, 'og:description');
|
|
339
226
|
const links = collectPageLinks($, finalUrl, maxLinks);
|
|
340
|
-
const htmlExcerpt = html.length > 4000 ? `${html.slice(0, 4000)}...` : html;
|
|
341
227
|
|
|
342
228
|
return {
|
|
343
229
|
url,
|
|
@@ -345,7 +231,6 @@ async function webFetchPage(args = {}) {
|
|
|
345
231
|
title,
|
|
346
232
|
description,
|
|
347
233
|
text,
|
|
348
|
-
html_excerpt: htmlExcerpt,
|
|
349
234
|
links,
|
|
350
235
|
metadata: {
|
|
351
236
|
status: response?.status?.() ?? null,
|
|
@@ -747,40 +632,6 @@ function findEnclosingSymbolLine(lines, anchorLine) {
|
|
|
747
632
|
return 0;
|
|
748
633
|
}
|
|
749
634
|
|
|
750
|
-
function buildUnifiedDiff(oldContent, newContent, filePath = 'file') {
|
|
751
|
-
const oldLines = splitLines(oldContent);
|
|
752
|
-
const newLines = splitLines(newContent);
|
|
753
|
-
let prefix = 0;
|
|
754
|
-
while (prefix < oldLines.length && prefix < newLines.length && oldLines[prefix] === newLines[prefix]) {
|
|
755
|
-
prefix += 1;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
let suffix = 0;
|
|
759
|
-
while (
|
|
760
|
-
suffix < oldLines.length - prefix &&
|
|
761
|
-
suffix < newLines.length - prefix &&
|
|
762
|
-
oldLines[oldLines.length - 1 - suffix] === newLines[newLines.length - 1 - suffix]
|
|
763
|
-
) {
|
|
764
|
-
suffix += 1;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
const oldChanged = oldLines.slice(prefix, oldLines.length - suffix);
|
|
768
|
-
const newChanged = newLines.slice(prefix, newLines.length - suffix);
|
|
769
|
-
const oldStart = prefix + 1;
|
|
770
|
-
const newStart = prefix + 1;
|
|
771
|
-
const oldCount = Math.max(1, oldChanged.length);
|
|
772
|
-
const newCount = Math.max(1, newChanged.length);
|
|
773
|
-
|
|
774
|
-
const body = [
|
|
775
|
-
`--- ${filePath}`,
|
|
776
|
-
`+++ ${filePath}`,
|
|
777
|
-
`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`,
|
|
778
|
-
...oldChanged.map((line) => `-${line}`),
|
|
779
|
-
...newChanged.map((line) => `+${line}`)
|
|
780
|
-
];
|
|
781
|
-
return body.join('\n');
|
|
782
|
-
}
|
|
783
|
-
|
|
784
635
|
async function getFileState(root, relativePath) {
|
|
785
636
|
const target = await resolveInWorkspace(root, relativePath);
|
|
786
637
|
const stat = await fs.stat(target);
|
|
@@ -924,12 +775,15 @@ async function writeFile(root, args) {
|
|
|
924
775
|
}
|
|
925
776
|
const previewStart = Math.max(0, (changeLine || 1) - 1);
|
|
926
777
|
const previewLines = afterLines.slice(previewStart, previewStart + 6);
|
|
778
|
+
const changed = countChangedLines(before, after);
|
|
927
779
|
return {
|
|
928
780
|
ok: true,
|
|
929
781
|
path: rawPath,
|
|
930
782
|
action: normalizedArgs?.append ? 'append' : existed ? 'overwrite' : 'create',
|
|
931
783
|
changed_line: changeLine || Math.max(1, afterLines.length),
|
|
932
|
-
diff_preview: previewLines.map((line, idx) => `${previewStart + idx + 1}| ${line}`).join('\n')
|
|
784
|
+
diff_preview: previewLines.map((line, idx) => `${previewStart + idx + 1}| ${line}`).join('\n'),
|
|
785
|
+
lines_added: changed.added,
|
|
786
|
+
lines_removed: changed.removed
|
|
933
787
|
};
|
|
934
788
|
}
|
|
935
789
|
|
|
@@ -1045,10 +899,9 @@ function shellCommandForBackgroundTask(command, shellSpec) {
|
|
|
1045
899
|
}
|
|
1046
900
|
|
|
1047
901
|
function appendRecentOutput(task, chunk) {
|
|
1048
|
-
const lines =
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
.filter(Boolean);
|
|
902
|
+
const lines = sanitizePreviewLines(chunk, { maxLineLength: 220 }).map((line) =>
|
|
903
|
+
trimLinePreview(line, 220)
|
|
904
|
+
);
|
|
1052
905
|
if (lines.length === 0) return;
|
|
1053
906
|
for (const line of lines) {
|
|
1054
907
|
backgroundTaskLogCursorCounter += 1;
|
|
@@ -1499,18 +1352,47 @@ async function validateEdit(root, args) {
|
|
|
1499
1352
|
throw new Error(`validate_edit does not support kind: ${kind}`);
|
|
1500
1353
|
}
|
|
1501
1354
|
|
|
1355
|
+
function countChangedLines(beforeContent, afterContent) {
|
|
1356
|
+
const before = splitLines(beforeContent);
|
|
1357
|
+
const after = splitLines(afterContent);
|
|
1358
|
+
const m = before.length;
|
|
1359
|
+
const n = after.length;
|
|
1360
|
+
// LCS via rolling DP — O(m*n) time, O(min(m,n)) space
|
|
1361
|
+
const short = m <= n ? before : after;
|
|
1362
|
+
const long = m <= n ? after : before;
|
|
1363
|
+
const shortLen = short.length;
|
|
1364
|
+
const longLen = long.length;
|
|
1365
|
+
let prev = new Array(longLen + 1).fill(0);
|
|
1366
|
+
let curr = new Array(longLen + 1).fill(0);
|
|
1367
|
+
for (let i = 1; i <= shortLen; i++) {
|
|
1368
|
+
for (let j = 1; j <= longLen; j++) {
|
|
1369
|
+
if (short[i - 1] === long[j - 1]) {
|
|
1370
|
+
curr[j] = prev[j - 1] + 1;
|
|
1371
|
+
} else {
|
|
1372
|
+
curr[j] = Math.max(prev[j], curr[j - 1]);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
[prev, curr] = [curr, prev];
|
|
1376
|
+
curr.fill(0);
|
|
1377
|
+
}
|
|
1378
|
+
const lcsLen = prev[longLen];
|
|
1379
|
+
return { added: n - lcsLen, removed: m - lcsLen };
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1502
1382
|
function editResult(pathText, action, beforeContent, afterContent, changedLine = 1) {
|
|
1503
1383
|
const afterLines = splitLines(afterContent);
|
|
1504
1384
|
const previewStart = Math.max(0, changedLine - 1);
|
|
1505
1385
|
const diffPreview = afterLines.slice(previewStart, previewStart + 6).map((line, idx) => `${previewStart + idx + 1}| ${line}`).join('\n');
|
|
1386
|
+
const changed = countChangedLines(beforeContent, afterContent);
|
|
1506
1387
|
return {
|
|
1507
1388
|
ok: true,
|
|
1508
1389
|
path: pathText,
|
|
1509
1390
|
action,
|
|
1510
1391
|
changed_line: changedLine,
|
|
1511
1392
|
diff_preview: diffPreview,
|
|
1512
|
-
|
|
1513
|
-
|
|
1393
|
+
new_hash: sha256(afterContent),
|
|
1394
|
+
lines_added: changed.added,
|
|
1395
|
+
lines_removed: changed.removed
|
|
1514
1396
|
};
|
|
1515
1397
|
}
|
|
1516
1398
|
|
|
@@ -1767,7 +1649,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1767
1649
|
};
|
|
1768
1650
|
const ensureProjectIndex = async () => {
|
|
1769
1651
|
const eventId = `project-index:${Date.now()}`;
|
|
1770
|
-
const name = 'project_index(.codemini
|
|
1652
|
+
const name = 'project_index(.codemini/project-map.json,.codemini/file-index.json)';
|
|
1771
1653
|
try {
|
|
1772
1654
|
const result = await initializeProjectIndex(workspaceRoot);
|
|
1773
1655
|
if (result?.skipped || !result?.summary) {
|
|
@@ -1799,7 +1681,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1799
1681
|
type: 'system_tool:end',
|
|
1800
1682
|
id: eventId,
|
|
1801
1683
|
name,
|
|
1802
|
-
summary: result?.summary || `updated .codemini
|
|
1684
|
+
summary: result?.summary || `updated .codemini for ${relativePath}`
|
|
1803
1685
|
});
|
|
1804
1686
|
return result;
|
|
1805
1687
|
} catch (error) {
|
|
@@ -1818,20 +1700,14 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1818
1700
|
function: {
|
|
1819
1701
|
name: 'read',
|
|
1820
1702
|
description:
|
|
1821
|
-
'Inspect code or text files. Use read(path) for normal file or line-window reads
|
|
1703
|
+
'Inspect code or text files. Use read(path) for normal file or line-window reads. Use start_line and end_line for ranges, or path:"src/app.ts:10-40" for inline ranges. Prefer this over run with cat, head, or tail.',
|
|
1822
1704
|
parameters: {
|
|
1823
1705
|
type: 'object',
|
|
1824
1706
|
properties: {
|
|
1825
1707
|
path: { type: 'string', description: 'File path to read. You can also include an inline range like src/app.ts:10-40.' },
|
|
1826
|
-
file_path: { type: 'string', description: 'Alias for path' },
|
|
1827
1708
|
start_line: { type: 'number', description: '1-based start line' },
|
|
1828
1709
|
end_line: { type: 'number', description: 'Inclusive end line' },
|
|
1829
|
-
offset: { type: 'number', description: 'Alias for start_line' },
|
|
1830
|
-
limit: { type: 'number', description: 'Number of lines to read starting from offset/start_line' },
|
|
1831
1710
|
max_chars: { type: 'number', description: 'Max chars to return' },
|
|
1832
|
-
include_content: { type: 'boolean', description: 'Legacy compatibility flag. Content is returned by default.' },
|
|
1833
|
-
read_token: { type: 'string', description: 'Legacy compatibility token. No longer required for content reads.' },
|
|
1834
|
-
metadata_only: { type: 'boolean', description: 'Set true to return metadata without content.' },
|
|
1835
1711
|
ast_target: { type: 'object', description: 'AST target from ast_query or a prior AST selection. When provided, read returns that node instead of a line window.' },
|
|
1836
1712
|
query: { type: 'string', description: 'Optional Tree-sitter query to run inline before reading the first matched AST node. Use with path for one-shot function/class/method reads.' },
|
|
1837
1713
|
capture_name: { type: 'string', description: 'Optional capture name to select when query is provided.' },
|
|
@@ -1846,14 +1722,12 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1846
1722
|
function: {
|
|
1847
1723
|
name: 'grep',
|
|
1848
1724
|
description:
|
|
1849
|
-
'Search file contents. Use this for code search before read or edit.
|
|
1725
|
+
'Search file contents. Use this for code search before read or edit. Do not use run with grep or rg for normal code search.',
|
|
1850
1726
|
parameters: {
|
|
1851
1727
|
type: 'object',
|
|
1852
1728
|
properties: {
|
|
1853
1729
|
pattern: { type: 'string', description: 'Search pattern' },
|
|
1854
|
-
query: { type: 'string', description: 'Alias for pattern' },
|
|
1855
1730
|
path: { type: 'string', description: 'Directory or file to search' },
|
|
1856
|
-
directory: { type: 'string', description: 'Alias for path' },
|
|
1857
1731
|
regex: { type: 'boolean', description: 'Treat pattern as regex' },
|
|
1858
1732
|
case_sensitive: { type: 'boolean', description: 'Case-sensitive matching' },
|
|
1859
1733
|
max_results: { type: 'number', description: 'Max matches to return' },
|
|
@@ -1868,12 +1742,11 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1868
1742
|
type: 'function',
|
|
1869
1743
|
function: {
|
|
1870
1744
|
name: 'list',
|
|
1871
|
-
description: 'List files and directories in a workspace path. Use this for quick directory discovery before deeper reads.
|
|
1745
|
+
description: 'List files and directories in a workspace path. Use this for quick directory discovery before deeper reads.',
|
|
1872
1746
|
parameters: {
|
|
1873
1747
|
type: 'object',
|
|
1874
1748
|
properties: {
|
|
1875
1749
|
path: { type: 'string', description: 'Directory path to list' },
|
|
1876
|
-
directory: { type: 'string', description: 'Alias for path' },
|
|
1877
1750
|
include_hidden: { type: 'boolean', description: 'Include dotfiles' }
|
|
1878
1751
|
}
|
|
1879
1752
|
}
|
|
@@ -1884,14 +1757,12 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1884
1757
|
function: {
|
|
1885
1758
|
name: 'glob',
|
|
1886
1759
|
description:
|
|
1887
|
-
'Find files by glob pattern. Use this when you already know a filename pattern such as src/**/*.ts.
|
|
1760
|
+
'Find files by glob pattern. Use this when you already know a filename pattern such as src/**/*.ts.',
|
|
1888
1761
|
parameters: {
|
|
1889
1762
|
type: 'object',
|
|
1890
1763
|
properties: {
|
|
1891
1764
|
pattern: { type: 'string', description: 'Glob pattern' },
|
|
1892
1765
|
path: { type: 'string', description: 'Directory to search' },
|
|
1893
|
-
query: { type: 'string', description: 'Alias for pattern' },
|
|
1894
|
-
directory: { type: 'string', description: 'Alias for path' },
|
|
1895
1766
|
include_hidden: { type: 'boolean', description: 'Include dotfiles' },
|
|
1896
1767
|
max_results: { type: 'number', description: 'Max results' }
|
|
1897
1768
|
},
|
|
@@ -1922,18 +1793,14 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1922
1793
|
function: {
|
|
1923
1794
|
name: 'edit',
|
|
1924
1795
|
description:
|
|
1925
|
-
'Edit existing files. Prefer one of these shapes: 1) {
|
|
1796
|
+
'Edit existing files. Prefer one of these shapes: 1) {path, old_text, new_text} for exact text replacement, 2) {path, symbol, edit:{kind:"replace_block", new_content:"..."}} for block replacement, 3) {path, anchor_text, position:"before"|"after", content:"..."} for inserts. Read first unless the exact target is already known. Prefer this over write for existing code changes.',
|
|
1926
1797
|
parameters: {
|
|
1927
1798
|
type: 'object',
|
|
1928
1799
|
properties: {
|
|
1929
|
-
|
|
1930
|
-
path: { type: 'string', description: 'Alias for file' },
|
|
1931
|
-
file_path: { type: 'string', description: 'Alias for file, compatible with simpler demo-style tool calls' },
|
|
1800
|
+
path: { type: 'string', description: 'File path to edit' },
|
|
1932
1801
|
new_content: { type: 'string', description: 'Replacement content' },
|
|
1933
1802
|
old_text: { type: 'string', description: 'Exact text to replace' },
|
|
1934
1803
|
new_text: { type: 'string', description: 'Replacement text' },
|
|
1935
|
-
old_string: { type: 'string', description: 'Alias for old_text' },
|
|
1936
|
-
new_string: { type: 'string', description: 'Alias for new_text' },
|
|
1937
1804
|
anchor_text: { type: 'string', description: 'Anchor text for inserts' },
|
|
1938
1805
|
content: { type: 'string', description: 'Content to insert or append' },
|
|
1939
1806
|
position: { type: 'string', description: 'before or after' },
|
|
@@ -1944,7 +1811,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1944
1811
|
line: { type: 'number', description: 'Line to target' },
|
|
1945
1812
|
edit: { type: 'object', description: 'Structured edit input' }
|
|
1946
1813
|
},
|
|
1947
|
-
required: ['
|
|
1814
|
+
required: ['path']
|
|
1948
1815
|
}
|
|
1949
1816
|
}
|
|
1950
1817
|
},
|
|
@@ -1953,16 +1820,12 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1953
1820
|
function: {
|
|
1954
1821
|
name: 'write',
|
|
1955
1822
|
description:
|
|
1956
|
-
'Create a new file or overwrite a file. Always include path and content.
|
|
1823
|
+
'Create a new file or overwrite a file. Always include path and content. Use this for new files or explicit full rewrites only. Example: {path:"src/page.html", content:"..."} . If the file path is not decided yet, do not call write yet. Prefer edit for existing code changes.',
|
|
1957
1824
|
parameters: {
|
|
1958
1825
|
type: 'object',
|
|
1959
1826
|
properties: {
|
|
1960
1827
|
path: { type: 'string', description: 'Required file path like src/app.js or pages/index.html. Never omit this.' },
|
|
1961
|
-
file_path: { type: 'string', description: 'Alias for path, compatible with simpler demo-style tool calls' },
|
|
1962
|
-
file: { type: 'string', description: 'Alias for path' },
|
|
1963
1828
|
content: { type: 'string', description: 'Content to write' },
|
|
1964
|
-
text: { type: 'string', description: 'Alias for content' },
|
|
1965
|
-
new_content: { type: 'string', description: 'Alias for content' },
|
|
1966
1829
|
append: { type: 'boolean', description: 'Append instead of overwrite' },
|
|
1967
1830
|
full_file_rewrite: { type: 'boolean', description: 'Set true for whole-file rewrites' }
|
|
1968
1831
|
},
|
|
@@ -1975,15 +1838,11 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1975
1838
|
function: {
|
|
1976
1839
|
name: 'delete',
|
|
1977
1840
|
description:
|
|
1978
|
-
'Delete a file or directory inside the workspace.
|
|
1841
|
+
'Delete a file or directory inside the workspace. Missing targets fail. Workspace escape attempts are rejected.',
|
|
1979
1842
|
parameters: {
|
|
1980
1843
|
type: 'object',
|
|
1981
1844
|
properties: {
|
|
1982
|
-
path: { type: 'string', description: 'File or directory path to delete' }
|
|
1983
|
-
file: { type: 'string', description: 'Alias for path' },
|
|
1984
|
-
file_path: { type: 'string', description: 'Alias for path' },
|
|
1985
|
-
directory: { type: 'string', description: 'Alias for path' },
|
|
1986
|
-
dir: { type: 'string', description: 'Alias for path' }
|
|
1845
|
+
path: { type: 'string', description: 'File or directory path to delete' }
|
|
1987
1846
|
},
|
|
1988
1847
|
required: ['path']
|
|
1989
1848
|
}
|
|
@@ -2210,52 +2069,23 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2210
2069
|
}
|
|
2211
2070
|
}
|
|
2212
2071
|
},
|
|
2213
|
-
|
|
2214
|
-
type: 'function',
|
|
2215
|
-
function: {
|
|
2216
|
-
name: 'remember_user',
|
|
2217
|
-
description: 'Store a durable user preference, communication habit, or long-term instruction for future sessions. Use this for things like reply style, language, explanation depth, or stable guardrails. Never store secrets, tokens, passwords, or one-off task details.',
|
|
2218
|
-
parameters: {
|
|
2219
|
-
type: 'object',
|
|
2220
|
-
properties: {
|
|
2221
|
-
content: { type: 'string', description: 'Stable preference or instruction to remember' },
|
|
2222
|
-
kind: { type: 'string', description: 'preference, workflow, constraint, or warning' },
|
|
2223
|
-
summary: { type: 'string', description: 'Short summary for the memory index' },
|
|
2224
|
-
replace_similar: { type: 'boolean', description: 'Replace an existing similar memory when true' }
|
|
2225
|
-
},
|
|
2226
|
-
required: ['content']
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
},
|
|
2230
|
-
remember_global: {
|
|
2072
|
+
save_memory: {
|
|
2231
2073
|
type: 'function',
|
|
2232
2074
|
function: {
|
|
2233
|
-
name: '
|
|
2234
|
-
description:
|
|
2235
|
-
|
|
2236
|
-
type: 'object',
|
|
2237
|
-
properties: {
|
|
2238
|
-
content: { type: 'string' },
|
|
2239
|
-
kind: { type: 'string' },
|
|
2240
|
-
summary: { type: 'string' },
|
|
2241
|
-
replace_similar: { type: 'boolean' }
|
|
2242
|
-
},
|
|
2243
|
-
required: ['content']
|
|
2244
|
-
}
|
|
2245
|
-
}
|
|
2246
|
-
},
|
|
2247
|
-
remember_project: {
|
|
2248
|
-
type: 'function',
|
|
2249
|
-
function: {
|
|
2250
|
-
name: 'remember_project',
|
|
2251
|
-
description: 'Store a durable project-specific convention, architecture note, key module warning, or local workflow expectation. Use this for repository-specific rules, important files, testing conventions, or architectural boundaries. Never store secrets or transient task state.',
|
|
2075
|
+
name: 'save_memory',
|
|
2076
|
+
description:
|
|
2077
|
+
'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.',
|
|
2252
2078
|
parameters: {
|
|
2253
2079
|
type: 'object',
|
|
2254
2080
|
properties: {
|
|
2255
|
-
content: { type: 'string' },
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2081
|
+
content: { type: 'string', description: 'The knowledge or observation to remember' },
|
|
2082
|
+
summary: { type: 'string', description: 'Short summary for the memory index (under 80 chars)' },
|
|
2083
|
+
scope: {
|
|
2084
|
+
type: 'string',
|
|
2085
|
+
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".'
|
|
2086
|
+
},
|
|
2087
|
+
kind: { type: 'string', description: 'Memory kind: preference, pattern, correction, observation, decision, failure, win, gap, convention. Default: observation' },
|
|
2088
|
+
replace_similar: { type: 'boolean', description: 'Replace an existing similar memory when true. Default: true.' }
|
|
2259
2089
|
},
|
|
2260
2090
|
required: ['content']
|
|
2261
2091
|
}
|
|
@@ -2305,26 +2135,6 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2305
2135
|
}
|
|
2306
2136
|
}
|
|
2307
2137
|
},
|
|
2308
|
-
capture_memory: {
|
|
2309
|
-
type: 'function',
|
|
2310
|
-
function: {
|
|
2311
|
-
name: 'capture_memory',
|
|
2312
|
-
description:
|
|
2313
|
-
'Capture a high-signal observation into the dream loop inbox during active work. Use this when you notice a reusable pattern, a user correction, a repeated failure with stable fix, a stable preference, or a workflow win. Do NOT use for casual chatter, trivial typos, or one-off noise. This is lightweight — entries land in inbox and are consolidated later.',
|
|
2314
|
-
parameters: {
|
|
2315
|
-
type: 'object',
|
|
2316
|
-
properties: {
|
|
2317
|
-
summary: { type: 'string', description: 'Short high-signal summary of the observation' },
|
|
2318
|
-
details: { type: 'string', description: 'Detailed explanation of the observation' },
|
|
2319
|
-
scope: { type: 'string', description: 'Scope: global, repo, or thread. Default: global' },
|
|
2320
|
-
type: { type: 'string', description: 'Observation type: correction, failure, preference, pattern, win, gap, decision. Default: observation' },
|
|
2321
|
-
tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags for categorization' },
|
|
2322
|
-
suggested_action: { type: 'string', description: 'Optional suggested follow-up action' }
|
|
2323
|
-
},
|
|
2324
|
-
required: ['summary']
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
},
|
|
2328
2138
|
dream_consolidate: {
|
|
2329
2139
|
type: 'function',
|
|
2330
2140
|
function: {
|
|
@@ -2584,42 +2394,32 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2584
2394
|
hasPendingApproval: nextPlan?.status === 'pending_approval'
|
|
2585
2395
|
};
|
|
2586
2396
|
},
|
|
2587
|
-
run: (
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
scope: 'global',
|
|
2603
|
-
content: args.content,
|
|
2604
|
-
kind: args.kind,
|
|
2605
|
-
summary: args.summary,
|
|
2606
|
-
replaceSimilar: args.replace_similar !== false,
|
|
2607
|
-
workspaceRoot,
|
|
2608
|
-
config
|
|
2609
|
-
});
|
|
2610
|
-
return { ok: true, scope: 'global', memory: saved };
|
|
2611
|
-
},
|
|
2612
|
-
remember_project: async (args = {}) => {
|
|
2397
|
+
run: Object.assign(
|
|
2398
|
+
(args) => runCommand(workspaceRoot, config, args),
|
|
2399
|
+
{
|
|
2400
|
+
prepareApproval: async (args) => ({
|
|
2401
|
+
command: args?.command || '',
|
|
2402
|
+
risk: args?._risk || 'high',
|
|
2403
|
+
evaluation: args?._evaluation || null
|
|
2404
|
+
})
|
|
2405
|
+
}
|
|
2406
|
+
),
|
|
2407
|
+
save_memory: async (args = {}) => {
|
|
2408
|
+
const rawScope = String(args.scope || 'global').toLowerCase();
|
|
2409
|
+
const memoryScope = rawScope === 'repo' || rawScope === 'project' ? 'project'
|
|
2410
|
+
: rawScope === 'user' ? 'user'
|
|
2411
|
+
: 'global';
|
|
2613
2412
|
const saved = await rememberMemory({
|
|
2614
|
-
scope:
|
|
2413
|
+
scope: memoryScope,
|
|
2615
2414
|
content: args.content,
|
|
2616
|
-
kind: args.kind,
|
|
2617
|
-
summary: args.summary,
|
|
2415
|
+
kind: args.kind || 'observation',
|
|
2416
|
+
summary: args.summary || String(args.content || '').slice(0, 80),
|
|
2417
|
+
source: 'tool',
|
|
2618
2418
|
replaceSimilar: args.replace_similar !== false,
|
|
2619
2419
|
workspaceRoot,
|
|
2620
2420
|
config
|
|
2621
2421
|
});
|
|
2622
|
-
return { ok: true, scope:
|
|
2422
|
+
return { ok: true, scope: memoryScope, memory: saved };
|
|
2623
2423
|
},
|
|
2624
2424
|
list_memory: async (args = {}) => ({
|
|
2625
2425
|
scope: String(args.scope || ''),
|
|
@@ -2634,18 +2434,6 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2634
2434
|
ok: true,
|
|
2635
2435
|
...(await forgetMemory({ scope: args.scope, id: args.id, workspaceRoot }))
|
|
2636
2436
|
}),
|
|
2637
|
-
capture_memory: async (args = {}) => {
|
|
2638
|
-
const entry = await captureToInbox({
|
|
2639
|
-
scope: args.scope,
|
|
2640
|
-
type: args.type,
|
|
2641
|
-
summary: args.summary,
|
|
2642
|
-
details: args.details,
|
|
2643
|
-
suggestedAction: args.suggested_action,
|
|
2644
|
-
tags: args.tags,
|
|
2645
|
-
source: 'tool'
|
|
2646
|
-
});
|
|
2647
|
-
return { ok: true, captured: entry };
|
|
2648
|
-
},
|
|
2649
2437
|
dream_consolidate: async (args = {}) => {
|
|
2650
2438
|
return runDreamConsolidation({
|
|
2651
2439
|
dryRun: args.dry_run === true,
|
|
@@ -2681,7 +2469,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2681
2469
|
}
|
|
2682
2470
|
};
|
|
2683
2471
|
|
|
2684
|
-
const
|
|
2472
|
+
const rawFormatters = {
|
|
2685
2473
|
read(result) {
|
|
2686
2474
|
if (typeof result === 'string') return result;
|
|
2687
2475
|
if (!result || typeof result !== 'object') return String(result);
|
|
@@ -2880,9 +2668,11 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2880
2668
|
}
|
|
2881
2669
|
return parts.join('\n');
|
|
2882
2670
|
}
|
|
2671
|
+
const runSummary = summarizeRunOutput(result);
|
|
2672
|
+
if (runSummary) return runSummary;
|
|
2883
2673
|
const command = String(result.command || '').slice(0, 200);
|
|
2884
|
-
const stdout = String(result.stdout || '')
|
|
2885
|
-
const stderr = String(result.stderr || '')
|
|
2674
|
+
const stdout = String(result.stdout || '');
|
|
2675
|
+
const stderr = String(result.stderr || '');
|
|
2886
2676
|
const code = result.code ?? 0;
|
|
2887
2677
|
const parts = [`[exit: ${code}]`];
|
|
2888
2678
|
if (command) parts.push(`command: ${command}`);
|
|
@@ -2903,6 +2693,11 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2903
2693
|
return result?.memory?.content ? `stored project memory: ${result.memory.content}` : JSON.stringify(result);
|
|
2904
2694
|
},
|
|
2905
2695
|
|
|
2696
|
+
save_memory(result) {
|
|
2697
|
+
const scope = result?.scope || 'global';
|
|
2698
|
+
return result?.memory?.content ? `stored ${scope} memory: ${result.memory.content}` : JSON.stringify(result);
|
|
2699
|
+
},
|
|
2700
|
+
|
|
2906
2701
|
list_memory(result) {
|
|
2907
2702
|
if (!result || typeof result !== 'object' || !Array.isArray(result.items)) return JSON.stringify(result);
|
|
2908
2703
|
if (result.items.length === 0) return `No ${result.scope || ''} memories found.`;
|
|
@@ -2938,8 +2733,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2938
2733
|
const kind = result.kind || '';
|
|
2939
2734
|
const content = result.content || result.source || '';
|
|
2940
2735
|
const header = `${kind} ${name}`;
|
|
2941
|
-
|
|
2942
|
-
return `${header}\n${content.slice(0, 1200)}\n... [omitted ${content.length - 1600} chars] ...\n${content.slice(-400)}`;
|
|
2736
|
+
return `${header}\n${content}`;
|
|
2943
2737
|
},
|
|
2944
2738
|
|
|
2945
2739
|
web_fetch(result) {
|
|
@@ -2951,7 +2745,9 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2951
2745
|
if (Array.isArray(result.links) && result.links.length > 0) {
|
|
2952
2746
|
lines.push(`links: ${result.links.slice(0, 5).map((item) => item.href).join(', ')}`);
|
|
2953
2747
|
}
|
|
2954
|
-
if (result.text)
|
|
2748
|
+
if (result.text) {
|
|
2749
|
+
lines.push(result.text);
|
|
2750
|
+
}
|
|
2955
2751
|
return lines.join('\n');
|
|
2956
2752
|
},
|
|
2957
2753
|
|
|
@@ -2992,6 +2788,13 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2992
2788
|
}
|
|
2993
2789
|
};
|
|
2994
2790
|
|
|
2791
|
+
const formatters = Object.fromEntries(
|
|
2792
|
+
Object.entries(rawFormatters).map(([name, formatter]) => [
|
|
2793
|
+
name,
|
|
2794
|
+
(result, args) => sanitizeTextForModel(formatter(result, args), getToolOutputSanitizeOptions(name))
|
|
2795
|
+
])
|
|
2796
|
+
);
|
|
2797
|
+
|
|
2995
2798
|
async function dispose() {
|
|
2996
2799
|
if (activeFffAdapter?.dispose) {
|
|
2997
2800
|
try {
|