zames_pro 2.82.137 → 2.82.139
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 +153 -0
- package/README.md +86 -63
- 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/llms-full.txt +44 -32
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +5 -0
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;
|
package/dist/cli-args.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// PURE CLI argument parsing
|
|
1
|
+
// PURE CLI argument parsing. index.ts used to hold `args` as a
|
|
2
2
|
// module-level constant plus three helpers closing over it, which made the
|
|
3
3
|
// parsing impossible to unit-test in isolation (index.ts is the entry point and
|
|
4
4
|
// is not covered). `createCliArgs(argv)` binds the argv ONCE and returns the
|
|
@@ -23,11 +23,9 @@ export function createCliArgs(argv) {
|
|
|
23
23
|
const args = Array.isArray(argv) ? argv : [];
|
|
24
24
|
const getArg = (flag, fallback = null) => {
|
|
25
25
|
const i = args.indexOf(flag);
|
|
26
|
-
//
|
|
27
|
-
// back
|
|
28
|
-
//
|
|
29
|
-
// returns `--debug` (the original inline helper did the same); we preserve
|
|
30
|
-
// it rather than "fix" a long-standing quirk in a refactor.
|
|
26
|
+
// Truthiness, not `!== undefined`: an EMPTY value (`--task ''`) must
|
|
27
|
+
// fall back too - an empty task is not a task. A following FLAG is not
|
|
28
|
+
// special-cased either (see the CliArgs doc above).
|
|
31
29
|
return i !== -1 && args[i + 1] ? args[i + 1] : fallback;
|
|
32
30
|
};
|
|
33
31
|
const hasFlag = (flag) => args.includes(flag);
|
package/dist/code-blocks.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
// PURE extraction of fenced code blocks from a Markdown answer.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// to a key / command. That needs the RAW fenced text (the highlighted render is
|
|
5
|
-
// full of ANSI and must NOT be copied), which is what this module returns.
|
|
6
|
-
// Kept pure so it is unit-tested without a terminal.
|
|
1
|
+
// PURE extraction of fenced code blocks from a Markdown answer. It returns the
|
|
2
|
+
// RAW fenced text: the highlighted render is full of ANSI and must never be
|
|
3
|
+
// what gets copied out of the TUI.
|
|
7
4
|
/**
|
|
8
5
|
* Extract every fenced code block (``` or ~~~) from `md`, in order. Handles a
|
|
9
6
|
* longer closing fence than the opening one and an unterminated final block
|
package/dist/commands.js
CHANGED
|
@@ -3,9 +3,9 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import { isImageName } from './attachments.js';
|
|
5
5
|
import { translate } from './i18n.js';
|
|
6
|
-
// Helpers for the
|
|
7
|
-
// /
|
|
8
|
-
// live agent/browser
|
|
6
|
+
// Helpers for the slash commands (/diff, /cost, /export, /doctor, /review,
|
|
7
|
+
// /add-dir, /queue, /goal, custom commands). Pure functions, unit-tested
|
|
8
|
+
// without a live agent/browser; index.ts only renders their output.
|
|
9
9
|
const NL = String.fromCharCode(10);
|
|
10
10
|
const CR = String.fromCharCode(13);
|
|
11
11
|
// ---------- /diff ----------
|
|
@@ -27,8 +27,8 @@ export function formatDiff(diffText, opts = {}, t = translate('en')) {
|
|
|
27
27
|
const rest = lines.length - maxLines;
|
|
28
28
|
return head + NL + t('diff.more_lines', { n: String(rest) });
|
|
29
29
|
}
|
|
30
|
-
// Git arguments for the `/diff` command. An ARRAY
|
|
31
|
-
// them through runGitArgs(), so nothing is interpreted by a shell.
|
|
30
|
+
// Git arguments for the `/diff` command. An ARRAY, not a shell string: the
|
|
31
|
+
// caller runs them through runGitArgs(), so nothing is interpreted by a shell.
|
|
32
32
|
export function diffGitArgs(staged = false) {
|
|
33
33
|
return staged ? ['diff', '--staged'] : ['diff'];
|
|
34
34
|
}
|
|
@@ -1038,10 +1038,6 @@ export function mergeMessages(msgs) {
|
|
|
1038
1038
|
list.length +
|
|
1039
1039
|
' messages while you were working. They are listed below in order — ' +
|
|
1040
1040
|
'treat them as one instruction.';
|
|
1041
|
-
// The parts are OPERATOR text: a line that reads `---` (a Markdown rule, a
|
|
1042
|
-
// pasted diff) would spoof a message boundary and the model would see an
|
|
1043
|
-
// extra message. Neutralize such a line inside a part; the separator below
|
|
1044
|
-
// stays the only one.
|
|
1045
1041
|
// The message text is OPERATOR input: a line that reads `---` (a Markdown
|
|
1046
1042
|
// rule, a pasted diff) or repeats the header sentence spoofs a message
|
|
1047
1043
|
// BOUNDARY / count — the model then sees an extra message that never
|
package/dist/config-command.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The /config subcommand DISPATCH
|
|
1
|
+
// The /config subcommand DISPATCH. The PURE text rendering lives
|
|
2
2
|
// in config-commands.ts; this module owns the parsing/routing of the text
|
|
3
3
|
// subcommands (list/get/set/reset/lang/path) and prints through injected
|
|
4
4
|
// callbacks. Everything that touches live state (the runtime config object,
|
|
@@ -26,7 +26,7 @@ export async function handleConfigCommand(input, deps) {
|
|
|
26
26
|
? all
|
|
27
27
|
: ['/config', ...all];
|
|
28
28
|
const sub = (parts[1] || '').toLowerCase();
|
|
29
|
-
//
|
|
29
|
+
// Bare `/config` opens the menu (a text list in non-TTY, via openMenu).
|
|
30
30
|
if (!sub || sub === 'menu') {
|
|
31
31
|
await openMenu();
|
|
32
32
|
return;
|
|
@@ -39,7 +39,8 @@ export async function handleConfigCommand(input, deps) {
|
|
|
39
39
|
print(theme.system(t('cfg.paths', { global: homeConfigPath, project: projectConfigPath })));
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
|
-
//
|
|
42
|
+
// `/config lang <ru|en>` is a shortcut for `set ui.locale`; `язык` is an
|
|
43
|
+
// accepted alias because the operator types the command in their own language.
|
|
43
44
|
if (sub === 'lang' || sub === 'language' || sub === 'язык') {
|
|
44
45
|
const val = parts[2];
|
|
45
46
|
if (!val) {
|
|
@@ -123,7 +124,8 @@ export async function handleConfigCommand(input, deps) {
|
|
|
123
124
|
print(theme.assistant(t('cfg.reset', { v: key })));
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
126
|
-
//
|
|
127
|
+
// An unknown subcommand reports the key AND shows the list, so the operator
|
|
128
|
+
// sees the valid keys without a second command.
|
|
127
129
|
printErr(theme.error(t('cfg.unknown_key', { v: sub })));
|
|
128
130
|
showList();
|
|
129
131
|
}
|
package/dist/config-commands.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// PURE rendering for the /config command's TEXT output
|
|
1
|
+
// PURE rendering for the /config command's TEXT output. These
|
|
2
2
|
// helpers format the schema into lines; they touch no browser/terminal state,
|
|
3
3
|
// so they are unit-tested without a live agent (the /config dispatch in
|
|
4
4
|
// index.ts only prints what these return). The interactive menu lives in
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
import { theme } from './theme.js';
|
|
7
7
|
/**
|
|
8
8
|
* The value shown for a field in the LIST: the default marker, on/off for a
|
|
9
|
-
* boolean, a fixed mask for a password, otherwise the JSON form.
|
|
10
|
-
* long-used inline logic so the output is byte-for-byte identical.
|
|
9
|
+
* boolean, a fixed mask for a password, otherwise the JSON form.
|
|
11
10
|
*/
|
|
12
11
|
export function configListValue(field, value, t) {
|
|
13
12
|
if (value === undefined)
|
package/dist/config-live.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
// construction, so a change must be pushed onto the live instance too or it
|
|
5
5
|
// is silently a no-op until a restart. The path->target mapping is PURE and
|
|
6
6
|
// unit-tested here; index.ts wires it to the live instances.
|
|
7
|
-
/** config path -> field name on the browser instance.
|
|
8
|
-
* share these names; a field that is absent on the active backend
|
|
9
|
-
*
|
|
7
|
+
/** config path -> field name on the browser instance. All three backends
|
|
8
|
+
* share these names; a field that is absent on the active backend is skipped
|
|
9
|
+
* rather than throwing, so one map serves every provider. */
|
|
10
10
|
export const BROWSER_LIVE_CONFIG = {
|
|
11
11
|
'browser.answerTimeoutMs': 'answerTimeoutMs',
|
|
12
12
|
'browser.askRetries': 'askRetries',
|
|
@@ -30,11 +30,9 @@ export function applyBrowserLiveConfig(path, value, browser) {
|
|
|
30
30
|
const field = BROWSER_LIVE_CONFIG[path];
|
|
31
31
|
if (!field || !(field in browser))
|
|
32
32
|
return false;
|
|
33
|
-
// A timing value must be a FINITE NON-NEGATIVE number
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// the derived `askDeadlineMs` update, leaving a deadline that no longer
|
|
37
|
-
// matches the timeout. Reject the bad value instead of corrupting the field.
|
|
33
|
+
// A timing value must be a FINITE NON-NEGATIVE number: a NaN reached a live
|
|
34
|
+
// field (a timer with NaN fires immediately) and a negative one is nonsense.
|
|
35
|
+
// Reject the value instead of corrupting the field.
|
|
38
36
|
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
39
37
|
return false;
|
|
40
38
|
}
|
|
@@ -45,11 +43,7 @@ export function applyBrowserLiveConfig(path, value, browser) {
|
|
|
45
43
|
// give up before the browser's own timeout ever fired. Re-derive it the same
|
|
46
44
|
// way the constructor does.
|
|
47
45
|
if (field === 'answerTimeoutMs' && 'askDeadlineMs' in browser) {
|
|
48
|
-
|
|
49
|
-
// left the deadline at its stale value when the timeout was set to 0.
|
|
50
|
-
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
51
|
-
browser.askDeadlineMs = value + 60_000;
|
|
52
|
-
}
|
|
46
|
+
browser.askDeadlineMs = value + 60_000;
|
|
53
47
|
}
|
|
54
48
|
return true;
|
|
55
49
|
}
|
package/dist/config-menu.js
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
import { theme } from './theme.js';
|
|
12
12
|
import { visLen } from './input.js';
|
|
13
13
|
const ESC = String.fromCharCode(27);
|
|
14
|
-
// Strip ANSI sequences to compute the visible length of a line.
|
|
15
|
-
//
|
|
16
|
-
//
|
|
14
|
+
// Strip ANSI sequences to compute the visible length of a line. A local copy
|
|
15
|
+
// (input.ts keeps its own private helper): the regex must match a real CSI,
|
|
16
|
+
// otherwise visCols over-counts and the erase moves to the wrong row.
|
|
17
17
|
function stripAnsiLocal(s) {
|
|
18
18
|
return s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
|
|
19
19
|
}
|
|
@@ -197,7 +197,8 @@ export function runConfigMenu(opts) {
|
|
|
197
197
|
lines.push(...tail);
|
|
198
198
|
// Erase the previous block and print the new one. We count VISUAL lines
|
|
199
199
|
// (accounting for wrapping by terminal width), otherwise on narrow
|
|
200
|
-
// terminals
|
|
200
|
+
// terminals the unerased tails leave garbage behind.
|
|
201
|
+
//
|
|
201
202
|
// Reserve the last column. A line whose visible width reaches exactly the
|
|
202
203
|
// terminal width hits the autowrap edge: depending on the terminal it
|
|
203
204
|
// either consumes two rows for the following `\n` or keeps a pending wrap,
|