zames_pro 2.19.0 → 2.20.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/README.md +3 -2
- package/dist/browser.js +60 -13
- package/dist/input.js +58 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -158,8 +158,9 @@ Codex CLI:
|
|
|
158
158
|
|
|
159
159
|
The token context is also shown live: the status line above the input has the
|
|
160
160
|
spinner/text on the left and the context on the right (e.g. `125k · 13%`,
|
|
161
|
-
percent of a 1M context). It
|
|
162
|
-
|
|
161
|
+
percent of a 1M context). It is COLORED by fill level: green below 50%,
|
|
162
|
+
yellow 50-80%, red above 80%. It comes from DeepSeek's
|
|
163
|
+
`accumulated_token_usage` and is hidden until the first answer delivers it.
|
|
163
164
|
|
|
164
165
|
|
|
165
166
|
## Project context, skills and memory
|
package/dist/browser.js
CHANGED
|
@@ -1196,13 +1196,14 @@ export class DeepSeekBrowser {
|
|
|
1196
1196
|
// replied to the wrong thing (or nothing), and the loop looked stalled.
|
|
1197
1197
|
if (norm(got) !== norm(text)) {
|
|
1198
1198
|
await input.click();
|
|
1199
|
-
await this.page.keyboard.press(
|
|
1200
|
-
await this.page.keyboard.press(
|
|
1199
|
+
await this.page.keyboard.press('Control+A');
|
|
1200
|
+
await this.page.keyboard.press('Delete');
|
|
1201
1201
|
await this.page.keyboard.insertText(text);
|
|
1202
1202
|
const got2 = await input.evaluate((el) => {
|
|
1203
|
-
if (el.tagName.toLowerCase() ===
|
|
1203
|
+
if (el.tagName.toLowerCase() === 'textarea' ||
|
|
1204
|
+
el.tagName.toLowerCase() === 'input')
|
|
1204
1205
|
return el.value;
|
|
1205
|
-
return el.innerText || el.textContent ||
|
|
1206
|
+
return el.innerText || el.textContent || '';
|
|
1206
1207
|
});
|
|
1207
1208
|
if (norm(got2) !== norm(text)) {
|
|
1208
1209
|
throw new Error(this._t('ds.input_partial', {
|
|
@@ -1366,7 +1367,14 @@ export class DeepSeekBrowser {
|
|
|
1366
1367
|
// the "new answer" check never fired — the source of the
|
|
1367
1368
|
// ds.send_no_new_answer flapping.
|
|
1368
1369
|
const beforeText = await this._readLastAnswerTextCleanDom().catch(() => '');
|
|
1369
|
-
this._askDebug('SEND agent=' +
|
|
1370
|
+
this._askDebug('SEND agent=' +
|
|
1371
|
+
agent +
|
|
1372
|
+
' len=' +
|
|
1373
|
+
prompt.length +
|
|
1374
|
+
' beforeLen=' +
|
|
1375
|
+
beforeText.length +
|
|
1376
|
+
' beforeHead=' +
|
|
1377
|
+
JSON.stringify(beforeText.slice(0, 60)));
|
|
1370
1378
|
await this._waitForSendSlot(agent);
|
|
1371
1379
|
// Esc/Ctrl+C pressed during the pause — do not send anything.
|
|
1372
1380
|
if (this._abort)
|
|
@@ -1459,7 +1467,12 @@ export class DeepSeekBrowser {
|
|
|
1459
1467
|
const netStarted = !!this._netCapture && this._netCaptureAt >= this._lastSentAt;
|
|
1460
1468
|
if (changed || netStarted || bodyLen > startBodyLen) {
|
|
1461
1469
|
started = true;
|
|
1462
|
-
this._askDebug('STARTED changed=' +
|
|
1470
|
+
this._askDebug('STARTED changed=' +
|
|
1471
|
+
changed +
|
|
1472
|
+
' netStarted=' +
|
|
1473
|
+
netStarted +
|
|
1474
|
+
' bodyGrew=' +
|
|
1475
|
+
(bodyLen > startBodyLen));
|
|
1463
1476
|
break;
|
|
1464
1477
|
}
|
|
1465
1478
|
// Fallback for an echo: the text equals beforeText, so it is the OLD
|
|
@@ -1480,7 +1493,18 @@ export class DeepSeekBrowser {
|
|
|
1480
1493
|
settledTicks = 0;
|
|
1481
1494
|
}
|
|
1482
1495
|
lastStartCur = cur;
|
|
1483
|
-
this._askDebug('START-loop curLen=' +
|
|
1496
|
+
this._askDebug('START-loop curLen=' +
|
|
1497
|
+
cur.length +
|
|
1498
|
+
' changed=' +
|
|
1499
|
+
changed +
|
|
1500
|
+
' netStarted=' +
|
|
1501
|
+
netStarted +
|
|
1502
|
+
' bodyLen=' +
|
|
1503
|
+
bodyLen +
|
|
1504
|
+
' settled=' +
|
|
1505
|
+
settledTicks +
|
|
1506
|
+
' generating=' +
|
|
1507
|
+
notGenerating);
|
|
1484
1508
|
await this.page.waitForTimeout(300);
|
|
1485
1509
|
}
|
|
1486
1510
|
if (!started) {
|
|
@@ -1490,7 +1514,10 @@ export class DeepSeekBrowser {
|
|
|
1490
1514
|
// previous tool call.
|
|
1491
1515
|
const cur = await this._readLastAnswerTextCleanDom().catch(() => '');
|
|
1492
1516
|
const fresh = !!this._netCapture && this._netCaptureAt >= this._lastSentAt;
|
|
1493
|
-
if (cur &&
|
|
1517
|
+
if (cur &&
|
|
1518
|
+
cur.trim() &&
|
|
1519
|
+
normText(cur) !== normText(beforeText) &&
|
|
1520
|
+
!(await this._isGenerating())) {
|
|
1494
1521
|
return cur;
|
|
1495
1522
|
}
|
|
1496
1523
|
if (fresh) {
|
|
@@ -1550,14 +1577,20 @@ export class DeepSeekBrowser {
|
|
|
1550
1577
|
}
|
|
1551
1578
|
}
|
|
1552
1579
|
const netFresh = !!this._netCapture && this._netCaptureAt >= this._lastSentAt;
|
|
1553
|
-
|
|
1580
|
+
// The DOM may be empty while the answer is already in the network
|
|
1581
|
+
// capture (a resumed chat re-renders its history, the answer selector
|
|
1582
|
+
// may lag, or page.evaluate times out). In that case fall back to the
|
|
1583
|
+
// raw capture: it is the CURRENT answer and must not be ignored just
|
|
1584
|
+
// because the DOM has not caught up.
|
|
1585
|
+
let cur = await this._readLastAnswerTextCleanDom().catch(() => '');
|
|
1586
|
+
if (!cur && netFresh)
|
|
1587
|
+
cur = this._cleanAnswer(this._netCapture);
|
|
1554
1588
|
// Ignore an "answer" that is identical to what was on the page BEFORE we
|
|
1555
1589
|
// sent the message: that is the previous answer, not a new one. Returning
|
|
1556
1590
|
// it would make the agent re-process the old tool call (or silently
|
|
1557
1591
|
// stop). We keep waiting instead. A fresh network capture is exempt: it
|
|
1558
1592
|
// belongs to the CURRENT send even if the DOM still shows the old text.
|
|
1559
|
-
const isNew = !!cur &&
|
|
1560
|
-
(netFresh || normText(cur) !== normText(beforeText));
|
|
1593
|
+
const isNew = !!cur && (netFresh || normText(cur) !== normText(beforeText));
|
|
1561
1594
|
// An echo/stale answer equals beforeText, so isNew stays false and the
|
|
1562
1595
|
// old loop waited until the full timeout — the "agent stopped after a
|
|
1563
1596
|
// tool call" hang. If generation has clearly ENDED (no Stop button) and
|
|
@@ -1581,7 +1614,18 @@ export class DeepSeekBrowser {
|
|
|
1581
1614
|
}
|
|
1582
1615
|
if (isNew)
|
|
1583
1616
|
last = cur;
|
|
1584
|
-
this._askDebug('FIN-loop isNew=' +
|
|
1617
|
+
this._askDebug('FIN-loop isNew=' +
|
|
1618
|
+
isNew +
|
|
1619
|
+
' sameAsBefore=' +
|
|
1620
|
+
sameAsBefore +
|
|
1621
|
+
' stable=' +
|
|
1622
|
+
stable +
|
|
1623
|
+
' curLen=' +
|
|
1624
|
+
cur.length +
|
|
1625
|
+
' lastLen=' +
|
|
1626
|
+
last.length +
|
|
1627
|
+
' netFresh=' +
|
|
1628
|
+
netFresh);
|
|
1585
1629
|
await this.page.waitForTimeout(Math.max(0, this.stabilityDelayMs));
|
|
1586
1630
|
}
|
|
1587
1631
|
if (last && (normText(last) !== normText(beforeText) || this._netCapture)) {
|
|
@@ -1593,7 +1637,10 @@ export class DeepSeekBrowser {
|
|
|
1593
1637
|
if (this._netCapture && this._netCaptureAt >= this._lastSentAt) {
|
|
1594
1638
|
return this._netCapture;
|
|
1595
1639
|
}
|
|
1596
|
-
this._askDebug('THROW no-new-answer lastLen=' +
|
|
1640
|
+
this._askDebug('THROW no-new-answer lastLen=' +
|
|
1641
|
+
last.length +
|
|
1642
|
+
' beforeLen=' +
|
|
1643
|
+
beforeText.length);
|
|
1597
1644
|
throw new Error(this._t('ds.send_no_new_answer'));
|
|
1598
1645
|
}
|
|
1599
1646
|
async dumpDom(filePath) {
|
package/dist/input.js
CHANGED
|
@@ -174,6 +174,24 @@ export function layoutInput(promptStr, buf, cursor, cols) {
|
|
|
174
174
|
// percentage of CONTEXT_LIMIT. Exported so it is unit-tested without a live
|
|
175
175
|
// editor. A null/undefined/NaN count renders an empty string (no status).
|
|
176
176
|
export const CONTEXT_LIMIT = 1_000_000;
|
|
177
|
+
// Thresholds for coloring the context counter. Below YELLOW the value is
|
|
178
|
+
// green (plenty of room), between YELLOW and RED it is yellow (getting full),
|
|
179
|
+
// above RED it is red (nearly exhausted). Percent of CONTEXT_LIMIT.
|
|
180
|
+
export const CONTEXT_YELLOW_PCT = 50;
|
|
181
|
+
export const CONTEXT_RED_PCT = 80;
|
|
182
|
+
// The color role for a context fill level, as a stable string so it can be
|
|
183
|
+
// unit-tested without a terminal: 'ok' | 'warn' | 'high'. Null tokens -> null.
|
|
184
|
+
export function tokenStatusLevel(tokens, limit = CONTEXT_LIMIT) {
|
|
185
|
+
if (typeof tokens !== 'number' || !Number.isFinite(tokens) || tokens < 0) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const pct = (tokens / limit) * 100;
|
|
189
|
+
if (pct >= CONTEXT_RED_PCT)
|
|
190
|
+
return 'high';
|
|
191
|
+
if (pct >= CONTEXT_YELLOW_PCT)
|
|
192
|
+
return 'warn';
|
|
193
|
+
return 'ok';
|
|
194
|
+
}
|
|
177
195
|
export function formatTokenStatus(tokens, limit = CONTEXT_LIMIT) {
|
|
178
196
|
if (typeof tokens !== 'number' || !Number.isFinite(tokens) || tokens < 0) {
|
|
179
197
|
return '';
|
|
@@ -247,6 +265,8 @@ export class LineEditor {
|
|
|
247
265
|
// percentage of the context limit, right-aligned above the input line. Null
|
|
248
266
|
// hides it. Updated by the caller from browser.getLastTokenUsage().
|
|
249
267
|
contextStatus;
|
|
268
|
+
// The raw token count behind contextStatus (for the color level).
|
|
269
|
+
contextTokens;
|
|
250
270
|
// A callback the editor calls to fetch the CURRENT token count before every
|
|
251
271
|
// status render, so the status line stays fresh without the caller polling.
|
|
252
272
|
onContextQuery;
|
|
@@ -283,6 +303,7 @@ export class LineEditor {
|
|
|
283
303
|
this.onClipboard = null;
|
|
284
304
|
this.locked = false;
|
|
285
305
|
this.contextStatus = null;
|
|
306
|
+
this.contextTokens = null;
|
|
286
307
|
this.onContextQuery = null;
|
|
287
308
|
}
|
|
288
309
|
// The token status for the CURRENT render: refreshed from onContextQuery
|
|
@@ -291,7 +312,9 @@ export class LineEditor {
|
|
|
291
312
|
if (this.onContextQuery) {
|
|
292
313
|
try {
|
|
293
314
|
const n = this.onContextQuery();
|
|
294
|
-
this.
|
|
315
|
+
this.contextTokens = typeof n === 'number' ? n : null;
|
|
316
|
+
this.contextStatus =
|
|
317
|
+
n === null || n === undefined ? null : formatTokenStatus(n) || null;
|
|
295
318
|
}
|
|
296
319
|
catch {
|
|
297
320
|
// A broken callback must never break the render.
|
|
@@ -299,6 +322,19 @@ export class LineEditor {
|
|
|
299
322
|
}
|
|
300
323
|
return this.contextStatus;
|
|
301
324
|
}
|
|
325
|
+
// The context text colored by fill level: green (ok), yellow (warn),
|
|
326
|
+
// red (high).
|
|
327
|
+
_contextText() {
|
|
328
|
+
const ctx = this._contextForRender();
|
|
329
|
+
if (!ctx)
|
|
330
|
+
return '';
|
|
331
|
+
const level = tokenStatusLevel(this.contextTokens);
|
|
332
|
+
if (level === 'high')
|
|
333
|
+
return theme.error(ctx);
|
|
334
|
+
if (level === 'warn')
|
|
335
|
+
return theme.warn(ctx);
|
|
336
|
+
return theme.success(ctx);
|
|
337
|
+
}
|
|
302
338
|
// Read the OS clipboard for an image and insert its marker. Used when the
|
|
303
339
|
// terminal sends no usable paste data (Ctrl+V / right-click / empty paste).
|
|
304
340
|
async _tryClipboard() {
|
|
@@ -420,6 +456,8 @@ export class LineEditor {
|
|
|
420
456
|
}
|
|
421
457
|
// Update the token-context text shown at the right of the status line.
|
|
422
458
|
setContextStatus(tokens) {
|
|
459
|
+
this.contextTokens =
|
|
460
|
+
typeof tokens === 'number' && Number.isFinite(tokens) ? tokens : null;
|
|
423
461
|
this.contextStatus = formatTokenStatus(tokens) || null;
|
|
424
462
|
this._render();
|
|
425
463
|
}
|
|
@@ -459,19 +497,31 @@ export class LineEditor {
|
|
|
459
497
|
// token context right-aligned on the SAME row (10k · 12%). The context is
|
|
460
498
|
// refreshed from onContextQuery() on every render, so it follows the live
|
|
461
499
|
// DeepSeek counter without a polling timer of its own.
|
|
462
|
-
const
|
|
463
|
-
const ctxText = ctx ? theme.dim(ctx) : '';
|
|
500
|
+
const ctxText = this._contextText();
|
|
464
501
|
if (this.statusText) {
|
|
465
502
|
if (ctxText) {
|
|
466
|
-
|
|
467
|
-
|
|
503
|
+
// Right-align the context on the SAME row as the status. When the
|
|
504
|
+
// status is too long to leave room, do NOT cram them together (that
|
|
505
|
+
// pushed the trailing `%` past the right edge and it got truncated):
|
|
506
|
+
// put the context on its own line instead.
|
|
507
|
+
const space = cols - visLen(this.statusText) - visLen(ctxText);
|
|
508
|
+
if (space >= 2) {
|
|
509
|
+
out += this.statusText + ' '.repeat(space) + ctxText + NL;
|
|
510
|
+
top = visRows(this.statusText + ' '.repeat(2) + ctxText, cols);
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
out += this.statusText + NL;
|
|
514
|
+
top = visRows(this.statusText, cols);
|
|
515
|
+
out += ' '.repeat(Math.max(0, cols - visLen(ctxText))) + ctxText + NL;
|
|
516
|
+
top += visRows(ctxText, cols);
|
|
517
|
+
}
|
|
468
518
|
}
|
|
469
519
|
else {
|
|
470
520
|
out += this.statusText + NL;
|
|
521
|
+
// The status may wrap onto several lines — we account for this,
|
|
522
|
+
// otherwise the block erase misses and statuses pile up.
|
|
523
|
+
top = visRows(this.statusText, cols);
|
|
471
524
|
}
|
|
472
|
-
// The status may wrap onto several lines — we account for this,
|
|
473
|
-
// otherwise the block erase misses and statuses pile up.
|
|
474
|
-
top = visRows(this.statusText + (ctxText ? ' '.repeat(2) + ctxText : ''), cols);
|
|
475
525
|
}
|
|
476
526
|
else if (ctxText) {
|
|
477
527
|
// Idle: no spinner, but the context still belongs on its own line just
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.20.0",
|
|
4
4
|
"description": "Terminal coding agent over chat.deepseek.com via Playwright",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
17
|
"start": "node ./dist/index.js",
|
|
18
|
-
"dev": "ZAMES_NET_DEBUG=1 tsx ./src/index.ts --dev",
|
|
18
|
+
"dev": "ZAMES_NET_DEBUG=1 tsx ./src/index.ts --dev --headed",
|
|
19
19
|
"build": "tsc -p tsconfig.build.json",
|
|
20
20
|
"typecheck": "tsc --noEmit",
|
|
21
21
|
"prepublishOnly": "npm run build",
|