zames_pro 2.29.2 → 2.29.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/dist/browser.js +57 -1
- package/dist/net-capture.js +26 -0
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { chromium, } from 'playwright';
|
|
2
|
-
import { extractAnswer, extractTokenUsage, dumpNetBody } from './net-capture.js';
|
|
2
|
+
import { extractAnswer, extractTokenUsage, dumpNetBody, isFinishedWithoutAnswer, } from './net-capture.js';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import fs from 'fs/promises';
|
|
@@ -317,6 +317,10 @@ export class DeepSeekBrowser {
|
|
|
317
317
|
page;
|
|
318
318
|
_netCapture;
|
|
319
319
|
_netCaptureAt;
|
|
320
|
+
// True when the LAST response body was a FINISHED turn with NO answer text
|
|
321
|
+
// (only reasoning). The DOM keeps showing reasoning/old text, so the loop
|
|
322
|
+
// must not wait it out — it clicks Continue / resends. Reset before a send.
|
|
323
|
+
_netNoAnswer;
|
|
320
324
|
_netChatId;
|
|
321
325
|
// The latest CONTEXT size (in tokens) DeepSeek reported in the current
|
|
322
326
|
// chat. It is the `accumulated_token_usage` counter taken from the SSE
|
|
@@ -380,6 +384,7 @@ export class DeepSeekBrowser {
|
|
|
380
384
|
this._stopped = false;
|
|
381
385
|
this._netCapture = '';
|
|
382
386
|
this._netCaptureAt = 0;
|
|
387
|
+
this._netNoAnswer = false;
|
|
383
388
|
this._netChatId = null;
|
|
384
389
|
this._lastTokenUsage = null;
|
|
385
390
|
this._toggles = { deepThinking, webSearch };
|
|
@@ -543,6 +548,19 @@ export class DeepSeekBrowser {
|
|
|
543
548
|
const extracted = extractAnswer(body);
|
|
544
549
|
if (extracted) {
|
|
545
550
|
this._netCapture = extracted;
|
|
551
|
+
this._netNoAnswer = false;
|
|
552
|
+
const cid = url.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/);
|
|
553
|
+
if (cid)
|
|
554
|
+
this._netChatId = cid[0];
|
|
555
|
+
this._netCaptureAt = Date.now();
|
|
556
|
+
}
|
|
557
|
+
else if (/chat\/(completion|continue)/i.test(url) &&
|
|
558
|
+
isFinishedWithoutAnswer(body)) {
|
|
559
|
+
// A FINISHED turn with NO RESPONSE fragment (only reasoning): the UI
|
|
560
|
+
// shows a Continue button. Record it with a fresh timestamp so the
|
|
561
|
+
// ask() loops do not wait out the timeout — they click Continue.
|
|
562
|
+
this._netNoAnswer = true;
|
|
563
|
+
this._netCapture = '';
|
|
546
564
|
const cid = url.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/);
|
|
547
565
|
if (cid)
|
|
548
566
|
this._netChatId = cid[0];
|
|
@@ -1633,6 +1651,7 @@ export class DeepSeekBrowser {
|
|
|
1633
1651
|
}
|
|
1634
1652
|
this._netCapture = '';
|
|
1635
1653
|
this._netCaptureAt = 0;
|
|
1654
|
+
this._netNoAnswer = false;
|
|
1636
1655
|
// Align the DeepSeek chat toggles (deep thinking / web search) with the
|
|
1637
1656
|
// configured state BEFORE typing. Doing it here (after the send-pause) it
|
|
1638
1657
|
// does not flip toggles for a generation that is not going to happen.
|
|
@@ -1697,6 +1716,19 @@ export class DeepSeekBrowser {
|
|
|
1697
1716
|
isGenerationIncompleteText(this._netCapture)) {
|
|
1698
1717
|
throw new GenerationIncompleteError(this._netCapture.slice(-300));
|
|
1699
1718
|
}
|
|
1719
|
+
// A FINISHED turn with NO answer (only reasoning): the UI shows a
|
|
1720
|
+
// Continue button. Click it (or, if autoContinue is off, keep waiting)
|
|
1721
|
+
// instead of spinning to the deadline on an empty answer.
|
|
1722
|
+
if (this._netNoAnswer && this._netCaptureAt >= this._lastSentAt) {
|
|
1723
|
+
this._askDebug('START-loop net-no-answer');
|
|
1724
|
+
if (this.autoContinue && (await this._clickContinueIfVisible())) {
|
|
1725
|
+
this._askDebug('CLICKED Continue (net-no-answer, start-loop)');
|
|
1726
|
+
this._netNoAnswer = false;
|
|
1727
|
+
this._netCaptureAt = 0;
|
|
1728
|
+
await this.page.waitForTimeout(500);
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1700
1732
|
// Reasoning-mode pause during the THINK phase: click Continue so the
|
|
1701
1733
|
// generation resumes (the button appears before any RESPONSE text, so
|
|
1702
1734
|
// the start-wait would otherwise spin until the deadline).
|
|
@@ -1848,6 +1880,20 @@ export class DeepSeekBrowser {
|
|
|
1848
1880
|
isGenerationIncompleteText(this._netCapture)) {
|
|
1849
1881
|
throw new GenerationIncompleteError(this._netCapture.slice(-300));
|
|
1850
1882
|
}
|
|
1883
|
+
// A FINISHED turn with NO answer (only reasoning): the UI shows a
|
|
1884
|
+
// Continue button. Click it and keep waiting — otherwise the loop spins
|
|
1885
|
+
// to the deadline on an empty answer and the operator sees "Stopped".
|
|
1886
|
+
if (this._netNoAnswer &&
|
|
1887
|
+
this._netCaptureAt >= this._lastSentAt &&
|
|
1888
|
+
this.autoContinue) {
|
|
1889
|
+
if (await this._clickContinueIfVisible()) {
|
|
1890
|
+
this._askDebug('CLICKED Continue (net-no-answer, finish-loop)');
|
|
1891
|
+
this._netNoAnswer = false;
|
|
1892
|
+
this._netCaptureAt = 0;
|
|
1893
|
+
await this.page.waitForTimeout(500);
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1851
1897
|
// Reasoning-mode pause: DeepSeek caps the THINK phase and shows a
|
|
1852
1898
|
// Continue button; the model does not resume by itself. Click it so the
|
|
1853
1899
|
// answer keeps flowing instead of waiting for the operator.
|
|
@@ -1917,6 +1963,16 @@ export class DeepSeekBrowser {
|
|
|
1917
1963
|
netFresh);
|
|
1918
1964
|
await this.page.waitForTimeout(Math.max(0, this.stabilityDelayMs));
|
|
1919
1965
|
}
|
|
1966
|
+
// A FINISHED turn with NO answer text (only reasoning) must NOT be
|
|
1967
|
+
// returned as a final answer, and must not hang until the timeout either.
|
|
1968
|
+
// Treat it like a truncated turn: ask() resends the prompt (the same thing
|
|
1969
|
+
// the Continue button does). Only if a real capture arrived in the
|
|
1970
|
+
// meantime do we fall through to return it.
|
|
1971
|
+
if (this._netNoAnswer &&
|
|
1972
|
+
this._netCaptureAt >= this._lastSentAt &&
|
|
1973
|
+
!this._netCapture) {
|
|
1974
|
+
throw new GenerationIncompleteError('finished without answer');
|
|
1975
|
+
}
|
|
1920
1976
|
if (last && (normText(last) !== normText(beforeText) || this._netCapture)) {
|
|
1921
1977
|
// If Continue is STILL on screen, the turn is paused, not finished:
|
|
1922
1978
|
// click once more and report what we have rather than silently stopping.
|
package/dist/net-capture.js
CHANGED
|
@@ -159,6 +159,32 @@ export function extractAnswer(body) {
|
|
|
159
159
|
return sse;
|
|
160
160
|
return extractFromJson(body);
|
|
161
161
|
}
|
|
162
|
+
// True when the SSE body shows a turn that ENDED (`quasi_status: FINISHED`)
|
|
163
|
+
// but produced NO answer text — the reasoning (THINK fragment) was generated
|
|
164
|
+
// and then the model stopped WITHOUT writing a RESPONSE fragment.
|
|
165
|
+
//
|
|
166
|
+
// This is the "Stopped + Continue button" case: DeepSeek finishes the turn
|
|
167
|
+
// with only reasoning, the UI shows a Continue button, and extractAnswer()
|
|
168
|
+
// returns '' (it only collects RESPONSE fragments). The old code then saw an
|
|
169
|
+
// empty capture, ignored it, and the finish loop waited out the whole timeout
|
|
170
|
+
// and threw ds.send_no_new_answer — to the operator the agent "hung on Stopped".
|
|
171
|
+
// Unlike generation_err, `quasi_status` here is FINISHED, so
|
|
172
|
+
// isGenerationIncompleteText() does NOT catch it.
|
|
173
|
+
//
|
|
174
|
+
export function isFinishedWithoutAnswer(body) {
|
|
175
|
+
const b = String(body || '');
|
|
176
|
+
if (!b)
|
|
177
|
+
return false;
|
|
178
|
+
const finished = /"quasi_status","v":"FINISHED"/i.test(b);
|
|
179
|
+
if (!finished)
|
|
180
|
+
return false;
|
|
181
|
+
// A RESPONSE fragment with actual content means the model DID answer.
|
|
182
|
+
if (/"type":"RESPONSE","content":"[^"]/.test(b))
|
|
183
|
+
return false;
|
|
184
|
+
// The turn carried reasoning (THINK) or was truncated mid-way; treat the
|
|
185
|
+
// absence of response content as "no answer produced".
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
162
188
|
// The CONTEXT size (in tokens) DeepSeek reports for the current answer.
|
|
163
189
|
//
|
|
164
190
|
// chat.deepseek.com does not expose prompt_tokens/completion_tokens the way
|