zames_pro 2.88.2 → 2.90.0
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 +54 -0
- package/README.md +32 -585
- package/dist/agent-loop.js +47 -4
- package/dist/ask-retry.js +49 -0
- package/dist/ask-state.js +80 -0
- package/dist/browser-base.js +69 -0
- package/dist/browser.js +170 -41
- package/dist/command-registry.js +58 -0
- package/dist/commands.js +25 -2
- package/dist/completion-check.js +77 -0
- package/dist/config-commands.js +12 -4
- package/dist/config-menu.js +179 -81
- package/dist/config-schema.js +562 -0
- package/dist/config.js +19 -405
- package/dist/dev-commands.js +27 -0
- package/dist/dev-watch.js +96 -0
- package/dist/first-run.js +80 -0
- package/dist/glm-browser.js +133 -32
- package/dist/i18n/common.js +6 -3
- package/dist/i18n/config.js +78 -3
- package/dist/i18n/deepseek.js +1 -0
- package/dist/i18n/wizard.js +25 -0
- package/dist/i18n.js +6 -1
- package/dist/index.js +1299 -924
- package/dist/info-commands.js +61 -0
- package/dist/input/layout.js +38 -1
- package/dist/input.js +2 -2
- package/dist/password-store.js +48 -0
- package/dist/prompt-budget.js +23 -0
- package/dist/provider-errors.js +53 -0
- package/dist/provider-fallback.js +46 -0
- package/dist/qwen-browser.js +155 -39
- package/dist/run-task.js +2 -1
- package/dist/scheduler-commands.js +41 -0
- package/dist/send-slot.js +36 -0
- package/dist/session-commands.js +42 -0
- package/dist/spinner.js +3 -3
- package/dist/swallow.js +45 -0
- package/dist/system-prompt.js +2 -2
- package/dist/tree-commands.js +36 -0
- package/dist/tui/ConfigMenu.js +149 -64
- package/dist/tui/FullScreenApp.js +13 -4
- package/dist/tui/InputLine.js +20 -3
- package/dist/tui/LogView.js +11 -5
- package/dist/tui/StatusBar.js +4 -1
- package/dist/tui/TuiApp.js +11 -2
- package/dist/tui/layout.js +14 -1
- package/dist/tui/store.js +2 -1
- package/dist/tui/ui-colors.js +2 -0
- package/dist/width.js +10 -5
- package/llms-full.txt +16 -292
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { chromium, } from 'playwright';
|
|
2
|
+
import { noteSwallowed } from './swallow.js';
|
|
2
3
|
import { interactiveBrowserLogin, loginIsInteractive, canShowWindow, } from './browser-login.js';
|
|
3
4
|
import { extractAnswer, extractTokenUsage, dumpNetBody, isFinishedWithoutAnswer, isFinishedWithAnswer, } from './net-capture.js';
|
|
4
5
|
import { rewriteFailedAttachments } from './commands.js';
|
|
@@ -46,6 +47,8 @@ export class DeepSeekBrowser {
|
|
|
46
47
|
// recovery is capped independently of askRetries.
|
|
47
48
|
maxContextLimitRetries;
|
|
48
49
|
onContextLimit;
|
|
50
|
+
/** N8: switch to an authorized fallback provider (see agent-loop). */
|
|
51
|
+
onProviderExhausted;
|
|
49
52
|
serverBusyWaitMs;
|
|
50
53
|
// Retries for a turn the server truncated (generation_err / INCOMPLETE).
|
|
51
54
|
maxIncompleteRetries;
|
|
@@ -192,6 +195,7 @@ export class DeepSeekBrowser {
|
|
|
192
195
|
this.incompleteWaitMs = incompleteWaitMs;
|
|
193
196
|
this.maxContextLimitRetries = maxContextLimitRetries;
|
|
194
197
|
this.onContextLimit = onContextLimit || null;
|
|
198
|
+
this.onProviderExhausted = null;
|
|
195
199
|
this.autoContinue = autoContinue;
|
|
196
200
|
this.continueMinGapMs = continueMinGapMs;
|
|
197
201
|
this.loginTimeoutMs = loginTimeoutMs;
|
|
@@ -264,7 +268,9 @@ export class DeepSeekBrowser {
|
|
|
264
268
|
this.onNotice(text);
|
|
265
269
|
return;
|
|
266
270
|
}
|
|
267
|
-
catch {
|
|
271
|
+
catch (e) {
|
|
272
|
+
noteSwallowed('_notice', e, this.debug);
|
|
273
|
+
}
|
|
268
274
|
}
|
|
269
275
|
console.error(text);
|
|
270
276
|
}
|
|
@@ -275,7 +281,9 @@ export class DeepSeekBrowser {
|
|
|
275
281
|
try {
|
|
276
282
|
this.onSendState(state);
|
|
277
283
|
}
|
|
278
|
-
catch {
|
|
284
|
+
catch (e) {
|
|
285
|
+
noteSwallowed('_emitSendState', e, this.debug);
|
|
286
|
+
}
|
|
279
287
|
}
|
|
280
288
|
}
|
|
281
289
|
// Best-effort: a page error while marking the tab title must never break the
|
|
@@ -284,7 +292,9 @@ export class DeepSeekBrowser {
|
|
|
284
292
|
try {
|
|
285
293
|
await this.page.evaluate(setBusyTitleInDom, busy);
|
|
286
294
|
}
|
|
287
|
-
catch {
|
|
295
|
+
catch (e) {
|
|
296
|
+
noteSwallowed('_setBusyTitle', e, this.debug);
|
|
297
|
+
}
|
|
288
298
|
}
|
|
289
299
|
async launch() {
|
|
290
300
|
// NOTE: we deliberately do NOT kill stray chrome processes here. Per the
|
|
@@ -417,7 +427,9 @@ export class DeepSeekBrowser {
|
|
|
417
427
|
if (h['x-ds-pow-response'])
|
|
418
428
|
this._apiPow = h['x-ds-pow-response'];
|
|
419
429
|
}
|
|
420
|
-
catch {
|
|
430
|
+
catch (e) {
|
|
431
|
+
noteSwallowed('_installNetHook', e, this.debug);
|
|
432
|
+
}
|
|
421
433
|
});
|
|
422
434
|
}
|
|
423
435
|
async _onResponse(resp) {
|
|
@@ -506,14 +518,23 @@ export class DeepSeekBrowser {
|
|
|
506
518
|
}
|
|
507
519
|
}
|
|
508
520
|
}
|
|
509
|
-
catch {
|
|
521
|
+
catch (e) {
|
|
522
|
+
noteSwallowed('_onResponse', e, this.debug);
|
|
523
|
+
}
|
|
510
524
|
}
|
|
511
525
|
async restart() {
|
|
526
|
+
// The dialogue the operator was in. A relaunch lands on the BASE url, whose
|
|
527
|
+
// URL has no id, so getCurrentChatId() answers null and the next
|
|
528
|
+
// `freshChat` opened a SECOND chat — the resumed conversation vanished.
|
|
529
|
+
// Remember it BEFORE the context dies and re-open it after the launch.
|
|
530
|
+
const chatBefore = (await this.getCurrentChatId().catch(() => null)) ?? null;
|
|
512
531
|
try {
|
|
513
532
|
if (this.context)
|
|
514
533
|
await this.context.close();
|
|
515
534
|
}
|
|
516
|
-
catch {
|
|
535
|
+
catch (e) {
|
|
536
|
+
noteSwallowed('restart', e, this.debug);
|
|
537
|
+
}
|
|
517
538
|
// A restart must REUSE the existing profile. `launch()` would clean
|
|
518
539
|
// Singleton files, which can race with a sibling agent on the same
|
|
519
540
|
// profile; here we just relaunch the persistent context in place. A fresh
|
|
@@ -521,6 +542,16 @@ export class DeepSeekBrowser {
|
|
|
521
542
|
// second window is created while one is alive.
|
|
522
543
|
await this._launchOnce();
|
|
523
544
|
await this._fixHeadlessUserAgent();
|
|
545
|
+
// Put the operator BACK into the chat instead of leaving them in a new one.
|
|
546
|
+
// A failure is not fatal: the caller (ask) retries the send and the loop's
|
|
547
|
+
// resumeChatId still points at the right chat.
|
|
548
|
+
if (chatBefore) {
|
|
549
|
+
const ok = await this.openChat(chatBefore).catch(() => false);
|
|
550
|
+
if (ok)
|
|
551
|
+
this._netChatId = chatBefore;
|
|
552
|
+
else if (this.debug)
|
|
553
|
+
console.error('restart: could not reopen chat ' + chatBefore);
|
|
554
|
+
}
|
|
524
555
|
}
|
|
525
556
|
/**
|
|
526
557
|
* PURE-ish gate: may the login open a visible window? False for --task, a
|
|
@@ -576,7 +607,9 @@ export class DeepSeekBrowser {
|
|
|
576
607
|
if (u)
|
|
577
608
|
await u.fill(this.auth.username).catch(() => { });
|
|
578
609
|
}
|
|
579
|
-
catch {
|
|
610
|
+
catch (e) {
|
|
611
|
+
noteSwallowed('_browserLogin', e, this.debug);
|
|
612
|
+
}
|
|
580
613
|
},
|
|
581
614
|
captureCredentials: async () => {
|
|
582
615
|
try {
|
|
@@ -591,7 +624,9 @@ export class DeepSeekBrowser {
|
|
|
591
624
|
try {
|
|
592
625
|
this.onAuthSave?.(user, pass);
|
|
593
626
|
}
|
|
594
|
-
catch {
|
|
627
|
+
catch (e) {
|
|
628
|
+
noteSwallowed('_browserLogin', e, this.debug);
|
|
629
|
+
}
|
|
595
630
|
});
|
|
596
631
|
await this.page.evaluate(() => {
|
|
597
632
|
const w = window;
|
|
@@ -609,11 +644,15 @@ export class DeepSeekBrowser {
|
|
|
609
644
|
if (fn)
|
|
610
645
|
fn(u ? u.value || '' : '', p ? p.value || '' : '');
|
|
611
646
|
}
|
|
612
|
-
catch {
|
|
647
|
+
catch (e) {
|
|
648
|
+
noteSwallowed('_browserLogin', e, this.debug);
|
|
649
|
+
}
|
|
613
650
|
}, true);
|
|
614
651
|
});
|
|
615
652
|
}
|
|
616
|
-
catch {
|
|
653
|
+
catch (e) {
|
|
654
|
+
noteSwallowed('_browserLogin', e, this.debug);
|
|
655
|
+
}
|
|
617
656
|
},
|
|
618
657
|
persistSession: () => this._persistSessionIfNeeded(),
|
|
619
658
|
// Route login notices through the SAME notice hook as every other
|
|
@@ -660,7 +699,9 @@ export class DeepSeekBrowser {
|
|
|
660
699
|
});
|
|
661
700
|
api = res.ok;
|
|
662
701
|
}
|
|
663
|
-
catch {
|
|
702
|
+
catch (e) {
|
|
703
|
+
noteSwallowed('probeLogin', e, this.debug);
|
|
704
|
+
}
|
|
664
705
|
const body = document.body ? document.body.innerText : '';
|
|
665
706
|
const signInVisible = /log ?in|sign ?in|войти/i.test(body);
|
|
666
707
|
return { hasToken, api, signInVisible };
|
|
@@ -691,12 +732,16 @@ export class DeepSeekBrowser {
|
|
|
691
732
|
if (session.length)
|
|
692
733
|
this._carryCookies = session;
|
|
693
734
|
}
|
|
694
|
-
catch {
|
|
735
|
+
catch (e) {
|
|
736
|
+
noteSwallowed('relaunchSafe', e, this.debug);
|
|
737
|
+
}
|
|
695
738
|
try {
|
|
696
739
|
if (this.context)
|
|
697
740
|
await this.context.close();
|
|
698
741
|
}
|
|
699
|
-
catch {
|
|
742
|
+
catch (e) {
|
|
743
|
+
noteSwallowed('relaunchSafe', e, this.debug);
|
|
744
|
+
}
|
|
700
745
|
const deadline = Date.now() + 15000;
|
|
701
746
|
while (Date.now() < deadline) {
|
|
702
747
|
if (!(await profileLooksLocked()))
|
|
@@ -748,7 +793,9 @@ export class DeepSeekBrowser {
|
|
|
748
793
|
}
|
|
749
794
|
}
|
|
750
795
|
}
|
|
751
|
-
catch {
|
|
796
|
+
catch (e) {
|
|
797
|
+
noteSwallowed('_openLoginPage', e, this.debug);
|
|
798
|
+
}
|
|
752
799
|
}
|
|
753
800
|
async waitForLogin() {
|
|
754
801
|
// 1) Already signed in (session cookie in the persistent profile)?
|
|
@@ -1003,7 +1050,9 @@ export class DeepSeekBrowser {
|
|
|
1003
1050
|
if (await tryLogin())
|
|
1004
1051
|
return true;
|
|
1005
1052
|
}
|
|
1006
|
-
catch {
|
|
1053
|
+
catch (e) {
|
|
1054
|
+
noteSwallowed('_fillLoginForm', e, this.debug);
|
|
1055
|
+
}
|
|
1007
1056
|
}
|
|
1008
1057
|
for (const sel of LOGIN_SUBMIT_SELECTORS) {
|
|
1009
1058
|
const btn = this.page.locator(sel).last();
|
|
@@ -1016,7 +1065,9 @@ export class DeepSeekBrowser {
|
|
|
1016
1065
|
if (await tryLogin())
|
|
1017
1066
|
return true;
|
|
1018
1067
|
}
|
|
1019
|
-
catch {
|
|
1068
|
+
catch (e) {
|
|
1069
|
+
noteSwallowed('_fillLoginForm', e, this.debug);
|
|
1070
|
+
}
|
|
1020
1071
|
}
|
|
1021
1072
|
// Button inside the same form as the password field.
|
|
1022
1073
|
const formClicked = await this.page
|
|
@@ -1123,7 +1174,9 @@ export class DeepSeekBrowser {
|
|
|
1123
1174
|
submitted = true;
|
|
1124
1175
|
break;
|
|
1125
1176
|
}
|
|
1126
|
-
catch {
|
|
1177
|
+
catch (e) {
|
|
1178
|
+
noteSwallowed('_handleMfaIfPresent', e, this.debug);
|
|
1179
|
+
}
|
|
1127
1180
|
}
|
|
1128
1181
|
if (!submitted)
|
|
1129
1182
|
await codeInput.press('Enter').catch(() => { });
|
|
@@ -1159,7 +1212,9 @@ export class DeepSeekBrowser {
|
|
|
1159
1212
|
await btn.click({ timeout: 2000 });
|
|
1160
1213
|
return true;
|
|
1161
1214
|
}
|
|
1162
|
-
catch {
|
|
1215
|
+
catch (e) {
|
|
1216
|
+
noteSwallowed('_clickMfaSendCode', e, this.debug);
|
|
1217
|
+
}
|
|
1163
1218
|
}
|
|
1164
1219
|
return false;
|
|
1165
1220
|
}
|
|
@@ -1267,7 +1322,9 @@ export class DeepSeekBrowser {
|
|
|
1267
1322
|
await loc.waitFor({ state: 'visible', timeout });
|
|
1268
1323
|
return loc;
|
|
1269
1324
|
}
|
|
1270
|
-
catch {
|
|
1325
|
+
catch (e) {
|
|
1326
|
+
noteSwallowed('_findVisible', e, this.debug);
|
|
1327
|
+
}
|
|
1271
1328
|
}
|
|
1272
1329
|
return null;
|
|
1273
1330
|
}
|
|
@@ -1377,7 +1434,9 @@ export class DeepSeekBrowser {
|
|
|
1377
1434
|
return true;
|
|
1378
1435
|
}
|
|
1379
1436
|
}
|
|
1380
|
-
catch {
|
|
1437
|
+
catch (e) {
|
|
1438
|
+
noteSwallowed('_continueButtonVisible', e, this.debug);
|
|
1439
|
+
}
|
|
1381
1440
|
// Fallback: the raw DOM scan (older/newer DeepSeek builds where the role
|
|
1382
1441
|
// is absent).
|
|
1383
1442
|
return this.page.evaluate(continueVisibleInDom).catch(() => false);
|
|
@@ -1418,7 +1477,9 @@ export class DeepSeekBrowser {
|
|
|
1418
1477
|
}
|
|
1419
1478
|
}
|
|
1420
1479
|
}
|
|
1421
|
-
catch {
|
|
1480
|
+
catch (e) {
|
|
1481
|
+
noteSwallowed('_clickContinueIfVisible', e, this.debug);
|
|
1482
|
+
}
|
|
1422
1483
|
if (found) {
|
|
1423
1484
|
// One Continue click per `continueMinGapMs`: a `chat/continue` request
|
|
1424
1485
|
// hits the rate limit like a send, and a run that keeps getting
|
|
@@ -1439,7 +1500,9 @@ export class DeepSeekBrowser {
|
|
|
1439
1500
|
try {
|
|
1440
1501
|
this.onSendPause(Math.max(0, Math.ceil(leftMs / 1000)));
|
|
1441
1502
|
}
|
|
1442
|
-
catch {
|
|
1503
|
+
catch (e) {
|
|
1504
|
+
noteSwallowed('_clickContinueIfVisible', e, this.debug);
|
|
1505
|
+
}
|
|
1443
1506
|
}
|
|
1444
1507
|
});
|
|
1445
1508
|
if (aborted)
|
|
@@ -1455,7 +1518,9 @@ export class DeepSeekBrowser {
|
|
|
1455
1518
|
this._notice(this._t('ds.continue_clicked'));
|
|
1456
1519
|
return true;
|
|
1457
1520
|
}
|
|
1458
|
-
catch {
|
|
1521
|
+
catch (e) {
|
|
1522
|
+
noteSwallowed('_clickContinueIfVisible', e, this.debug);
|
|
1523
|
+
}
|
|
1459
1524
|
}
|
|
1460
1525
|
// 2) Fallback: raw DOM scan + full pointer/mouse event sequence (an older
|
|
1461
1526
|
// build without a proper role, or a click intercepted by an overlay).
|
|
@@ -1493,7 +1558,9 @@ export class DeepSeekBrowser {
|
|
|
1493
1558
|
return { loc: b, pressed };
|
|
1494
1559
|
}
|
|
1495
1560
|
}
|
|
1496
|
-
catch {
|
|
1561
|
+
catch (e) {
|
|
1562
|
+
noteSwallowed('_findToggle', e, this.debug);
|
|
1563
|
+
}
|
|
1497
1564
|
}
|
|
1498
1565
|
return null;
|
|
1499
1566
|
}
|
|
@@ -1508,7 +1575,9 @@ export class DeepSeekBrowser {
|
|
|
1508
1575
|
}
|
|
1509
1576
|
return;
|
|
1510
1577
|
}
|
|
1511
|
-
catch {
|
|
1578
|
+
catch (e) {
|
|
1579
|
+
noteSwallowed('_setToggle', e, this.debug);
|
|
1580
|
+
}
|
|
1512
1581
|
}
|
|
1513
1582
|
async _applyToggles() {
|
|
1514
1583
|
await this._setToggle(DEEP_THINKING_RE, this.deepThinking);
|
|
@@ -1539,7 +1608,9 @@ export class DeepSeekBrowser {
|
|
|
1539
1608
|
if (found)
|
|
1540
1609
|
return found.pressed;
|
|
1541
1610
|
}
|
|
1542
|
-
catch {
|
|
1611
|
+
catch (e) {
|
|
1612
|
+
noteSwallowed('getToggleStates', e, this.debug);
|
|
1613
|
+
}
|
|
1543
1614
|
return fallback;
|
|
1544
1615
|
};
|
|
1545
1616
|
return {
|
|
@@ -1568,7 +1639,9 @@ export class DeepSeekBrowser {
|
|
|
1568
1639
|
}
|
|
1569
1640
|
}
|
|
1570
1641
|
}
|
|
1571
|
-
catch {
|
|
1642
|
+
catch (e) {
|
|
1643
|
+
noteSwallowed('stopGeneration', e, this.debug);
|
|
1644
|
+
}
|
|
1572
1645
|
// 2) Legacy selector path.
|
|
1573
1646
|
const btn = await this._findVisible(STOP_SELECTORS, 500);
|
|
1574
1647
|
if (btn) {
|
|
@@ -1576,7 +1649,9 @@ export class DeepSeekBrowser {
|
|
|
1576
1649
|
await btn.click({ timeout: 1500 });
|
|
1577
1650
|
return true;
|
|
1578
1651
|
}
|
|
1579
|
-
catch {
|
|
1652
|
+
catch (e) {
|
|
1653
|
+
noteSwallowed('stopGeneration', e, this.debug);
|
|
1654
|
+
}
|
|
1580
1655
|
}
|
|
1581
1656
|
// 3) Raw DOM scan + the full pointer/mouse sequence (older builds without
|
|
1582
1657
|
// a proper role, or a click intercepted by an overlay).
|
|
@@ -1589,7 +1664,9 @@ export class DeepSeekBrowser {
|
|
|
1589
1664
|
await this.page.keyboard.press('Escape');
|
|
1590
1665
|
return true;
|
|
1591
1666
|
}
|
|
1592
|
-
catch {
|
|
1667
|
+
catch (e) {
|
|
1668
|
+
noteSwallowed('stopGeneration', e, this.debug);
|
|
1669
|
+
}
|
|
1593
1670
|
return false;
|
|
1594
1671
|
}
|
|
1595
1672
|
async ask(prompt, { timeout = this.answerTimeoutMs, agent = false, attachments = [], } = {}) {
|
|
@@ -1597,6 +1674,8 @@ export class DeepSeekBrowser {
|
|
|
1597
1674
|
let attempt = 0;
|
|
1598
1675
|
let rateLimitRetries = 0;
|
|
1599
1676
|
let serverBusyRetries = 0;
|
|
1677
|
+
// N8: the fallback switch is tried ONCE per ask(), not per family.
|
|
1678
|
+
let fallbackAttempted = false;
|
|
1600
1679
|
// Bumped when DeepSeek truncated the turn (`generation_err`/INCOMPLETE).
|
|
1601
1680
|
// The retry does NOT consume a regular ask() attempt: it is a server-side
|
|
1602
1681
|
// hiccup, not a failed send, and it is more likely in the reasoning
|
|
@@ -1733,6 +1812,32 @@ export class DeepSeekBrowser {
|
|
|
1733
1812
|
this._lastSentAt = Date.now();
|
|
1734
1813
|
continue;
|
|
1735
1814
|
}
|
|
1815
|
+
// The retry BUDGETS of the specific families are exhausted (or this
|
|
1816
|
+
// is a generic failure on the last attempt): give the injected N8
|
|
1817
|
+
// hook ONE chance to continue the task on an AUTHORIZED fallback
|
|
1818
|
+
// provider. It performs the switch and, when it succeeds, the SAME
|
|
1819
|
+
// prompt is resent on the new backend.
|
|
1820
|
+
if (!fallbackAttempted && this.onProviderExhausted) {
|
|
1821
|
+
const budgetLeft = rateLimitRetries < this.maxRateLimitRetries ||
|
|
1822
|
+
serverBusyRetries < this.maxServerBusyRetries ||
|
|
1823
|
+
attempt < this.askRetries - 1;
|
|
1824
|
+
if (!budgetLeft) {
|
|
1825
|
+
fallbackAttempted = true;
|
|
1826
|
+
let switched = false;
|
|
1827
|
+
try {
|
|
1828
|
+
switched = await this.onProviderExhausted(e);
|
|
1829
|
+
}
|
|
1830
|
+
catch (re) {
|
|
1831
|
+
noteSwallowed('_onProviderExhausted', re, this.debug);
|
|
1832
|
+
}
|
|
1833
|
+
if (switched) {
|
|
1834
|
+
attempt = 0;
|
|
1835
|
+
rateLimitRetries = 0;
|
|
1836
|
+
serverBusyRetries = 0;
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1736
1841
|
this._notice(theme.warn(this._t('ds.ask_retry', {
|
|
1737
1842
|
attempt: attempt + 1,
|
|
1738
1843
|
max: this.askRetries,
|
|
@@ -1790,7 +1895,9 @@ export class DeepSeekBrowser {
|
|
|
1790
1895
|
await input.fill(text, { timeout: 5000 });
|
|
1791
1896
|
return;
|
|
1792
1897
|
}
|
|
1793
|
-
catch {
|
|
1898
|
+
catch (e) {
|
|
1899
|
+
noteSwallowed('_setInputText', e, this.debug);
|
|
1900
|
+
}
|
|
1794
1901
|
}
|
|
1795
1902
|
await input.click();
|
|
1796
1903
|
await this.page.keyboard.press('Control+A');
|
|
@@ -1852,7 +1959,9 @@ export class DeepSeekBrowser {
|
|
|
1852
1959
|
await this._awaitUploadPreview(files.map((f) => f.name), 2500);
|
|
1853
1960
|
return failed;
|
|
1854
1961
|
}
|
|
1855
|
-
catch {
|
|
1962
|
+
catch (e) {
|
|
1963
|
+
noteSwallowed('_attachFiles', e, this.debug);
|
|
1964
|
+
}
|
|
1856
1965
|
}
|
|
1857
1966
|
// Fallback: set the files directly on the hidden <input type=file>.
|
|
1858
1967
|
const input = this.page.locator('input[type="file"]').first();
|
|
@@ -1891,7 +2000,9 @@ export class DeepSeekBrowser {
|
|
|
1891
2000
|
try {
|
|
1892
2001
|
this.onSendPause(secs);
|
|
1893
2002
|
}
|
|
1894
|
-
catch {
|
|
2003
|
+
catch (e) {
|
|
2004
|
+
noteSwallowed('_waitRateLimit', e, this.debug);
|
|
2005
|
+
}
|
|
1895
2006
|
}
|
|
1896
2007
|
};
|
|
1897
2008
|
this._emitSendState('paused');
|
|
@@ -1933,7 +2044,9 @@ export class DeepSeekBrowser {
|
|
|
1933
2044
|
try {
|
|
1934
2045
|
this.onSendPause(secs);
|
|
1935
2046
|
}
|
|
1936
|
-
catch {
|
|
2047
|
+
catch (e) {
|
|
2048
|
+
noteSwallowed('_waitForSendSlot', e, this.debug);
|
|
2049
|
+
}
|
|
1937
2050
|
}
|
|
1938
2051
|
else {
|
|
1939
2052
|
// Fallback when no UI hook is wired (a direct ask() from a script):
|
|
@@ -1984,7 +2097,9 @@ export class DeepSeekBrowser {
|
|
|
1984
2097
|
const dir = path.join(os.homedir(), '.zames');
|
|
1985
2098
|
void fs.appendFile(path.join(dir, 'ask-debug.log'), line).catch(() => { });
|
|
1986
2099
|
}
|
|
1987
|
-
catch {
|
|
2100
|
+
catch (e) {
|
|
2101
|
+
noteSwallowed('_askDebug', e, this.debug);
|
|
2102
|
+
}
|
|
1988
2103
|
}
|
|
1989
2104
|
async _askOnce(prompt, { timeout, agent, attachments = [], }) {
|
|
1990
2105
|
// We reset the abort flag ONLY at the very start of the send.
|
|
@@ -2026,7 +2141,9 @@ export class DeepSeekBrowser {
|
|
|
2026
2141
|
try {
|
|
2027
2142
|
this.onSendStart();
|
|
2028
2143
|
}
|
|
2029
|
-
catch {
|
|
2144
|
+
catch (e) {
|
|
2145
|
+
noteSwallowed('_askOnce', e, this.debug);
|
|
2146
|
+
}
|
|
2030
2147
|
}
|
|
2031
2148
|
this._emitSendState('generating');
|
|
2032
2149
|
void this._setBusyTitle(true);
|
|
@@ -2080,7 +2197,9 @@ export class DeepSeekBrowser {
|
|
|
2080
2197
|
sent = true;
|
|
2081
2198
|
break;
|
|
2082
2199
|
}
|
|
2083
|
-
catch {
|
|
2200
|
+
catch (e) {
|
|
2201
|
+
noteSwallowed('_askOnce', e, this.debug);
|
|
2202
|
+
}
|
|
2084
2203
|
}
|
|
2085
2204
|
if (!sent)
|
|
2086
2205
|
await this.page.waitForTimeout(150);
|
|
@@ -2106,7 +2225,9 @@ export class DeepSeekBrowser {
|
|
|
2106
2225
|
this._lastSentAt = Date.now();
|
|
2107
2226
|
}
|
|
2108
2227
|
}
|
|
2109
|
-
catch {
|
|
2228
|
+
catch (e) {
|
|
2229
|
+
noteSwallowed('_askOnce', e, this.debug);
|
|
2230
|
+
}
|
|
2110
2231
|
// Wait for the start: either Stop appeared, or the answer text changed,
|
|
2111
2232
|
// or the page grew (a new answer node / longer last answer). In parallel we
|
|
2112
2233
|
// catch the rate-limit toast (only toasts, not the whole body).
|
|
@@ -2598,7 +2719,9 @@ export class DeepSeekBrowser {
|
|
|
2598
2719
|
await this.page.waitForTimeout(500);
|
|
2599
2720
|
return;
|
|
2600
2721
|
}
|
|
2601
|
-
catch {
|
|
2722
|
+
catch (e) {
|
|
2723
|
+
noteSwallowed('_ensureSidebarOpen', e, this.debug);
|
|
2724
|
+
}
|
|
2602
2725
|
}
|
|
2603
2726
|
}
|
|
2604
2727
|
async listChats(limit = 30) {
|
|
@@ -2626,7 +2749,9 @@ export class DeepSeekBrowser {
|
|
|
2626
2749
|
await this.page.waitForTimeout(100);
|
|
2627
2750
|
}
|
|
2628
2751
|
}
|
|
2629
|
-
catch {
|
|
2752
|
+
catch (e) {
|
|
2753
|
+
noteSwallowed('openChat', e, this.debug);
|
|
2754
|
+
}
|
|
2630
2755
|
}
|
|
2631
2756
|
try {
|
|
2632
2757
|
await this.page.goto(chatUrl(id), {
|
|
@@ -2815,11 +2940,15 @@ export class DeepSeekBrowser {
|
|
|
2815
2940
|
}
|
|
2816
2941
|
}
|
|
2817
2942
|
}
|
|
2818
|
-
catch {
|
|
2943
|
+
catch (e) {
|
|
2944
|
+
noteSwallowed('close', e, this.debug);
|
|
2945
|
+
}
|
|
2819
2946
|
try {
|
|
2820
2947
|
if (this.context)
|
|
2821
2948
|
await this.context.close();
|
|
2822
2949
|
}
|
|
2823
|
-
catch {
|
|
2950
|
+
catch (e) {
|
|
2951
|
+
noteSwallowed('close', e, this.debug);
|
|
2952
|
+
}
|
|
2824
2953
|
}
|
|
2825
2954
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// N2: the slash-command REGISTRY.
|
|
2
|
+
//
|
|
3
|
+
// `main()` in index.ts used to be one long chain of `if (lower === '/x')`
|
|
4
|
+
// branches (~60 of them), and help, the «/» hints, the dev-only guard and the
|
|
5
|
+
// dispatch were built from DIFFERENT lists — a command could be dispatched but
|
|
6
|
+
// missing from the help, or the other way round. A command is now a DATA
|
|
7
|
+
// ENTRY: name, aliases, whether it is dev-only, and a handler that receives a
|
|
8
|
+
// `CommandContext`. The registry below owns the informational group first; the
|
|
9
|
+
// remaining groups move in as they are extracted (one group per commit), and
|
|
10
|
+
// `printHelp`/`buildSlashCommands`/`isDevOnlyCommand` keep their existing
|
|
11
|
+
// sources until the last group lands.
|
|
12
|
+
/**
|
|
13
|
+
* Match a typed line against the registry. Returns the command and its
|
|
14
|
+
* arguments (everything after the matched name), or null. Matching is
|
|
15
|
+
* case-insensitive and requires a word boundary: `/statusx` must NOT match
|
|
16
|
+
* `/status`.
|
|
17
|
+
*/
|
|
18
|
+
export function matchCommand(commands, input) {
|
|
19
|
+
const trimmed = (input || '').trim();
|
|
20
|
+
if (!trimmed.startsWith('/'))
|
|
21
|
+
return null;
|
|
22
|
+
const body = trimmed.slice(1);
|
|
23
|
+
const space = body.search(/\s/);
|
|
24
|
+
const name = (space < 0 ? body : body.slice(0, space)).toLowerCase();
|
|
25
|
+
const rest = space < 0 ? '' : body.slice(space).trim();
|
|
26
|
+
for (const c of commands) {
|
|
27
|
+
if (c.name.toLowerCase() === name)
|
|
28
|
+
return { command: c, rest };
|
|
29
|
+
for (const a of c.aliases || []) {
|
|
30
|
+
if (a.toLowerCase() === name)
|
|
31
|
+
return { command: c, rest };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Dispatch a line through the registry. Returns false when nothing matched, so
|
|
38
|
+
* the caller can fall through to the next group (or the task path).
|
|
39
|
+
*
|
|
40
|
+
* A dev-only command typed by a regular user must be REJECTED with a clear
|
|
41
|
+
* message — hiding it from the help is not enough, the user can type it by
|
|
42
|
+
* hand. `devMode` and `onDevOnlyRejected` are injected for that.
|
|
43
|
+
*/
|
|
44
|
+
export async function dispatchCommand(commands, input, deps) {
|
|
45
|
+
const hit = matchCommand(commands, input);
|
|
46
|
+
if (!hit)
|
|
47
|
+
return false;
|
|
48
|
+
if (hit.command.devOnly && !deps.devMode) {
|
|
49
|
+
deps.onDevOnlyRejected?.(hit.command.name);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
await hit.command.handler({
|
|
53
|
+
input: (input || '').trim(),
|
|
54
|
+
rest: hit.rest,
|
|
55
|
+
print: deps.print,
|
|
56
|
+
});
|
|
57
|
+
return true;
|
|
58
|
+
}
|
package/dist/commands.js
CHANGED
|
@@ -2,7 +2,8 @@ import { providerLabel, providerHasModelPicker } from './providers.js';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import { isImageName } from './attachments.js';
|
|
5
|
-
import {
|
|
5
|
+
import { SYSTEM_PROMPT_BUDGET_BYTES, kb, overBudget } from './prompt-budget.js';
|
|
6
|
+
import { translate, DEFAULT_LOCALE, } from './i18n.js';
|
|
6
7
|
// Helpers for the slash commands (/diff, /cost, /export, /doctor, /review,
|
|
7
8
|
// /add-dir, /queue, /goal, custom commands). Pure functions, unit-tested
|
|
8
9
|
// without a live agent/browser; index.ts only renders their output.
|
|
@@ -345,6 +346,28 @@ export function renderDoctor(d, t = translate('en')) {
|
|
|
345
346
|
tools: String(d.mcpTools),
|
|
346
347
|
}));
|
|
347
348
|
row(d.transcriptOk, 'transcript', onoff(d.transcriptOk));
|
|
349
|
+
if (d.passwordStore) {
|
|
350
|
+
row(!d.passwordStore.degraded, 'password', d.passwordStore.store +
|
|
351
|
+
(d.passwordStore.degraded ? ' (keychain unavailable)' : ''));
|
|
352
|
+
}
|
|
353
|
+
if (d.swallowedTop && d.swallowedTop.length) {
|
|
354
|
+
const txt = d.swallowedTop.map((x) => x.tag + ':' + x.count).join(', ');
|
|
355
|
+
// WARN, not OK: a swallowed failure is a SELECTOR (or a page read) that
|
|
356
|
+
// broke and was hidden - exactly what this row exists to surface.
|
|
357
|
+
row(false, 'swallowed', txt);
|
|
358
|
+
}
|
|
359
|
+
if (typeof d.systemPromptBytes === 'number') {
|
|
360
|
+
const budget = d.systemPromptBudget ?? SYSTEM_PROMPT_BUDGET_BYTES;
|
|
361
|
+
const size = kb(d.systemPromptBytes);
|
|
362
|
+
const ctx = typeof d.contextBytes === 'number' && d.contextBytes > 0
|
|
363
|
+
? ' + ' + kb(d.contextBytes) + ' context'
|
|
364
|
+
: '';
|
|
365
|
+
row(!overBudget(d.systemPromptBytes, budget), 'prompt', size +
|
|
366
|
+
ctx +
|
|
367
|
+
(overBudget(d.systemPromptBytes, budget)
|
|
368
|
+
? ' > ' + kb(budget) + ' budget'
|
|
369
|
+
: ' (budget ' + kb(budget) + ')'));
|
|
370
|
+
}
|
|
348
371
|
if (typeof d.contextLimit === 'number' && d.contextLimit > 0) {
|
|
349
372
|
row(true, 'context', t('doctor.tokens', {
|
|
350
373
|
v: d.contextLimit.toLocaleString('en-US'),
|
|
@@ -595,7 +618,7 @@ export function formatRestoredHistory(messages, opts = {}) {
|
|
|
595
618
|
* the system prompt), so the model is told to keep facts, decisions, file
|
|
596
619
|
* paths, commands and the exact current state of the work.
|
|
597
620
|
*/
|
|
598
|
-
export function buildCompactPrompt(locale =
|
|
621
|
+
export function buildCompactPrompt(locale = DEFAULT_LOCALE) {
|
|
599
622
|
if (locale === 'en') {
|
|
600
623
|
return ('Compact the conversation so far into a handover summary for a NEW chat. ' +
|
|
601
624
|
'This summary is the ONLY context the new chat will start with, so it must be self-sufficient. ' +
|