zames_pro 2.95.2 → 2.95.3

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,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.95.3] - 2026-11-04
11
+
12
+ ### Fixed
13
+
14
+ - **deepseek:** an appended answer is not a dead turn
15
+ - slow start
16
+
10
17
  ## [2.95.2] - 2026-11-04
11
18
 
12
19
  ### Changed
package/dist/browser.js CHANGED
@@ -2297,11 +2297,15 @@ export class DeepSeekBrowser {
2297
2297
  // Wait for the start: either Stop appeared, or the answer text changed,
2298
2298
  // or the page grew (a new answer node / longer last answer). In parallel we
2299
2299
  // catch the rate-limit toast (only toasts, not the whole body).
2300
- // MEASURED live: an answer either starts within a few seconds or the send
2301
- // did not go out at all. The old 30s + 20s windows meant FIFTY seconds of
2302
- // a frozen "generating" status on every missed start; 12s + 10s is
2303
- // plenty for DeepSeek to show the Stop button or start streaming.
2304
- const startDeadline = Date.now() + 12_000;
2300
+ //
2301
+ // MEASURED on 90 live turns: the server needs a MEDIAN of ~16s to deliver a
2302
+ // completion body (min 11.7s, max 25.8s), so the old 12s window was shorter
2303
+ // than a NORMAL turn. The loop then declared "the send did not go out" and
2304
+ // pressed Enter AGAIN — a DUPLICATE turn that ended in "Messages too
2305
+ // frequent". The window only has to cover the SEND (the input clearing, the
2306
+ // Stop button appearing), not the answer, so 30s is generous without
2307
+ // delaying a real miss by minutes.
2308
+ const startDeadline = Date.now() + 30_000;
2305
2309
  // A CHEAP growth signal (message-node count + last-answer length)
2306
2310
  // instead of document.body.innerText.length, which walked the whole page.
2307
2311
  const startSignal = await this._chatSignal();
@@ -2314,6 +2318,7 @@ export class DeepSeekBrowser {
2314
2318
  // the text has been stable for a couple of ticks.
2315
2319
  let settledTicks = 0;
2316
2320
  let lastStartCur = '';
2321
+ let stopTicks = 0;
2317
2322
  while (Date.now() < startDeadline) {
2318
2323
  if (this._abort || this._askCancelled)
2319
2324
  return '(прервано пользователем)';
@@ -2441,7 +2446,20 @@ export class DeepSeekBrowser {
2441
2446
  // 'changed' can stay false for a while — without this check ask() used
2442
2447
  // to hang until the full timeout and the agent appeared to "stop".
2443
2448
  const netStarted = !!this._netCapture && this._netCaptureAt >= this._lastSentAt;
2444
- if (changed || netStarted || grew) {
2449
+ // The Stop button is the SERVER's own "a turn is running" signal, and it
2450
+ // appears long before the first token reaches the DOM or the completion
2451
+ // body is complete (measured: the body lands a median 16s after the
2452
+ // send, while Stop shows up within a second). Without it a SLOW network
2453
+ // looked like a failed send: the loop hit the start deadline and pressed
2454
+ // Enter again, producing a duplicate turn. A lingering Stop from the
2455
+ // previous generation is ruled out by requiring TWO consecutive ticks
2456
+ // with the button visible, so a single stray paint does not start us.
2457
+ const stopVisible = await this._isGenerating();
2458
+ if (stopVisible)
2459
+ stopTicks++;
2460
+ else
2461
+ stopTicks = 0;
2462
+ if (changed || netStarted || grew || stopTicks >= 2) {
2445
2463
  started = true;
2446
2464
  this._askDebug('STARTED changed=' +
2447
2465
  changed +
@@ -257,13 +257,16 @@ export function isFinishedWithoutAnswer(body) {
257
257
  const finished = FINISHED_STATUS_RE.test(b);
258
258
  if (!finished)
259
259
  return false;
260
- // A RESPONSE fragment with real content means the model DID answer.
261
- const len = responseContentLength(b);
262
- if (len >= MIN_REAL_ANSWER_CHARS)
263
- return false;
264
- // No RESPONSE fragment at all (len === -1) or only a truncated head:
265
- // treat the absence of a real answer as "no answer produced".
266
- return true;
260
+ // MEASURED live: `responseContentLength` only sees the SNAPSHOT fragment
261
+ // (`v.response.fragments[]`), while DeepSeek appends the REAL answer through
262
+ // bare `data: {"v":...}` chunks. A live body with a `\n\n` snapshot and
263
+ // 1121 chars of appended text measured 2 chars, so 18 of 71 healthy bodies
264
+ // were classified as "finished WITHOUT an answer" — the finish loop then
265
+ // threw `GenerationIncompleteError` (no Continue button), ask() resent the
266
+ // task and the operator saw "DeepSeek оборвал ответ" on a perfectly good
267
+ // turn. The DECODED answer is the truth: use the real extractor.
268
+ const text = extractAnswer(b).trim();
269
+ return text === '' || TRUNCATED_HEAD_RE.test(text);
267
270
  }
268
271
  /**
269
272
  * True when a FINISHED body still carries NO complete answer — its only
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.95.2",
3
+ "version": "2.95.3",
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": {
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "scripts": {
22
22
  "start": "node ./dist/index.js",
23
- "dev": "ZAMES_NET_DEBUG=1 tsx ./src/index.ts --dev --headed",
23
+ "dev": "ZAMES_NET_DEBUG=1 ZAMES_ASK_DEBUG=1 tsx ./src/index.ts --dev --headed",
24
24
  "clean": "tsx scripts/clean-dist.mts",
25
25
  "build": "tsx scripts/clean-dist.mts && tsc -p tsconfig.build.json",
26
26
  "coverage:gate": "tsx scripts/coverage-gate.mts",