zames_pro 2.49.0 → 2.49.2

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.
@@ -201,6 +201,12 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
201
201
  // the (fresh) chat grows past this plus a margin, so a chat that starts
202
202
  // above the threshold does not compact on every tool call.
203
203
  let lastAutoCompactTokens = -1;
204
+ // Set when an auto-compact returned the SAME chat id it started in — i.e.
205
+ // the "fresh chat" never opened and the compaction did nothing. Without this
206
+ // guard a broken newChat() (see B2) made the loop compact on EVERY tool call
207
+ // while the context kept growing. Defense in depth: the root cause is fixed,
208
+ // but a future regression must not turn into an endless "compacting" loop.
209
+ let autoCompactDisabled = false;
204
210
  // The message of a respond that arrived TOGETHER with real tool calls.
205
211
  // It is not delivered immediately (the tools must run first), but if the
206
212
  // model then stops without calling respond again, this is the best final
@@ -735,7 +741,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
735
741
  // (plus a small margin), so a fresh chat does not compact again on
736
742
  // every tool call.
737
743
  const armed = !firedRecently || tokens > lastAutoCompactTokens + 1;
738
- if (pct >= autoCompactPct && armed) {
744
+ if (pct >= autoCompactPct && armed && !autoCompactDisabled) {
739
745
  lastAutoCompactTokens = tokens;
740
746
  safeWarning(translate(locale)('compact.auto_trigger', {
741
747
  pct: String(Math.round(pct)),
@@ -743,9 +749,23 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
743
749
  }));
744
750
  transcript?.log('auto_compact_trigger', { tokens, pct });
745
751
  try {
752
+ const beforeChat = await browser
753
+ .getCurrentChatId()
754
+ .catch(() => null);
746
755
  const newChat = await onAutoCompact();
747
756
  if (newChat) {
748
- transcript?.log('auto_compact_done', { chatId: newChat });
757
+ if (beforeChat && newChat === beforeChat) {
758
+ // The chat did NOT change: the summary was posted back into
759
+ // the old chat and the context was not reset. Warn once and
760
+ // stop auto-compacting for the rest of the task instead of
761
+ // looping on every tool call.
762
+ autoCompactDisabled = true;
763
+ safeWarning(translate(locale)('compact.auto_same_chat'));
764
+ transcript?.log('auto_compact_same_chat', { chatId: newChat });
765
+ }
766
+ else {
767
+ transcript?.log('auto_compact_done', { chatId: newChat });
768
+ }
749
769
  // Continue in the fresh chat. The next send is a tool-result
750
770
  // (agent: true), which is fine: the new chat already holds the
751
771
  // system prompt + carryover. `lastAutoCompactTokens` stays at the
package/dist/i18n.js CHANGED
@@ -289,6 +289,10 @@ const CATALOG = {
289
289
  ru: '🗜️ Контекст заполнен на {pct}% ({tokens} токенов) — сжимаю историю и открываю новый чат...',
290
290
  en: '🗜️ Context at {pct}% ({tokens} tokens) — compacting the history and opening a fresh chat...',
291
291
  },
292
+ 'compact.auto_same_chat': {
293
+ ru: '🗜️ Сжатие не открыло новый чат (тот же id) — отключаю авто-сжатие до конца задачи, чтобы не зациклиться.',
294
+ en: '🗜️ Compaction did not open a fresh chat (same id) — disabling auto-compact for the rest of the task to avoid a loop.',
295
+ },
292
296
  'compact.start': {
293
297
  ru: '🗜️ Сжимаю историю чата (DeepSeek)...',
294
298
  en: '🗜️ Compacting the chat history (DeepSeek)...',
package/dist/input.js CHANGED
@@ -93,7 +93,12 @@ export function visRows(s, cols) {
93
93
  const len = visLen(s);
94
94
  return Math.max(1, Math.ceil(len / width));
95
95
  }
96
- // Visible length of a line without ANSI sequences.
96
+ // Visible length of a line without ANSI sequences. WIDE characters (emoji,
97
+ // CJK) count as 2 columns — a plain code-point count made the status-line
98
+ // arithmetic (right-align, wrapping, block erase) disagree with the real
99
+ // screen, so a status containing an emoji/CJK could wrap past the edge and
100
+ // make the block erase miss (status "stacking"). Combining marks and the
101
+ // zero-width joiner/space/variation selectors count as 0.
97
102
  export function visLen(s) {
98
103
  s = String(s);
99
104
  let n = 0;
@@ -109,11 +114,48 @@ export function visLen(s) {
109
114
  }
110
115
  continue;
111
116
  }
112
- n++;
113
- i++;
117
+ const cp = s.codePointAt(i);
118
+ n += charWidth(cp);
119
+ i += cp > 0xffff ? 2 : 1;
114
120
  }
115
121
  return n;
116
122
  }
123
+ // Display width of one code point in columns (0, 1 or 2). Based on the
124
+ // conventional wcwidth ranges — good enough for terminal layout, and it makes
125
+ // the emoji used in the status line (🧠🌐) count as 2, while math symbols like
126
+ // ⧗ (U+29D7) stay 1, matching a real terminal.
127
+ export function charWidth(cp) {
128
+ // Zero width: combining marks, ZWJ/ZWNJ, variation selectors.
129
+ if (cp === 0x200b ||
130
+ cp === 0x200c ||
131
+ cp === 0x200d ||
132
+ (cp >= 0x0300 && cp <= 0x036f) ||
133
+ (cp >= 0x1ab0 && cp <= 0x1aff) ||
134
+ (cp >= 0x1dc0 && cp <= 0x1dff) ||
135
+ (cp >= 0x20d0 && cp <= 0x20ff) ||
136
+ (cp >= 0xfe00 && cp <= 0xfe0f) ||
137
+ (cp >= 0xfe20 && cp <= 0xfe2f)) {
138
+ return 0;
139
+ }
140
+ // Wide: CJK, Hangul, fullwidth forms, emoji.
141
+ if ((cp >= 0x1100 && cp <= 0x115f) ||
142
+ (cp >= 0x2e80 && cp <= 0x303e) ||
143
+ (cp >= 0x3041 && cp <= 0x33ff) ||
144
+ (cp >= 0x3400 && cp <= 0x4dbf) ||
145
+ (cp >= 0x4e00 && cp <= 0x9fff) ||
146
+ (cp >= 0xa000 && cp <= 0xa4cf) ||
147
+ (cp >= 0xac00 && cp <= 0xd7a3) ||
148
+ (cp >= 0xf900 && cp <= 0xfaff) ||
149
+ (cp >= 0xfe30 && cp <= 0xfe4f) ||
150
+ (cp >= 0xff00 && cp <= 0xff60) ||
151
+ (cp >= 0xffe0 && cp <= 0xffe6) ||
152
+ (cp >= 0x1f000 && cp <= 0x1f02f) ||
153
+ (cp >= 0x1f300 && cp <= 0x1faff) ||
154
+ (cp >= 0x20000 && cp <= 0x3fffd)) {
155
+ return 2;
156
+ }
157
+ return 1;
158
+ }
117
159
  export function layoutInput(promptStr, buf, cursor, cols) {
118
160
  const width = Math.max(20, Number(cols) || 80);
119
161
  const promptW = visLen(promptStr);
@@ -128,21 +170,29 @@ export function layoutInput(promptStr, buf, cursor, cols) {
128
170
  const avail = Math.max(1, width - promptW);
129
171
  const start = i;
130
172
  // Build the next visual line. First we take as many
131
- // characters as fit (count < avail). If we hit the width
173
+ // characters as fit (columns < avail). If we hit the width
132
174
  // and the next character is not the end of the line, we roll back to the
133
175
  // last space so we don't split a word in the middle (word-wrap).
176
+ // Wrapping and the cursor column are measured in COLUMNS (charWidth),
177
+ // not code points — otherwise a CJK/emoji input would overflow the row
178
+ // and put the cursor on the wrong cell.
134
179
  let text = '';
135
- let count = 0;
136
- let lastSpace = -1; // index of the space in text (by Array.from)
137
- while (i < chars.length && chars[i] !== NL && count < avail) {
180
+ let widthUsed = 0;
181
+ let charIdx = 0;
182
+ let lastSpace = -1; // char index of the space within text
183
+ while (i < chars.length && chars[i] !== NL) {
184
+ const w = charWidth(chars[i].codePointAt(0));
185
+ if (widthUsed + w > avail)
186
+ break;
138
187
  if (chars[i] === ' ')
139
- lastSpace = count;
188
+ lastSpace = charIdx;
140
189
  text += chars[i];
141
190
  i++;
142
- count++;
191
+ charIdx++;
192
+ widthUsed += w;
143
193
  }
144
194
  const atLineEnd = i >= chars.length || chars[i] === NL;
145
- if (!atLineEnd && count >= avail && lastSpace > 0) {
195
+ if (!atLineEnd && widthUsed >= avail && lastSpace > 0) {
146
196
  // Move the "tail" of the line to the next visual line.
147
197
  const textChars = Array.from(text);
148
198
  const tailLen = textChars.length - lastSpace;
@@ -162,7 +212,8 @@ export function layoutInput(promptStr, buf, cursor, cols) {
162
212
  let cursorCol = promptW;
163
213
  for (let r = 0; r < rows.length; r++) {
164
214
  const row = rows[r];
165
- const len = Array.from(row.text).length;
215
+ const rowChars = Array.from(row.text);
216
+ const len = rowChars.length;
166
217
  const from = row.start;
167
218
  const to = row.start + len;
168
219
  if (cur >= from && cur <= to) {
@@ -170,7 +221,12 @@ export function layoutInput(promptStr, buf, cursor, cols) {
170
221
  if (cur === to && next && next.start === to)
171
222
  continue;
172
223
  cursorRow = r;
173
- cursorCol = promptW + (cur - from);
224
+ // Cursor column in COLUMNS (wide chars count as 2).
225
+ let w = 0;
226
+ for (let k = 0; k < cur - from; k++) {
227
+ w += charWidth(rowChars[k].codePointAt(0));
228
+ }
229
+ cursorCol = promptW + w;
174
230
  break;
175
231
  }
176
232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.49.0",
3
+ "version": "2.49.2",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {