zames_pro 2.95.0 → 2.95.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.95.2] - 2026-11-04
11
+
12
+ ### Changed
13
+
14
+ - fix-deepseek-head-only
15
+ - test: pin the streamed-answer head snapshot (the resend loop)
16
+ - docs: theme gallery, mobile menu and code copy buttons
17
+
18
+ ## [2.95.1] - 2026-11-03
19
+
20
+ ### Fixed
21
+
22
+ - **loop:** skip the stall path for a stale BashOutput poll
23
+
10
24
  ## [2.95.0] - 2026-11-03
11
25
 
12
26
  ### Added
@@ -523,6 +523,15 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
523
523
  }
524
524
  }
525
525
  if (!parsed) {
526
+ // SILENT BACKGROUND POLL: {"tool": "BashOutput", "args": {...}} for a
527
+ // process that has ALREADY finished (the watchdog killed its promise,
528
+ // or the id is stale). The tool itself answers "no background process"
529
+ // and does not move the task forward, so an EMPTY model turn is not a
530
+ // stall worth a dev dump / a nudge - the loop simply re-asks quietly.
531
+ // Matching is DELIBERATELY narrow: only BashOutput with a plain id.
532
+ if (isEmptyPollCall(rawResponse)) {
533
+ continue;
534
+ }
526
535
  // The answer looks like a (possibly truncated) tool call. We catch not
527
536
  // only explicit JSON but also XML/DSML forms, "dirty" variants and
528
537
  // unclosed fragments: if such an answer is silently taken as final, the
@@ -1387,6 +1396,30 @@ let unparsedDumps = 0;
1387
1396
  const UNPARSED_DUMP_MAX = 20;
1388
1397
  const UNPARSED_DUMP_BYTES = 64 * 1024;
1389
1398
  /** Dev mode: save an unrecognized (but call-looking) answer for analysis. */
1399
+ /**
1400
+ * True when the raw answer is a bare `{"tool":"BashOutput","args":{"id":...}}`
1401
+ * (optionally wrapped in whitespace/fences). A finished background process is
1402
+ * polled by the model with such a call; the tool returns "no background
1403
+ * process with id ..." which does not advance the task. Used to skip the
1404
+ * stall nudge / dev dump for this one case only.
1405
+ */
1406
+ export function isEmptyPollCall(raw) {
1407
+ const t = String(raw || '').trim();
1408
+ if (!t || t.length > 400)
1409
+ return false;
1410
+ let obj;
1411
+ try {
1412
+ obj = JSON.parse(t);
1413
+ }
1414
+ catch {
1415
+ return false;
1416
+ }
1417
+ const o = obj;
1418
+ if (!o || o.tool !== 'BashOutput')
1419
+ return false;
1420
+ const a = o.args;
1421
+ return !!a && typeof a.id === 'string';
1422
+ }
1390
1423
  export async function dumpUnparsedCall(raw, meta) {
1391
1424
  if (unparsedDumps >= UNPARSED_DUMP_MAX)
1392
1425
  return '';
package/dist/browser.js CHANGED
@@ -510,6 +510,12 @@ export class DeepSeekBrowser {
510
510
  this._lastSentAt = Date.now();
511
511
  }
512
512
  this._netBody = body;
513
+ // The answer clock drives the "is this body FRESH?" gate in both
514
+ // ask loops. A body that carries a (head) fragment but no decodable
515
+ // answer text left the clock at 0, so every detector below saw a
516
+ // stale body and stayed silent. Give it a fresh timestamp.
517
+ if (!this._netCaptureAt)
518
+ this._netCaptureAt = Date.now();
513
519
  const cid = url.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/);
514
520
  if (cid)
515
521
  this._netChatId = cid[0];
@@ -2631,6 +2637,15 @@ export class DeepSeekBrowser {
2631
2637
  const noAnswer = !doneAnswer &&
2632
2638
  this._netNoAnswer &&
2633
2639
  this._netCaptureAt >= this._lastSentAt;
2640
+ // A HEAD-ONLY snapshot is NOT a dead turn: DeepSeek opens a turn
2641
+ // with a RESPONSE fragment that holds only newlines, and the text
2642
+ // arrives in the following bare `data: {"v":...}` chunks.
2643
+ // `_netBody` is the body of the LAST network event, so a poll that
2644
+ // lands between the snapshot and the first chunk sees FINISHED with
2645
+ // no answer, and the old code threw at once (the operator saw
2646
+ // "DeepSeek оборвал ответ") while the chat was still streaming.
2647
+ const headOnly = this._netCaptureAt >= this._lastSentAt &&
2648
+ isFinishedAwaitingAnswer(this._netBody);
2634
2649
  if ((truncated || noAnswer) && this.autoContinue) {
2635
2650
  // MEASURED: a COMPLETE DSML tool call ends with the event `close`
2636
2651
  // and `{click_behavior:'none', auto_resume:false}`, yet the DOM may
@@ -2660,7 +2675,9 @@ export class DeepSeekBrowser {
2660
2675
  // autoContinue is off. Do NOT wait out the whole timeout — the turn is
2661
2676
  // dead (truncated / finished with no answer), so tell ask() to RESEND
2662
2677
  // the prompt right away (that is the only remaining way to continue).
2663
- if (truncated || noAnswer) {
2678
+ // EXCEPT a head-only snapshot: the rest of the answer is still on
2679
+ // its way (see `headOnly`), so keep waiting for the append chunks.
2680
+ if ((truncated || noAnswer) && !headOnly) {
2664
2681
  throw new GenerationIncompleteError((this._netBody || 'finished without answer').slice(-300));
2665
2682
  }
2666
2683
  // Reasoning-mode pause: DeepSeek caps the THINK phase and shows a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.95.0",
3
+ "version": "2.95.2",
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": {