zames_pro 2.96.10 → 2.96.11

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,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.96.11] - 2026-11-06
11
+
12
+ ### Fixed
13
+
14
+ - **prompt:** delete a whole code point in the forward-Delete branch
15
+ - **completion-check:** treat extension-less build files as runnable artifacts
16
+ - **login:** classify a guest page with a captcha as a challenge
17
+ - **compact:** keep the message count in an empty-fallback reason
18
+ - **restore-history:** decode every JSON escape in one pass so a doubled backslash keeps the next escape
19
+
10
20
  ## [2.96.10] - 2026-11-06
11
21
 
12
22
  ### Fixed
@@ -175,10 +175,13 @@ export function classifyLoginProbe(input) {
175
175
  const role = String(input.role || '').toLowerCase();
176
176
  if (role === 'user' || role === 'admin')
177
177
  return 'in';
178
- if (role === 'guest')
179
- return input.signInVisible ? 'out' : 'in';
178
+ // A captcha is the site asking for a HUMAN: a guest page that renders one is
179
+ // NOT a usable session (the visible-window flow exists for exactly this), so
180
+ // it must be classified before the guest shortcut below.
180
181
  if (input.captcha)
181
182
  return 'challenge';
183
+ if (role === 'guest')
184
+ return input.signInVisible ? 'out' : 'in';
182
185
  if (input.hasToken === true)
183
186
  return 'in';
184
187
  if (input.hasToken === false)
package/dist/compact.js CHANGED
@@ -143,7 +143,9 @@ export async function performCompact(opts) {
143
143
  // The reason must be ACTIONABLE: a model error, a BLOCKED history
144
144
  // fetch (auth/HTTP) and a TOO-SHORT history end here, and a bare
145
145
  // "unknown" hides which one it was.
146
- const err = summaryErr
146
+ // A model error is reported verbatim: it already names the cause.
147
+ const unusableReply = !!summaryErr && /^\s*[\[\{]/.test(summaryErr);
148
+ const err = summaryErr && !unusableReply
147
149
  ? summaryErr
148
150
  : histErr
149
151
  ? t('compact.summary_failed', { v: histErr })
@@ -152,8 +154,10 @@ export async function performCompact(opts) {
152
154
  ' (' +
153
155
  historyLen +
154
156
  ')'
155
- : t('compact.summary_failed', { v: 'history too short' });
156
- ' (' + historyLen + ')';
157
+ : t('compact.summary_failed', { v: 'history too short' }) +
158
+ ' (' +
159
+ historyLen +
160
+ ')';
157
161
  log(t('compact.summary_failed', { v: err }));
158
162
  return {
159
163
  ok: false,
@@ -34,12 +34,27 @@ const RUNNABLE_EXT = new Set([
34
34
  '.php',
35
35
  '.css',
36
36
  ]);
37
+ // Extension-LESS build artifacts: they are executable "does it run" targets
38
+ // just like a script, and they were the one family the guard silently skipped
39
+ // (a written Makefile/Dockerfile and an answer DONE with no Bash call in
40
+ // between is exactly the live failure this module exists for).
41
+ const RUNNABLE_NAMES = new Set([
42
+ 'makefile',
43
+ 'gnumakefile',
44
+ 'dockerfile',
45
+ 'containerfile',
46
+ 'build.ninja',
47
+ 'justfile',
48
+ ]);
37
49
  export function isRunnablePath(p) {
38
50
  if (!p)
39
51
  return false;
40
52
  const lower = p.toLowerCase();
53
+ const base = lower.slice(lower.lastIndexOf('/') + 1);
54
+ if (RUNNABLE_NAMES.has(base))
55
+ return true;
41
56
  const dot = lower.lastIndexOf('.');
42
- if (dot < 0)
57
+ if (dot < 0 || dot < lower.lastIndexOf('/'))
43
58
  return false;
44
59
  return RUNNABLE_EXT.has(lower.slice(dot));
45
60
  }
package/dist/prompt.js CHANGED
@@ -200,9 +200,23 @@ export async function promptOnce(question) {
200
200
  continue;
201
201
  }
202
202
  if (rest.startsWith('[3~')) {
203
- // Delete.
203
+ // Delete removes a CODE POINT, not one UTF-16 unit: in front of an
204
+ // emoji a unit-sized cut left the LOW surrogate in the line — a
205
+ // broken code point the caller received as its answer (same rule
206
+ // as the Backspace branch above). A unit cut is only safe OFF a
207
+ // surrogate: inside a PAIR it must move to the pair's start first.
204
208
  if (cursor < line.length) {
205
- line = line.slice(0, cursor) + line.slice(cursor + 1);
209
+ const at = line.charCodeAt(cursor);
210
+ if (at >= 0xdc00 && at <= 0xdfff)
211
+ cursor--;
212
+ const next = line.charCodeAt(cursor);
213
+ const follow = cursor + 1 < line.length ? line.charCodeAt(cursor + 1) : 0;
214
+ const pair = next >= 0xd800 &&
215
+ next <= 0xdbff &&
216
+ follow >= 0xdc00 &&
217
+ follow <= 0xdfff;
218
+ const cut = cursor + (pair ? 2 : 1);
219
+ line = line.slice(0, cursor) + line.slice(cut);
206
220
  redraw();
207
221
  }
208
222
  s = s.slice(3);
@@ -56,12 +56,32 @@ export function parseAssistantToolCall(text) {
56
56
  export function unescapeJsonString(s) {
57
57
  // A `\uD800`-style escape that is NOT followed by a low surrogate (a
58
58
  // truncated or hand-written payload) would become a LONE surrogate - a
59
- // broken code point the restored dialogue prints as `?`. Decode a valid PAIR
60
- // as one code point and drop an unpaired half.
59
+ // broken code point the restored dialogue prints as `?`. Decode a valid
60
+ // PAIR as one code point and drop an unpaired half.
61
+ //
62
+ // ONE pass for EVERY escape family: `\\` is consumed before the simple
63
+ // escapes (`\n`, `\t`, ...) look at the second half of a DOUBLED
64
+ // backslash. JSON stores a literal backslash before `n` (`C:\\notes`,
65
+ // a printed `\n`) as `\\\\n`; decoding `\n` first turned that pair
66
+ // into a real LINE BREAK and lost the backslash, reflowing the restored
67
+ // answer.
61
68
  let pendingHigh = '';
62
- return String(s || '')
63
- .replace(/\\u([0-9a-fA-F]{4})/g, (_x, h) => {
64
- const cp = parseInt(h, 16);
69
+ return String(s || '').replace(/\\(?:u([0-9a-fA-F]{4})|([nrt"\\]))/g, (_m, hex, ch) => {
70
+ if (!hex) {
71
+ if (ch === 'n')
72
+ return NL;
73
+ if (ch === 'r')
74
+ return String.fromCharCode(13);
75
+ if (ch === 't')
76
+ return String.fromCharCode(9);
77
+ if (ch === '"')
78
+ return '"';
79
+ pendingHigh = '';
80
+ return '\\';
81
+ }
82
+ const cp = parseInt(hex, 16);
83
+ // A valid PAIR is one code point; an UNPAIRED half is dropped (it
84
+ // would print as `?`).
65
85
  if (cp >= 0xd800 && cp <= 0xdbff) {
66
86
  pendingHigh = String.fromCharCode(cp);
67
87
  return '';
@@ -73,12 +93,7 @@ export function unescapeJsonString(s) {
73
93
  }
74
94
  pendingHigh = '';
75
95
  return String.fromCharCode(cp);
76
- })
77
- .replace(/\\n/g, NL)
78
- .replace(/\\r/g, String.fromCharCode(13))
79
- .replace(/\\t/g, String.fromCharCode(9))
80
- .replace(/\\"/g, '"')
81
- .replace(/\\\\/g, '\\');
96
+ });
82
97
  }
83
98
  /**
84
99
  * Normalize one restored message for display:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.96.10",
3
+ "version": "2.96.11",
4
4
  "description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen or GLM — 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": {