zames_pro 2.82.137 → 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 +70 -0
- package/dist/agent-loop.js +3 -6
- 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 +14 -20
- package/dist/checkpoint.js +6 -10
- package/dist/cli-args.js +4 -6
- package/dist/code-blocks.js +3 -6
- package/dist/commands.js +5 -9
- 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 +5 -8
- 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 +0 -11
- package/dist/input/suggest.js +4 -7
- package/dist/input.js +3 -8
- 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 +4 -4
- 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 +5 -9
- package/dist/system-prompt.js +1 -1
- package/dist/tui/widgets.js +0 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,76 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.82.138] - 2026-10-25
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- docs(types): fix a stale provider count
|
|
15
|
+
- docs(tui/widgets): prune a section divider
|
|
16
|
+
- docs(tui): prune section dividers
|
|
17
|
+
- docs(system-prompt): drop a backlog-id reference
|
|
18
|
+
- docs(statusline): prune and fix stale comments
|
|
19
|
+
- docs(status-command): prune and fix stale comments
|
|
20
|
+
- docs(spinner): prune and fix stale comments
|
|
21
|
+
- docs(shell): prune and fix stale comments
|
|
22
|
+
- docs(sessions): prune and fix stale comments
|
|
23
|
+
- docs(send-throttle): prune and fix stale comments
|
|
24
|
+
- docs(send-telemetry): prune and fix stale comments
|
|
25
|
+
- docs(send-runtime): prune and fix stale comments
|
|
26
|
+
- docs(self-review): prune section dividers
|
|
27
|
+
- docs(run-task): prune and fix stale comments
|
|
28
|
+
- docs(qwen-ui): prune and fix stale comments
|
|
29
|
+
- docs(qwen-net-capture): prune and fix stale comments
|
|
30
|
+
- docs(qwen-chats): prune and fix stale comments
|
|
31
|
+
- docs(qwen-browser): prune section dividers
|
|
32
|
+
- docs(prompt): prune and fix stale comments
|
|
33
|
+
- docs(permissions): prune and fix stale comments
|
|
34
|
+
- docs(path-token): prune and fix stale comments
|
|
35
|
+
- docs(net-capture): prune and fix stale comments
|
|
36
|
+
- docs(mcp): prune and fix stale comments
|
|
37
|
+
- docs(markdown): prune and fix stale comments
|
|
38
|
+
- docs(limits-command): prune and fix stale comments
|
|
39
|
+
- docs(input/suggest): prune and fix stale comments
|
|
40
|
+
- docs(input/layout): drop a backlog-id reference
|
|
41
|
+
- docs(input): prune backlog ids and section dividers
|
|
42
|
+
- docs(index): prune section dividers
|
|
43
|
+
- docs(i18n): prune and fix stale comments
|
|
44
|
+
- docs(i18n): drop backlog-id references from common.ts comments
|
|
45
|
+
- docs(goal): prune and fix stale comments
|
|
46
|
+
- docs(glm-ui): prune and fix stale comments
|
|
47
|
+
- docs(glm-net-capture): prune and fix stale comments
|
|
48
|
+
- docs(glm-chats): prune and fix stale comments
|
|
49
|
+
- docs(glm-browser): prune and fix stale comments
|
|
50
|
+
- docs(glm-browser): prune and fix stale comments
|
|
51
|
+
- docs(gitTools): prune and fix stale comments
|
|
52
|
+
- docs(fsutil): prune and fix stale comments
|
|
53
|
+
- docs(extraTools): prune and fix stale comments
|
|
54
|
+
- docs(diff): prune and fix stale comments
|
|
55
|
+
- docs(diagnostics): prune and fix stale comments
|
|
56
|
+
- docs(deepseek-ui): prune and fix stale comments
|
|
57
|
+
- docs(config): prune and fix stale comments
|
|
58
|
+
- docs(config-menu): prune and fix stale comments
|
|
59
|
+
- docs(config-live): prune and fix stale comments
|
|
60
|
+
- docs(config-commands): prune and fix stale comments
|
|
61
|
+
- docs(config-command): prune and fix stale comments
|
|
62
|
+
- docs(commands): prune and fix stale comments
|
|
63
|
+
- docs(code-blocks): prune and fix stale comments
|
|
64
|
+
- docs(cli-args): prune and fix stale comments
|
|
65
|
+
- docs(checkpoint): prune and fix stale comments
|
|
66
|
+
- docs(changelog): prune and fix stale comments
|
|
67
|
+
- docs(browser): prune and fix stale comments
|
|
68
|
+
- docs(browser-pure): prune and fix stale comments
|
|
69
|
+
- docs(browser-login): prune and fix stale comments
|
|
70
|
+
- docs(browser-dom): prune and fix stale comments
|
|
71
|
+
- docs(browser-chats): prune and fix stale comments
|
|
72
|
+
- docs(banner): prune and fix stale comments
|
|
73
|
+
- docs(backlog): prune and fix stale comments
|
|
74
|
+
- docs(attachments): prune and fix stale comments
|
|
75
|
+
- docs(attach-refs): prune and fix stale comments
|
|
76
|
+
- docs(attach-flow): prune and fix stale comments
|
|
77
|
+
- docs(answer-parse): prune and fix stale comments
|
|
78
|
+
- docs(agent-loop): prune and fix stale comments
|
|
79
|
+
|
|
10
80
|
## [2.82.137] - 2026-10-24
|
|
11
81
|
|
|
12
82
|
### Changed
|
package/dist/agent-loop.js
CHANGED
|
@@ -233,14 +233,10 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
233
233
|
// call but was not recognized (e.g. a DSML/XML form the strict parser
|
|
234
234
|
// missed), the tool did NOT run and the echo is the only copy of the
|
|
235
235
|
// call — it must be executed, not discarded as stale.
|
|
236
|
-
// const ranToolLastTurn follows the previous turn's actual effect.
|
|
237
236
|
let lastTurnRanTool = false;
|
|
238
237
|
let justRanTool = false;
|
|
239
238
|
let watchdogRetries = 0;
|
|
240
239
|
const MAX_WATCHDOG_RETRIES = 3;
|
|
241
|
-
// Retry budget for a browser.ask() TIMEOUT (not for content): the send did
|
|
242
|
-
// not come back in time. This is separate from unparsedRetries because a
|
|
243
|
-
// timeout is an infrastructure failure, not a model protocol violation.
|
|
244
240
|
// Loop detection: the model sometimes repeats the SAME tool call with the
|
|
245
241
|
// SAME arguments (e.g. Read the same file) turn after turn. There was no
|
|
246
242
|
// explicit detector for that — the stale-answer guard only catches a
|
|
@@ -263,6 +259,9 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
263
259
|
// Set when the argument-error series hits the cap; appended to the message
|
|
264
260
|
// AFTER the tool results are formatted (they rebuild `message`).
|
|
265
261
|
let argErrNudge = '';
|
|
262
|
+
// Retry budget for a browser.ask() TIMEOUT (not for content): the send did
|
|
263
|
+
// not come back in time. Separate from unparsedRetries because a timeout is
|
|
264
|
+
// an infrastructure failure, not a model protocol violation.
|
|
266
265
|
let afterToolRetries = 0;
|
|
267
266
|
// toolsRanInTask: how many tools actually ran in THIS task. The protocol
|
|
268
267
|
// guard uses it to tell "started, then slipped into chat mode" (a reasoning
|
|
@@ -1065,8 +1064,6 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
1065
1064
|
const poll = setInterval(syncToolAbort, 100);
|
|
1066
1065
|
if (typeof poll.unref === 'function')
|
|
1067
1066
|
poll.unref();
|
|
1068
|
-
// Time the tool: the transcript carries durationMs so /cost can
|
|
1069
|
-
// show where the time goes (frequent Read→Edit cycles vs slow Bash).
|
|
1070
1067
|
const toolStart = Date.now();
|
|
1071
1068
|
try {
|
|
1072
1069
|
result = await tool.fn(call.args, { signal: toolAbort.signal });
|
package/dist/answer-parse.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { parseXmlToolCalls } from './xml-toolcall.js';
|
|
2
2
|
import { normText } from './browser-pure.js';
|
|
3
|
-
// Pure, state-free helpers
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// loop needs and RE-EXPORTS the public ones, so the existing API (tests import
|
|
8
|
-
// parseToolCall / responseLooksLikeToolCall / truncateToolResult from './agent-loop.js')
|
|
9
|
-
// is unchanged.
|
|
3
|
+
// Pure, state-free helpers split out of agent-loop.ts: the tool-call PARSER
|
|
4
|
+
// (JSON / permissive / XML-DSML repair) and the small answer heuristics. None
|
|
5
|
+
// of them touch the browser or the loop state, so the module is safe to import
|
|
6
|
+
// on its own; tests and agent-loop.ts import the functions from here directly.
|
|
10
7
|
// Cap a tool result for the model, but make the truncation EXPLICIT: a bare
|
|
11
8
|
// slice() silently hid the tail and the model had no idea it was looking at a
|
|
12
9
|
// partial output (it would act on a half-read file/log).
|
|
@@ -537,11 +534,9 @@ function parseToolCallPermissive(text) {
|
|
|
537
534
|
const tool = toolMatch[1];
|
|
538
535
|
// The model occasionally puts the argument keys INLINE with "tool" —
|
|
539
536
|
// {"tool": "Bash", "command": "..."} — with no "args" wrapper at all.
|
|
540
|
-
// The strict parser rejects
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
// after the budget ran out the run stopped with the model's text as the
|
|
544
|
-
// final answer. Flatten the inline keys into args when "args" is absent.
|
|
537
|
+
// The strict parser rejects such a call (no obj.args); flatten the inline
|
|
538
|
+
// keys into args instead of letting the loop re-ask until it gives up and
|
|
539
|
+
// stops with the model's text as the final answer.
|
|
545
540
|
if (text.indexOf('"args"') === -1) {
|
|
546
541
|
const braceIdx = text.indexOf('{');
|
|
547
542
|
if (braceIdx === -1)
|
|
@@ -730,16 +725,15 @@ function repairToolCallPreamble(text) {
|
|
|
730
725
|
const m = t.match(/(?:^|[^A-Za-z0-9_])(?:\*\*)?(tool)(?:\*\*)?["'`\u2018\u2019\u201c\u201d]*\s*:/);
|
|
731
726
|
if (!m || m.index === undefined)
|
|
732
727
|
return null;
|
|
733
|
-
// We look for the start from the first quote/bracket around the key, otherwise from the word tool.
|
|
734
728
|
let start = m.index;
|
|
735
729
|
const brace = t.indexOf('{', Math.max(0, start - 1));
|
|
736
730
|
if (brace !== -1 && brace < start)
|
|
737
731
|
start = brace;
|
|
738
732
|
let frag = t.slice(start);
|
|
739
|
-
//
|
|
733
|
+
// The head lost its opening `{`, so rebuild one around the key below.
|
|
740
734
|
if (!frag.startsWith('{')) {
|
|
741
|
-
// The key
|
|
742
|
-
//
|
|
735
|
+
// The key lost its opening quote (`tool":`), so drop the junk before it
|
|
736
|
+
// and re-add the quote.
|
|
743
737
|
frag = frag.replace(/^[^A-Za-z0-9_]*/, '');
|
|
744
738
|
frag = frag.replace(/^(?:\*\*)?(["'`\u2018\u2019\u201c\u201d]*)(tool)(?:\*\*)?["'`\u2018\u2019\u201c\u201d]*\s*:/, '"$2":');
|
|
745
739
|
frag = '{' + frag;
|
package/dist/attach-refs.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { looksLikeFilePath, isImageName } from './attachments.js';
|
|
4
4
|
import { extractAtFileRefs } from './path-token.js';
|
|
5
|
-
// Path resolution and `@file` reference inlining
|
|
5
|
+
// Path resolution and `@file` reference inlining.
|
|
6
6
|
// Extracted from src/index.ts so the logic is unit-testable without the whole
|
|
7
7
|
// CLI. This module touches only the filesystem (no browser/terminal).
|
|
8
8
|
const NL = String.fromCharCode(10);
|
|
@@ -81,12 +81,12 @@ async function isInside(workdir, target) {
|
|
|
81
81
|
const rel = path.relative(root, real);
|
|
82
82
|
return !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
83
83
|
}
|
|
84
|
-
// Per-file and total caps for `@path` inlining
|
|
84
|
+
// Per-file and total caps for `@path` inlining. A reference should SAVE
|
|
85
85
|
// round-trips, not flood the context: a huge file is truncated with a note
|
|
86
86
|
// telling the agent to read the rest itself.
|
|
87
87
|
export const AT_REF_FILE_CAP = 60 * 1024;
|
|
88
88
|
export const AT_REF_TOTAL_CAP = 200 * 1024;
|
|
89
|
-
// Inline `@path` references found in a task text
|
|
89
|
+
// Inline `@path` references found in a task text. Returns the augmented
|
|
90
90
|
// text plus the list of files actually inlined (for the operator notice).
|
|
91
91
|
// Unknown/unreadable references are left in place untouched -- they may be
|
|
92
92
|
// part of the prose; we never fail the task over a missing file.
|
package/dist/attachments.js
CHANGED
|
@@ -284,13 +284,8 @@ export function hasClipboardTool() {
|
|
|
284
284
|
}
|
|
285
285
|
return false;
|
|
286
286
|
}
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
// tool was used (or the reason nothing was found) — the caller shows that note
|
|
290
|
-
// to the user so a failed paste is never silent.
|
|
291
|
-
// True when running inside WSL. There the X11 clipboard is a SEPARATE
|
|
292
|
-
// clipboard from the Windows one: the user copies an image in Windows, so we
|
|
293
|
-
// must read it through powershell.exe, not xclip.
|
|
287
|
+
// True when the Windows filesystem is visible from here, i.e. a
|
|
288
|
+
// powershell.exe call can reach the Windows clipboard.
|
|
294
289
|
export function windowsReachable() {
|
|
295
290
|
if (process.platform === 'win32')
|
|
296
291
|
return true;
|
|
@@ -328,6 +323,11 @@ const PS_GET_FILE_DROP = 'Add-Type -AssemblyName System.Windows.Forms; ' +
|
|
|
328
323
|
'$d=[System.Windows.Forms.Clipboard]::GetDataObject(); ' +
|
|
329
324
|
'if($d -and $d.GetDataPresent([System.Windows.Forms.DataFormats]::FileDrop)){' +
|
|
330
325
|
'$f=$d.GetData([System.Windows.Forms.DataFormats]::FileDrop); $f -join [Environment]::NewLine}';
|
|
326
|
+
// Read an image from the OS clipboard, trying every known tool for the current
|
|
327
|
+
// platform. The returned `via` names the tool that worked, or why nothing was
|
|
328
|
+
// found — the caller shows it to the operator so a failed paste is never
|
|
329
|
+
// silent. On WSL the Windows clipboard comes FIRST: it is a DIFFERENT clipboard
|
|
330
|
+
// from X11, and the user copies the image in Windows, not in the X session.
|
|
331
331
|
export async function readClipboardImageDetailed() {
|
|
332
332
|
if (process.platform === 'linux' ||
|
|
333
333
|
process.platform === 'freebsd' ||
|
|
@@ -410,7 +410,6 @@ export async function readClipboardImageDetailed() {
|
|
|
410
410
|
}
|
|
411
411
|
return { data: null, via: 'unsupported-platform' };
|
|
412
412
|
}
|
|
413
|
-
// Backwards-compatible wrapper used elsewhere.
|
|
414
413
|
export async function readClipboardImage() {
|
|
415
414
|
return (await readClipboardImageDetailed()).data;
|
|
416
415
|
}
|
package/dist/backlog.js
CHANGED
|
@@ -39,11 +39,8 @@ export function parseBacklogNote(note) {
|
|
|
39
39
|
break;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// carriage return.
|
|
45
|
-
// Normalize BOTH line endings first: a lone `\r` (a classic-Mac paste) is
|
|
46
|
-
// a line break too, and `body.split(NL)` kept it inside the title.
|
|
42
|
+
// Normalize CRLF AND a lone `\r` (a classic-Mac paste is a line break too):
|
|
43
|
+
// left in place, the `\r` stayed in the title and reached the heading.
|
|
47
44
|
const bodyLines = body.split(/\r\n?|\n/).map((l) => l.replace(/\r$/, ''));
|
|
48
45
|
const title = bodyLines[0];
|
|
49
46
|
const rest = bodyLines.slice(1).join(NL).trim();
|
package/dist/banner.js
CHANGED
|
@@ -13,7 +13,7 @@ function width(s) {
|
|
|
13
13
|
* widest line (clamped to `maxWidth`), padded by one space each side. Lines
|
|
14
14
|
* longer than the inner width are truncated with an ellipsis.
|
|
15
15
|
*/
|
|
16
|
-
function box(lines, maxWidth
|
|
16
|
+
function box(lines, maxWidth) {
|
|
17
17
|
// A caller may pass 0 ("auto") or a tiny width on a narrow terminal. With
|
|
18
18
|
// `maxWidth - 4` going negative the inner width collapsed to the 10-column
|
|
19
19
|
// floor while every content line was clipped to `inner - 2`, so the title,
|
|
@@ -25,22 +25,21 @@ function box(lines, maxWidth, border = '#c8b06a') {
|
|
|
25
25
|
const top = theme.prompt('╭' + '─'.repeat(inner) + '╮');
|
|
26
26
|
const bottom = theme.prompt('╰' + '─'.repeat(inner) + '╯');
|
|
27
27
|
const body = lines.map((l) => {
|
|
28
|
-
// Truncate to the inner width so a long path never breaks the frame.
|
|
29
28
|
const maxContent = Math.max(0, inner - 2);
|
|
30
29
|
const clipped = clip(l, maxContent);
|
|
31
30
|
const w = width(clipped);
|
|
32
31
|
const pad = Math.max(0, inner - 1 - w);
|
|
33
32
|
return (theme.prompt('│') + ' ' + clipped + ' '.repeat(pad) + theme.prompt('│'));
|
|
34
33
|
});
|
|
35
|
-
void border;
|
|
36
34
|
return [top, ...body, bottom].join('\n');
|
|
37
35
|
}
|
|
38
36
|
/** Truncate an (ANSI-carrying) string to `max` visible columns. */
|
|
39
37
|
function clip(s, max) {
|
|
40
38
|
if (width(s) <= max)
|
|
41
39
|
return s;
|
|
42
|
-
// The
|
|
43
|
-
//
|
|
40
|
+
// The content carries a couple of ANSI runs, so the tail is dropped by
|
|
41
|
+
// VISIBLE width and escape sequences are copied whole — a plain slice would
|
|
42
|
+
// cut an escape in half and leak its remainder as text.
|
|
44
43
|
let out = '';
|
|
45
44
|
let visible = 0;
|
|
46
45
|
let i = 0;
|
package/dist/browser-chats.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// PURE parsing of the DeepSeek history/message payloads
|
|
1
|
+
// PURE parsing of the DeepSeek history/message payloads. These
|
|
2
2
|
// helpers are extracted from DeepSeekBrowser.fetchChatMessages: they used to be
|
|
3
3
|
// copy-pasted INLINE twice (the in-page evaluate AND the Playwright-request
|
|
4
4
|
// fallback), so the same role/fragment extraction lived in two places and could
|
|
@@ -69,9 +69,9 @@ export function parseHistoryMessages(messages) {
|
|
|
69
69
|
return { list, usage };
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
-
* Pull `chat_messages` out of a history_messages JSON response
|
|
73
|
-
*
|
|
74
|
-
*
|
|
72
|
+
* Pull `chat_messages` out of a history_messages JSON response; returns null
|
|
73
|
+
* when the shape does not match, so the caller can report a clear error
|
|
74
|
+
* instead of silently restoring an empty dialogue.
|
|
75
75
|
*/
|
|
76
76
|
export function extractChatMessages(json) {
|
|
77
77
|
const j = json;
|
package/dist/browser-dom.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Pure in-page DOM helpers extracted from DeepSeekBrowser
|
|
1
|
+
// Pure in-page DOM helpers extracted from DeepSeekBrowser.
|
|
2
2
|
//
|
|
3
3
|
// Every function here runs INSIDE page.evaluate, so it MUST be fully
|
|
4
4
|
// self-contained: Playwright serializes the function SOURCE and evaluates it in
|
|
@@ -79,9 +79,10 @@ export function readMfaAccountInDom() {
|
|
|
79
79
|
return '';
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
|
-
* The answer as RENDERED on the page
|
|
83
|
-
*
|
|
84
|
-
*
|
|
82
|
+
* The answer as RENDERED on the page. The freshness checks in `_askOnce` MUST
|
|
83
|
+
* compare rendered text, not the network capture, so they can see that the DOM
|
|
84
|
+
* actually changed. Reasoning blocks (think class) are skipped and must never
|
|
85
|
+
* be picked up as the answer.
|
|
85
86
|
*/
|
|
86
87
|
export function readLastAnswerInDom(opts) {
|
|
87
88
|
const sels = opts.sels;
|
|
@@ -159,10 +160,9 @@ export function continueVisibleCheapInDom(nameRe) {
|
|
|
159
160
|
return false;
|
|
160
161
|
}
|
|
161
162
|
/**
|
|
162
|
-
* Find the Stop button
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* also means Stop.
|
|
163
|
+
* Find the Stop button. The class alone is not enough: during generation the
|
|
164
|
+
* send button (the same circle button) swaps its icon to a "square" while
|
|
165
|
+
* keeping its classes, so a square icon (svg rect) also means Stop.
|
|
166
166
|
*/
|
|
167
167
|
export function stopButtonVisibleInDom(btnRe) {
|
|
168
168
|
const re = new RegExp(btnRe, 'i');
|
|
@@ -186,9 +186,10 @@ export function stopButtonVisibleInDom(btnRe) {
|
|
|
186
186
|
return false;
|
|
187
187
|
}
|
|
188
188
|
/**
|
|
189
|
-
* Raw-DOM Stop click with the full pointer/mouse sequence
|
|
190
|
-
*
|
|
191
|
-
*
|
|
189
|
+
* Raw-DOM Stop click with the full pointer/mouse sequence. The label check is
|
|
190
|
+
* skipped here on purpose — the square-icon button may carry no label at all,
|
|
191
|
+
* and a plain .click() was swallowed by an overlay. Returns true when a
|
|
192
|
+
* square-icon button was found and clicked.
|
|
192
193
|
*/
|
|
193
194
|
export function clickStopInDom(btnRe) {
|
|
194
195
|
const re = new RegExp(btnRe, 'i');
|
|
@@ -310,9 +311,8 @@ export function readElementText(el) {
|
|
|
310
311
|
return el.innerText || el.textContent || '';
|
|
311
312
|
}
|
|
312
313
|
/**
|
|
313
|
-
* Lowercased tagName of an element
|
|
314
|
-
*
|
|
315
|
-
* contenteditable box.
|
|
314
|
+
* Lowercased tagName of an element, used to tell a native input/textarea from
|
|
315
|
+
* a contenteditable box. Runs bound to the element.
|
|
316
316
|
*/
|
|
317
317
|
export function elementTagNameInDom(el) {
|
|
318
318
|
return el.tagName.toLowerCase();
|
|
@@ -387,7 +387,8 @@ export function submitLoginFormInDom(opts) {
|
|
|
387
387
|
return true;
|
|
388
388
|
}
|
|
389
389
|
}
|
|
390
|
-
//
|
|
390
|
+
// No labelled submit control matched, so a React-rendered form is the likely
|
|
391
|
+
// case: click the primary-looking button instead.
|
|
391
392
|
const primary = btns.find((b) => {
|
|
392
393
|
const cls = (b.className || '').toString();
|
|
393
394
|
return /primary|submit|ds-button--primary/i.test(cls);
|
package/dist/browser-login.js
CHANGED
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
// 2. autoLogin() with saved creds -> done (this is the FIRST step after
|
|
17
17
|
// the probe; credentials are permanent)
|
|
18
18
|
// 3. guest is allowed and no login was requested -> work as a guest
|
|
19
|
-
// 4. not interactive -> LoginRequiredError (fast, no window
|
|
19
|
+
// 4. not interactive -> LoginRequiredError (fast, no window,
|
|
20
|
+
// so a cold login fails instead of
|
|
21
|
+
// hanging)
|
|
20
22
|
// 5. no display -> fall back to the in-TUI forms
|
|
21
23
|
// 6. headless -> relaunchSafe(headed), sign in
|
|
22
24
|
// 7. wait until probeLogin() === 'in' (poll), the operator closes the window
|
|
@@ -57,8 +59,9 @@ export async function interactiveBrowserLogin(deps) {
|
|
|
57
59
|
// 4) Non-interactive: fail fast instead of hanging on a window nobody sees.
|
|
58
60
|
if (!deps.interactive)
|
|
59
61
|
throw new LoginRequiredError(deps.provider);
|
|
60
|
-
// 5) No display: the in-TUI form is the accepted fallback
|
|
61
|
-
//
|
|
62
|
+
// 5) No display: the in-TUI form is the accepted fallback — a captcha cannot
|
|
63
|
+
// be typed in a terminal, but a password/code form still can. Without one
|
|
64
|
+
// there is nothing to offer, so fail like a non-interactive run.
|
|
62
65
|
if (!deps.canShowWindow) {
|
|
63
66
|
if (deps.tuiLogin) {
|
|
64
67
|
const ok = await deps.tuiLogin().catch(() => false);
|
|
@@ -109,8 +112,9 @@ async function runHeadedLogin(deps) {
|
|
|
109
112
|
const r = await deps.probeLogin().catch(() => 'unknown');
|
|
110
113
|
if (r === 'in')
|
|
111
114
|
return true;
|
|
112
|
-
// A closed window is the operator's "I am done" signal
|
|
113
|
-
//
|
|
115
|
+
// A closed window is the operator's "I am done" signal. The session may
|
|
116
|
+
// have been established while the page was closing, so probe ONCE more —
|
|
117
|
+
// skipping that probe threw LoginRequiredError on a signed-in operator.
|
|
114
118
|
if (deps.windowClosed()) {
|
|
115
119
|
deps.notice('login.window_closed');
|
|
116
120
|
const again = await deps
|
package/dist/browser-pure.js
CHANGED
|
@@ -14,7 +14,7 @@ export function cleanAnswer(raw) {
|
|
|
14
14
|
return '';
|
|
15
15
|
return raw;
|
|
16
16
|
}
|
|
17
|
-
// Pure, state-free helpers extracted from browser.ts
|
|
17
|
+
// Pure, state-free helpers extracted from browser.ts. Nothing
|
|
18
18
|
// here touches the DeepSeekBrowser instance state, so this module is safe to
|
|
19
19
|
// import anywhere (including hot-reloaded modules) without pulling in the
|
|
20
20
|
// live Playwright context. browser.ts re-exports the public names, so the
|
|
@@ -318,15 +318,16 @@ export function normText(s) {
|
|
|
318
318
|
.replace(/\s+/g, ' ')
|
|
319
319
|
.trim();
|
|
320
320
|
}
|
|
321
|
-
//
|
|
322
|
-
//
|
|
321
|
+
// Distinct from the other failures so ask() waits a long time (DeepSeek limits
|
|
322
|
+
// reset over minutes) and then retries the send itself.
|
|
323
323
|
export class RateLimitError extends Error {
|
|
324
324
|
constructor(detail) {
|
|
325
325
|
super('Messages too frequent. Try again later. ' + detail);
|
|
326
326
|
this.name = 'RateLimitError';
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
|
-
//
|
|
329
|
+
// A transient DeepSeek overload: clears in seconds, so ask() retries it quickly
|
|
330
|
+
// instead of waiting out the rate-limit pause.
|
|
330
331
|
export class ServerBusyError extends Error {
|
|
331
332
|
constructor(detail) {
|
|
332
333
|
super('Server busy. Try again later. ' + detail);
|
|
@@ -357,7 +358,6 @@ export class GenerationIncompleteError extends Error {
|
|
|
357
358
|
this.name = 'GenerationIncompleteError';
|
|
358
359
|
}
|
|
359
360
|
}
|
|
360
|
-
// ---------- profile cleanup ----------
|
|
361
361
|
export async function cleanSingletonFiles(dir = USER_DATA_DIR) {
|
|
362
362
|
const names = ['SingletonLock', 'SingletonCookie', 'SingletonSocket'];
|
|
363
363
|
for (const name of names) {
|
package/dist/browser.js
CHANGED
|
@@ -18,7 +18,7 @@ import { translate, DEFAULT_LOCALE } from './i18n.js';
|
|
|
18
18
|
// here preserves the existing public API (test/*.test.ts import them from
|
|
19
19
|
// browser.ts).
|
|
20
20
|
export { STOP_NAME_RE, CONTINUE_NAME_RE };
|
|
21
|
-
// The pure, state-free helpers live in browser-pure.ts
|
|
21
|
+
// The pure, state-free helpers live in browser-pure.ts: they are
|
|
22
22
|
// imported for the class below and RE-EXPORTED here, so the public API of this
|
|
23
23
|
// module is unchanged (tests and other modules import these names from
|
|
24
24
|
// './browser.js').
|
|
@@ -278,12 +278,8 @@ export class DeepSeekBrowser {
|
|
|
278
278
|
catch { }
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
// visible there). We prefix the tab title with a "⏳"; the ORIGINAL title is
|
|
284
|
-
// saved ONCE on the window and restored when generation ends, so DeepSeek's
|
|
285
|
-
// own chat title is never lost. Best-effort: a page error must never break
|
|
286
|
-
// the send loop.
|
|
281
|
+
// Best-effort: a page error while marking the tab title must never break the
|
|
282
|
+
// send loop.
|
|
287
283
|
async _setBusyTitle(busy) {
|
|
288
284
|
try {
|
|
289
285
|
await this.page.evaluate(setBusyTitleInDom, busy);
|
|
@@ -1313,8 +1309,8 @@ export class DeepSeekBrowser {
|
|
|
1313
1309
|
const raw = await this._readLastAnswerTextDom().catch(() => '');
|
|
1314
1310
|
return this._cleanAnswer(raw);
|
|
1315
1311
|
}
|
|
1316
|
-
// The pure
|
|
1317
|
-
//
|
|
1312
|
+
// The filter itself is pure (cleanAnswer in browser-pure.ts); the method is
|
|
1313
|
+
// kept so the existing `this._cleanAnswer(...)` call sites stay unchanged.
|
|
1318
1314
|
_cleanAnswer(raw) {
|
|
1319
1315
|
return cleanAnswer(raw);
|
|
1320
1316
|
}
|
|
@@ -2752,17 +2748,11 @@ export class DeepSeekBrowser {
|
|
|
2752
2748
|
// bottom. Used by /resume and /resume-id so the operator sees the restored
|
|
2753
2749
|
// context in the terminal instead of just "Chat opened.".
|
|
2754
2750
|
//
|
|
2755
|
-
// Best-effort DOM scraping
|
|
2756
|
-
//
|
|
2757
|
-
// ds-assistant-message-* class
|
|
2758
|
-
//
|
|
2759
|
-
// skipped
|
|
2760
|
-
// Read the WHOLE visible dialogue of the currently open chat, top to
|
|
2761
|
-
// bottom. Used by /resume and /resume-id so the operator sees the restored
|
|
2762
|
-
// context in the terminal instead of just "Chat opened.".
|
|
2763
|
-
//
|
|
2764
|
-
// The in-page scraper (scrapeChatMessages, browser-chats.ts) is a named
|
|
2765
|
-
// export so the class body stays thin; it runs inside the page.
|
|
2751
|
+
// Best-effort DOM scraping (the in-page scrapeChatMessages in
|
|
2752
|
+
// browser-chats.ts, kept as a named export so the class stays thin): role
|
|
2753
|
+
// detection relies on the ds-assistant-message-* class, anything that is not
|
|
2754
|
+
// an assistant block is a user message, and the model's reasoning
|
|
2755
|
+
// (.ds-think-content) is skipped exactly like in _readLastAnswerText.
|
|
2766
2756
|
async readChatMessages() {
|
|
2767
2757
|
if (!this.page)
|
|
2768
2758
|
return [];
|
package/dist/changelog.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
// Conventional-Commits -> CHANGELOG draft. PURE and
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// Parse one commit SUBJECT into a conventional commit, or null when it
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// fix!: change Y feat!: breaking
|
|
11
|
-
// Explicit breaking markers: `!` after the type/scope. A BREAKING CHANGE
|
|
12
|
-
// body footer is beyond a subject-only parser (git log %s only) -- out of scope.
|
|
1
|
+
// Conventional-Commits -> CHANGELOG draft. PURE and unit-tested: the draft is
|
|
2
|
+
// built from git log by scripts/changelog-draft.mts (and the release script),
|
|
3
|
+
// and `syncUnreleased` rewrites the Unreleased section only on the explicit
|
|
4
|
+
// --write flag — the changelog must stay a deliberate document, not a
|
|
5
|
+
// byproduct of whatever landed on the branch.
|
|
6
|
+
// Parse one commit SUBJECT into a conventional commit, or null when it does
|
|
7
|
+
// not follow the format (a free-form subject is skipped from the draft).
|
|
8
|
+
// `feat: add X`, `feat(cli): add X` and `fix!: change Y` are all understood. A
|
|
9
|
+
// `BREAKING CHANGE:` body footer is out of scope: git log %s carries no body.
|
|
13
10
|
export function parseConventionalCommit(subject) {
|
|
14
11
|
const s = String(subject ?? '').trim();
|
|
15
12
|
if (!s)
|
|
@@ -37,11 +34,9 @@ const KIND_BY_TYPE = {
|
|
|
37
34
|
};
|
|
38
35
|
// Types that never belong in a user-facing changelog (build/CI/internal).
|
|
39
36
|
const SKIP_TYPES = new Set(['chore', 'ci', 'test', 'build', 'docs', 'internal']);
|
|
40
|
-
// Group parsed commits by changelog kind.
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// order), and duplicate lines are dropped so a rebase that repeats a subject
|
|
44
|
-
// does not flip the draft twice.
|
|
37
|
+
// Group parsed commits by changelog kind. A scope is prefixed as `**scope:** `
|
|
38
|
+
// and a breaking marker appended as `(BREAKING)`. Duplicate lines are dropped
|
|
39
|
+
// so a rebase that repeats a subject does not list it twice.
|
|
45
40
|
export function groupCommits(forCommit) {
|
|
46
41
|
const groups = {
|
|
47
42
|
Added: [],
|
|
@@ -63,9 +58,8 @@ export function groupCommits(forCommit) {
|
|
|
63
58
|
}
|
|
64
59
|
return groups;
|
|
65
60
|
}
|
|
66
|
-
// Render the groups as a changelog body
|
|
67
|
-
//
|
|
68
|
-
// omitted. Returns '' when there is nothing to report.
|
|
61
|
+
// Render the groups as a changelog body, in the standard Keep-a-Changelog
|
|
62
|
+
// order and with empty groups omitted. Returns '' when there is nothing to report.
|
|
69
63
|
export function renderChangelogDraft(groups) {
|
|
70
64
|
const order = ['Added', 'Fixed', 'Changed', 'Removed'];
|
|
71
65
|
const out = [];
|
package/dist/checkpoint.js
CHANGED
|
@@ -4,7 +4,7 @@ import os from 'os';
|
|
|
4
4
|
import { execFile } from 'child_process';
|
|
5
5
|
import { randomBytes } from 'crypto';
|
|
6
6
|
import { writeJsonAtomic } from './fsutil.js';
|
|
7
|
-
// Checkpoints / rewind
|
|
7
|
+
// Checkpoints / rewind.
|
|
8
8
|
//
|
|
9
9
|
// Undo (src/undo.ts) reverts ONE file write. A checkpoint snapshots the WHOLE
|
|
10
10
|
// working tree at the start of a task, so a risky task can be rolled back with
|
|
@@ -21,7 +21,6 @@ const CHECKPOINT_DIR = path.join(os.homedir(), '.zames', 'checkpoints');
|
|
|
21
21
|
// dir. Everything else is part of the snapshot. Patterns are anchored to the
|
|
22
22
|
// archive root ('./x') so a nested `src/tmp` is NOT accidentally excluded.
|
|
23
23
|
export const CHECKPOINT_EXCLUDES = ['node_modules', '.git', 'dist', 'tmp'];
|
|
24
|
-
// ---------- pure helpers (unit-tested without tar) ----------
|
|
25
24
|
export function tarCreateArgs(archive, _dir) {
|
|
26
25
|
const args = ['-czf', archive];
|
|
27
26
|
for (const e of CHECKPOINT_EXCLUDES)
|
|
@@ -40,15 +39,14 @@ export function rotateRecords(records, max) {
|
|
|
40
39
|
return records.slice();
|
|
41
40
|
return records.slice(records.length - max);
|
|
42
41
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Resolve a `/rewind` selector against records sorted NEWEST FIRST.
|
|
45
|
-
* `1` = most recent; a non-numeric selector is matched as a stamp prefix.
|
|
46
|
-
*/
|
|
47
42
|
/** True for a selector that is exactly a full epoch-ms stamp (13 digits). */
|
|
48
43
|
export function isFullStampSelector(selector) {
|
|
49
44
|
return /^\d{13}$/.test(String(selector ?? '').trim());
|
|
50
45
|
}
|
|
51
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a `/rewind` selector against records sorted NEWEST FIRST.
|
|
48
|
+
* `1` = most recent; a longer number is matched as a stamp prefix.
|
|
49
|
+
*/
|
|
52
50
|
export function pickRecord(records, selector,
|
|
53
51
|
/** The FULL record set, when `records` is only a display window. */
|
|
54
52
|
allRecords) {
|
|
@@ -62,7 +60,7 @@ allRecords) {
|
|
|
62
60
|
// happened to share its first digits with a NEWER one was resolved to that
|
|
63
61
|
// WRONG record. A full stamp is therefore matched against the full set
|
|
64
62
|
// FIRST, and only then treated as an index.
|
|
65
|
-
if (
|
|
63
|
+
if (isFullStampSelector(sel)) {
|
|
66
64
|
const pool = allRecords && allRecords.length ? allRecords : records;
|
|
67
65
|
const exact = pool.find((r) => String(r.stamp) === sel);
|
|
68
66
|
if (exact)
|
|
@@ -85,7 +83,6 @@ allRecords) {
|
|
|
85
83
|
pool.find((r) => String(r.stamp).startsWith(sel)) ||
|
|
86
84
|
null);
|
|
87
85
|
}
|
|
88
|
-
// ---------- tar / fs plumbing ----------
|
|
89
86
|
function runTar(args, cwd, timeout = 60_000) {
|
|
90
87
|
return new Promise((resolve) => {
|
|
91
88
|
// `execFile` throws SYNCHRONOUSLY on a spawn error (ENOENT for the shell,
|
|
@@ -137,7 +134,6 @@ async function copyTree(from, to) {
|
|
|
137
134
|
await fs.copyFile(src, dst);
|
|
138
135
|
}
|
|
139
136
|
}
|
|
140
|
-
// ---------- store ----------
|
|
141
137
|
export class CheckpointStore {
|
|
142
138
|
enabled;
|
|
143
139
|
maxBackups;
|