zames_pro 2.17.0 → 2.18.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 -0
- package/dist/agent-loop.js +17 -5
- package/dist/browser.js +292 -14
- package/dist/commands.js +72 -0
- package/dist/i18n.js +83 -0
- package/dist/index.js +68 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -147,6 +147,9 @@ Codex CLI:
|
|
|
147
147
|
alwaysConfirm regex list).
|
|
148
148
|
- /add-dir <path> — validate an extra directory (the sandbox is fixed at
|
|
149
149
|
startup; relaunch with --dir to write there).
|
|
150
|
+
- /resume <n> (after /chats) and /resume-id <id> — open a chat and PRINT its
|
|
151
|
+
dialogue into the terminal, so the restored context is visible. Only the
|
|
152
|
+
last 20 messages are shown (`RESTORED_HISTORY_LIMIT`).
|
|
150
153
|
- /review [focus] [--staged] — ask the agent to review uncommitted changes
|
|
151
154
|
and report findings (no code changes).
|
|
152
155
|
|
package/dist/agent-loop.js
CHANGED
|
@@ -129,6 +129,9 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
129
129
|
let unparsedRetries = 0;
|
|
130
130
|
const MAX_UNPARSED_RETRIES = 4;
|
|
131
131
|
let finalRespondAsked = false;
|
|
132
|
+
// The operator is told ONCE that the agent is re-asking the model for a
|
|
133
|
+
// proper tool call (not on every retry — that would spam the terminal).
|
|
134
|
+
let toolRetryWarned = false;
|
|
132
135
|
// Guard against "the agent stalled": DeepSeek sometimes sends a final text
|
|
133
136
|
// that merely DESCRIBES the next tool call (or cuts the answer off
|
|
134
137
|
// mid-word), and the agent silently finishes the task even though the work
|
|
@@ -207,9 +210,9 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
207
210
|
attempt: afterToolRetries,
|
|
208
211
|
error: e.message,
|
|
209
212
|
});
|
|
210
|
-
safeWarning(
|
|
211
|
-
Math.round(askDeadlineMs / 1000)
|
|
212
|
-
|
|
213
|
+
safeWarning(translate(locale)('ds.answer_timeout', {
|
|
214
|
+
sec: Math.round(askDeadlineMs / 1000),
|
|
215
|
+
}));
|
|
213
216
|
if (afterToolRetries < MAX_AFTER_TOOL_RETRIES) {
|
|
214
217
|
afterToolRetries++;
|
|
215
218
|
await new Promise((r) => setTimeout(r, 1500));
|
|
@@ -218,8 +221,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
218
221
|
transcript?.log('ask_timeout_exhausted', {
|
|
219
222
|
message: 'ask() did not return an answer and the retry limit is exhausted',
|
|
220
223
|
});
|
|
221
|
-
safeWarning('
|
|
222
|
-
'stopping. No model answer — check the DeepSeek chat manually.');
|
|
224
|
+
safeWarning(translate(locale)('ds.answer_timeout_give_up'));
|
|
223
225
|
return 'ask() watchdog: no model answer received';
|
|
224
226
|
}
|
|
225
227
|
await reportChat();
|
|
@@ -402,6 +404,16 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
402
404
|
attempt: unparsedRetries,
|
|
403
405
|
response: rawResponse.slice(0, 500),
|
|
404
406
|
});
|
|
407
|
+
// Tell the operator WHY nothing is happening: the model wrote text
|
|
408
|
+
// instead of a tool call and the agent is asking it to continue. Only
|
|
409
|
+
// once per task, otherwise the retry budget spams the terminal.
|
|
410
|
+
if (!toolRetryWarned) {
|
|
411
|
+
toolRetryWarned = true;
|
|
412
|
+
safeWarning(translate(locale)('ds.tool_retry', {
|
|
413
|
+
attempt: unparsedRetries,
|
|
414
|
+
max: MAX_UNPARSED_RETRIES,
|
|
415
|
+
}));
|
|
416
|
+
}
|
|
405
417
|
message =
|
|
406
418
|
(justRanTool
|
|
407
419
|
? 'You stopped after a tool call and wrote plain text. '
|
package/dist/browser.js
CHANGED
|
@@ -277,6 +277,13 @@ export class DeepSeekBrowser {
|
|
|
277
277
|
_netCapture;
|
|
278
278
|
_netCaptureAt;
|
|
279
279
|
_netChatId;
|
|
280
|
+
// Authorization / PoW headers sniffed from DeepSeek's own API requests, so
|
|
281
|
+
// fetchChatMessages can replay them (a bare fetch does not get them).
|
|
282
|
+
_apiAuth;
|
|
283
|
+
_apiPow;
|
|
284
|
+
// Last error from fetchChatMessages ('' on success). Surfaced under --debug
|
|
285
|
+
// so a missing restored history can be diagnosed.
|
|
286
|
+
_lastHistoryError;
|
|
280
287
|
_netSniff;
|
|
281
288
|
_netSniffLimit;
|
|
282
289
|
_netHookInstalled;
|
|
@@ -321,6 +328,9 @@ export class DeepSeekBrowser {
|
|
|
321
328
|
this._netCapture = '';
|
|
322
329
|
this._netCaptureAt = 0;
|
|
323
330
|
this._netChatId = null;
|
|
331
|
+
this._apiAuth = '';
|
|
332
|
+
this._apiPow = '';
|
|
333
|
+
this._lastHistoryError = '';
|
|
324
334
|
this._netSniff = [];
|
|
325
335
|
this._netSniffLimit = 5;
|
|
326
336
|
this._netHookInstalled = false;
|
|
@@ -424,6 +434,23 @@ export class DeepSeekBrowser {
|
|
|
424
434
|
pg.on('response', (resp) => {
|
|
425
435
|
void this._onResponse(resp).catch(() => { });
|
|
426
436
|
});
|
|
437
|
+
// DeepSeek's own API calls carry an Authorization header (the app reads a
|
|
438
|
+
// token from its storage and sets it explicitly). A bare fetch from our
|
|
439
|
+
// code does NOT get it, so history_messages would answer 401/empty.
|
|
440
|
+
// Sniff the header off the real requests and replay it later.
|
|
441
|
+
pg.on('request', (req) => {
|
|
442
|
+
try {
|
|
443
|
+
const url = req.url();
|
|
444
|
+
if (!/deepseek\.com\/api\//i.test(url))
|
|
445
|
+
return;
|
|
446
|
+
const h = req.headers();
|
|
447
|
+
if (h['authorization'])
|
|
448
|
+
this._apiAuth = h['authorization'];
|
|
449
|
+
if (h['x-ds-pow-response'])
|
|
450
|
+
this._apiPow = h['x-ds-pow-response'];
|
|
451
|
+
}
|
|
452
|
+
catch { }
|
|
453
|
+
});
|
|
427
454
|
}
|
|
428
455
|
async _onResponse(resp) {
|
|
429
456
|
try {
|
|
@@ -1017,10 +1044,17 @@ export class DeepSeekBrowser {
|
|
|
1017
1044
|
if (e instanceof RateLimitError) {
|
|
1018
1045
|
rateLimitRetries++;
|
|
1019
1046
|
if (rateLimitRetries > this.maxRateLimitRetries) {
|
|
1020
|
-
console.error(theme.error(
|
|
1047
|
+
console.error(theme.error(this._t('ds.rate_limit_give_up', {
|
|
1048
|
+
attempt: rateLimitRetries,
|
|
1049
|
+
min: Math.ceil(this.rateLimitWaitMs / 60000),
|
|
1050
|
+
})));
|
|
1021
1051
|
throw e;
|
|
1022
1052
|
}
|
|
1023
|
-
console.error(theme.warn(
|
|
1053
|
+
console.error(theme.warn(this._t('ds.rate_limit_wait', {
|
|
1054
|
+
min: Math.ceil(this.rateLimitWaitMs / 60000),
|
|
1055
|
+
attempt: rateLimitRetries,
|
|
1056
|
+
max: this.maxRateLimitRetries,
|
|
1057
|
+
})));
|
|
1024
1058
|
// Interruptible: Esc/Ctrl+C must cancel this long wait too,
|
|
1025
1059
|
// otherwise "stop" stays frozen for up to 5 minutes.
|
|
1026
1060
|
const aborted = await this._sleepInterruptible(this.rateLimitWaitMs);
|
|
@@ -1028,15 +1062,44 @@ export class DeepSeekBrowser {
|
|
|
1028
1062
|
return '(прервано пользователем)';
|
|
1029
1063
|
continue;
|
|
1030
1064
|
}
|
|
1031
|
-
|
|
1065
|
+
// Server busy / overloaded: clear in seconds, so we retry quickly
|
|
1066
|
+
// (unlike the rate limit). Without this branch the error fell into the
|
|
1067
|
+
// generic ask() retry and the operator saw a bare message with no
|
|
1068
|
+
// explanation of what DeepSeek is doing.
|
|
1069
|
+
if (e instanceof ServerBusyError) {
|
|
1070
|
+
serverBusyRetries++;
|
|
1071
|
+
if (serverBusyRetries > this.maxServerBusyRetries) {
|
|
1072
|
+
console.error(theme.error(this._t('ds.server_busy_give_up', {
|
|
1073
|
+
attempt: serverBusyRetries,
|
|
1074
|
+
})));
|
|
1075
|
+
throw e;
|
|
1076
|
+
}
|
|
1077
|
+
console.error(theme.warn(this._t('ds.server_busy_wait', {
|
|
1078
|
+
sec: Math.ceil(this.serverBusyWaitMs / 1000),
|
|
1079
|
+
attempt: serverBusyRetries,
|
|
1080
|
+
max: this.maxServerBusyRetries,
|
|
1081
|
+
})));
|
|
1082
|
+
const aborted = await this._sleepInterruptible(this.serverBusyWaitMs);
|
|
1083
|
+
if (aborted)
|
|
1084
|
+
return '(прервано пользователем)';
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
console.error('\n' +
|
|
1088
|
+
theme.warn(this._t('ds.ask_retry', {
|
|
1089
|
+
attempt,
|
|
1090
|
+
max: this.askRetries,
|
|
1091
|
+
error: e.message,
|
|
1092
|
+
})));
|
|
1032
1093
|
if (/closed|crash|Target page|browser/i.test(e.message)) {
|
|
1033
|
-
console.error('
|
|
1094
|
+
console.error(theme.warn(this._t('ds.ask_restart_browser')));
|
|
1034
1095
|
try {
|
|
1035
1096
|
await this.restart();
|
|
1036
1097
|
await this.waitForLogin();
|
|
1037
1098
|
}
|
|
1038
1099
|
catch (re) {
|
|
1039
|
-
console.error(
|
|
1100
|
+
console.error(theme.warn(this._t('ds.ask_restart_failed', {
|
|
1101
|
+
error: re.message,
|
|
1102
|
+
})));
|
|
1040
1103
|
}
|
|
1041
1104
|
}
|
|
1042
1105
|
if (attempt < this.askRetries) {
|
|
@@ -1044,7 +1107,10 @@ export class DeepSeekBrowser {
|
|
|
1044
1107
|
}
|
|
1045
1108
|
}
|
|
1046
1109
|
}
|
|
1047
|
-
throw new Error(
|
|
1110
|
+
throw new Error(this._t('ds.ask_failed', {
|
|
1111
|
+
max: this.askRetries,
|
|
1112
|
+
error: lastErr?.message || '',
|
|
1113
|
+
}));
|
|
1048
1114
|
}
|
|
1049
1115
|
async _setInputText(input, text) {
|
|
1050
1116
|
const tag = await input.evaluate((el) => el.tagName.toLowerCase());
|
|
@@ -1100,7 +1166,10 @@ export class DeepSeekBrowser {
|
|
|
1100
1166
|
return el.innerText || el.textContent || "";
|
|
1101
1167
|
});
|
|
1102
1168
|
if (norm(got2) !== norm(text)) {
|
|
1103
|
-
throw new Error(
|
|
1169
|
+
throw new Error(this._t('ds.input_partial', {
|
|
1170
|
+
got: norm(got2).length,
|
|
1171
|
+
want: norm(text).length,
|
|
1172
|
+
}));
|
|
1104
1173
|
}
|
|
1105
1174
|
}
|
|
1106
1175
|
}
|
|
@@ -1250,7 +1319,7 @@ export class DeepSeekBrowser {
|
|
|
1250
1319
|
return '(прервано пользователем)';
|
|
1251
1320
|
const input = await this._findVisible(INPUT_SELECTORS, 10_000);
|
|
1252
1321
|
if (!input) {
|
|
1253
|
-
throw new Error('
|
|
1322
|
+
throw new Error(this._t('ds.input_missing'));
|
|
1254
1323
|
}
|
|
1255
1324
|
const beforeText = await this._readLastAnswerTextClean().catch(() => '');
|
|
1256
1325
|
this._askDebug('SEND agent=' + agent + ' len=' + prompt.length + ' beforeLen=' + beforeText.length + ' beforeHead=' + JSON.stringify(beforeText.slice(0, 60)));
|
|
@@ -1411,8 +1480,7 @@ export class DeepSeekBrowser {
|
|
|
1411
1480
|
}
|
|
1412
1481
|
}
|
|
1413
1482
|
if (!started) {
|
|
1414
|
-
throw new Error('
|
|
1415
|
-
'Проверьте чат DeepSeek вручную.');
|
|
1483
|
+
throw new Error(this._t('ds.send_no_start'));
|
|
1416
1484
|
}
|
|
1417
1485
|
// Wait until the answer stops changing. We check the "not generating"
|
|
1418
1486
|
// condition via text growth, NOT via _isGenerating().
|
|
@@ -1482,12 +1550,11 @@ export class DeepSeekBrowser {
|
|
|
1482
1550
|
return this._netCapture;
|
|
1483
1551
|
}
|
|
1484
1552
|
this._askDebug('THROW no-new-answer lastLen=' + last.length + ' beforeLen=' + beforeText.length);
|
|
1485
|
-
throw new Error('
|
|
1486
|
-
'Возможно, сообщение не отправилось.');
|
|
1553
|
+
throw new Error(this._t('ds.send_no_new_answer'));
|
|
1487
1554
|
}
|
|
1488
1555
|
async dumpDom(filePath) {
|
|
1489
1556
|
if (!this.page)
|
|
1490
|
-
throw new Error('
|
|
1557
|
+
throw new Error(this._t('ds.not_launched'));
|
|
1491
1558
|
const html = await this.page.content();
|
|
1492
1559
|
await fs.writeFile(filePath, html, 'utf-8');
|
|
1493
1560
|
const report = await this.page.evaluate((sels) => {
|
|
@@ -1581,9 +1648,220 @@ export class DeepSeekBrowser {
|
|
|
1581
1648
|
return true;
|
|
1582
1649
|
}
|
|
1583
1650
|
catch (e) {
|
|
1584
|
-
throw new Error(
|
|
1651
|
+
throw new Error(this._t('ds.open_chat_failed', { id, error: e.message }));
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
// Fetch the WHOLE dialogue from DeepSeek's own history endpoint instead of
|
|
1655
|
+
// scraping the DOM. The rendered page only shows a part of the history (the
|
|
1656
|
+
// list is virtualized), while /api/v0/chat/history_messages returns every
|
|
1657
|
+
// message of the chat as JSON. The request is made INSIDE the page, so the
|
|
1658
|
+
// auth cookie and the CSRF/PoW handling are the browser's.
|
|
1659
|
+
//
|
|
1660
|
+
// Shape of data.biz_data.chat_messages[]: { role: 'USER'|'ASSISTANT',
|
|
1661
|
+
// fragments: [{ type: 'REQUEST'|'RESPONSE'|'THINK'|'FILE', content }] }.
|
|
1662
|
+
// We keep REQUEST (user) and RESPONSE (assistant); THINK (reasoning) is
|
|
1663
|
+
// skipped and FILE fragments carry no text.
|
|
1664
|
+
async fetchChatMessages(id) {
|
|
1665
|
+
this._lastHistoryError = '';
|
|
1666
|
+
if (!this.page || !id)
|
|
1667
|
+
return [];
|
|
1668
|
+
// The Authorization header is sniffed off DeepSeek's own API requests. On
|
|
1669
|
+
// a freshly opened chat that request may not have fired yet — wait a bit
|
|
1670
|
+
// (up to 3s) so the replay is authorized instead of answering 401/empty.
|
|
1671
|
+
for (let i = 0; i < 15 && !this._apiAuth; i++) {
|
|
1672
|
+
await this.page.waitForTimeout(200);
|
|
1673
|
+
}
|
|
1674
|
+
const auth = this._apiAuth;
|
|
1675
|
+
const pow = this._apiPow;
|
|
1676
|
+
const res = await this.page
|
|
1677
|
+
.evaluate(async (opts) => {
|
|
1678
|
+
try {
|
|
1679
|
+
const url = 'https://chat.deepseek.com/api/v0/chat/history_messages?chat_session_id=' +
|
|
1680
|
+
encodeURIComponent(opts.chatId);
|
|
1681
|
+
const headers = {
|
|
1682
|
+
accept: 'application/json',
|
|
1683
|
+
};
|
|
1684
|
+
if (opts.auth)
|
|
1685
|
+
headers['authorization'] = opts.auth;
|
|
1686
|
+
if (opts.pow)
|
|
1687
|
+
headers['x-ds-pow-response'] = opts.pow;
|
|
1688
|
+
const resp = await fetch(url, {
|
|
1689
|
+
credentials: 'include',
|
|
1690
|
+
headers,
|
|
1691
|
+
});
|
|
1692
|
+
if (!resp.ok)
|
|
1693
|
+
return { error: 'HTTP ' + resp.status, list: [] };
|
|
1694
|
+
const json = await resp.json();
|
|
1695
|
+
const messages = json && json.data && json.data.biz_data
|
|
1696
|
+
? json.data.biz_data.chat_messages
|
|
1697
|
+
: null;
|
|
1698
|
+
if (!Array.isArray(messages)) {
|
|
1699
|
+
return { error: 'no chat_messages in response', list: [] };
|
|
1700
|
+
}
|
|
1701
|
+
const NL = String.fromCharCode(10);
|
|
1702
|
+
const out = [];
|
|
1703
|
+
for (const m of messages) {
|
|
1704
|
+
const role = m && m.role === 'ASSISTANT' ? 'assistant' : 'user';
|
|
1705
|
+
const want = role === 'assistant' ? 'RESPONSE' : 'REQUEST';
|
|
1706
|
+
let text = '';
|
|
1707
|
+
for (const fr of (m && m.fragments) || []) {
|
|
1708
|
+
if (!fr || fr.type !== want)
|
|
1709
|
+
continue;
|
|
1710
|
+
if (typeof fr.content === 'string')
|
|
1711
|
+
text += fr.content;
|
|
1712
|
+
}
|
|
1713
|
+
text = text.replace(new RegExp(NL + '{3,}', 'g'), NL + NL).trim();
|
|
1714
|
+
if (text)
|
|
1715
|
+
out.push({ role, text });
|
|
1716
|
+
}
|
|
1717
|
+
return { error: '', list: out };
|
|
1718
|
+
}
|
|
1719
|
+
catch (e) {
|
|
1720
|
+
return { error: 'fetch failed: ' + e.message, list: [] };
|
|
1721
|
+
}
|
|
1722
|
+
}, { chatId: id, auth, pow })
|
|
1723
|
+
.catch((e) => ({
|
|
1724
|
+
error: 'evaluate failed: ' + e.message,
|
|
1725
|
+
list: [],
|
|
1726
|
+
}));
|
|
1727
|
+
this._lastHistoryError = res?.error || '';
|
|
1728
|
+
const list = (res?.list || []);
|
|
1729
|
+
if (list.length || !res?.error)
|
|
1730
|
+
return list;
|
|
1731
|
+
// Fallback: Playwright's own request context (shares the browser cookies)
|
|
1732
|
+
// when the in-page fetch was blocked (CSP, CORS, a page error).
|
|
1733
|
+
this._lastHistoryError = res.error;
|
|
1734
|
+
try {
|
|
1735
|
+
const resp = await this.page.request.get('https://chat.deepseek.com/api/v0/chat/history_messages?chat_session_id=' +
|
|
1736
|
+
encodeURIComponent(id), { headers: { accept: 'application/json' } });
|
|
1737
|
+
if (!resp.ok())
|
|
1738
|
+
return list;
|
|
1739
|
+
const json = (await resp.json());
|
|
1740
|
+
const messages = json?.data?.biz_data?.chat_messages;
|
|
1741
|
+
if (!Array.isArray(messages))
|
|
1742
|
+
return list;
|
|
1743
|
+
const NL = String.fromCharCode(10);
|
|
1744
|
+
const out = [];
|
|
1745
|
+
for (const m of messages) {
|
|
1746
|
+
const role = m && m.role === 'ASSISTANT' ? 'assistant' : 'user';
|
|
1747
|
+
const want = role === 'assistant' ? 'RESPONSE' : 'REQUEST';
|
|
1748
|
+
let text = '';
|
|
1749
|
+
for (const fr of (m && m.fragments) || []) {
|
|
1750
|
+
if (!fr || fr.type !== want)
|
|
1751
|
+
continue;
|
|
1752
|
+
if (typeof fr.content === 'string')
|
|
1753
|
+
text += fr.content;
|
|
1754
|
+
}
|
|
1755
|
+
text = text.replace(new RegExp(NL + '{3,}', 'g'), NL + NL).trim();
|
|
1756
|
+
if (text)
|
|
1757
|
+
out.push({ role, text });
|
|
1758
|
+
}
|
|
1759
|
+
this._lastHistoryError = '';
|
|
1760
|
+
return out;
|
|
1761
|
+
}
|
|
1762
|
+
catch (e) {
|
|
1763
|
+
this._lastHistoryError = 'request failed: ' + e.message;
|
|
1764
|
+
return list;
|
|
1585
1765
|
}
|
|
1586
1766
|
}
|
|
1767
|
+
// Read the WHOLE visible dialogue of the currently open chat, top to
|
|
1768
|
+
// bottom. Used by /resume and /resume-id so the operator sees the restored
|
|
1769
|
+
// context in the terminal instead of just "Chat opened.".
|
|
1770
|
+
//
|
|
1771
|
+
// Best-effort DOM scraping: DeepSeek renders every message as a markdown
|
|
1772
|
+
// block (assistant) or a plain bubble (user). Role detection relies on the
|
|
1773
|
+
// ds-assistant-message-* class; anything that is not an assistant block is
|
|
1774
|
+
// treated as a user message. The model's reasoning (.ds-think-content) is
|
|
1775
|
+
// skipped, exactly like in _readLastAnswerText.
|
|
1776
|
+
async readChatMessages() {
|
|
1777
|
+
if (!this.page)
|
|
1778
|
+
return [];
|
|
1779
|
+
const raw = await this.page
|
|
1780
|
+
.evaluate(() => {
|
|
1781
|
+
const inThink = (e) => {
|
|
1782
|
+
let n = e;
|
|
1783
|
+
while (n) {
|
|
1784
|
+
const cls = (n.className || '').toString();
|
|
1785
|
+
if (/ds-think-content|thinking-content/i.test(cls))
|
|
1786
|
+
return true;
|
|
1787
|
+
n = n.parentElement;
|
|
1788
|
+
}
|
|
1789
|
+
return false;
|
|
1790
|
+
};
|
|
1791
|
+
const textOf = (e) => {
|
|
1792
|
+
const h = e;
|
|
1793
|
+
const t = h.innerText || h.textContent || '';
|
|
1794
|
+
const NL = String.fromCharCode(10);
|
|
1795
|
+
return t.replace(new RegExp(NL + '{3,}', 'g'), NL + NL).trim();
|
|
1796
|
+
};
|
|
1797
|
+
// A message block is "assistant" when it contains an answer markdown
|
|
1798
|
+
// wrapper (ds-markdown / ds-assistant-message), otherwise it is a
|
|
1799
|
+
// user bubble. DeepSeek's class names drift between builds, so the
|
|
1800
|
+
// detection is content-based, not class-prefix-based.
|
|
1801
|
+
const looksAssistant = (e) => {
|
|
1802
|
+
const cls = (e.className || '').toString();
|
|
1803
|
+
if (/ds-assistant-message|assistant-message/i.test(cls))
|
|
1804
|
+
return true;
|
|
1805
|
+
if (e.querySelector('[class*="ds-assistant-message"]'))
|
|
1806
|
+
return true;
|
|
1807
|
+
// A user bubble has no rendered markdown; an answer does.
|
|
1808
|
+
if (e.querySelector('[class*="ds-markdown"]'))
|
|
1809
|
+
return true;
|
|
1810
|
+
return false;
|
|
1811
|
+
};
|
|
1812
|
+
// Message-level containers first (broad), then the answer wrappers.
|
|
1813
|
+
const containerSels = [
|
|
1814
|
+
'[data-message-id]',
|
|
1815
|
+
'[class*="ds-message"]',
|
|
1816
|
+
'[class*="chat-message"]',
|
|
1817
|
+
'[class*="message-item"]',
|
|
1818
|
+
'[class*="_message"]',
|
|
1819
|
+
];
|
|
1820
|
+
let blocks = [];
|
|
1821
|
+
for (const s of containerSels) {
|
|
1822
|
+
const found = Array.from(document.querySelectorAll(s)).filter((e) => !inThink(e) && textOf(e).length > 0);
|
|
1823
|
+
if (found.length) {
|
|
1824
|
+
blocks = found;
|
|
1825
|
+
break;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
const out = [];
|
|
1829
|
+
if (blocks.length) {
|
|
1830
|
+
for (const b of blocks) {
|
|
1831
|
+
const t = textOf(b);
|
|
1832
|
+
if (!t)
|
|
1833
|
+
continue;
|
|
1834
|
+
out.push({
|
|
1835
|
+
role: looksAssistant(b) ? 'assistant' : 'user',
|
|
1836
|
+
text: t,
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
if (out.length)
|
|
1840
|
+
return out;
|
|
1841
|
+
}
|
|
1842
|
+
// Last resort: assistant answers only (no user turns) — better than
|
|
1843
|
+
// nothing when no message container matched.
|
|
1844
|
+
const sels = [
|
|
1845
|
+
'div.ds-assistant-message-main-content',
|
|
1846
|
+
'div[class*="ds-assistant-message-main-content"]',
|
|
1847
|
+
'div[class*="ds-markdown"]',
|
|
1848
|
+
];
|
|
1849
|
+
for (const s of sels) {
|
|
1850
|
+
const list = Array.from(document.querySelectorAll(s)).filter((e) => !inThink(e));
|
|
1851
|
+
if (!list.length)
|
|
1852
|
+
continue;
|
|
1853
|
+
for (const el of list) {
|
|
1854
|
+
const t = textOf(el);
|
|
1855
|
+
if (t)
|
|
1856
|
+
out.push({ role: 'assistant', text: t });
|
|
1857
|
+
}
|
|
1858
|
+
break;
|
|
1859
|
+
}
|
|
1860
|
+
return out;
|
|
1861
|
+
})
|
|
1862
|
+
.catch(() => []);
|
|
1863
|
+
return raw;
|
|
1864
|
+
}
|
|
1587
1865
|
async getCurrentChatId() {
|
|
1588
1866
|
try {
|
|
1589
1867
|
const url = this.page.url();
|
package/dist/commands.js
CHANGED
|
@@ -186,6 +186,78 @@ export function resolveExtraDir(input, workdir) {
|
|
|
186
186
|
}
|
|
187
187
|
return { path: abs };
|
|
188
188
|
}
|
|
189
|
+
/** How many characters of the dialogue are printed by default. */
|
|
190
|
+
export const RESTORED_HISTORY_LIMIT = 20;
|
|
191
|
+
/**
|
|
192
|
+
* Is this message worth showing to the operator as part of the dialogue?
|
|
193
|
+
*
|
|
194
|
+
* The chat history contains a lot of protocol noise that is meaningless in
|
|
195
|
+
* the terminal: the system-prompt (a huge REQUEST), tool results
|
|
196
|
+
* ("Tool result for ..."), the agent's raw tool-calls (JSON / DSML), the
|
|
197
|
+
* "You stopped after a tool result..." nudges, and the agent's <system> notes.
|
|
198
|
+
* We keep only real user turns and real assistant answers.
|
|
199
|
+
*/
|
|
200
|
+
export function isDisplayableMessage(m) {
|
|
201
|
+
const text = String((m && m.text) || '').trim();
|
|
202
|
+
if (!text)
|
|
203
|
+
return false;
|
|
204
|
+
if (text.length > 100_000)
|
|
205
|
+
return false; // the system-prompt / a giant blob
|
|
206
|
+
if (m.role === 'user') {
|
|
207
|
+
// Tool output, the system-prompt and the corrective nudges are not the
|
|
208
|
+
// operator's words.
|
|
209
|
+
if (/^Tool result for /.test(text))
|
|
210
|
+
return false;
|
|
211
|
+
if (/^You are a coding agent running in a terminal/.test(text))
|
|
212
|
+
return false;
|
|
213
|
+
if (/^You stopped after a tool result/.test(text))
|
|
214
|
+
return false;
|
|
215
|
+
if (/^\[system\]/.test(text))
|
|
216
|
+
return false;
|
|
217
|
+
if (/^The user ran \//.test(text))
|
|
218
|
+
return false;
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
// Assistant: a tool-call (JSON or DSML) is not an answer to show. The
|
|
222
|
+
// DSML markers use FULL-WIDTH vertical bars (||DSML), so the regex must
|
|
223
|
+
// match them, not the ASCII pipe.
|
|
224
|
+
if (/DSML/i.test(text))
|
|
225
|
+
return false;
|
|
226
|
+
if (/^<system>/i.test(text))
|
|
227
|
+
return false;
|
|
228
|
+
if (/"tool"\s*:/.test(text.slice(0, 600))) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
if (/^\s*[\[{]/.test(text) && /"args"\s*:/.test(text.slice(0, 600))) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Keep only the last `limit` messages and drop empties/service noise. The
|
|
238
|
+
* source (network history or DOM) may contain protocol messages; they are
|
|
239
|
+
* filtered out here so the terminal output stays readable.
|
|
240
|
+
*/
|
|
241
|
+
export function trimRestoredMessages(messages, limit = RESTORED_HISTORY_LIMIT) {
|
|
242
|
+
const clean = (messages || []).filter((m) => m && typeof m.text === 'string' && isDisplayableMessage(m));
|
|
243
|
+
if (limit > 0 && clean.length > limit)
|
|
244
|
+
return clean.slice(clean.length - limit);
|
|
245
|
+
return clean;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Render the restored dialogue as plain text lines: "❯ ..." for the operator,
|
|
249
|
+
* "● ..." for the agent. Markdown rendering is done by the caller (it needs
|
|
250
|
+
* the terminal width); this helper is pure and unit-tested.
|
|
251
|
+
*/
|
|
252
|
+
export function formatRestoredHistory(messages, opts = {}) {
|
|
253
|
+
const list = trimRestoredMessages(messages, opts.limit ?? RESTORED_HISTORY_LIMIT);
|
|
254
|
+
const out = [];
|
|
255
|
+
for (const m of list) {
|
|
256
|
+
const marker = m.role === 'user' ? '❯ ' : '● ';
|
|
257
|
+
out.push(marker + m.text.trim());
|
|
258
|
+
}
|
|
259
|
+
return out.join(NL + NL);
|
|
260
|
+
}
|
|
189
261
|
// ---------- /review ----------
|
|
190
262
|
export function buildReviewPrompt(focus, hasStaged = false) {
|
|
191
263
|
const scope = hasStaged ? 'staged' : 'uncommitted';
|
package/dist/i18n.js
CHANGED
|
@@ -209,6 +209,83 @@ const CATALOG = {
|
|
|
209
209
|
'msg.reload_error': { ru: 'Ошибка reload:', en: 'Reload error:' },
|
|
210
210
|
'msg.abort_gen_short': { ru: '⏹ Esc — прерываю генерацию...', en: '⏹ Esc — aborting generation...' },
|
|
211
211
|
'msg.abort_ctrlc_short': { ru: '⏹ Ctrl+C — прерываю генерацию...', en: '⏹ Ctrl+C — aborting generation...' },
|
|
212
|
+
// ---------- DeepSeek-side problems (what is happening right now) ----------
|
|
213
|
+
// The operator must understand WHY the agent is waiting or stopped: a rate
|
|
214
|
+
// limit, a server hiccup, a refused send, a login problem. These strings are
|
|
215
|
+
// printed around the moment they happen (browser.ts / agent-loop.ts), not as
|
|
216
|
+
// a bare error at the end of the run.
|
|
217
|
+
'ds.rate_limit_wait': {
|
|
218
|
+
ru: '⏳ DeepSeek: «слишком часто». Жду {min} мин ({attempt}/{max}) и повторю отправку...',
|
|
219
|
+
en: '⏳ DeepSeek: "too frequent". Waiting {min} min ({attempt}/{max}), then resending...',
|
|
220
|
+
},
|
|
221
|
+
'ds.rate_limit_give_up': {
|
|
222
|
+
ru: '✖ DeepSeek не принял сообщение после {attempt} пауз по {min} мин. Подожди и попробуй снова.',
|
|
223
|
+
en: '✖ DeepSeek refused the message after {attempt} waits of {min} min. Wait and try again.',
|
|
224
|
+
},
|
|
225
|
+
'ds.server_busy_wait': {
|
|
226
|
+
ru: '⏳ DeepSeek: сервер занят. Жду {sec}с ({attempt}/{max}) и повторю...',
|
|
227
|
+
en: '⏳ DeepSeek: server busy. Waiting {sec}s ({attempt}/{max}), then retrying...',
|
|
228
|
+
},
|
|
229
|
+
'ds.server_busy_give_up': {
|
|
230
|
+
ru: '✖ DeepSeek: сервер не ответил после {attempt} повторов. Попробуй позже.',
|
|
231
|
+
en: '✖ DeepSeek: the server did not respond after {attempt} retries. Try again later.',
|
|
232
|
+
},
|
|
233
|
+
'ds.ask_retry': {
|
|
234
|
+
ru: '⚠ Ответ не получен ({attempt}/{max}): {error} — повторяю...',
|
|
235
|
+
en: '⚠ No answer yet ({attempt}/{max}): {error} — retrying...',
|
|
236
|
+
},
|
|
237
|
+
'ds.ask_restart_browser': {
|
|
238
|
+
ru: '⚠ Браузер потерял страницу — перезапускаю и вхожу заново...',
|
|
239
|
+
en: '⚠ The browser lost the page — restarting and signing in again...',
|
|
240
|
+
},
|
|
241
|
+
'ds.ask_restart_failed': {
|
|
242
|
+
ru: '⚠ Не удалось перезапустить браузер: {error}',
|
|
243
|
+
en: '⚠ Could not restart the browser: {error}',
|
|
244
|
+
},
|
|
245
|
+
'ds.ask_failed': {
|
|
246
|
+
ru: '✖ Не удалось получить ответ от DeepSeek после {max} попыток: {error}',
|
|
247
|
+
en: '✖ Could not get an answer from DeepSeek after {max} attempts: {error}',
|
|
248
|
+
},
|
|
249
|
+
'ds.input_missing': {
|
|
250
|
+
ru: '✖ Не найдено поле ввода на странице DeepSeek. Запусти /debug-dom и поправь INPUT_SELECTORS.',
|
|
251
|
+
en: '✖ The DeepSeek input field was not found. Run /debug-dom and fix INPUT_SELECTORS.',
|
|
252
|
+
},
|
|
253
|
+
'ds.input_partial': {
|
|
254
|
+
ru: '✖ Не удалось вставить текст в поле ввода целиком ({got} из {want} символов). Сообщение не отправлено, чтобы не отправить обрезанный текст. Попробуй ещё раз или разбей сообщение.',
|
|
255
|
+
en: '✖ Could not paste the whole text into the input ({got} of {want} chars). The message was not sent to avoid sending a truncated text. Retry or split the message.',
|
|
256
|
+
},
|
|
257
|
+
'ds.send_no_start': {
|
|
258
|
+
ru: '✖ Ответ не начал генерироваться за 35с даже после повторной отправки. Проверь чат DeepSeek вручную (возможно, кнопка отправки не нажимается или сессия разлогинилась).',
|
|
259
|
+
en: '✖ The answer did not start generating within 35s even after resending. Check the DeepSeek chat manually (the send button may not be clickable or the session may have expired).',
|
|
260
|
+
},
|
|
261
|
+
'ds.send_no_new_answer': {
|
|
262
|
+
ru: '✖ Новый ответ не получен — на странице остался прежний текст. Возможно, сообщение не отправилось. Проверь чат DeepSeek вручную.',
|
|
263
|
+
en: '✖ No new answer received — the page still shows the previous text. The message may not have been sent. Check the DeepSeek chat manually.',
|
|
264
|
+
},
|
|
265
|
+
'ds.answer_timeout': {
|
|
266
|
+
ru: '⏳ DeepSeek не ответил за {sec}с — повторяю запрос...',
|
|
267
|
+
en: '⏳ DeepSeek did not answer within {sec}s — retrying the request...',
|
|
268
|
+
},
|
|
269
|
+
'ds.answer_timeout_give_up': {
|
|
270
|
+
ru: '✖ DeepSeek перестал отвечать, лимит повторов исчерпан. Модель не дала ответа — проверь чат DeepSeek вручную.',
|
|
271
|
+
en: '✖ DeepSeek stopped responding, retry limit exhausted. The model gave no answer — check the DeepSeek chat manually.',
|
|
272
|
+
},
|
|
273
|
+
'ds.tool_retry': {
|
|
274
|
+
ru: '⏳ Агент не распознал ответ модели — прошу продолжить ({attempt}/{max})...',
|
|
275
|
+
en: '⏳ The agent did not recognize the model answer — asking it to continue ({attempt}/{max})...',
|
|
276
|
+
},
|
|
277
|
+
'ds.stalled': {
|
|
278
|
+
ru: '⚠ Агент остановился, не завершив задачу (модель перестала вызывать инструменты). Проверь чат DeepSeek — задача может быть не выполнена.',
|
|
279
|
+
en: '⚠ The agent stopped before finishing (the model stopped calling tools). Check the DeepSeek chat — the task may be incomplete.',
|
|
280
|
+
},
|
|
281
|
+
'ds.not_launched': {
|
|
282
|
+
ru: '✖ Браузер не запущен.',
|
|
283
|
+
en: '✖ The browser is not launched.',
|
|
284
|
+
},
|
|
285
|
+
'ds.open_chat_failed': {
|
|
286
|
+
ru: '✖ Не удалось открыть чат {id}: {error}',
|
|
287
|
+
en: '✖ Could not open chat {id}: {error}',
|
|
288
|
+
},
|
|
212
289
|
// ---------- self-review ----------
|
|
213
290
|
'self.review_failed': { ru: 'Самообзор провалился:', en: 'Self-review failed:' },
|
|
214
291
|
'self.fix_usage': { ru: 'Использование: /self-fix <name> [фокус]', en: 'Usage: /self-fix <name> [focus]' },
|
|
@@ -238,6 +315,12 @@ const CATALOG = {
|
|
|
238
315
|
'chats.prompt_will_resend': { ru: ' Системный промпт будет переслан на следующей задаче.\n', en: ' System prompt will be resent on the next task.\n' },
|
|
239
316
|
'chats.current_id': { ru: 'Текущий chat id: {v}', en: 'Current chat id: {v}' },
|
|
240
317
|
'chats.not_created': { ru: 'Чат ещё не создан.', en: 'No chat created yet.' },
|
|
318
|
+
'chats.history_title': { ru: 'Диалог чата:', en: 'Chat dialogue:' },
|
|
319
|
+
'chats.history_empty': { ru: 'Диалог пуст или не удалось прочитать сообщения.', en: 'The dialogue is empty or the messages could not be read.' },
|
|
320
|
+
'chats.history_service_only': { ru: 'В этом чате нет пользовательских реплик — только служебные сообщения агента (вызовы инструментов).', en: 'This chat has no user turns — only the agent service messages (tool calls).' },
|
|
321
|
+
'chats.history_truncated': { ru: '… показаны последние {n} сообщений.', en: '… showing the last {n} messages.' },
|
|
322
|
+
'chats.history_you': { ru: 'Вы', en: 'You' },
|
|
323
|
+
'chats.history_agent': { ru: 'Агент', en: 'Agent' },
|
|
241
324
|
'sessions.dir': { ru: 'Папка сессий: {v}', en: 'Sessions dir: {v}' },
|
|
242
325
|
'sessions.none': { ru: 'Сохранённых сессий нет. Они появятся после первой задачи/чата.', en: 'No saved sessions. They appear after the first task/chat.' },
|
|
243
326
|
'sessions.restore_hint': { ru: 'Восстановить: /resume-id <id> (полный id) или /resume <n> после /chats.', en: 'Restore: /resume-id <id> (full id) or /resume <n> after /chats.' },
|
package/dist/index.js
CHANGED
|
@@ -16,10 +16,12 @@ import { translate, normalizeLocale, localeDisplayName, isLocale, } from './i18n
|
|
|
16
16
|
import { Transcript } from './transcript.js';
|
|
17
17
|
import { UndoStore } from './undo.js';
|
|
18
18
|
import { selfReview, selfDiff, selfApply, selfList } from './self-review.js';
|
|
19
|
-
import { formatDiff, diffGitArgs, parseTranscript, summarizeTranscript, renderCost, formatExport, defaultExportPath, renderDoctor, renderPermissions, resolveExtraDir, buildReviewPrompt, } from './commands.js';
|
|
19
|
+
import { formatDiff, diffGitArgs, parseTranscript, summarizeTranscript, renderCost, formatExport, defaultExportPath, renderDoctor, renderPermissions, resolveExtraDir, buildReviewPrompt, trimRestoredMessages, RESTORED_HISTORY_LIMIT, } from './commands.js';
|
|
20
|
+
import { renderMarkdown } from './markdown.js';
|
|
20
21
|
import { closeWeb } from './web.js';
|
|
21
22
|
import { saveSession, loadLastSession, listSessions, sessionsDir, } from './sessions.js';
|
|
22
23
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const NL = String.fromCharCode(10);
|
|
23
25
|
// True when a DeepSeek session/credentials were stored earlier (auth.json).
|
|
24
26
|
// Used by /doctor to report that auto re-login is ready.
|
|
25
27
|
function authMarkerExists() {
|
|
@@ -788,6 +790,68 @@ async function resolveWorkdir() {
|
|
|
788
790
|
}
|
|
789
791
|
return dir;
|
|
790
792
|
}
|
|
793
|
+
// ---------- restored dialogue ----------
|
|
794
|
+
/**
|
|
795
|
+
* Print the dialogue of the just-opened chat into the terminal. Called by
|
|
796
|
+
* /resume, /resume-id and on startup when a previous session is restored, so
|
|
797
|
+
* the operator sees the context instead of a bare "Chat opened.".
|
|
798
|
+
*
|
|
799
|
+
* Best-effort: a DOM-scraping failure must never break the restore.
|
|
800
|
+
*/
|
|
801
|
+
async function printRestoredHistory(browser, ui, chatId = null) {
|
|
802
|
+
// Prefer DeepSeek's own history endpoint (complete, unvirtualized); fall
|
|
803
|
+
// back to scraping the rendered DOM when the request fails.
|
|
804
|
+
let all = [];
|
|
805
|
+
let fetchCount = -1;
|
|
806
|
+
let domCount = -1;
|
|
807
|
+
if (chatId && browser.fetchChatMessages) {
|
|
808
|
+
all = await browser.fetchChatMessages(chatId).catch(() => []);
|
|
809
|
+
fetchCount = all.length;
|
|
810
|
+
}
|
|
811
|
+
if (!all.length && browser.readChatMessages) {
|
|
812
|
+
all = await browser.readChatMessages().catch(() => []);
|
|
813
|
+
domCount = all.length;
|
|
814
|
+
}
|
|
815
|
+
const messages = trimRestoredMessages(all);
|
|
816
|
+
// Always surface the diagnostic line when the history could not be turned
|
|
817
|
+
// into anything printable — otherwise "the dialogue is empty" is a dead end
|
|
818
|
+
// (was the fetch blocked? did the chat really have no user turns?).
|
|
819
|
+
if (!all.length || (debug && !messages.length)) {
|
|
820
|
+
console.log(theme.dim(`[history] chat=${chatId || '-'} fetch=${fetchCount} dom=${domCount} raw=${all.length} shown=${messages.length} auth=${browser._apiAuth ? 'yes' : 'no'} err=${browser._lastHistoryError || '-'}`));
|
|
821
|
+
}
|
|
822
|
+
if (!all.length) {
|
|
823
|
+
console.log(theme.system(t('chats.history_empty')));
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (!messages.length) {
|
|
827
|
+
// The history exists but contains only service/protocol messages (e.g.
|
|
828
|
+
// a chat where only the system-prompt and tool-calls were stored).
|
|
829
|
+
console.log(theme.system(t('chats.history_service_only', { n: String(all.length) })));
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
const out = (text) => {
|
|
833
|
+
if (ui)
|
|
834
|
+
ui.printAbove(text);
|
|
835
|
+
else
|
|
836
|
+
console.log(text);
|
|
837
|
+
};
|
|
838
|
+
out(theme.system(t('chats.history_title')));
|
|
839
|
+
for (const m of messages) {
|
|
840
|
+
if (m.role === 'user') {
|
|
841
|
+
out(theme.user('❯ ' + t('chats.history_you') + ': ') + m.text.trim());
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
out(NL +
|
|
845
|
+
theme.assistant('● ' + t('chats.history_agent') + ':') +
|
|
846
|
+
NL +
|
|
847
|
+
renderMarkdown(m.text.trim()) +
|
|
848
|
+
NL);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (all.length > messages.length) {
|
|
852
|
+
out(theme.dim(t('chats.history_truncated', { n: String(RESTORED_HISTORY_LIMIT) })));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
791
855
|
// ---------- task runner ----------
|
|
792
856
|
async function runTask(browser, tools, taskText, workdir, opts, attachments = []) {
|
|
793
857
|
const { transcript, freshChat, sendSystemPrompt, queue = [], ui: editor, onChatReady, } = opts;
|
|
@@ -1091,6 +1155,7 @@ async function main() {
|
|
|
1091
1155
|
sendSystemPromptNext = resendPrompt;
|
|
1092
1156
|
saveLastChat(resumeId, currentWorkdir);
|
|
1093
1157
|
console.log(theme.system(t('msg.chat_opened') + ' ' + resumeId + String.fromCharCode(10)));
|
|
1158
|
+
await printRestoredHistory(browser, null, resumeId);
|
|
1094
1159
|
}
|
|
1095
1160
|
catch (e) {
|
|
1096
1161
|
console.error(theme.error(t('msg.open_chat_error', { v: e.message })));
|
|
@@ -1774,6 +1839,7 @@ async function main() {
|
|
|
1774
1839
|
theme.system(resendPrompt
|
|
1775
1840
|
? ' Системный промпт будет переслан на следующей задаче.\n'
|
|
1776
1841
|
: ' Контекст чата сохранён. Системный промпт не пересылается (--resend-prompt чтобы дослать).\n'));
|
|
1842
|
+
await printRestoredHistory(browser, editor, pick.id);
|
|
1777
1843
|
}
|
|
1778
1844
|
catch (e) {
|
|
1779
1845
|
console.error(theme.error(t('msg.open_chat_error', { v: '' })), e.message);
|
|
@@ -1831,6 +1897,7 @@ async function main() {
|
|
|
1831
1897
|
transcript.log('resume_chat', { id });
|
|
1832
1898
|
console.log(theme.assistant('Чат открыт.') +
|
|
1833
1899
|
theme.system(String.fromCharCode(10)));
|
|
1900
|
+
await printRestoredHistory(browser, editor, id);
|
|
1834
1901
|
}
|
|
1835
1902
|
catch (e) {
|
|
1836
1903
|
console.error(theme.error(t('msg.open_chat_error', { v: '' })), e.message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.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": "tsx ./src/index.ts --dev",
|
|
18
|
+
"dev": "ZAMES_NET_DEBUG=1 tsx ./src/index.ts --dev",
|
|
19
19
|
"build": "tsc -p tsconfig.build.json",
|
|
20
20
|
"typecheck": "tsc --noEmit",
|
|
21
21
|
"prepublishOnly": "npm run build",
|