zames_pro 2.10.2 → 2.10.4
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/agent-loop.js +20 -1
- package/dist/browser.js +47 -7
- package/package.json +1 -1
package/dist/agent-loop.js
CHANGED
|
@@ -2,6 +2,7 @@ import { buildSystemPrompt } from './system-prompt.js';
|
|
|
2
2
|
import { getGitContext, formatGitContext } from './gitTools.js';
|
|
3
3
|
import { parseXmlToolCalls } from './xml-toolcall.js';
|
|
4
4
|
import { translate } from './i18n.js';
|
|
5
|
+
import { normText } from './browser.js';
|
|
5
6
|
export async function runAgentLoop({ browser, tools, task, workdir, maxIterations = 40, freshChat = false, sendSystemPrompt = false, transcript = null, attachments = [], onThinking = () => { }, onAssistantThought = () => { }, onToolCall = () => { }, onToolResult = () => { }, onAssistantMessage = () => { }, onChatReady = () => { }, onWarning = () => { }, debugLog = false, locale = 'ru', }) {
|
|
6
7
|
// UI callbacks must NEVER break the agent loop. A rendering error (a huge
|
|
7
8
|
// tool result, a broken markdown frame, a closed terminal) used to throw
|
|
@@ -189,14 +190,22 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
189
190
|
// cut-off "Stale. Let me ..." or a truncated JSON). All three mean the
|
|
190
191
|
// turn is unfinished: nudge instead of stopping.
|
|
191
192
|
const wdEmpty = !String(rawResponse || '').trim();
|
|
193
|
+
// STALE is compared on NORMALIZED text: DeepSeek often echoes the
|
|
194
|
+
// previous answer with different markdown emphasis (`**done**` vs `done`),
|
|
195
|
+
// which defeated an exact match and made the loop run the SAME tool again —
|
|
196
|
+
// the "stopped after a tool call" signature with a duplicate call.
|
|
192
197
|
const wdStale = justRanTool &&
|
|
193
198
|
lastRaw.trim() !== '' &&
|
|
194
|
-
rawResponse.trim() === lastRaw.trim()
|
|
199
|
+
(rawResponse.trim() === lastRaw.trim() ||
|
|
200
|
+
normForStale(rawResponse) === normForStale(lastRaw));
|
|
195
201
|
const wdNoCall = justRanTool &&
|
|
196
202
|
!wdEmpty &&
|
|
197
203
|
!wdStale &&
|
|
198
204
|
parseToolCall(rawResponse) === null &&
|
|
199
205
|
!responseLooksLikeToolCall(rawResponse);
|
|
206
|
+
// A stale answer is discarded even when it parses to a valid call: it is a
|
|
207
|
+
// duplicate of the previous turn, and re-running the tool would repeat
|
|
208
|
+
// side effects and stall the loop.
|
|
200
209
|
if (!isFirst && (wdEmpty || wdStale || wdNoCall) && watchdogRetries < MAX_WATCHDOG_RETRIES) {
|
|
201
210
|
watchdogRetries++;
|
|
202
211
|
transcript?.log('watchdog_nudge', {
|
|
@@ -522,6 +531,16 @@ function isMeaningfulRespond(msg) {
|
|
|
522
531
|
return false;
|
|
523
532
|
return true;
|
|
524
533
|
}
|
|
534
|
+
// Normalize an answer for STALE comparison: collapse whitespace AND strip
|
|
535
|
+
// markdown emphasis/code markers. DeepSeek echoes the previous turn with a
|
|
536
|
+
// different emphasis (`**done**` vs `done`), which defeated an exact match and
|
|
537
|
+
// made the loop re-run the same tool ("stopped after a tool call").
|
|
538
|
+
function normForStale(s) {
|
|
539
|
+
return normText(s)
|
|
540
|
+
.replace(/[*_`#>]+/g, '')
|
|
541
|
+
.replace(/[ \t]+/g, ' ')
|
|
542
|
+
.trim();
|
|
543
|
+
}
|
|
525
544
|
// Text that promises a tool call in the future tense but contains no call
|
|
526
545
|
// itself. DeepSeek regularly "hangs" like this: it writes
|
|
527
546
|
// "Now update README to mention …", "Let me run the tests", "I'll check now"
|
package/dist/browser.js
CHANGED
|
@@ -646,15 +646,23 @@ export class DeepSeekBrowser {
|
|
|
646
646
|
.evaluate(() => document.body.innerText.length)
|
|
647
647
|
.catch(() => 0);
|
|
648
648
|
let started = false;
|
|
649
|
+
// A stale/echo answer (the model repeats the previous text, or the answer
|
|
650
|
+
// legitimately equals it) does NOT change `cur`. In that case the old loop
|
|
651
|
+
// either threw "did not start" after 15s or hung until the full timeout —
|
|
652
|
+
// the operator saw the agent "stop after a tool call". We now also accept
|
|
653
|
+
// the answer when the generation has clearly SETTLED: no Stop button and
|
|
654
|
+
// the text has been stable for a couple of ticks.
|
|
655
|
+
let settledTicks = 0;
|
|
656
|
+
let lastStartCur = '';
|
|
649
657
|
while (Date.now() < startDeadline) {
|
|
650
658
|
if (this._abort)
|
|
651
659
|
return '(прервано пользователем)';
|
|
652
660
|
const pageText = await this._readPageText();
|
|
653
661
|
if (isRateLimitText(pageText)) {
|
|
654
662
|
throw new RateLimitError(pageText.slice(0, 300));
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
663
|
+
}
|
|
664
|
+
if (isServerBusyText(pageText)) {
|
|
665
|
+
throw new ServerBusyError(pageText.slice(0, 300));
|
|
658
666
|
}
|
|
659
667
|
const cur = await this._readLastAnswerTextClean().catch(() => '');
|
|
660
668
|
const bodyLen = await this.page
|
|
@@ -678,9 +686,30 @@ export class DeepSeekBrowser {
|
|
|
678
686
|
started = true;
|
|
679
687
|
break;
|
|
680
688
|
}
|
|
689
|
+
// Fallback for an echo/stale answer: the send happened (lastSentAt was
|
|
690
|
+
// just updated), the Stop button is gone and the text stopped changing.
|
|
691
|
+
// Two stable ticks in a row mean the turn is over even if it equals the
|
|
692
|
+
// previous text — return it instead of hanging/throwing.
|
|
693
|
+
const notGenerating = !(await this._isGenerating());
|
|
694
|
+
if (cur && cur === lastStartCur && notGenerating && cur.trim()) {
|
|
695
|
+
settledTicks++;
|
|
696
|
+
if (settledTicks >= 2) {
|
|
697
|
+
return cur;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
settledTicks = 0;
|
|
702
|
+
}
|
|
703
|
+
lastStartCur = cur;
|
|
681
704
|
await this.page.waitForTimeout(300);
|
|
682
705
|
}
|
|
683
706
|
if (!started) {
|
|
707
|
+
// Last chance: the answer may have arrived and settled exactly at the
|
|
708
|
+
// deadline. Return the current text instead of a hard error.
|
|
709
|
+
const cur = await this._readLastAnswerTextClean().catch(() => '');
|
|
710
|
+
if (cur && cur.trim() && !(await this._isGenerating())) {
|
|
711
|
+
return cur;
|
|
712
|
+
}
|
|
684
713
|
// We NO LONGER check the limit over the whole page text — that caused
|
|
685
714
|
// false positives and 5-minute waits. We just report that
|
|
686
715
|
// generation did not start.
|
|
@@ -718,21 +747,32 @@ export class DeepSeekBrowser {
|
|
|
718
747
|
// belongs to the CURRENT send even if the DOM still shows the old text.
|
|
719
748
|
const isNew = !!cur &&
|
|
720
749
|
(netFresh || normText(cur) !== normText(beforeText));
|
|
721
|
-
|
|
750
|
+
// An echo/stale answer equals beforeText, so isNew stays false and the
|
|
751
|
+
// old loop waited until the full timeout — the "agent stopped after a
|
|
752
|
+
// tool call" hang. If generation has clearly ENDED (no Stop button) and
|
|
753
|
+
// the text is stable, accept it (even when it repeats the previous one).
|
|
754
|
+
const sameAsBefore = !!cur && !isNew && normText(cur) === normText(beforeText);
|
|
755
|
+
if ((isNew || sameAsBefore) && cur === last) {
|
|
722
756
|
stable++;
|
|
723
|
-
if (stable >= 2)
|
|
724
|
-
|
|
757
|
+
if (stable >= 2) {
|
|
758
|
+
if (isNew || !(await this._isGenerating()))
|
|
759
|
+
return cur;
|
|
760
|
+
}
|
|
725
761
|
}
|
|
726
762
|
else {
|
|
727
763
|
stable = 0;
|
|
728
764
|
}
|
|
729
|
-
if (isNew)
|
|
765
|
+
if (isNew || sameAsBefore)
|
|
730
766
|
last = cur;
|
|
731
767
|
await this.page.waitForTimeout(800);
|
|
732
768
|
}
|
|
733
769
|
if (last && (normText(last) !== normText(beforeText) || this._netCapture)) {
|
|
734
770
|
return last;
|
|
735
771
|
}
|
|
772
|
+
// Fallback: the turn settled on a text identical to the previous answer.
|
|
773
|
+
if (last && !(await this._isGenerating())) {
|
|
774
|
+
return last;
|
|
775
|
+
}
|
|
736
776
|
if (this._netCapture && this._netCaptureAt >= this._lastSentAt) {
|
|
737
777
|
return this._netCapture;
|
|
738
778
|
}
|