zames_pro 2.29.3 → 2.29.5
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 +51 -16
- package/dist/commands.js +88 -33
- package/dist/index.js +7 -1
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -340,6 +340,13 @@ export class DeepSeekBrowser {
|
|
|
340
340
|
_lastHistoryError;
|
|
341
341
|
_netSniff;
|
|
342
342
|
_netSniffLimit;
|
|
343
|
+
// The RAW SSE body of the LAST answer response. Unlike `_netCapture`, this
|
|
344
|
+
// is kept even when the body carries no RESPONSE text (a truncated turn:
|
|
345
|
+
// generation_err / INCOMPLETE, or FINISHED-with-only-reasoning). The
|
|
346
|
+
// incomplete/no-answer detectors MUST run on the raw body — guarding them on
|
|
347
|
+
// `_netCapture` (which is '' in exactly those cases) made them dead code and
|
|
348
|
+
// the agent hung until the timeout.
|
|
349
|
+
_netBody;
|
|
343
350
|
_netHookInstalled;
|
|
344
351
|
// Fired right when a message is actually typed/sent (AFTER the send-pause
|
|
345
352
|
// and attachments). Used to start the "agent is working" spinner only when
|
|
@@ -393,6 +400,7 @@ export class DeepSeekBrowser {
|
|
|
393
400
|
this._lastHistoryError = '';
|
|
394
401
|
this._netSniff = [];
|
|
395
402
|
this._netSniffLimit = 5;
|
|
403
|
+
this._netBody = '';
|
|
396
404
|
this._netHookInstalled = false;
|
|
397
405
|
this.onSendStart = null;
|
|
398
406
|
this.onSendPause = null;
|
|
@@ -554,17 +562,31 @@ export class DeepSeekBrowser {
|
|
|
554
562
|
this._netChatId = cid[0];
|
|
555
563
|
this._netCaptureAt = Date.now();
|
|
556
564
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
this._netNoAnswer = true;
|
|
563
|
-
this._netCapture = '';
|
|
565
|
+
// Store the RAW body for the CURRENT answer (completion/continue) so the
|
|
566
|
+
// truncated/no-answer detectors can inspect it even when there is no
|
|
567
|
+
// RESPONSE text (extractAnswer() returns '' for those bodies).
|
|
568
|
+
if (/chat\/(completion|continue)/i.test(url)) {
|
|
569
|
+
this._netBody = body;
|
|
564
570
|
const cid = url.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/);
|
|
565
571
|
if (cid)
|
|
566
572
|
this._netChatId = cid[0];
|
|
567
|
-
|
|
573
|
+
if (!extracted) {
|
|
574
|
+
if (isFinishedWithoutAnswer(body)) {
|
|
575
|
+
// A FINISHED turn with NO RESPONSE fragment (only reasoning): the
|
|
576
|
+
// UI shows Stopped + Continue. Record it with a fresh timestamp
|
|
577
|
+
// so the ask() loops do not wait out the timeout — they click
|
|
578
|
+
// Continue / resend.
|
|
579
|
+
this._netNoAnswer = true;
|
|
580
|
+
this._netCapture = '';
|
|
581
|
+
}
|
|
582
|
+
// A truncated turn (generation_err / INCOMPLETE) also has no
|
|
583
|
+
// RESPONSE; make the body visible to the incomplete check by
|
|
584
|
+
// refreshing the timestamp.
|
|
585
|
+
if (isGenerationIncompleteText(body)) {
|
|
586
|
+
this._netCapture = '';
|
|
587
|
+
}
|
|
588
|
+
this._netCaptureAt = Date.now();
|
|
589
|
+
}
|
|
568
590
|
}
|
|
569
591
|
}
|
|
570
592
|
catch { }
|
|
@@ -1652,6 +1674,7 @@ export class DeepSeekBrowser {
|
|
|
1652
1674
|
this._netCapture = '';
|
|
1653
1675
|
this._netCaptureAt = 0;
|
|
1654
1676
|
this._netNoAnswer = false;
|
|
1677
|
+
this._netBody = '';
|
|
1655
1678
|
// Align the DeepSeek chat toggles (deep thinking / web search) with the
|
|
1656
1679
|
// configured state BEFORE typing. Doing it here (after the send-pause) it
|
|
1657
1680
|
// does not flip toggles for a generation that is not going to happen.
|
|
@@ -1711,10 +1734,13 @@ export class DeepSeekBrowser {
|
|
|
1711
1734
|
// The turn was truncated by the server: the SSE body carries
|
|
1712
1735
|
// finish_reason=generation_err / quasi_status=INCOMPLETE and the UI
|
|
1713
1736
|
// shows "Continue". Retry instead of waiting out the whole timeout.
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1737
|
+
// NOTE: the check runs on the RAW `_netBody`, not on `_netCapture`: a
|
|
1738
|
+
// truncated body has NO RESPONSE fragment, so extractAnswer() returned
|
|
1739
|
+
// '' and `_netCapture` was empty — guarding on it made this branch dead
|
|
1740
|
+
// code and the agent hung on "Stopped" until the timeout.
|
|
1741
|
+
if (this._netCaptureAt >= this._lastSentAt &&
|
|
1742
|
+
isGenerationIncompleteText(this._netBody)) {
|
|
1743
|
+
throw new GenerationIncompleteError(this._netBody.slice(-300));
|
|
1718
1744
|
}
|
|
1719
1745
|
// A FINISHED turn with NO answer (only reasoning): the UI shows a
|
|
1720
1746
|
// Continue button. Click it (or, if autoContinue is off, keep waiting)
|
|
@@ -1805,6 +1831,14 @@ export class DeepSeekBrowser {
|
|
|
1805
1831
|
// previous tool call.
|
|
1806
1832
|
const cur = await this._readLastAnswerTextCleanDom().catch(() => '');
|
|
1807
1833
|
const fresh = !!this._netCapture && this._netCaptureAt >= this._lastSentAt;
|
|
1834
|
+
// The turn was truncated / produced no answer (generation_err,
|
|
1835
|
+
// INCOMPLETE, or FINISHED-with-only-reasoning): the message WAS sent,
|
|
1836
|
+
// the server just cut the turn. Resend (ask() handles it) instead of
|
|
1837
|
+
// treating this as "send failed" and hammering Enter.
|
|
1838
|
+
if (this._netCaptureAt >= this._lastSentAt &&
|
|
1839
|
+
(isGenerationIncompleteText(this._netBody) || this._netNoAnswer)) {
|
|
1840
|
+
throw new GenerationIncompleteError(this._netBody.slice(-300));
|
|
1841
|
+
}
|
|
1808
1842
|
if (cur &&
|
|
1809
1843
|
cur.trim() &&
|
|
1810
1844
|
normText(cur) !== normText(beforeText) &&
|
|
@@ -1875,10 +1909,11 @@ export class DeepSeekBrowser {
|
|
|
1875
1909
|
// keeps the partial answer, so without this check the loop waits out the
|
|
1876
1910
|
// whole timeout and throws ds.send_no_new_answer — the operator sees the
|
|
1877
1911
|
// agent "stop" with a Continue button in the chat. Retry the send.
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1912
|
+
// Runs on the RAW `_netBody` (a truncated body has no RESPONSE, so
|
|
1913
|
+
// `_netCapture` is empty and the old guard was dead code).
|
|
1914
|
+
if (this._netCaptureAt >= this._lastSentAt &&
|
|
1915
|
+
isGenerationIncompleteText(this._netBody)) {
|
|
1916
|
+
throw new GenerationIncompleteError(this._netBody.slice(-300));
|
|
1882
1917
|
}
|
|
1883
1918
|
// A FINISHED turn with NO answer (only reasoning): the UI shows a
|
|
1884
1919
|
// Continue button. Click it and keep waiting — otherwise the loop spins
|
package/dist/commands.js
CHANGED
|
@@ -197,60 +197,115 @@ export function resolveExtraDir(input, workdir) {
|
|
|
197
197
|
}
|
|
198
198
|
return { path: abs };
|
|
199
199
|
}
|
|
200
|
-
/** How many
|
|
200
|
+
/** How many messages of the dialogue are printed by default. */
|
|
201
201
|
export const RESTORED_HISTORY_LIMIT = 20;
|
|
202
202
|
/**
|
|
203
|
-
*
|
|
203
|
+
* DeepSeek stores the assistant turn VERBATIM: either a raw tool-call JSON
|
|
204
|
+
* ({"tool":"Read","args":{...}}, frequently dirty/truncated) or a `respond`
|
|
205
|
+
* call that carries the real message for the operator
|
|
206
|
+
* ({"tool":"respond","args":{"message":"..."}}). The operator only wants the
|
|
207
|
+
* `respond` message (and plain-text answers) — a tool-call is protocol noise.
|
|
204
208
|
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* "You stopped after a tool result..." nudges, and the agent's <system> notes.
|
|
209
|
-
* We keep only real user turns and real assistant answers.
|
|
209
|
+
* Returns the tool name and, for `respond`, the unwrapped message. Tolerant on
|
|
210
|
+
* purpose: the stored JSON is often dirty (a stray `}}`, a missing "args"
|
|
211
|
+
* wrapper), so a bare JSON.parse is not enough.
|
|
210
212
|
*/
|
|
211
|
-
export function
|
|
213
|
+
export function parseAssistantToolCall(text) {
|
|
214
|
+
const t = String(text || '').trim();
|
|
215
|
+
if (!t)
|
|
216
|
+
return null;
|
|
217
|
+
// Broken heads like `<|tool": ...` or `tool": "Bash"` are tolerated.
|
|
218
|
+
const m = t.match(/tool"?\s*:\s*"([^"\s]+)"/);
|
|
219
|
+
if (!m)
|
|
220
|
+
return null;
|
|
221
|
+
const tool = m[1];
|
|
222
|
+
if (tool !== 'respond')
|
|
223
|
+
return { tool };
|
|
224
|
+
const greedy = t.match(/"message"\s*:\s*"([\s\S]*)"\s*\}*\s*$/);
|
|
225
|
+
let raw = greedy ? greedy[1] : '';
|
|
226
|
+
if (!greedy) {
|
|
227
|
+
const idx = t.indexOf('"message"');
|
|
228
|
+
if (idx >= 0)
|
|
229
|
+
raw = t.slice(idx + 9).replace(/^\s*:\s*"/, '');
|
|
230
|
+
}
|
|
231
|
+
return { tool, message: unescapeJsonString(raw) };
|
|
232
|
+
}
|
|
233
|
+
/** Decode the JSON escape sequences that appear inside a captured string. */
|
|
234
|
+
export function unescapeJsonString(s) {
|
|
235
|
+
return String(s || '')
|
|
236
|
+
.replace(/\\u([0-9a-fA-F]{4})/g, (_x, h) => String.fromCharCode(parseInt(h, 16)))
|
|
237
|
+
.replace(/\\n/g, NL)
|
|
238
|
+
.replace(/\\r/g, String.fromCharCode(13))
|
|
239
|
+
.replace(/\\t/g, String.fromCharCode(9))
|
|
240
|
+
.replace(/\\"/g, '"')
|
|
241
|
+
.replace(/\\\\/g, '\\');
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Normalize one restored message for display:
|
|
245
|
+
* - USER: drop the protocol noise (system-prompt, tool results, nudges) — it
|
|
246
|
+
* is not the operator's words.
|
|
247
|
+
* - ASSISTANT: unwrap a `respond` call to its message; drop any other
|
|
248
|
+
* tool-call (protocol noise); keep plain-text answers.
|
|
249
|
+
* Returns null when the message must not be shown.
|
|
250
|
+
*/
|
|
251
|
+
export function normalizeRestoredMessage(m) {
|
|
212
252
|
const text = String((m && m.text) || '').trim();
|
|
213
253
|
if (!text)
|
|
214
|
-
return
|
|
254
|
+
return null;
|
|
215
255
|
if (text.length > 100_000)
|
|
216
|
-
return
|
|
256
|
+
return null; // the system-prompt / a giant blob
|
|
217
257
|
if (m.role === 'user') {
|
|
218
|
-
// Tool output, the system-prompt and the corrective nudges are not the
|
|
219
|
-
// operator's words.
|
|
220
258
|
if (/^Tool result for /.test(text))
|
|
221
|
-
return
|
|
259
|
+
return null;
|
|
222
260
|
if (/^You are a coding agent running in a terminal/.test(text))
|
|
223
|
-
return
|
|
261
|
+
return null;
|
|
224
262
|
if (/^You stopped after a tool result/.test(text))
|
|
225
|
-
return
|
|
263
|
+
return null;
|
|
226
264
|
if (/^\[system\]/.test(text))
|
|
227
|
-
return
|
|
265
|
+
return null;
|
|
228
266
|
if (/^The user ran \//.test(text))
|
|
229
|
-
return
|
|
230
|
-
return
|
|
267
|
+
return null;
|
|
268
|
+
return { role: 'user', text };
|
|
231
269
|
}
|
|
232
|
-
// Assistant: a tool-call (JSON or DSML) is not an answer to show. The
|
|
233
|
-
// DSML markers use FULL-WIDTH vertical bars (||DSML), so the regex must
|
|
234
|
-
// match them, not the ASCII pipe.
|
|
235
270
|
if (/DSML/i.test(text))
|
|
236
|
-
return
|
|
271
|
+
return null;
|
|
237
272
|
if (/^<system>/i.test(text))
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
|
|
273
|
+
return null;
|
|
274
|
+
// The model sometimes emits its own safety-classification block as a plain
|
|
275
|
+
// "answer"; it is not for the operator.
|
|
276
|
+
if (/<ds_safety>/i.test(text))
|
|
277
|
+
return null;
|
|
278
|
+
if (/^Safe$/i.test(text))
|
|
279
|
+
return null;
|
|
280
|
+
const call = parseAssistantToolCall(text);
|
|
281
|
+
if (call) {
|
|
282
|
+
if (call.tool !== 'respond')
|
|
283
|
+
return null;
|
|
284
|
+
const msg = (call.message || '').trim();
|
|
285
|
+
if (!msg)
|
|
286
|
+
return null;
|
|
287
|
+
return { role: 'assistant', text: msg };
|
|
241
288
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
289
|
+
// A plain-text answer (written without the respond wrapper) — show as-is.
|
|
290
|
+
return { role: 'assistant', text };
|
|
291
|
+
}
|
|
292
|
+
/** Back-compat predicate: is the message worth showing at all? */
|
|
293
|
+
export function isDisplayableMessage(m) {
|
|
294
|
+
return normalizeRestoredMessage(m) !== null;
|
|
246
295
|
}
|
|
247
296
|
/**
|
|
248
|
-
* Keep only the last `limit` messages
|
|
249
|
-
*
|
|
250
|
-
* filtered out here so the terminal output stays readable.
|
|
297
|
+
* Keep only the last `limit` messages, dropping protocol noise and UNWRAPPING
|
|
298
|
+
* the assistant `respond` calls to their operator-facing text.
|
|
251
299
|
*/
|
|
252
300
|
export function trimRestoredMessages(messages, limit = RESTORED_HISTORY_LIMIT) {
|
|
253
|
-
const clean =
|
|
301
|
+
const clean = [];
|
|
302
|
+
for (const m of messages || []) {
|
|
303
|
+
if (!m || typeof m.text !== 'string')
|
|
304
|
+
continue;
|
|
305
|
+
const n = normalizeRestoredMessage(m);
|
|
306
|
+
if (n)
|
|
307
|
+
clean.push(n);
|
|
308
|
+
}
|
|
254
309
|
if (limit > 0 && clean.length > limit)
|
|
255
310
|
return clean.slice(clean.length - limit);
|
|
256
311
|
return clean;
|
package/dist/index.js
CHANGED
|
@@ -828,6 +828,12 @@ async function printRestoredHistory(browser, ui, chatId = null) {
|
|
|
828
828
|
all = await browser.readChatMessages().catch(() => []);
|
|
829
829
|
domCount = all.length;
|
|
830
830
|
}
|
|
831
|
+
// `all` is the raw history (mostly protocol noise). `displayable` is what
|
|
832
|
+
// survives normalization; `messages` is the last RESTORED_HISTORY_LIMIT of
|
|
833
|
+
// those. The "truncated" note must compare the DISPLAYABLE count with the
|
|
834
|
+
// limit (comparing `all.length` always fired, because tool-calls are
|
|
835
|
+
// dropped and the raw history is always longer).
|
|
836
|
+
const displayable = trimRestoredMessages(all, 0);
|
|
831
837
|
const messages = trimRestoredMessages(all);
|
|
832
838
|
// Always surface the diagnostic line when the history could not be turned
|
|
833
839
|
// into anything printable — otherwise "the dialogue is empty" is a dead end
|
|
@@ -868,7 +874,7 @@ async function printRestoredHistory(browser, ui, chatId = null) {
|
|
|
868
874
|
NL);
|
|
869
875
|
}
|
|
870
876
|
}
|
|
871
|
-
if (
|
|
877
|
+
if (displayable.length > messages.length) {
|
|
872
878
|
out(theme.dim(t('chats.history_truncated', { n: String(RESTORED_HISTORY_LIMIT) })));
|
|
873
879
|
}
|
|
874
880
|
}
|