zames_pro 2.10.8 → 2.10.9

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.
@@ -20,6 +20,11 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
20
20
  };
21
21
  };
22
22
  const safeThinking = safe(onThinking);
23
+ // Start the "working" indicator ONLY when a message is really sent (after
24
+ // the send-pause), not while the loop is still preparing the request.
25
+ // Previously safeThinking() fired before browser.ask(), so the spinner ran
26
+ // through the whole throttle pause / chat-open phase with nothing in flight.
27
+ browser.onSendStart = safeThinking;
23
28
  const safeAssistantThought = safe(onAssistantThought);
24
29
  const safeToolCall = safe(onToolCall);
25
30
  const safeToolResult = safe(onToolResult);
@@ -134,7 +139,9 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
134
139
  let afterToolRetries = 0;
135
140
  const MAX_AFTER_TOOL_RETRIES = 6;
136
141
  for (let i = 0; i < maxIterations; i++) {
137
- safeThinking();
142
+ // NOTE: the spinner is NOT started here. browser.onSendStart fires it
143
+ // right when the message is actually typed/sent (after the send-pause),
144
+ // so no spinner runs during the pre-send phase.
138
145
  // The first message (task) is user input: no throttle.
139
146
  // Subsequent ones (tool-result and resend requests) are agent sends:
140
147
  // throttled so we don't hit the rate limit.
package/dist/browser.js CHANGED
@@ -139,6 +139,11 @@ export class DeepSeekBrowser {
139
139
  _netSniff;
140
140
  _netSniffLimit;
141
141
  _netHookInstalled;
142
+ // Fired right when a message is actually typed/sent (AFTER the send-pause
143
+ // and attachments). Used to start the "agent is working" spinner only when
144
+ // a generation really begins, not during the pre-send phase (chat open,
145
+ // throttle wait), which used to show a spinner with no work in flight.
146
+ onSendStart;
142
147
  constructor({ headless = false, debug = false, channel = 'chrome', answerTimeoutMs = 180000, askRetries = 3, stabilityChecks = 3, stabilityDelayMs = 1000, minSendIntervalMs = 15000, rateLimitWaitMs = 300000, maxRateLimitRetries = 6, maxServerBusyRetries = 5, serverBusyWaitMs = 3000, } = {}) {
143
148
  this.headless = headless;
144
149
  this.debug = debug;
@@ -161,6 +166,7 @@ export class DeepSeekBrowser {
161
166
  this._netSniff = [];
162
167
  this._netSniffLimit = 5;
163
168
  this._netHookInstalled = false;
169
+ this.onSendStart = null;
164
170
  }
165
171
  async launch() {
166
172
  await killStaleChrome();
@@ -670,6 +676,16 @@ export class DeepSeekBrowser {
670
676
  // Esc/Ctrl+C pressed during the pause — do not send anything.
671
677
  if (this._abort)
672
678
  return '(прервано пользователем)';
679
+ // The pause is over and we are really about to type/send: only NOW start
680
+ // the "working" indicator. The spinner used to be started by the caller
681
+ // BEFORE ask(), so it ran for the whole throttle pause and chat-opening
682
+ // phase with no generation in flight.
683
+ if (this.onSendStart) {
684
+ try {
685
+ this.onSendStart();
686
+ }
687
+ catch { }
688
+ }
673
689
  this._netCapture = '';
674
690
  this._netCaptureAt = 0;
675
691
  // Attach files/images FIRST (before the text): the DeepSeek upload widget
package/dist/i18n.js CHANGED
@@ -128,6 +128,7 @@ const CATALOG = {
128
128
  'msg.new_chat': { ru: 'Создаю новый чат...', en: 'Creating a new chat...' },
129
129
  'msg.new_chat_ok': { ru: 'Новый чат.', en: 'New chat.' },
130
130
  'msg.resuming': { ru: 'Восстанавливаю сессию {id}...', en: 'Restoring session {id}...' },
131
+ 'msg.input_locked': { ru: 'Идёт операция, ввод временно заблокирован…', en: 'Operation in progress, input is temporarily locked…' },
131
132
  'msg.opening_chat': { ru: 'Открываю чат {id}...', en: 'Opening chat {id}...' },
132
133
  'msg.chat_opened': { ru: 'Чат открыт.', en: 'Chat opened.' },
133
134
  'msg.interactive': { ru: 'Интерактивный режим. Введите задачу. Команды — /help. Выход — /exit.', en: 'Interactive mode. Enter a task. Commands — /help. Exit — /exit.' },
package/dist/index.js CHANGED
@@ -714,9 +714,15 @@ async function runTask(browser, tools, taskText, workdir, opts, attachments = []
714
714
  };
715
715
  // Execute the task, then everything the user managed to type while it
716
716
  // ran. The queue may be replenished right during draining.
717
+ //
718
+ // NOTE: we do NOT call ui.thinking() here. runAgentLoop() fires
719
+ // onThinking() right before the actual browser send (after the send
720
+ // pause, system-prompt, etc.), so the spinner only appears when a
721
+ // generation really starts. Calling it here made the spinner run for
722
+ // the whole pre-send phase (chat creation, throttle wait) with no
723
+ // generation in flight.
717
724
  while (true) {
718
- ui.thinking();
719
- await mod.runAgentLoop({
725
+ const outcome = await mod.runAgentLoop({
720
726
  browser,
721
727
  tools,
722
728
  task: next.text,
@@ -735,6 +741,16 @@ async function runTask(browser, tools, taskText, workdir, opts, attachments = []
735
741
  debugLog: debug,
736
742
  locale: currentLocale,
737
743
  });
744
+ // The loop may end WITHOUT a model answer: an exhausted iteration
745
+ // limit or an ask() watchdog (the model stopped responding). In that
746
+ // case no assistant message was shown, and the operator saw the run
747
+ // just "stop" after a tool call with no explanation. Surface it.
748
+ if (outcome &&
749
+ (outcome.startsWith('Достигнут лимит итераций') ||
750
+ outcome.startsWith('ask() watchdog'))) {
751
+ ui.warning(outcome);
752
+ transcript?.log('agent_no_answer', { outcome });
753
+ }
738
754
  // Aborted (Esc/Ctrl+C) — we don't start the next tasks from the queue
739
755
  // and clear it, so "stop" really stops everything.
740
756
  if (browser._stopped) {
@@ -770,6 +786,9 @@ async function runTask(browser, tools, taskText, workdir, opts, attachments = []
770
786
  finally {
771
787
  stopWatching();
772
788
  ui.stop();
789
+ // Detach the send hook so a later browser.ask() outside this task cannot
790
+ // start a stale spinner.
791
+ browser.onSendStart = null;
773
792
  }
774
793
  }
775
794
  // ---------- main ----------
@@ -1339,6 +1358,8 @@ async function main() {
1339
1358
  }
1340
1359
  if (['/new', '/clear', 'new'].includes(lower)) {
1341
1360
  console.log(theme.system(t('msg.new_chat')));
1361
+ if (editor)
1362
+ editor.lock(t('msg.input_locked'));
1342
1363
  try {
1343
1364
  await browser.newChat();
1344
1365
  freshChatNext = false;
@@ -1351,6 +1372,10 @@ async function main() {
1351
1372
  catch (e) {
1352
1373
  console.error(theme.error(t('msg.new_chat_error', { v: e.message })), e.message);
1353
1374
  }
1375
+ finally {
1376
+ if (editor)
1377
+ editor.unlock();
1378
+ }
1354
1379
  continue;
1355
1380
  }
1356
1381
  // ---------- Self-review ----------
@@ -1360,6 +1385,8 @@ async function main() {
1360
1385
  const originalWorkdir = reviewMode
1361
1386
  ? reviewMode.originalWorkdir
1362
1387
  : currentWorkdir;
1388
+ if (editor)
1389
+ editor.lock(t('msg.input_locked'));
1363
1390
  try {
1364
1391
  const result = await mod.selfReview({
1365
1392
  browser,
@@ -1388,6 +1415,10 @@ async function main() {
1388
1415
  if (debug)
1389
1416
  console.error(e.stack);
1390
1417
  }
1418
+ finally {
1419
+ if (editor)
1420
+ editor.unlock();
1421
+ }
1391
1422
  continue;
1392
1423
  }
1393
1424
  if (lower === '/self-fix' || lower.startsWith('/self-fix ')) {
@@ -1513,6 +1544,8 @@ async function main() {
1513
1544
  // ---------- Regular commands ----------
1514
1545
  if (lower === '/chats') {
1515
1546
  const spin = editor || mod.createSpinner(currentLocale);
1547
+ if (editor)
1548
+ editor.lock(t('msg.input_locked'));
1516
1549
  spin.thinking();
1517
1550
  try {
1518
1551
  lastChats = await browser.listChats(30);
@@ -1533,6 +1566,10 @@ async function main() {
1533
1566
  spin.stop();
1534
1567
  console.error(theme.error(t('chats.fetch_error')), e.message);
1535
1568
  }
1569
+ finally {
1570
+ if (editor)
1571
+ editor.unlock();
1572
+ }
1536
1573
  continue;
1537
1574
  }
1538
1575
  if (lower === '/resume' || lower.startsWith('/resume ')) {
@@ -1556,6 +1593,8 @@ async function main() {
1556
1593
  continue;
1557
1594
  }
1558
1595
  console.log(theme.system(t('chats.opening', { v: pick.title })));
1596
+ if (editor)
1597
+ editor.lock(t('msg.input_locked'));
1559
1598
  try {
1560
1599
  await browser.openChat(pick.id);
1561
1600
  currentChatId = pick.id;
@@ -1571,6 +1610,10 @@ async function main() {
1571
1610
  catch (e) {
1572
1611
  console.error(theme.error(t('msg.open_chat_error', { v: '' })), e.message);
1573
1612
  }
1613
+ finally {
1614
+ if (editor)
1615
+ editor.unlock();
1616
+ }
1574
1617
  continue;
1575
1618
  }
1576
1619
  if (lower === '/chat') {
@@ -1608,6 +1651,8 @@ async function main() {
1608
1651
  console.error(theme.error(t('sessions.resume_id_usage')));
1609
1652
  continue;
1610
1653
  }
1654
+ if (editor)
1655
+ editor.lock(t('msg.input_locked'));
1611
1656
  try {
1612
1657
  console.log(theme.system(t('msg.opening_chat', { id })));
1613
1658
  await browser.openChat(id);
@@ -1622,6 +1667,10 @@ async function main() {
1622
1667
  catch (e) {
1623
1668
  console.error(theme.error(t('msg.open_chat_error', { v: '' })), e.message);
1624
1669
  }
1670
+ finally {
1671
+ if (editor)
1672
+ editor.unlock();
1673
+ }
1625
1674
  continue;
1626
1675
  }
1627
1676
  if (lower === '/pwd') {
package/dist/input.js CHANGED
@@ -207,6 +207,11 @@ export class LineEditor {
207
207
  // Clipboard callback: tries to read an image from the OS clipboard when the
208
208
  // terminal itself sends no usable data (Ctrl+V, right-click, empty paste).
209
209
  onClipboard;
210
+ // When locked, the editor ignores text input and Enter (submit). Used while
211
+ // a long operation runs (chat resume/open, /new, self-review): otherwise the
212
+ // user could type and send a message mid-operation, and it would be queued
213
+ // and sent right after the operation, breaking the restored session.
214
+ locked;
210
215
  constructor({ prompt = '> ', commands = [] } = {}) {
211
216
  this.promptStr = prompt;
212
217
  this.buf = '';
@@ -236,6 +241,7 @@ export class LineEditor {
236
241
  this._tmpDir = '';
237
242
  this.onAttach = null;
238
243
  this.onClipboard = null;
244
+ this.locked = false;
239
245
  }
240
246
  // Read the OS clipboard for an image and insert its marker. Used when the
241
247
  // terminal sends no usable paste data (Ctrl+V / right-click / empty paste).
@@ -333,6 +339,19 @@ export class LineEditor {
333
339
  this._writeBlock();
334
340
  this._render();
335
341
  }
342
+ // Lock input while a long operation runs (chat resume/open, /new,
343
+ // self-review). The line stays visible but text input and Enter are ignored,
344
+ // so a message typed mid-operation is not queued and sent afterwards,
345
+ // breaking the restored session. Ctrl+C/Ctrl+D still work so the user can
346
+ // abort.
347
+ lock(status) {
348
+ this.locked = true;
349
+ if (status)
350
+ this.setStatus(theme.dim(status));
351
+ }
352
+ unlock() {
353
+ this.locked = false;
354
+ }
336
355
  setPrompt(str) {
337
356
  this.promptStr = str;
338
357
  this._render();
@@ -481,6 +500,10 @@ export class LineEditor {
481
500
  }
482
501
  // ---------- input ----------
483
502
  _submit() {
503
+ if (this.locked) {
504
+ this._render();
505
+ return;
506
+ }
484
507
  const display = this.buf;
485
508
  if (!display.trim()) {
486
509
  this._render();
@@ -716,6 +739,25 @@ export class LineEditor {
716
739
  }
717
740
  _handle(data) {
718
741
  let s = data.toString('utf-8');
742
+ // While locked (a long operation is running), swallow all input except
743
+ // Ctrl+C / Ctrl+D — so the user can still abort, but cannot type a message
744
+ // that would be queued and sent after the operation.
745
+ if (this.locked) {
746
+ if (s.length === 1) {
747
+ const c = s.charCodeAt(0);
748
+ if (c === 3) {
749
+ if (this.onCtrlC)
750
+ this.onCtrlC();
751
+ return;
752
+ }
753
+ if (c === 4) {
754
+ if (this.onCtrlC)
755
+ this.onCtrlC();
756
+ return;
757
+ }
758
+ }
759
+ return;
760
+ }
719
761
  if (!this._inPaste && s === ESC) {
720
762
  if (this.onEscape)
721
763
  this.onEscape();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.10.8",
3
+ "version": "2.10.9",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {