zames_pro 2.82.136 → 2.82.138
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/dist/agent-loop.js +11 -14
- package/dist/answer-parse.js +10 -16
- package/dist/attach-refs.js +3 -3
- package/dist/attachments.js +7 -8
- package/dist/backlog.js +2 -5
- package/dist/banner.js +4 -5
- package/dist/browser-chats.js +4 -4
- package/dist/browser-dom.js +16 -15
- package/dist/browser-login.js +9 -5
- package/dist/browser-pure.js +5 -5
- package/dist/browser.js +10 -20
- package/dist/changelog.js +15 -21
- package/dist/checkpoint.js +6 -10
- package/dist/cli-args.js +4 -6
- package/dist/code-blocks.js +3 -6
- package/dist/commands.js +6 -10
- package/dist/compact.js +1 -1
- package/dist/config-command.js +6 -4
- package/dist/config-commands.js +2 -3
- package/dist/config-live.js +7 -13
- package/dist/config-menu.js +5 -4
- package/dist/config.js +6 -9
- package/dist/deepseek-ui.js +7 -7
- package/dist/diagnostics.js +5 -7
- package/dist/diff.js +8 -10
- package/dist/extraTools.js +3 -3
- package/dist/fsutil.js +0 -4
- package/dist/gitTools.js +5 -4
- package/dist/glm-browser.js +2 -10
- package/dist/glm-chats.js +0 -1
- package/dist/glm-net-capture.js +1 -1
- package/dist/glm-ui.js +2 -2
- package/dist/goal.js +1 -4
- package/dist/i18n/common.js +2 -2
- package/dist/i18n.js +1 -2
- package/dist/index.js +8 -19
- package/dist/input/suggest.js +4 -7
- package/dist/input.js +6 -11
- package/dist/markdown.js +0 -1
- package/dist/mcp.js +2 -5
- package/dist/net-capture.js +1 -1
- package/dist/path-token.js +7 -14
- package/dist/prompt.js +4 -3
- package/dist/qwen-browser.js +1 -8
- package/dist/qwen-chats.js +0 -1
- package/dist/qwen-net-capture.js +4 -7
- package/dist/qwen-ui.js +0 -3
- package/dist/run-task.js +11 -11
- package/dist/self-review.js +0 -5
- package/dist/send-runtime.js +6 -10
- package/dist/send-telemetry.js +5 -7
- package/dist/send-throttle.js +4 -5
- package/dist/sessions.js +6 -7
- package/dist/spinner.js +3 -4
- package/dist/statusline.js +6 -10
- package/dist/subagent.js +1 -1
- package/dist/system-prompt.js +1 -1
- package/dist/tools.js +1 -1
- package/dist/tui/widgets.js +0 -1
- package/package.json +1 -1
package/dist/goal.js
CHANGED
|
@@ -69,13 +69,10 @@ export function buildContinuationDirective(reason) {
|
|
|
69
69
|
// Collapse whitespace in the model-written reason: a NEWLINE started a new
|
|
70
70
|
// line of the directive, letting the evaluator's text inject an extra
|
|
71
71
|
// instruction into the next turn.
|
|
72
|
-
(
|
|
72
|
+
(String(reason ?? '').trim()
|
|
73
73
|
? 'Evaluator note: ' + String(reason).trim().replace(/\s+/g, ' ')
|
|
74
74
|
: ''));
|
|
75
75
|
}
|
|
76
|
-
function argumentReason(reason) {
|
|
77
|
-
return String(reason ?? '').trim().length > 0;
|
|
78
|
-
}
|
|
79
76
|
/**
|
|
80
77
|
* The text a verdict may be read from. A plain answer is returned as-is; a
|
|
81
78
|
* TOOL CALL (JSON or DSML/XML) yields only its `respond` message, because the
|
package/dist/i18n/common.js
CHANGED
|
@@ -199,7 +199,7 @@ export const commonMessages = {
|
|
|
199
199
|
ru: '📎 Вложен файл {marker} {name} ({size}) — {path}',
|
|
200
200
|
en: '📎 Attached file {marker} {name} ({size}) — {path}',
|
|
201
201
|
},
|
|
202
|
-
//
|
|
202
|
+
// Shown when `@path` references in a task were inlined as file contents.
|
|
203
203
|
'msg.at_refs': {
|
|
204
204
|
ru: '📄 Встроено файлов по @-ссылкам: {n} —',
|
|
205
205
|
en: '📄 Inlined @-referenced files: {n} —',
|
|
@@ -303,7 +303,7 @@ export const commonMessages = {
|
|
|
303
303
|
},
|
|
304
304
|
'cost.auto_compacts': { ru: ' авто-сжатий: {n}', en: ' auto-compacts: {n}' },
|
|
305
305
|
'cost.by_tool': { ru: ' по инструментам:', en: ' by tool:' },
|
|
306
|
-
//
|
|
306
|
+
// Total time spent inside tools, then a top-N per-tool breakdown.
|
|
307
307
|
'cost.by_time': {
|
|
308
308
|
ru: ' время в инструментах: {dur}; топ по времени:',
|
|
309
309
|
en: ' time in tools: {dur}; top by time:',
|
package/dist/i18n.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Goals:
|
|
4
4
|
// * change the interface language (help, service messages);
|
|
5
|
-
// * the agent's answer language — via the system-prompt
|
|
6
|
-
// * the agent's answer language for a plain language.
|
|
5
|
+
// * the agent's answer language — via the system-prompt.
|
|
7
6
|
//
|
|
8
7
|
// Strategy: a single key->translation map. New keys are easy to add.
|
|
9
8
|
// Russian texts (ru) are the default, English (en) is the alternative.
|
package/dist/index.js
CHANGED
|
@@ -76,7 +76,6 @@ function authMarkerExists(provider = config.provider) {
|
|
|
76
76
|
return false;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
// ---------- CLI parsing ----------
|
|
80
79
|
// The pure helpers live in src/cli-args.ts and are bound to this process's
|
|
81
80
|
// argv ONCE here. Call sites keep using getArg/hasFlag/getPositional.
|
|
82
81
|
const { getArg, hasFlag, getPositional } = createCliArgs(process.argv.slice(2));
|
|
@@ -182,7 +181,6 @@ if (jsonMode) {
|
|
|
182
181
|
console.error(...a);
|
|
183
182
|
});
|
|
184
183
|
}
|
|
185
|
-
// ---------- session persistence ----------
|
|
186
184
|
// We store sessions (DeepSeek chats) in ~/.zames/.sessions so they survive
|
|
187
185
|
// a restart and are available for explicit restore (--resume-last,
|
|
188
186
|
// --chat <id>, /resume-id <id>). They are NOT brought up automatically on launch.
|
|
@@ -231,7 +229,6 @@ async function saveGoal(workdir, goal) {
|
|
|
231
229
|
// best-effort: a failed write must not break the command
|
|
232
230
|
}
|
|
233
231
|
}
|
|
234
|
-
// ---------- hot reload ----------
|
|
235
232
|
// We keep references to the logic modules in the mod object. The /reload
|
|
236
233
|
// command re-reads them via a dynamic import with a timestamp query — Node
|
|
237
234
|
// caches ESM by URL, so such an import returns a fresh version of the module.
|
|
@@ -257,7 +254,7 @@ const RELOADABLE = [
|
|
|
257
254
|
'config',
|
|
258
255
|
'fsutil',
|
|
259
256
|
'backlog',
|
|
260
|
-
//
|
|
257
|
+
// Modules index.ts now calls through the mod bag: without this a
|
|
261
258
|
// /reload silently kept the OLD commands/run-task/attach-refs until a restart.
|
|
262
259
|
// 'input/layout' is NOT listed: its only live consumers are input.ts (the
|
|
263
260
|
// long-lived LineEditor, itself not reloadable) and spinner.ts, which already
|
|
@@ -406,7 +403,6 @@ function warnIfBrowserChanged() {
|
|
|
406
403
|
}
|
|
407
404
|
catch { }
|
|
408
405
|
}
|
|
409
|
-
// ---------- helpers ----------
|
|
410
406
|
function printHelp() {
|
|
411
407
|
// Self-development commands (backlog / self-review) are shown ONLY in dev
|
|
412
408
|
// mode. A regular package install must not advertise them to the user — and
|
|
@@ -594,7 +590,7 @@ async function refreshDynamicCommands(workdir) {
|
|
|
594
590
|
}
|
|
595
591
|
const cmds = await loadCommands(workdir);
|
|
596
592
|
for (const c of cmds) {
|
|
597
|
-
//
|
|
593
|
+
// Carry the declared argument hint as a separate field so the hint is
|
|
598
594
|
// SHOWN in the suggest list but never inserted into the input line.
|
|
599
595
|
out.push({
|
|
600
596
|
name: '/' + c.name,
|
|
@@ -619,7 +615,7 @@ async function expandSlashTarget(workdir, name, rest) {
|
|
|
619
615
|
const commands = await loadCommands(workdir);
|
|
620
616
|
const cmd = commands.find((c) => c.name.toLowerCase() === name.toLowerCase());
|
|
621
617
|
if (cmd) {
|
|
622
|
-
//
|
|
618
|
+
// Refuse to run when declared positional arguments are missing.
|
|
623
619
|
const missing = mod.commands.missingCommandArgs(cmd.arguments, rest);
|
|
624
620
|
if (missing.length)
|
|
625
621
|
return { missing };
|
|
@@ -689,7 +685,6 @@ async function cleanTmpDir() {
|
|
|
689
685
|
console.error('tmp: cleanup failed:', e.message);
|
|
690
686
|
}
|
|
691
687
|
}
|
|
692
|
-
// ---------- workdir resolution ----------
|
|
693
688
|
// The agent works in the directory it was launched from (process.cwd()).
|
|
694
689
|
// This is the sandbox root: tools cannot go above it.
|
|
695
690
|
async function resolveWorkdir() {
|
|
@@ -701,7 +696,6 @@ async function resolveWorkdir() {
|
|
|
701
696
|
}
|
|
702
697
|
return dir;
|
|
703
698
|
}
|
|
704
|
-
// ---------- restored dialogue ----------
|
|
705
699
|
/**
|
|
706
700
|
* The answer timeout for a one-off provider call (compact summary, etc.).
|
|
707
701
|
* Qwen's cold guest turns are far slower than DeepSeek's, so using
|
|
@@ -1403,14 +1397,13 @@ async function main() {
|
|
|
1403
1397
|
// Timestamp of the last Ctrl+C while the agent was busy. Two presses
|
|
1404
1398
|
// within 2s escalate from "abort the running tool" to "stop the whole run".
|
|
1405
1399
|
let lastCtrlCAt = 0;
|
|
1406
|
-
// ---------- review mode state ----------
|
|
1407
1400
|
// null — normal mode.
|
|
1408
1401
|
// { snapDir, snapName, originalWorkdir } — we're inside a snapshot, the chat
|
|
1409
1402
|
// is already initialized with the review prompt, the user can just type "fix...".
|
|
1410
1403
|
let reviewMode = null;
|
|
1411
1404
|
process.on('SIGINT', async () => {
|
|
1412
1405
|
running = false;
|
|
1413
|
-
// Kill any background processes
|
|
1406
|
+
// Kill any background processes so no dev-server outlives the agent.
|
|
1414
1407
|
killAllBackground();
|
|
1415
1408
|
await browser.close().catch(() => { });
|
|
1416
1409
|
// Close the lazy headless browser from web.js, otherwise it would stay
|
|
@@ -1458,7 +1451,6 @@ async function main() {
|
|
|
1458
1451
|
console.error(theme.error(t('msg.open_chat_error', { v: e.message })));
|
|
1459
1452
|
}
|
|
1460
1453
|
}
|
|
1461
|
-
// ---------- input: permanent line below + status above ----------
|
|
1462
1454
|
// In TTY we use LineEditor: it owns the input all the time, shows the
|
|
1463
1455
|
// status above the input line and prints the agent's answers ABOVE it, so
|
|
1464
1456
|
// the typed text is never overwritten by output. In non-TTY (pipe) —
|
|
@@ -1468,7 +1460,7 @@ async function main() {
|
|
|
1468
1460
|
// swap. Assigned where the editor is built (see the login wiring below);
|
|
1469
1461
|
// a no-op until then so /provider before setup is harmless.
|
|
1470
1462
|
let wireLoginCallbacks = () => { };
|
|
1471
|
-
//
|
|
1463
|
+
// statusLine hook timer (cleared on exit).
|
|
1472
1464
|
let statusLineTimer = null;
|
|
1473
1465
|
let waiter = null;
|
|
1474
1466
|
const takeInput = () => {
|
|
@@ -1619,7 +1611,7 @@ async function main() {
|
|
|
1619
1611
|
};
|
|
1620
1612
|
};
|
|
1621
1613
|
wireLoginCallbacks(browser);
|
|
1622
|
-
//
|
|
1614
|
+
// statusLine hook: run the configured command on its own timer (never
|
|
1623
1615
|
// per-render — a spawn would block typing) and show its first stdout line
|
|
1624
1616
|
// in the status line. Off when ui.statusLineCommand is empty.
|
|
1625
1617
|
if (config.ui.statusLineCommand) {
|
|
@@ -1878,7 +1870,6 @@ async function main() {
|
|
|
1878
1870
|
edAny._origLog = origLog;
|
|
1879
1871
|
edAny._origErr = origErr;
|
|
1880
1872
|
}
|
|
1881
|
-
// ---------- /config ----------
|
|
1882
1873
|
// View and edit settings without a restart. Values are validated against
|
|
1883
1874
|
// CONFIG_SCHEMA (src/config.ts) and written to the project .zamesrc.json.
|
|
1884
1875
|
// The language (ui.locale) is applied immediately: we change currentLocale,
|
|
@@ -2407,7 +2398,6 @@ async function main() {
|
|
|
2407
2398
|
}
|
|
2408
2399
|
continue;
|
|
2409
2400
|
}
|
|
2410
|
-
// ---------- Self-review ----------
|
|
2411
2401
|
if (lower === '/self-review' || lower.startsWith('/self-review ')) {
|
|
2412
2402
|
const focus = trimmed.slice('/self-review'.length).trim();
|
|
2413
2403
|
// Remember where to return
|
|
@@ -2571,7 +2561,6 @@ async function main() {
|
|
|
2571
2561
|
}
|
|
2572
2562
|
continue;
|
|
2573
2563
|
}
|
|
2574
|
-
// ---------- Regular commands ----------
|
|
2575
2564
|
if (lower === '/chats' || lower.startsWith('/chats ')) {
|
|
2576
2565
|
const filter = trimmed.slice('/chats'.length).trim().toLowerCase();
|
|
2577
2566
|
const spin = editor || mod.createSpinner(currentLocale);
|
|
@@ -4065,7 +4054,7 @@ async function main() {
|
|
|
4065
4054
|
}
|
|
4066
4055
|
}
|
|
4067
4056
|
}
|
|
4068
|
-
//
|
|
4057
|
+
// A custom command with mandatory `arguments:` refuses to run until the
|
|
4069
4058
|
// operator supplies them (by position).
|
|
4070
4059
|
if (missingArgs) {
|
|
4071
4060
|
console.error(theme.error(t('msg.missing_args', { v: missingArgs.join(', ') })));
|
|
@@ -4081,7 +4070,7 @@ async function main() {
|
|
|
4081
4070
|
: expandedTask !== null
|
|
4082
4071
|
? expandedTask
|
|
4083
4072
|
: trimmed;
|
|
4084
|
-
//
|
|
4073
|
+
// Inline any `@path` references into the task so the agent does not
|
|
4085
4074
|
// spend a round-trip reading a file the operator already pointed at. Only
|
|
4086
4075
|
// existing files are inlined; a bare `@name` in prose is left untouched.
|
|
4087
4076
|
let finalTaskText = taskText;
|
package/dist/input/suggest.js
CHANGED
|
@@ -107,13 +107,10 @@ export function completeSlashCommand(buf, sugg, selected) {
|
|
|
107
107
|
prefix = prefix.slice(0, -1);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
//
|
|
111
|
-
// everything
|
|
112
|
-
// common first letter like "/h")
|
|
113
|
-
// never typed.
|
|
114
|
-
// With an EMPTY buffer everything matches and the shared prefix collapses
|
|
115
|
-
// to a bare "/" (or a common first letter like "/h"): Tab must not guess a
|
|
116
|
-
// command the operator has not started typing.
|
|
110
|
+
// A completion must EXTEND what is already typed: with an empty buffer the
|
|
111
|
+
// list matches everything and the shared prefix used to collapse to a bare
|
|
112
|
+
// "/" (or a common first letter like "/h"), so Tab inserted something the
|
|
113
|
+
// operator never typed.
|
|
117
114
|
if (typed.length < 2)
|
|
118
115
|
return null;
|
|
119
116
|
if (!prefix.toLowerCase().startsWith(typed))
|
package/dist/input.js
CHANGED
|
@@ -96,7 +96,7 @@ export class LineEditor {
|
|
|
96
96
|
// is stored so it can be removed in dispose().
|
|
97
97
|
_onResize;
|
|
98
98
|
_resizeTimer;
|
|
99
|
-
// ESC disambiguation
|
|
99
|
+
// ESC disambiguation: a read may end on a bare ESC that is really the
|
|
100
100
|
// head of a control sequence split across two reads. We hold it briefly and
|
|
101
101
|
// fire onEscape only if no tail arrives.
|
|
102
102
|
_escPending;
|
|
@@ -173,7 +173,7 @@ export class LineEditor {
|
|
|
173
173
|
// A callback the editor calls before every render to read the agent's
|
|
174
174
|
// current task-list summary ("tasks: 2/5"), empty when there is no list.
|
|
175
175
|
onTasksQuery;
|
|
176
|
-
// Extra status text produced by an external command (
|
|
176
|
+
// Extra status text produced by an external command (the statusLine hook).
|
|
177
177
|
// Set via setStatusLine(); shown before the queue/tasks badges. Kept as a
|
|
178
178
|
// plain field because the CALLER runs the command on its own timer (a
|
|
179
179
|
// per-render spawn would block typing).
|
|
@@ -361,7 +361,7 @@ export class LineEditor {
|
|
|
361
361
|
return '';
|
|
362
362
|
}
|
|
363
363
|
}
|
|
364
|
-
// The statusLine hook text
|
|
364
|
+
// The statusLine hook text, shown before the queue/tasks badges.
|
|
365
365
|
// A trailing space separates it from whatever follows; empty hides it.
|
|
366
366
|
_statusLineBadge() {
|
|
367
367
|
return this.statusLine ? theme.system(this.statusLine) + ' ' : '';
|
|
@@ -605,7 +605,7 @@ export class LineEditor {
|
|
|
605
605
|
}
|
|
606
606
|
// Update the context window size (from config.ui.contextLimit). The next
|
|
607
607
|
// render recomputes the percentage and its color with the new limit.
|
|
608
|
-
// Set the statusLine hook text
|
|
608
|
+
// Set the statusLine hook text. The caller runs the command on its
|
|
609
609
|
// own timer; this only stores + repaints. An empty string hides the badge.
|
|
610
610
|
setStatusLine(text) {
|
|
611
611
|
const t = String(text || '').trim();
|
|
@@ -644,7 +644,6 @@ export class LineEditor {
|
|
|
644
644
|
this.pastes = [];
|
|
645
645
|
this._render();
|
|
646
646
|
}
|
|
647
|
-
// ---------- rendering ----------
|
|
648
647
|
_eraseBlock() {
|
|
649
648
|
if (!this.rendered)
|
|
650
649
|
return;
|
|
@@ -936,7 +935,6 @@ export class LineEditor {
|
|
|
936
935
|
process.stdout.write(String(text) + NL);
|
|
937
936
|
this._writeBlock();
|
|
938
937
|
}
|
|
939
|
-
// ---------- spinner-compatible interface ----------
|
|
940
938
|
setStatus(text) {
|
|
941
939
|
this.statusText = text || '';
|
|
942
940
|
this._render();
|
|
@@ -1124,7 +1122,6 @@ export class LineEditor {
|
|
|
1124
1122
|
const wrapped = wrapToWidth('⚠ ' + msg, maxW);
|
|
1125
1123
|
this.printAbove(NL + theme.warn(wrapped));
|
|
1126
1124
|
}
|
|
1127
|
-
// ---------- input ----------
|
|
1128
1125
|
_submit() {
|
|
1129
1126
|
if (this.locked) {
|
|
1130
1127
|
this._render();
|
|
@@ -1367,7 +1364,7 @@ export class LineEditor {
|
|
|
1367
1364
|
}
|
|
1368
1365
|
// Bare ESC: wait a moment for a possible control-sequence tail before firing
|
|
1369
1366
|
// onEscape. Without this a sequence split across two reads (ESC, then "[A")
|
|
1370
|
-
// looked like a lone Escape and aborted the current generation
|
|
1367
|
+
// looked like a lone Escape and aborted the current generation.
|
|
1371
1368
|
_scheduleEscape() {
|
|
1372
1369
|
this._escPending = true;
|
|
1373
1370
|
if (this._escTimer)
|
|
@@ -1460,7 +1457,6 @@ export class LineEditor {
|
|
|
1460
1457
|
chars.splice(this.cursor, i - this.cursor);
|
|
1461
1458
|
this.buf = chars.join('');
|
|
1462
1459
|
}
|
|
1463
|
-
// ---------- history ----------
|
|
1464
1460
|
_historyUp() {
|
|
1465
1461
|
if (!this.history.length)
|
|
1466
1462
|
return;
|
|
@@ -1485,7 +1481,6 @@ export class LineEditor {
|
|
|
1485
1481
|
}
|
|
1486
1482
|
this.cursor = Array.from(this.buf).length;
|
|
1487
1483
|
}
|
|
1488
|
-
// ---------- reverse-search (Ctrl+R) ----------
|
|
1489
1484
|
// Start an incremental reverse search, keeping the current buffer aside so
|
|
1490
1485
|
// Esc restores it (bash behavior). An empty history leaves the editor
|
|
1491
1486
|
// untouched.
|
|
@@ -1596,7 +1591,7 @@ export class LineEditor {
|
|
|
1596
1591
|
// A previous read ended on a bare ESC. That is ambiguous: the Escape key
|
|
1597
1592
|
// or the head of a control sequence split across reads (arrows, Ctrl+Del).
|
|
1598
1593
|
// Reattach the ESC and parse the whole sequence, so a split arrow does not
|
|
1599
|
-
// abort the generation
|
|
1594
|
+
// abort the generation.
|
|
1600
1595
|
if (this._escPending) {
|
|
1601
1596
|
this._escPending = false;
|
|
1602
1597
|
if (this._escTimer) {
|
package/dist/markdown.js
CHANGED
package/dist/mcp.js
CHANGED
|
@@ -477,11 +477,8 @@ export async function createMcpPool(opts) {
|
|
|
477
477
|
}
|
|
478
478
|
}
|
|
479
479
|
// Distinct servers can produce the SAME qualified name (server 'a' + tool
|
|
480
|
-
// 'b__c' vs server 'a__b' + tool 'c')
|
|
481
|
-
//
|
|
482
|
-
// tool silently unreachable (the model calls "the right name", a DIFFERENT
|
|
483
|
-
// tool runs). Rename the later one with a numeric suffix so every name is
|
|
484
|
-
// unique.
|
|
480
|
+
// 'b__c' vs server 'a__b' + tool 'c'); dedupeToolNames below explains why
|
|
481
|
+
// that has to be fixed rather than left to tools.find().
|
|
485
482
|
dedupeToolNames(tools);
|
|
486
483
|
let closed = false;
|
|
487
484
|
return {
|
package/dist/net-capture.js
CHANGED
|
@@ -259,7 +259,7 @@ export function isFinishedWithAnswer(body) {
|
|
|
259
259
|
return false;
|
|
260
260
|
// A truncated RESPONSE head (a lone `{`) is NOT a complete answer: the
|
|
261
261
|
// finish loop would return it as the model's text and the tool-call parser
|
|
262
|
-
// would fail on it. Require real content (see
|
|
262
|
+
// would fail on it. Require real content (see responseTextLength).
|
|
263
263
|
return responseContentLength(b) >= MIN_REAL_ANSWER_CHARS;
|
|
264
264
|
}
|
|
265
265
|
// The CONTEXT size (in tokens) DeepSeek reports for the current answer.
|
package/dist/path-token.js
CHANGED
|
@@ -118,15 +118,11 @@ export function extractAtFileRefs(text) {
|
|
|
118
118
|
}
|
|
119
119
|
let j = i + 1;
|
|
120
120
|
let tok = '';
|
|
121
|
-
// The token is rejected WHOLE when it contains an at-sign that is NOT a
|
|
122
|
-
// list separator — `@Component` / `user@host` are a decorator and an
|
|
123
|
-
// email. Trimming the prefix at that at-sign would otherwise inline a
|
|
124
|
-
// bogus path.
|
|
125
|
-
let hadInnerAt = false;
|
|
126
121
|
// A token that contains an at-sign (`@Component`, `user@host`) is a
|
|
127
|
-
// decorator
|
|
128
|
-
//
|
|
129
|
-
// BEFORE it to the inliner
|
|
122
|
+
// decorator or an email, NOT a file reference, and the WHOLE token is
|
|
123
|
+
// rejected: the scan used to stop at that at-sign and hand the part
|
|
124
|
+
// BEFORE it to the inliner, which inlined a bogus path.
|
|
125
|
+
let hadInnerAt = false;
|
|
130
126
|
const isRef = (t) => t.length > 0 && !t.includes('@');
|
|
131
127
|
while (j < n && !isRefEnd(chars[j])) {
|
|
132
128
|
// A COMMA directly followed by another `@` starts the NEXT reference:
|
|
@@ -154,12 +150,9 @@ export function extractAtFileRefs(text) {
|
|
|
154
150
|
seen.add(cleaned);
|
|
155
151
|
out.push(cleaned);
|
|
156
152
|
}
|
|
157
|
-
// The
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
// The scan stopped ON a separator that split the token (`,`, `;` or a
|
|
161
|
-
// second at-sign): resume AT it so the next part is scanned as its own
|
|
162
|
-
// reference. Continuing past it dropped the second file of a list.
|
|
153
|
+
// The scan may have stopped ON a separator that split the token (`,`, `;`
|
|
154
|
+
// or a second at-sign): resume AT it so the next part is scanned as its
|
|
155
|
+
// own reference. Continuing past it dropped the second file of a list.
|
|
163
156
|
const sep = j < n ? chars[j] : '';
|
|
164
157
|
if (j > i + 1 && (sep === ',' || sep === ';' || sep === '@'))
|
|
165
158
|
i = j;
|
package/dist/prompt.js
CHANGED
|
@@ -31,7 +31,8 @@ export async function promptOnce(question) {
|
|
|
31
31
|
const wasRaw = stdin.isRaw;
|
|
32
32
|
stdin.setRawMode(true);
|
|
33
33
|
stdin.resume();
|
|
34
|
-
//
|
|
34
|
+
// Bracketed paste is enabled/disabled in pairs, so the terminal is left in
|
|
35
|
+
// normal mode however the prompt exits.
|
|
35
36
|
stdout.write('\x1b[?2004h');
|
|
36
37
|
stdout.write(question);
|
|
37
38
|
let line = '';
|
|
@@ -40,11 +41,11 @@ export async function promptOnce(question) {
|
|
|
40
41
|
const PASTE_START = '\x1b[200~';
|
|
41
42
|
const PASTE_END = '\x1b[201~';
|
|
42
43
|
const redraw = () => {
|
|
43
|
-
//
|
|
44
|
+
// Redraw in place: CR + erase-to-end, then the prompt and the line again.
|
|
44
45
|
stdout.write(String.fromCharCode(13));
|
|
45
46
|
stdout.write('\x1b[K');
|
|
46
47
|
stdout.write(question + line);
|
|
47
|
-
//
|
|
48
|
+
// Step back to the cursor, measured in UTF-16 units.
|
|
48
49
|
const back = line.length - cursor;
|
|
49
50
|
if (back > 0)
|
|
50
51
|
stdout.write('\x1b[' + back + 'D');
|
package/dist/qwen-browser.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// and the chat-id scheme. DeepSeekBrowser is already large; folding a second
|
|
7
7
|
// site into it would make both harder to audit. The shared surface is the
|
|
8
8
|
// ChatProvider interface (types.ts); the agent loop only needs BrowserLike and
|
|
9
|
-
// never sees
|
|
9
|
+
// never sees a concrete class.
|
|
10
10
|
//
|
|
11
11
|
// Verified against a live session (headless Chromium, guest + logged in):
|
|
12
12
|
// input -> textarea.message-input-textarea
|
|
@@ -233,7 +233,6 @@ export class QwenBrowser {
|
|
|
233
233
|
sendIntervalMs() {
|
|
234
234
|
return this.minSendIntervalMs;
|
|
235
235
|
}
|
|
236
|
-
// ---------- lifecycle ----------
|
|
237
236
|
async launch() {
|
|
238
237
|
if (await profileLooksLocked(QWEN_USER_DATA_DIR)) {
|
|
239
238
|
if (this.debug)
|
|
@@ -487,7 +486,6 @@ export class QwenBrowser {
|
|
|
487
486
|
}
|
|
488
487
|
catch { }
|
|
489
488
|
}
|
|
490
|
-
// ---------- login ----------
|
|
491
489
|
// Are we signed in? Detected from the PAGE, the same way a human sees it: a
|
|
492
490
|
// signed-out visitor has the header "Log in" / "Sign up" buttons, a signed-in
|
|
493
491
|
// one shows the account menu instead. The `auths/refresh` API was the first
|
|
@@ -1005,7 +1003,6 @@ export class QwenBrowser {
|
|
|
1005
1003
|
}
|
|
1006
1004
|
return false;
|
|
1007
1005
|
}
|
|
1008
|
-
// ---------- chat management ----------
|
|
1009
1006
|
async newChat() {
|
|
1010
1007
|
const before = await this.getCurrentChatId();
|
|
1011
1008
|
const btn = this.page.locator(NEW_CHAT_SELECTOR).first();
|
|
@@ -1272,7 +1269,6 @@ export class QwenBrowser {
|
|
|
1272
1269
|
getLastTokenUsage() {
|
|
1273
1270
|
return this._lastTokenUsage;
|
|
1274
1271
|
}
|
|
1275
|
-
// ---------- answer reading ----------
|
|
1276
1272
|
async _readLastAnswerText() {
|
|
1277
1273
|
if (this._netCapture && this._netCaptureAt >= this._lastSentAt) {
|
|
1278
1274
|
return this._netCapture;
|
|
@@ -1346,7 +1342,6 @@ export class QwenBrowser {
|
|
|
1346
1342
|
catch { }
|
|
1347
1343
|
return false;
|
|
1348
1344
|
}
|
|
1349
|
-
// ---------- toggles (model thinking / search) ----------
|
|
1350
1345
|
getToggleStatesSync() {
|
|
1351
1346
|
return this._toggles;
|
|
1352
1347
|
}
|
|
@@ -1523,7 +1518,6 @@ export class QwenBrowser {
|
|
|
1523
1518
|
}
|
|
1524
1519
|
catch { }
|
|
1525
1520
|
}
|
|
1526
|
-
// ---------- diagnostics ----------
|
|
1527
1521
|
async dumpDom(filePath) {
|
|
1528
1522
|
if (!this.page)
|
|
1529
1523
|
throw new Error('qwen: browser not launched');
|
|
@@ -1566,7 +1560,6 @@ export class QwenBrowser {
|
|
|
1566
1560
|
chatUrl(id) {
|
|
1567
1561
|
return chatUrl(id);
|
|
1568
1562
|
}
|
|
1569
|
-
// ---------- ask ----------
|
|
1570
1563
|
async _waitForSendSlot(agent) {
|
|
1571
1564
|
if (!agent)
|
|
1572
1565
|
return;
|
package/dist/qwen-chats.js
CHANGED
|
@@ -84,7 +84,6 @@ export function scrapeQwenChatMessages(opts) {
|
|
|
84
84
|
}
|
|
85
85
|
return out;
|
|
86
86
|
}
|
|
87
|
-
// ---------- API payload parsers (pure, unit-tested) ----------
|
|
88
87
|
/**
|
|
89
88
|
* Parse the chat list from `GET /api/v2/chats/?page=1&exclude_project=true`.
|
|
90
89
|
* The shape has drifted between builds, so accept several:
|
package/dist/qwen-net-capture.js
CHANGED
|
@@ -223,13 +223,10 @@ export function isFinishedWithoutAnswer(body) {
|
|
|
223
223
|
return extractFromSse(b) === '';
|
|
224
224
|
}
|
|
225
225
|
// True when the body carries Qwen's rate-limit / busy notice, so ask() can
|
|
226
|
-
// back off instead of waiting out the deadline.
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
// ERROR payload (`event: error` / `"type":"error"`) or as a code; answer text
|
|
231
|
-
// lives in `choices[].delta.content`. We therefore only inspect the body when
|
|
232
|
-
// it is NOT a plain answer stream, or when it carries no answer content.
|
|
226
|
+
// back off instead of waiting out the deadline. The classifier runs against the
|
|
227
|
+
// RAW body, where a phrase like `please try again later` also matches a NORMAL
|
|
228
|
+
// answer (the model writing about retry logic) and made ask() back off on a
|
|
229
|
+
// good turn. So it only counts an ERROR payload or a body with NO answer text.
|
|
233
230
|
export function isRateLimitText(body) {
|
|
234
231
|
const b = String(body || '');
|
|
235
232
|
if (!b)
|
package/dist/qwen-ui.js
CHANGED
|
@@ -174,9 +174,6 @@ export const STATUS_RE = /^(thinking|searching|generating|thinking completed|о
|
|
|
174
174
|
// Qwen's rate-limit / busy notices (best-effort; the toast may be short-lived).
|
|
175
175
|
export const RATE_LIMIT_RE = /too many requests|rate limit|too frequent|please try again later|слишком часто/i;
|
|
176
176
|
export const SERVER_BUSY_RE = /server is busy|service unavailable|try again later/i;
|
|
177
|
-
// ---------- toggles ----------
|
|
178
|
-
// The "Auto" thinking/search toggle and the mode selector. Read-only probing
|
|
179
|
-
// support; the model's thinking mode is set via config when supported.
|
|
180
177
|
// ---------- toggles (thinking mode / web search) ----------
|
|
181
178
|
// Qwen exposes thinking as a MODE dropdown (`.qwen-thinking-selector`, label
|
|
182
179
|
// "Auto" by default) with options Auto / Thinking / Fast — NOT a boolean
|
package/dist/run-task.js
CHANGED
|
@@ -5,7 +5,7 @@ import { formatCompactTokens, pasteReplacement, expandPastes } from './input.js'
|
|
|
5
5
|
import { DEFAULT_LOCALE } from './i18n.js';
|
|
6
6
|
import { providerAskDeadlineMs } from './config.js';
|
|
7
7
|
// Ask the operator to approve a tool call flagged by an `ask` permission rule
|
|
8
|
-
//
|
|
8
|
+
// While the LineEditor owns the terminal it must be paused first, or the
|
|
9
9
|
// confirmation prompt and the input line fight for the same keys. A non-TTY run
|
|
10
10
|
// has no way to answer, so it DENIES (never silently allows a guarded call).
|
|
11
11
|
export async function askOperatorConfirm(editor, question, t,
|
|
@@ -330,9 +330,9 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
|
|
|
330
330
|
});
|
|
331
331
|
try {
|
|
332
332
|
// Set when the loop ends without a real answer (iteration limit / watchdog)
|
|
333
|
-
// so the one-shot mode can exit non-zero.
|
|
333
|
+
// so the one-shot mode can exit non-zero.
|
|
334
334
|
let failure = null;
|
|
335
|
-
//
|
|
335
|
+
// The machine-readable reason for the last task's outcome.
|
|
336
336
|
let outcomeKind = 'ok';
|
|
337
337
|
// How many auto-continue turns a completion goal has already spent.
|
|
338
338
|
let goalTurns = 0;
|
|
@@ -351,7 +351,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
|
|
|
351
351
|
// Calling it here made the spinner run for the whole pre-send phase (chat
|
|
352
352
|
// creation, throttle wait) with no generation in flight.
|
|
353
353
|
while (true) {
|
|
354
|
-
//
|
|
354
|
+
// Per-task summary: duration, number of tool calls and tokens spent.
|
|
355
355
|
const taskStart = Date.now();
|
|
356
356
|
// Token delta for THIS task: accumulated_token_usage is cumulative for
|
|
357
357
|
// the whole chat, so the per-task spend is after - before. Null when the
|
|
@@ -389,7 +389,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
|
|
|
389
389
|
},
|
|
390
390
|
onWarning: (msg) => ui.warning(msg),
|
|
391
391
|
onAskPermission: async (info) => {
|
|
392
|
-
//
|
|
392
|
+
// A rule with action "ask". Show the reason and wait for the
|
|
393
393
|
// operator. In non-TTY this denies.
|
|
394
394
|
ui.warning(t('perm.ask', { tool: info.tool, reason: info.reason }));
|
|
395
395
|
const ok = await askOperatorConfirm(editor || null, t('perm.confirm'), t, locale);
|
|
@@ -440,7 +440,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
|
|
|
440
440
|
}
|
|
441
441
|
}
|
|
442
442
|
: onAskUser,
|
|
443
|
-
//
|
|
443
|
+
// The check command appended to a tool result after an edit
|
|
444
444
|
// (off by default; config.diagnostics).
|
|
445
445
|
diagnostics: deps.config.diagnostics,
|
|
446
446
|
});
|
|
@@ -452,7 +452,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
|
|
|
452
452
|
ui.warning(outcome);
|
|
453
453
|
transcript?.log('agent_no_answer', { outcome });
|
|
454
454
|
failure = outcome;
|
|
455
|
-
//
|
|
455
|
+
// A machine-readable reason so `--output-format jsonl` consumers
|
|
456
456
|
// (CI) can tell a real error from an exhausted limit / watchdog.
|
|
457
457
|
outcomeKind = outcome.startsWith('Iteration limit reached')
|
|
458
458
|
? 'iteration_limit'
|
|
@@ -461,8 +461,8 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
|
|
|
461
461
|
else {
|
|
462
462
|
outcomeKind = 'ok';
|
|
463
463
|
}
|
|
464
|
-
//
|
|
465
|
-
//
|
|
464
|
+
// One summary line per task, printed even on an abort, so the operator
|
|
465
|
+
// sees what happened.
|
|
466
466
|
const tokensAfter = getTokenUsage ? getTokenUsage() : null;
|
|
467
467
|
const tokenDelta = typeof tokensBefore === 'number' && typeof tokensAfter === 'number'
|
|
468
468
|
? Math.max(0, tokensAfter - tokensBefore)
|
|
@@ -570,7 +570,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
|
|
|
570
570
|
sendSystemPrompt: false,
|
|
571
571
|
};
|
|
572
572
|
}
|
|
573
|
-
// Stop hook
|
|
573
|
+
// Stop hook: the whole task (including drained queue messages) is
|
|
574
574
|
// done. A cleanup/notification hook runs here. Best-effort.
|
|
575
575
|
try {
|
|
576
576
|
// LOAD the config: `undefined` here made every Stop hook a no-op — the
|
|
@@ -584,7 +584,7 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
|
|
|
584
584
|
ui.warning(out);
|
|
585
585
|
}
|
|
586
586
|
catch { }
|
|
587
|
-
//
|
|
587
|
+
// A single terminal event carrying the reason, so a jsonl consumer
|
|
588
588
|
// does not have to guess from the exit code alone.
|
|
589
589
|
transcript?.log('task_result', {
|
|
590
590
|
ok: !failure,
|
package/dist/self-review.js
CHANGED
|
@@ -122,7 +122,6 @@ async function copyContextFiles(srcDir, snapDir) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
// ---------- /self-review ----------
|
|
126
125
|
export async function selfReview({ browser, focus, transcript, locale, }) {
|
|
127
126
|
const t = translator(locale);
|
|
128
127
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
@@ -190,7 +189,6 @@ export async function selfReview({ browser, focus, transcript, locale, }) {
|
|
|
190
189
|
console.log();
|
|
191
190
|
return { snapDir, reportPath, changed };
|
|
192
191
|
}
|
|
193
|
-
// ---------- /self-diff ----------
|
|
194
192
|
export async function selfDiff({ name, locale, }) {
|
|
195
193
|
const t = translator(locale);
|
|
196
194
|
const snapDir = path.join(SNAP_ROOT, name);
|
|
@@ -218,7 +216,6 @@ export async function selfDiff({ name, locale, }) {
|
|
|
218
216
|
console.log(theme.system(t('self.no_diff')));
|
|
219
217
|
}
|
|
220
218
|
}
|
|
221
|
-
// ---------- /self-apply ----------
|
|
222
219
|
export async function selfApply({ name, locale, }) {
|
|
223
220
|
const t = translator(locale);
|
|
224
221
|
const snapDir = path.join(SNAP_ROOT, name);
|
|
@@ -247,7 +244,6 @@ export async function selfApply({ name, locale, }) {
|
|
|
247
244
|
console.log(theme.system(t('self.backup', { dir: backupDir })));
|
|
248
245
|
console.log(theme.warn('\n' + t('self.restart_hint') + '\n'));
|
|
249
246
|
}
|
|
250
|
-
// ---------- /self-list ----------
|
|
251
247
|
export async function selfList({ locale, } = {}) {
|
|
252
248
|
const t = translator(locale);
|
|
253
249
|
const snapRoot = path.join(SNAP_ROOT);
|
|
@@ -285,7 +281,6 @@ export async function selfList({ locale, } = {}) {
|
|
|
285
281
|
console.log();
|
|
286
282
|
console.log(theme.system(t('self.list_commands') + '\n'));
|
|
287
283
|
}
|
|
288
|
-
// ---------- helpers ----------
|
|
289
284
|
async function diffFiles(origDir, newDir) {
|
|
290
285
|
const files = await listTsFiles(newDir).catch(() => []);
|
|
291
286
|
const changed = [];
|