zames_pro 2.72.4 → 2.72.6

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 CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.72.6] - 2026-10-09
11
+
12
+ ### Fixed
13
+
14
+ - **browser:** detect the property form of quasi_status (Stopped/FINISHED hang)
15
+
16
+ ### Fixed
17
+
18
+ - **browser:** detect the property form of quasi_status (Stopped/FINISHED hang)
19
+
20
+ ## [2.72.5] - 2026-10-09
21
+
22
+ ### Changed
23
+
24
+ - **input:** extract the pure slash-command suggestion logic into input/suggest.ts (C3)
25
+
26
+ ### Changed
27
+
28
+ - **input:** extract the pure slash-command suggestion logic into input/suggest.ts (C3)
29
+
10
30
  ## [2.72.4] - 2026-10-09
11
31
 
12
32
  ### Changed
@@ -213,11 +213,18 @@ export const RATE_LIMIT_RE = /(messages? too frequent|too many requests|rate lim
213
213
  export const SERVER_BUSY_RE = /(server (is )?busy|server error|service (is )?unavailable|temporarily unavailable|internal server error|502|503|504|server overloaded|сервер занят|сервер перегружен|сервис недоступен|внутренняя ошибка|попробуйте позже)/i;
214
214
  // ---------- network protocol (SSE) ----------
215
215
  export const GENERATION_ERR_RE = /"finish_reason":\s*"generation_err"/i;
216
- export const INCOMPLETE_STATUS_RE = /"quasi_status","v":"INCOMPLETE"/i;
216
+ // DeepSeek emits the generation status in TWO shapes: the initial message as a
217
+ // JSON property (`"quasi_status":"FINISHED"` inside v.response) and later
218
+ // BATCH updates (`{"p":"quasi_status","v":"FINISHED"}`). The old regex only
219
+ // matched the BATCH form, so a FINISHED-without-answer turn that came in the
220
+ // PROPERTY form (the real "Server is temporarily unavailable" case) was NOT
221
+ // detected — the agent hung on "Stopped" until the timeout. Match both.
222
+ export const INCOMPLETE_STATUS_RE = /"quasi_status"(?::|,"v":)"INCOMPLETE"/i;
217
223
  // A turn that ENDED (`quasi_status: FINISHED`) but produced NO answer text —
218
224
  // the reasoning was generated and the model stopped WITHOUT a RESPONSE
219
- // fragment. The UI shows Stopped + Continue.
220
- export const FINISHED_STATUS_RE = /"quasi_status","v":"FINISHED"/i;
225
+ // fragment. The UI shows Stopped + Continue. Matches both the JSON-property
226
+ // and the BATCH shape (see INCOMPLETE_STATUS_RE).
227
+ export const FINISHED_STATUS_RE = /"quasi_status"(?::|,"v":)"FINISHED"/i;
221
228
  export const RESPONSE_CONTENT_RE = /"type":"RESPONSE","content":"[^"]/;
222
229
  // Only a completion/continue turn carries a real answer. Gating the capture
223
230
  // assignment to these URLs keeps a future endpoint (which happens to return a
