zames_pro 2.20.0 → 2.21.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/dist/agent-loop.js +20 -12
- package/dist/attachments.js +80 -17
- package/dist/browser.js +55 -12
- package/dist/commands.js +7 -2
- package/dist/config-menu.js +3 -1
- package/dist/config.js +157 -23
- package/dist/context.js +68 -13
- package/dist/extraTools.js +8 -2
- package/dist/i18n.js +745 -187
- package/dist/index.js +37 -16
- package/dist/input.js +136 -21
- package/dist/mcp.js +16 -4
- package/dist/path-token.js +4 -3
- package/dist/self-review.js +6 -6
- package/dist/spinner.js +8 -2
- package/dist/system-prompt.js +19 -5
- package/dist/tools.js +2 -6
- package/dist/web.js +1 -1
- package/dist/xml-toolcall.js +10 -5
- package/package.json +13 -2
- package/scripts/eslint-ts6.mjs +51 -0
package/dist/agent-loop.js
CHANGED
|
@@ -47,7 +47,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
47
47
|
id = await browser.getCurrentChatId();
|
|
48
48
|
}
|
|
49
49
|
catch {
|
|
50
|
-
id = null
|
|
50
|
+
// keep id = null
|
|
51
51
|
}
|
|
52
52
|
if (id && id !== lastReportedChatId) {
|
|
53
53
|
lastReportedChatId = id;
|
|
@@ -56,7 +56,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
56
56
|
};
|
|
57
57
|
await reportChat();
|
|
58
58
|
if (sendSystemPrompt) {
|
|
59
|
-
let gitText
|
|
59
|
+
let gitText;
|
|
60
60
|
try {
|
|
61
61
|
const ctx = await getGitContext(workdir);
|
|
62
62
|
gitText = formatGitContext(ctx);
|
|
@@ -64,7 +64,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
64
64
|
catch (e) {
|
|
65
65
|
gitText = `(git context error: ${e.message})`;
|
|
66
66
|
}
|
|
67
|
-
let context
|
|
67
|
+
let context;
|
|
68
68
|
try {
|
|
69
69
|
context = await loadProjectContext(workdir);
|
|
70
70
|
}
|
|
@@ -267,7 +267,9 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
267
267
|
// A stale answer is discarded only when it does NOT parse as a tool
|
|
268
268
|
// call (see wdStale above); an echo of a call that never ran is a real
|
|
269
269
|
// call and must be executed, not dropped.
|
|
270
|
-
if (!isFirst &&
|
|
270
|
+
if (!isFirst &&
|
|
271
|
+
(wdEmpty || wdStale || wdNoCall) &&
|
|
272
|
+
watchdogRetries < MAX_WATCHDOG_RETRIES) {
|
|
271
273
|
watchdogRetries++;
|
|
272
274
|
transcript?.log('watchdog_nudge', {
|
|
273
275
|
attempt: watchdogRetries,
|
|
@@ -372,7 +374,8 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
372
374
|
// README…" or "Let me run the tests…" and goes silent. If this is taken
|
|
373
375
|
// as final, the agent stalls without doing the work. We ask it to
|
|
374
376
|
// continue and to actually call a tool this time (or respond if truly done).
|
|
375
|
-
if (looksLikeUnfinishedWork(trimmed) &&
|
|
377
|
+
if (looksLikeUnfinishedWork(trimmed) &&
|
|
378
|
+
looksDoneRetries < MAX_LOOKSDONE_RETRIES) {
|
|
376
379
|
looksDoneRetries++;
|
|
377
380
|
unparsedRetries++;
|
|
378
381
|
transcript?.log('unfinished_retry', {
|
|
@@ -454,9 +457,12 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
454
457
|
// a READABLE report, but say explicitly that the task may be incomplete:
|
|
455
458
|
// never let a reasoning paragraph masquerade as a finished result.
|
|
456
459
|
const lastText = (rawResponse || '').trim();
|
|
457
|
-
return
|
|
458
|
-
? lastText +
|
|
459
|
-
|
|
460
|
+
return lastText
|
|
461
|
+
? lastText +
|
|
462
|
+
String.fromCharCode(10) +
|
|
463
|
+
String.fromCharCode(10) +
|
|
464
|
+
'(The model stopped calling tools before finishing. The task may be incomplete - check the DeepSeek chat.)'
|
|
465
|
+
: 'The model stopped calling tools before finishing the task.';
|
|
460
466
|
}
|
|
461
467
|
const suspiciousFinal = responseLooksLikeToolCall(rawResponse) ||
|
|
462
468
|
looksLikeUnfinishedWork((rawResponse || '').trim()) ||
|
|
@@ -608,8 +614,8 @@ export function responseLooksLikeToolCall(rawResponse) {
|
|
|
608
614
|
// argument key (args/command/path/...). We require the opening bracket at
|
|
609
615
|
// the start (after spaces/prefix) so we don't catch ordinary prose with
|
|
610
616
|
// colons like "path: ...".
|
|
611
|
-
/^\s*[\[\{]/.test(raw) &&
|
|
612
|
-
/["']?(?:tool|args|command|path|old_string|content|content_base64)["']?\s*:/.test(raw) ||
|
|
617
|
+
(/^\s*[\[\{]/.test(raw) &&
|
|
618
|
+
/["']?(?:tool|args|command|path|old_string|content|content_base64)["']?\s*:/.test(raw)) ||
|
|
613
619
|
/<\s*\|?\s*(DSML|invoke|parameter)/i.test(raw) ||
|
|
614
620
|
/^\s*\[?\s*\{[^}]*$/.test(raw.trim()));
|
|
615
621
|
}
|
|
@@ -839,7 +845,7 @@ function parseArgsPermissive(str) {
|
|
|
839
845
|
break;
|
|
840
846
|
if (str[i] !== '"')
|
|
841
847
|
return null;
|
|
842
|
-
|
|
848
|
+
const keyEnd = str.indexOf('"', i + 1);
|
|
843
849
|
if (keyEnd === -1)
|
|
844
850
|
return null;
|
|
845
851
|
const key = str.slice(i + 1, keyEnd);
|
|
@@ -994,7 +1000,9 @@ function parseToolCallPermissive(text) {
|
|
|
994
1000
|
if (braceIdx === -1)
|
|
995
1001
|
return null;
|
|
996
1002
|
const endBrace = findMatching(text, braceIdx, '{', '}');
|
|
997
|
-
const objText = endBrace === -1
|
|
1003
|
+
const objText = endBrace === -1
|
|
1004
|
+
? text.slice(braceIdx)
|
|
1005
|
+
: text.slice(braceIdx, endBrace + 1);
|
|
998
1006
|
const inlineArgs = parseArgsPermissive(objText) || parseArgsGreedy(objText);
|
|
999
1007
|
if (!inlineArgs)
|
|
1000
1008
|
return null;
|
package/dist/attachments.js
CHANGED
|
@@ -4,14 +4,23 @@ import os from 'os';
|
|
|
4
4
|
import { execFileSync } from 'child_process';
|
|
5
5
|
import { extractPathToken } from './path-token.js';
|
|
6
6
|
export { extractPathToken };
|
|
7
|
-
export const IMAGE_EXTS = [
|
|
7
|
+
export const IMAGE_EXTS = [
|
|
8
|
+
'.png',
|
|
9
|
+
'.jpg',
|
|
10
|
+
'.jpeg',
|
|
11
|
+
'.gif',
|
|
12
|
+
'.webp',
|
|
13
|
+
'.bmp',
|
|
14
|
+
'.svg',
|
|
15
|
+
'.ico',
|
|
16
|
+
];
|
|
8
17
|
export const MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
|
|
9
18
|
function sanitize(name) {
|
|
10
|
-
return String(name || '')
|
|
19
|
+
return (String(name || '')
|
|
11
20
|
.replace(/[\u0000-\u001f\u007f]/g, '')
|
|
12
21
|
.replace(/[\\/:*?"<>|]/g, '_')
|
|
13
22
|
.replace(/\s+/g, '_')
|
|
14
|
-
.slice(0, 80) || 'file';
|
|
23
|
+
.slice(0, 80) || 'file');
|
|
15
24
|
}
|
|
16
25
|
export function guessMime(name) {
|
|
17
26
|
const ext = path.extname(String(name || '')).toLowerCase();
|
|
@@ -74,7 +83,10 @@ export function parseDataUrl(text) {
|
|
|
74
83
|
if (!m)
|
|
75
84
|
return null;
|
|
76
85
|
try {
|
|
77
|
-
return {
|
|
86
|
+
return {
|
|
87
|
+
mime: m[1] || 'application/octet-stream',
|
|
88
|
+
data: Buffer.from(m[2].replace(/\s+/g, ''), 'base64'),
|
|
89
|
+
};
|
|
78
90
|
}
|
|
79
91
|
catch {
|
|
80
92
|
return null;
|
|
@@ -189,7 +201,9 @@ export function hasClipboardTool() {
|
|
|
189
201
|
if (process.platform === 'win32' || process.platform === 'darwin')
|
|
190
202
|
return true;
|
|
191
203
|
if (process.platform === 'linux') {
|
|
192
|
-
return Boolean(tryCommand('xclip', ['-version']) ||
|
|
204
|
+
return Boolean(tryCommand('xclip', ['-version']) ||
|
|
205
|
+
tryCommand('xsel', ['--version']) ||
|
|
206
|
+
tryCommand('wl-paste', ['--version']));
|
|
193
207
|
}
|
|
194
208
|
return false;
|
|
195
209
|
}
|
|
@@ -238,7 +252,9 @@ const PS_GET_FILE_DROP = 'Add-Type -AssemblyName System.Windows.Forms; ' +
|
|
|
238
252
|
'if($d -and $d.GetDataPresent([System.Windows.Forms.DataFormats]::FileDrop)){' +
|
|
239
253
|
'$f=$d.GetData([System.Windows.Forms.DataFormats]::FileDrop); $f -join [Environment]::NewLine}';
|
|
240
254
|
export async function readClipboardImageDetailed() {
|
|
241
|
-
if (process.platform === 'linux' ||
|
|
255
|
+
if (process.platform === 'linux' ||
|
|
256
|
+
process.platform === 'freebsd' ||
|
|
257
|
+
process.platform === 'openbsd') {
|
|
242
258
|
const wayland = process.env.WAYLAND_DISPLAY;
|
|
243
259
|
const x11 = process.env.DISPLAY;
|
|
244
260
|
const attempts = [];
|
|
@@ -249,14 +265,34 @@ export async function readClipboardImageDetailed() {
|
|
|
249
265
|
return { data: win, via: 'powershell.exe' };
|
|
250
266
|
}
|
|
251
267
|
if (wayland || !x11)
|
|
252
|
-
attempts.push({
|
|
268
|
+
attempts.push({
|
|
269
|
+
bin: 'wl-paste',
|
|
270
|
+
args: ['--type', 'image/png', '--no-newline'],
|
|
271
|
+
via: 'wl-paste',
|
|
272
|
+
});
|
|
253
273
|
if (x11 || !wayland) {
|
|
254
|
-
attempts.push({
|
|
255
|
-
|
|
274
|
+
attempts.push({
|
|
275
|
+
bin: 'xclip',
|
|
276
|
+
args: ['-selection', 'clipboard', '-t', 'image/png', '-o'],
|
|
277
|
+
via: 'xclip',
|
|
278
|
+
});
|
|
279
|
+
attempts.push({
|
|
280
|
+
bin: 'xsel',
|
|
281
|
+
args: ['--clipboard', '--output'],
|
|
282
|
+
via: 'xsel',
|
|
283
|
+
});
|
|
256
284
|
}
|
|
257
285
|
// Even if the session env is missing, try the other family as a fallback.
|
|
258
|
-
attempts.push({
|
|
259
|
-
|
|
286
|
+
attempts.push({
|
|
287
|
+
bin: 'wl-paste',
|
|
288
|
+
args: ['--type', 'image/png', '--no-newline'],
|
|
289
|
+
via: 'wl-paste',
|
|
290
|
+
});
|
|
291
|
+
attempts.push({
|
|
292
|
+
bin: 'xclip',
|
|
293
|
+
args: ['-selection', 'clipboard', '-t', 'image/png', '-o'],
|
|
294
|
+
via: 'xclip',
|
|
295
|
+
});
|
|
260
296
|
const tried = [];
|
|
261
297
|
for (const a of attempts) {
|
|
262
298
|
if (tried.includes(a.via))
|
|
@@ -272,7 +308,10 @@ export async function readClipboardImageDetailed() {
|
|
|
272
308
|
}
|
|
273
309
|
if (process.platform === 'darwin') {
|
|
274
310
|
const data = tryCommand('pngpaste', ['-']);
|
|
275
|
-
return {
|
|
311
|
+
return {
|
|
312
|
+
data: data && data.length ? data : null,
|
|
313
|
+
via: data ? 'pngpaste' : 'no-tool:pngpaste',
|
|
314
|
+
};
|
|
276
315
|
}
|
|
277
316
|
if (process.platform === 'win32') {
|
|
278
317
|
// PowerShell reads the clipboard image and writes raw PNG bytes to stdout.
|
|
@@ -281,8 +320,16 @@ export async function readClipboardImageDetailed() {
|
|
|
281
320
|
'if($img){$ms=New-Object System.IO.MemoryStream; ' +
|
|
282
321
|
'$img.Save($ms,[System.Drawing.Imaging.ImageFormat]::Png); ' +
|
|
283
322
|
'[Console]::OpenStandardOutput().Write($ms.ToArray(),0,$ms.Length)}';
|
|
284
|
-
const data = tryCommand('powershell', [
|
|
285
|
-
|
|
323
|
+
const data = tryCommand('powershell', [
|
|
324
|
+
'-NoProfile',
|
|
325
|
+
'-NonInteractive',
|
|
326
|
+
'-Command',
|
|
327
|
+
ps,
|
|
328
|
+
]);
|
|
329
|
+
return {
|
|
330
|
+
data: data && data.length ? data : null,
|
|
331
|
+
via: data ? 'powershell' : 'no-tool:powershell',
|
|
332
|
+
};
|
|
286
333
|
}
|
|
287
334
|
return { data: null, via: 'unsupported-platform' };
|
|
288
335
|
}
|
|
@@ -329,7 +376,13 @@ export async function findFileByName(workdir, name) {
|
|
|
329
376
|
export function readWindowsClipboardFiles() {
|
|
330
377
|
if (!isWsl())
|
|
331
378
|
return [];
|
|
332
|
-
const out = tryCommand('powershell.exe', [
|
|
379
|
+
const out = tryCommand('powershell.exe', [
|
|
380
|
+
'-STA',
|
|
381
|
+
'-NoProfile',
|
|
382
|
+
'-NonInteractive',
|
|
383
|
+
'-Command',
|
|
384
|
+
PS_GET_FILE_DROP,
|
|
385
|
+
]);
|
|
333
386
|
if (!out)
|
|
334
387
|
return [];
|
|
335
388
|
const text = out.toString('utf-8');
|
|
@@ -360,10 +413,20 @@ export function winPathToWsl(winPath) {
|
|
|
360
413
|
export async function readWindowsClipboardImage() {
|
|
361
414
|
if (!isWsl())
|
|
362
415
|
return null;
|
|
363
|
-
const out = tryCommand('powershell.exe', [
|
|
416
|
+
const out = tryCommand('powershell.exe', [
|
|
417
|
+
'-STA',
|
|
418
|
+
'-NoProfile',
|
|
419
|
+
'-NonInteractive',
|
|
420
|
+
'-EncodedCommand',
|
|
421
|
+
PS_IMAGE_ENCODED,
|
|
422
|
+
]);
|
|
364
423
|
if (!out)
|
|
365
424
|
return null;
|
|
366
|
-
const first = out
|
|
425
|
+
const first = out
|
|
426
|
+
.toString('utf-8')
|
|
427
|
+
.trim()
|
|
428
|
+
.split(String.fromCharCode(10))[0]
|
|
429
|
+
.trim();
|
|
367
430
|
if (!first)
|
|
368
431
|
return null;
|
|
369
432
|
const wslPath = winPathToWsl(first);
|
package/dist/browser.js
CHANGED
|
@@ -165,7 +165,15 @@ const STOP_SELECTORS = [
|
|
|
165
165
|
// Otherwise the agent takes a status (Reading...) for an answer and breaks parsing.
|
|
166
166
|
const STATUS_RE = /^(reading|thinking|searching|analyzing|generating|stop|остановить|читаю|думаю|поиск|анализ)[\s.…]*$/i;
|
|
167
167
|
// DeepSeek's answer when the rate limit is exceeded.
|
|
168
|
-
|
|
168
|
+
//
|
|
169
|
+
// IMPORTANT: the generic "try again later" / "повторите позже" tail is NOT
|
|
170
|
+
// part of this pattern. DeepSeek appends it to MANY toasts ("Server busy.
|
|
171
|
+
// Try again later.", "Service unavailable. Try again later."), so matching it
|
|
172
|
+
// here classified every server hiccup as a RATE LIMIT and sent the agent into
|
|
173
|
+
// a 5-minute wait (and, after the finish-loop stopped reading toasts every
|
|
174
|
+
// tick, looked like "the agent hung"). The rate limit is recognized by its
|
|
175
|
+
// specific wording only.
|
|
176
|
+
const RATE_LIMIT_RE = /(messages? too frequent|too many requests|rate limit|слишком часто|сообщени[яе] слишком част)/i;
|
|
169
177
|
// Transient server-side hiccups (DeepSeek overloaded / hiccup). Unlike the
|
|
170
178
|
// rate limit, these usually clear in seconds, so we retry quickly instead of
|
|
171
179
|
// waiting minutes. "Server busy", 503, "temporarily unavailable", etc.
|
|
@@ -415,7 +423,7 @@ export class DeepSeekBrowser {
|
|
|
415
423
|
try {
|
|
416
424
|
this.context = await chromium.launchPersistentContext(USER_DATA_DIR, options);
|
|
417
425
|
}
|
|
418
|
-
catch
|
|
426
|
+
catch {
|
|
419
427
|
if (this.debug)
|
|
420
428
|
console.error('profile: сношу целиком');
|
|
421
429
|
await nukeProfile();
|
|
@@ -501,7 +509,13 @@ export class DeepSeekBrowser {
|
|
|
501
509
|
await this.context.close();
|
|
502
510
|
}
|
|
503
511
|
catch { }
|
|
504
|
-
|
|
512
|
+
// A restart must REUSE the existing profile. `launch()` would call
|
|
513
|
+
// killStaleChrome() and clean Singleton files, which can race with a
|
|
514
|
+
// sibling agent on the same profile; here we just relaunch the persistent
|
|
515
|
+
// context in place. A fresh window appears only because the old context was
|
|
516
|
+
// actually closed — no second window is created while one is alive.
|
|
517
|
+
await this._launchOnce();
|
|
518
|
+
await this._fixHeadlessUserAgent();
|
|
505
519
|
}
|
|
506
520
|
async waitForLogin() {
|
|
507
521
|
// 1) Already signed in (session cookie in the persistent profile)?
|
|
@@ -1096,7 +1110,11 @@ export class DeepSeekBrowser {
|
|
|
1096
1110
|
})));
|
|
1097
1111
|
// Interruptible: Esc/Ctrl+C must cancel this long wait too,
|
|
1098
1112
|
// otherwise "stop" stays frozen for up to 5 minutes.
|
|
1099
|
-
|
|
1113
|
+
// The wait is ANIMATED through onSendPause (a live countdown), so the
|
|
1114
|
+
// operator sees the agent is waiting on the rate limit instead of a
|
|
1115
|
+
// frozen spinner. `_lastSentAt` is bumped so the countdown renders.
|
|
1116
|
+
const waitSec = Math.ceil(this.rateLimitWaitMs / 1000);
|
|
1117
|
+
const aborted = await this._waitRateLimit(waitSec);
|
|
1100
1118
|
if (aborted)
|
|
1101
1119
|
return '(прервано пользователем)';
|
|
1102
1120
|
continue;
|
|
@@ -1129,7 +1147,12 @@ export class DeepSeekBrowser {
|
|
|
1129
1147
|
max: this.askRetries,
|
|
1130
1148
|
error: e.message,
|
|
1131
1149
|
})));
|
|
1132
|
-
|
|
1150
|
+
// Only a genuinely LOST page justifies a restart. "Target page,
|
|
1151
|
+
// context or browser has been closed" means the page is gone for good;
|
|
1152
|
+
// a bare "browser" mention (e.g. a selector error containing the word)
|
|
1153
|
+
// must NOT tear the window down — that was the "a new window opened"
|
|
1154
|
+
// surprise during a resumed chat. We re-open the SAME context instead.
|
|
1155
|
+
if (/target page, context or browser has been closed|page.*has been closed/i.test(e.message)) {
|
|
1133
1156
|
console.error(theme.warn(this._t('ds.ask_restart_browser')));
|
|
1134
1157
|
try {
|
|
1135
1158
|
await this.restart();
|
|
@@ -1276,6 +1299,22 @@ export class DeepSeekBrowser {
|
|
|
1276
1299
|
// Wait for the upload to finish (the attach preview to appear).
|
|
1277
1300
|
await this.page.waitForTimeout(2000);
|
|
1278
1301
|
}
|
|
1302
|
+
// Wait out a rate-limit pause, showing an animated countdown through
|
|
1303
|
+
// onSendPause so the operator sees the agent is alive (not frozen). Returns
|
|
1304
|
+
// true when the wait was cut short by Esc/Ctrl+C.
|
|
1305
|
+
async _waitRateLimit(totalSec) {
|
|
1306
|
+
const tick = (leftMs) => {
|
|
1307
|
+
const secs = Math.max(0, Math.ceil(leftMs / 1000));
|
|
1308
|
+
if (this.onSendPause) {
|
|
1309
|
+
try {
|
|
1310
|
+
this.onSendPause(secs);
|
|
1311
|
+
}
|
|
1312
|
+
catch { }
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
tick(totalSec * 1000);
|
|
1316
|
+
return await this._sleepInterruptible(totalSec * 1000, tick);
|
|
1317
|
+
}
|
|
1279
1318
|
// Pause between sends. Applied ONLY to agent messages
|
|
1280
1319
|
// (tool-result, system-prompt) so we don't hit the rate limit.
|
|
1281
1320
|
// User input is sent without delay.
|
|
@@ -1560,20 +1599,24 @@ export class DeepSeekBrowser {
|
|
|
1560
1599
|
const deadline = Date.now() + timeout;
|
|
1561
1600
|
let last = '';
|
|
1562
1601
|
let stable = 0;
|
|
1563
|
-
let tick = 0;
|
|
1564
1602
|
while (Date.now() < deadline) {
|
|
1565
1603
|
if (this._abort) {
|
|
1566
1604
|
return last || '(прервано пользователем)';
|
|
1567
1605
|
}
|
|
1568
|
-
//
|
|
1569
|
-
//
|
|
1570
|
-
|
|
1606
|
+
// Rate-limit / server-busy toast. The old code only read the toasts
|
|
1607
|
+
// when `tick % 5 === 0` (once per ~2s), and the throw on the rate limit
|
|
1608
|
+
// made the server-busy branch unreachable. A toast that appears and
|
|
1609
|
+
// disappears between those checks was missed entirely, so the run
|
|
1610
|
+
// waited out the whole deadline and threw ds.send_no_new_answer. We now
|
|
1611
|
+
// read the toast on EVERY tick (a cheap, selector-scoped evaluate) and
|
|
1612
|
+
// classify both conditions independently.
|
|
1613
|
+
{
|
|
1571
1614
|
const pageText = await this._readPageText();
|
|
1572
1615
|
if (isRateLimitText(pageText)) {
|
|
1573
1616
|
throw new RateLimitError(pageText.slice(0, 300));
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1617
|
+
}
|
|
1618
|
+
if (isServerBusyText(pageText)) {
|
|
1619
|
+
throw new ServerBusyError(pageText.slice(0, 300));
|
|
1577
1620
|
}
|
|
1578
1621
|
}
|
|
1579
1622
|
const netFresh = !!this._netCapture && this._netCaptureAt >= this._lastSentAt;
|
package/dist/commands.js
CHANGED
|
@@ -7,7 +7,10 @@ const CR = String.fromCharCode(13);
|
|
|
7
7
|
// ---------- /diff ----------
|
|
8
8
|
export function formatDiff(diffText, opts = {}) {
|
|
9
9
|
const maxLines = opts.maxLines ?? 200;
|
|
10
|
-
const trimmed = String(diffText ?? '')
|
|
10
|
+
const trimmed = String(diffText ?? '')
|
|
11
|
+
.split(CR + NL)
|
|
12
|
+
.join(NL)
|
|
13
|
+
.trimEnd();
|
|
11
14
|
if (!trimmed)
|
|
12
15
|
return '(no changes)';
|
|
13
16
|
const lines = trimmed.split(NL);
|
|
@@ -17,7 +20,9 @@ export function formatDiff(diffText, opts = {}) {
|
|
|
17
20
|
const rest = lines.length - maxLines;
|
|
18
21
|
return (head +
|
|
19
22
|
NL +
|
|
20
|
-
'... [' +
|
|
23
|
+
'... [' +
|
|
24
|
+
rest +
|
|
25
|
+
' more lines, use Bash git diff for the full output]');
|
|
21
26
|
}
|
|
22
27
|
export function diffGitArgs(staged = false) {
|
|
23
28
|
return staged ? 'git diff --staged' : 'git diff';
|
package/dist/config-menu.js
CHANGED
|
@@ -66,7 +66,9 @@ export function runConfigMenu(opts) {
|
|
|
66
66
|
}
|
|
67
67
|
const selected = i === cursor;
|
|
68
68
|
const marker = selected ? theme.prompt('\u276f ') : ' ';
|
|
69
|
-
const label = selected
|
|
69
|
+
const label = selected
|
|
70
|
+
? theme.prompt(f.labelKey ? t(f.labelKey) : f.path)
|
|
71
|
+
: t(f.labelKey || f.path);
|
|
70
72
|
const val = displayValue(f, get(f.path), t);
|
|
71
73
|
const valText = selected ? theme.assistant(val) : theme.dim(val);
|
|
72
74
|
const pathText = selected ? theme.dim(' ' + f.path) : '';
|
package/dist/config.js
CHANGED
|
@@ -85,29 +85,163 @@ function deepMerge(target, source) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
export const CONFIG_SCHEMA = [
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{
|
|
110
|
-
|
|
88
|
+
{
|
|
89
|
+
path: 'ui.locale',
|
|
90
|
+
type: 'enum',
|
|
91
|
+
values: ['ru', 'en'],
|
|
92
|
+
labelKey: 'cfg.f.ui_locale',
|
|
93
|
+
groupKey: 'cfg.group.ui',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
path: 'maxIterations',
|
|
97
|
+
type: 'number',
|
|
98
|
+
min: 0,
|
|
99
|
+
max: 100000,
|
|
100
|
+
labelKey: 'cfg.f.maxIterations',
|
|
101
|
+
groupKey: 'cfg.group.agent',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
path: 'headless',
|
|
105
|
+
type: 'boolean',
|
|
106
|
+
labelKey: 'cfg.f.headless',
|
|
107
|
+
groupKey: 'cfg.group.agent',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
path: 'browser.auth.username',
|
|
111
|
+
type: 'string',
|
|
112
|
+
labelKey: 'cfg.f.browser_authUsername',
|
|
113
|
+
groupKey: 'cfg.group.browser',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
path: 'browser.auth.password',
|
|
117
|
+
type: 'string',
|
|
118
|
+
labelKey: 'cfg.f.browser_authPassword',
|
|
119
|
+
groupKey: 'cfg.group.browser',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
path: 'browser.auth.saveSession',
|
|
123
|
+
type: 'boolean',
|
|
124
|
+
labelKey: 'cfg.f.browser_authSaveSession',
|
|
125
|
+
groupKey: 'cfg.group.browser',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
path: 'debug',
|
|
129
|
+
type: 'boolean',
|
|
130
|
+
labelKey: 'cfg.f.debug',
|
|
131
|
+
groupKey: 'cfg.group.agent',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
path: 'hotReload',
|
|
135
|
+
type: 'boolean',
|
|
136
|
+
labelKey: 'cfg.f.hotReload',
|
|
137
|
+
groupKey: 'cfg.group.agent',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
path: 'confirmation.write',
|
|
141
|
+
type: 'boolean',
|
|
142
|
+
labelKey: 'cfg.f.confirmation_write',
|
|
143
|
+
groupKey: 'cfg.group.confirmation',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
path: 'confirmation.edit',
|
|
147
|
+
type: 'boolean',
|
|
148
|
+
labelKey: 'cfg.f.confirmation_edit',
|
|
149
|
+
groupKey: 'cfg.group.confirmation',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
path: 'confirmation.bash',
|
|
153
|
+
type: 'boolean',
|
|
154
|
+
labelKey: 'cfg.f.confirmation_bash',
|
|
155
|
+
groupKey: 'cfg.group.confirmation',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
path: 'undo.enabled',
|
|
159
|
+
type: 'boolean',
|
|
160
|
+
labelKey: 'cfg.f.undo_enabled',
|
|
161
|
+
groupKey: 'cfg.group.undo',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
path: 'undo.maxBackups',
|
|
165
|
+
type: 'number',
|
|
166
|
+
min: 1,
|
|
167
|
+
max: 100000,
|
|
168
|
+
labelKey: 'cfg.f.undo_maxBackups',
|
|
169
|
+
groupKey: 'cfg.group.undo',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
path: 'transcript.enabled',
|
|
173
|
+
type: 'boolean',
|
|
174
|
+
labelKey: 'cfg.f.transcript_enabled',
|
|
175
|
+
groupKey: 'cfg.group.transcript',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
path: 'browser.answerTimeoutMs',
|
|
179
|
+
type: 'number',
|
|
180
|
+
min: 1000,
|
|
181
|
+
max: 3600000,
|
|
182
|
+
labelKey: 'cfg.f.browser_answerTimeoutMs',
|
|
183
|
+
groupKey: 'cfg.group.browser',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
path: 'browser.askRetries',
|
|
187
|
+
type: 'number',
|
|
188
|
+
min: 1,
|
|
189
|
+
max: 100,
|
|
190
|
+
labelKey: 'cfg.f.browser_askRetries',
|
|
191
|
+
groupKey: 'cfg.group.browser',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
path: 'browser.stabilityChecks',
|
|
195
|
+
type: 'number',
|
|
196
|
+
min: 1,
|
|
197
|
+
max: 100,
|
|
198
|
+
labelKey: 'cfg.f.browser_stabilityChecks',
|
|
199
|
+
groupKey: 'cfg.group.browser',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
path: 'browser.stabilityDelayMs',
|
|
203
|
+
type: 'number',
|
|
204
|
+
min: 0,
|
|
205
|
+
max: 60000,
|
|
206
|
+
labelKey: 'cfg.f.browser_stabilityDelayMs',
|
|
207
|
+
groupKey: 'cfg.group.browser',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
path: 'browser.minSendIntervalMs',
|
|
211
|
+
type: 'number',
|
|
212
|
+
min: 0,
|
|
213
|
+
max: 600000,
|
|
214
|
+
labelKey: 'cfg.f.browser_minSendIntervalMs',
|
|
215
|
+
groupKey: 'cfg.group.browser',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
path: 'browser.rateLimitWaitMs',
|
|
219
|
+
type: 'number',
|
|
220
|
+
min: 0,
|
|
221
|
+
max: 3600000,
|
|
222
|
+
labelKey: 'cfg.f.browser_rateLimitWaitMs',
|
|
223
|
+
groupKey: 'cfg.group.browser',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
path: 'browser.maxRateLimitRetries',
|
|
227
|
+
type: 'number',
|
|
228
|
+
min: 0,
|
|
229
|
+
max: 100,
|
|
230
|
+
labelKey: 'cfg.f.browser_maxRateLimitRetries',
|
|
231
|
+
groupKey: 'cfg.group.browser',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
path: 'browser.deepThinking',
|
|
235
|
+
type: 'boolean',
|
|
236
|
+
labelKey: 'cfg.f.browser_deepThinking',
|
|
237
|
+
groupKey: 'cfg.group.browser',
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
path: 'browser.webSearch',
|
|
241
|
+
type: 'boolean',
|
|
242
|
+
labelKey: 'cfg.f.browser_webSearch',
|
|
243
|
+
groupKey: 'cfg.group.browser',
|
|
244
|
+
},
|
|
111
245
|
];
|
|
112
246
|
export function getConfigField(path) {
|
|
113
247
|
return CONFIG_SCHEMA.find((f) => f.path === path);
|