@@ -0,0 +1,81 @@
1
+ // PURE slash-command suggestion logic — the match / paging / completion half
2
+ // of the LineEditor's «/» hint list (src/input.ts). No terminal access and no
3
+ // editor state: given the buffer text and the command list these decide which
4
+ // commands to show, the visible window, and the Tab completion. Split out
5
+ // (C3) so the fuzzy match can be unit-tested without driving the editor.
6
+ import { SUGGEST_PAGE, isSubsequence } from './layout.js';
7
+ /**
8
+ * The commands matching the current buffer. Shown only when the line starts
9
+ * with «/» and is a single short word. The match is fuzzy: an exact prefix
10
+ * first, then a substring, then a subsequence (e.g. "hst" -> "/history"), so
11
+ * a half-remembered command name still surfaces. Order is preserved.
12
+ */
13
+ export function matchSlashCommands(buf, commands) {
14
+ const b = buf;
15
+ if (!b.startsWith('/'))
16
+ return [];
17
+ if (b.includes(String.fromCharCode(10)) || b.includes(' '))
18
+ return [];
19
+ if (b.length > 40)
20
+ return [];
21
+ const q = b.toLowerCase();
22
+ if (q === '/')
23
+ return commands;
24
+ // Match on the part AFTER the leading slash: comparing the raw query (which
25
+ // itself starts with '/') against the full name made the fuzzy subsequence
26
+ // match fire on the slash alone (e.g. "/x" matched "/self-fix").
27
+ const body = q.slice(1);
28
+ const prefix = [];
29
+ const substring = [];
30
+ const fuzzy = [];
31
+ // A single character only matches by PREFIX: a one-letter substring /
32
+ // subsequence match is far too loose ("x" would pull in "/self-fix").
33
+ const loose = body.length >= 2;
34
+ for (const c of commands) {
35
+ const name = c.name.toLowerCase().slice(1);
36
+ if (name.startsWith(body))
37
+ prefix.push(c);
38
+ else if (loose && name.includes(body))
39
+ substring.push(c);
40
+ else if (loose && isSubsequence(body, name))
41
+ fuzzy.push(c);
42
+ }
43
+ return prefix.concat(substring, fuzzy);
44
+ }
45
+ /**
46
+ * The visible slice of the suggestion list for a given scroll offset. Returns
47
+ * the shown commands, the CLAMPED offset and how many rows the block takes
48
+ * (the page plus an optional "more" line). Pure — the caller renders the rows.
49
+ */
50
+ export function suggestWindow(sugg, offset) {
51
+ if (!sugg.length)
52
+ return { shown: [], off: 0, moreCount: 0 };
53
+ const pageSize = SUGGEST_PAGE;
54
+ const maxOffset = Math.max(0, sugg.length - pageSize);
55
+ const off = Math.max(0, Math.min(offset, maxOffset));
56
+ const shown = sugg.slice(off, off + pageSize);
57
+ const moreCount = sugg.length > pageSize ? sugg.length - shown.length : 0;
58
+ return { shown, off, moreCount };
59
+ }
60
+ /**
61
+ * Tab completion. With an explicit selection, insert that command. With a
62
+ * single match, insert it whole. Otherwise complete up to the longest common
63
+ * prefix of the matches. Returns the new buffer, or null when nothing should
64
+ * change.
65
+ */
66
+ export function completeSlashCommand(buf, sugg, selected) {
67
+ if (!sugg.length)
68
+ return null;
69
+ if (selected >= 0 && selected < sugg.length) {
70
+ return sugg[selected].name + ' ';
71
+ }
72
+ if (sugg.length === 1)
73
+ return sugg[0].name + ' ';
74
+ let prefix = sugg[0].name;
75
+ for (const c of sugg) {
76
+ while (!c.name.toLowerCase().startsWith(prefix.toLowerCase())) {
77
+ prefix = prefix.slice(0, -1);
78
+ }
79
+ }
80
+ return prefix.length > buf.length ? prefix : null;
81
+ }
package/dist/input.js CHANGED
@@ -4,12 +4,15 @@ import { contentWidth } from './width.js';
4
4
  import { randomThinkingPhrase, stripEllipsis, DOTS, renderDots, } from './spinner.js';
5
5
  import { translate } from './i18n.js';
6
6
  import { AttachmentStore, parseImagePaste, looksLikeFilePath, } from './attachments.js';
7
- import { NL, CR, ESC, PASTE_START, PASTE_END, SUGGEST_PAGE, isSubsequence, pasteReplacement, expandPastes, safeJson, visRows, visLen, charWidth, layoutInput, formatTokenStatus, tokenStatusLevel, truncateToWidth, wrapToWidth, CONTEXT_LIMIT, } from './input/layout.js';
7
+ import { NL, CR, ESC, PASTE_START, PASTE_END, SUGGEST_PAGE, pasteReplacement, expandPastes, safeJson, visRows, visLen, charWidth, layoutInput, formatTokenStatus, tokenStatusLevel, truncateToWidth, wrapToWidth, CONTEXT_LIMIT, } from './input/layout.js';
8
8
  // The pure layout/formatting helpers (visual rows, cursor position, visible
9
9
  // width, paste markers, token formatting) live in ./input/layout.js. They are
10
10
  // re-exported here so every existing import from './input.js' keeps working,
11
11
  // and so tests can import them from either module (C3 step 3).
12
12
  export { PASTE_MIN_LINES, SUGGEST_PAGE, isSubsequence, countPasteLines, formatPasteMarker, pasteReplacement, expandPastes, visRows, visLen, charWidth, layoutInput, tokenStatusLevel, formatTokenStatus, formatCompactTokens, truncateToWidth, wrapToWidth, CONTEXT_LIMIT, CONTEXT_YELLOW_PCT, CONTEXT_RED_PCT, } from './input/layout.js';
13
+ // PURE slash-command suggestion logic (match / paging / completion), split out
14
+ // of the class so the fuzzy match is unit-tested without driving the editor.
15
+ import { matchSlashCommands, suggestWindow, completeSlashCommand, } from './input/suggest.js';
13
16
  // How long a lone ESC is held before it is treated as the Escape key (and not
14
17
  // as the head of a control sequence split across two reads). 40ms is below a
15
18
  // human "two Escapes" interval but above typical chunk-split latency.
@@ -388,37 +391,7 @@ export class LineEditor {
388
391
  // first, then a substring, then a subsequence (e.g. "hst" -> "/history"),
389
392
  // so a half-remembered command name still surfaces. Order is preserved.
390
393
  _suggestions() {
391
- const b = this.buf;
392
- if (!b.startsWith('/'))
393
- return [];
394
- if (b.includes(NL) || b.includes(' '))
395
- return [];
396
- if (b.length > 40)
397
- return [];
398
- const q = b.toLowerCase();
399
- if (q === '/')
400
- return this.slashCommands;
401
- // Match on the part AFTER the leading slash: comparing the raw query
402
- // (which itself starts with '/') against the full name made the fuzzy
403
- // subsequence match fire on the slash alone (e.g. "/x" matched
404
- // "/self-fix"), so a stray letter pulled in unrelated commands.
405
- const body = q.slice(1);
406
- const prefix = [];
407
- const substring = [];
408
- const fuzzy = [];
409
- // A single character only matches by PREFIX: a one-letter substring /
410
- // subsequence match is far too loose ("x" would pull in "/self-fix").
411
- const loose = body.length >= 2;
412
- for (const c of this.slashCommands) {
413
- const name = c.name.toLowerCase().slice(1);
414
- if (name.startsWith(body))
415
- prefix.push(c);
416
- else if (loose && name.includes(body))
417
- substring.push(c);
418
- else if (loose && isSubsequence(body, name))
419
- fuzzy.push(c);
420
- }
421
- return prefix.concat(substring, fuzzy);
394
+ return matchSlashCommands(this.buf, this.slashCommands);
422
395
  }
423
396
  // Render the suggestion block for the current query. `offset` scrolls the
424
397
  // window so Ctrl+N/P can page through more than the visible size (the list
@@ -436,10 +409,7 @@ export class LineEditor {
436
409
  }
437
410
  if (!sugg.length)
438
411
  return { lines: [], rows: 0 };
439
- const pageSize = SUGGEST_PAGE;
440
- const maxOffset = Math.max(0, sugg.length - pageSize);
441
- const off = Math.max(0, Math.min(offset, maxOffset));
442
- const shown = sugg.slice(off, off + pageSize);
412
+ const { shown, off, moreCount } = suggestWindow(sugg, offset);
443
413
  this._suggestOffset = off;
444
414
  this._suggestCount = sugg.length;
445
415
  const label = (c) => c.hint ? c.name + ' ' + c.hint : c.name;
@@ -450,12 +420,10 @@ export class LineEditor {
450
420
  const desc = theme.dim(' ' + c.description);
451
421
  return (selected ? theme.prompt(' ▸ ') : ' ') + name + desc;
452
422
  });
453
- if (sugg.length > pageSize) {
423
+ if (moreCount > 0) {
454
424
  const t = translate(this.locale);
455
425
  lines.push(theme.dim(' ' +
456
- t('editor.more', {
457
- n: String(sugg.length - shown.length),
458
- }) +
426
+ t('editor.more', { n: String(moreCount) }) +
459
427
  ' ' +
460
428
  t('editor.page_hint')));
461
429
  }
@@ -497,27 +465,9 @@ export class LineEditor {
497
465
  // Tab: complete the command up to the common prefix; if there is a single
498
466
  // match — insert it whole and add a space.
499
467
  _completeCommand() {
500
- const sugg = this._suggestions();
501
- if (!sugg.length)
502
- return;
503
- if (this._suggestSelected >= 0 && this._suggestSelected < sugg.length) {
504
- this.buf = sugg[this._suggestSelected].name + ' ';
505
- this.cursor = Array.from(this.buf).length;
506
- return;
507
- }
508
- if (sugg.length === 1) {
509
- this.buf = sugg[0].name + ' ';
510
- this.cursor = Array.from(this.buf).length;
511
- return;
512
- }
513
- let prefix = sugg[0].name;
514
- for (const c of sugg) {
515
- while (!c.name.toLowerCase().startsWith(prefix.toLowerCase())) {
516
- prefix = prefix.slice(0, -1);
517
- }
518
- }
519
- if (prefix.length > this.buf.length) {
520
- this.buf = prefix;
468
+ const next = completeSlashCommand(this.buf, this._suggestions(), this._suggestSelected);
469
+ if (next !== null) {
470
+ this.buf = next;
521
471
  this.cursor = Array.from(this.buf).length;
522
472
  }
523
473
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.72.4",
3
+ "version": "2.72.6",
4
4
  "description": "Terminal coding agent that drives chat.deepseek.com through Playwright — no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
5
5
  "type": "module",
6
6
  "bin": {