zames_pro 2.16.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 +40 -1
- package/dist/agent-loop.js +22 -6
- package/dist/browser.js +828 -29
- package/dist/commands.js +73 -0
- package/dist/config-menu.js +4 -0
- package/dist/config.js +11 -1
- package/dist/i18n.js +119 -1
- package/dist/index.js +111 -5
- package/dist/input.js +51 -9
- package/dist/spinner.js +39 -6
- package/package.json +2 -2
package/dist/commands.js
CHANGED
|
@@ -153,6 +153,7 @@ export function renderDoctor(d) {
|
|
|
153
153
|
row(d.gitOk, 'git', d.gitOk ? 'repo (' + (d.gitBranch || 'detached') + ')' : 'not a repository');
|
|
154
154
|
row(d.configOk, 'config', d.configOk ? 'loaded' : 'error: ' + (d.configError || 'unknown'));
|
|
155
155
|
row(true, 'browser', d.browserChannel ? d.browserChannel : 'bundled chromium');
|
|
156
|
+
row(!!d.authSaved, 'auth', d.authSaved ? 'session saved (auto re-login ready)' : 'no saved session');
|
|
156
157
|
row(!!d.clipboardTool, 'clipboard', d.clipboardTool || 'no tool found (image paste disabled)');
|
|
157
158
|
row(true, 'mcp', d.mcpServers + ' server(s), ' + d.mcpTools + ' tool(s)');
|
|
158
159
|
row(d.transcriptOk, 'transcript', d.transcriptOk ? 'on' : 'off');
|
|
@@ -185,6 +186,78 @@ export function resolveExtraDir(input, workdir) {
|
|
|
185
186
|
}
|
|
186
187
|
return { path: abs };
|
|
187
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
|
+
}
|
|
188
261
|
// ---------- /review ----------
|
|
189
262
|
export function buildReviewPrompt(focus, hasStaged = false) {
|
|
190
263
|
const scope = hasStaged ? 'staged' : 'uncommitted';
|
package/dist/config-menu.js
CHANGED
|
@@ -19,6 +19,10 @@ function displayValue(field, value, t) {
|
|
|
19
19
|
return t('cfg.menu.default');
|
|
20
20
|
if (typeof value === 'boolean')
|
|
21
21
|
return value ? t('common.on') : t('common.off');
|
|
22
|
+
// Never print a password in clear text: show a fixed mask when set.
|
|
23
|
+
if (/password/i.test(field.path) && String(value).length > 0) {
|
|
24
|
+
return '********';
|
|
25
|
+
}
|
|
22
26
|
return String(value);
|
|
23
27
|
}
|
|
24
28
|
function groupLabel(field, t) {
|
package/dist/config.js
CHANGED
|
@@ -7,7 +7,9 @@ const HOME_CONFIG = path.join(ZAMES_HOME, 'config.json');
|
|
|
7
7
|
const PROJECT_CONFIG = path.join(process.cwd(), '.zamesrc.json');
|
|
8
8
|
export const DEFAULTS = {
|
|
9
9
|
maxIterations: 0,
|
|
10
|
-
|
|
10
|
+
// Headless by default: the browser runs without a visible window. Set
|
|
11
|
+
// headless: false (or pass --headed) to watch/debug the DeepSeek page.
|
|
12
|
+
headless: true,
|
|
11
13
|
debug: false,
|
|
12
14
|
browserChannel: null,
|
|
13
15
|
confirmation: {
|
|
@@ -43,6 +45,11 @@ export const DEFAULTS = {
|
|
|
43
45
|
maxRateLimitRetries: 6,
|
|
44
46
|
deepThinking: false,
|
|
45
47
|
webSearch: true,
|
|
48
|
+
auth: {
|
|
49
|
+
username: '',
|
|
50
|
+
password: '',
|
|
51
|
+
saveSession: true,
|
|
52
|
+
},
|
|
46
53
|
},
|
|
47
54
|
ui: {
|
|
48
55
|
locale: DEFAULT_LOCALE,
|
|
@@ -81,6 +88,9 @@ export const CONFIG_SCHEMA = [
|
|
|
81
88
|
{ path: 'ui.locale', type: 'enum', values: ['ru', 'en'], labelKey: 'cfg.f.ui_locale', groupKey: 'cfg.group.ui' },
|
|
82
89
|
{ path: 'maxIterations', type: 'number', min: 0, max: 100000, labelKey: 'cfg.f.maxIterations', groupKey: 'cfg.group.agent' },
|
|
83
90
|
{ path: 'headless', type: 'boolean', labelKey: 'cfg.f.headless', groupKey: 'cfg.group.agent' },
|
|
91
|
+
{ path: 'browser.auth.username', type: 'string', labelKey: 'cfg.f.browser_authUsername', groupKey: 'cfg.group.browser' },
|
|
92
|
+
{ path: 'browser.auth.password', type: 'string', labelKey: 'cfg.f.browser_authPassword', groupKey: 'cfg.group.browser' },
|
|
93
|
+
{ path: 'browser.auth.saveSession', type: 'boolean', labelKey: 'cfg.f.browser_authSaveSession', groupKey: 'cfg.group.browser' },
|
|
84
94
|
{ path: 'debug', type: 'boolean', labelKey: 'cfg.f.debug', groupKey: 'cfg.group.agent' },
|
|
85
95
|
{ path: 'hotReload', type: 'boolean', labelKey: 'cfg.f.hotReload', groupKey: 'cfg.group.agent' },
|
|
86
96
|
{ path: 'confirmation.write', type: 'boolean', labelKey: 'cfg.f.confirmation_write', groupKey: 'cfg.group.confirmation' },
|
package/dist/i18n.js
CHANGED
|
@@ -46,7 +46,8 @@ const CATALOG = {
|
|
|
46
46
|
'help.opt.new_chat': { ru: 'начать новый чат (по умолчанию)', en: 'start a new chat (default)' },
|
|
47
47
|
'help.opt.resend_prompt': { ru: 'дослать system-prompt в существующий чат', en: 'resend system-prompt into an existing chat' },
|
|
48
48
|
'help.opt.max_iter': { ru: 'лимит итераций, 0 = без лимита ({n})', en: 'iteration limit, 0 = unlimited ({n})' },
|
|
49
|
-
'help.opt.headless': { ru: 'браузер без UI', en: 'headless browser' },
|
|
49
|
+
'help.opt.headless': { ru: 'браузер без UI (по умолчанию)', en: 'headless browser (default)' },
|
|
50
|
+
'help.opt.headed': { ru: 'показать окно браузера (отладка/вход)', en: 'show the browser window (debug/sign-in)' },
|
|
50
51
|
'help.opt.debug': { ru: 'подробный лог', en: 'verbose log' },
|
|
51
52
|
'help.opt.calibrate': { ru: 'режим калибровки селекторов', en: 'selector calibration mode' },
|
|
52
53
|
'help.opt.dev': { ru: 'режим разработки: авто-перечитывание модулей', en: 'dev mode: auto-reload modules' },
|
|
@@ -132,6 +133,9 @@ const CATALOG = {
|
|
|
132
133
|
en: 'Mixing the slurry…|Whipping it to a smooth paste…|Stirring logic into it…|Shaking it into a cocktail…|Whisking it around…|Blending it with caffeine…|Kneading dough out of it…|Whipping it into foam…|Tossing it with a spatula…|Kneading clay out of it…|Homogenizing it into concrete…|Shaking it until clear…|Blending it into a smoothie…|Mixing cement out of it…|Whipping porridge out of it…|Stirring it with hope…|Swirling the sediment…|Running the blender over it…|Mixing it beyond recognition…|Mixing mortar out of it…|Whipping a cocktail of it…|Stirring it with the deadline spatula…|Whipping foam out of it…|Homogenizing it evenly…|Tumbling it in a cement mixer…|Beating it into a uniform mass…|Stirring it until clear…|Mixing it from whatever is around…',
|
|
133
134
|
},
|
|
134
135
|
'spinner.hint': { ru: 'Esc — стоп', en: 'Esc — stop' },
|
|
136
|
+
'spinner.pause': { ru: '⏳ пауза {n}с перед отправкой', en: '⏳ pause {n}s before send' },
|
|
137
|
+
'editor.more': { ru: '…ещё {n}', en: '…{n} more' },
|
|
138
|
+
'editor.answer': { ru: '● Ответ', en: '● Answer' },
|
|
135
139
|
// ---------- status ----------
|
|
136
140
|
'status.workdir': { ru: 'Рабочая директория: {v}', en: 'Working directory: {v}' },
|
|
137
141
|
'status.review_mode': { ru: 'Режим ревью: {v}', en: 'Review mode: {v}' },
|
|
@@ -205,6 +209,83 @@ const CATALOG = {
|
|
|
205
209
|
'msg.reload_error': { ru: 'Ошибка reload:', en: 'Reload error:' },
|
|
206
210
|
'msg.abort_gen_short': { ru: '⏹ Esc — прерываю генерацию...', en: '⏹ Esc — aborting generation...' },
|
|
207
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
|
+
},
|
|
208
289
|
// ---------- self-review ----------
|
|
209
290
|
'self.review_failed': { ru: 'Самообзор провалился:', en: 'Self-review failed:' },
|
|
210
291
|
'self.fix_usage': { ru: 'Использование: /self-fix <name> [фокус]', en: 'Usage: /self-fix <name> [focus]' },
|
|
@@ -234,6 +315,12 @@ const CATALOG = {
|
|
|
234
315
|
'chats.prompt_will_resend': { ru: ' Системный промпт будет переслан на следующей задаче.\n', en: ' System prompt will be resent on the next task.\n' },
|
|
235
316
|
'chats.current_id': { ru: 'Текущий chat id: {v}', en: 'Current chat id: {v}' },
|
|
236
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' },
|
|
237
324
|
'sessions.dir': { ru: 'Папка сессий: {v}', en: 'Sessions dir: {v}' },
|
|
238
325
|
'sessions.none': { ru: 'Сохранённых сессий нет. Они появятся после первой задачи/чата.', en: 'No saved sessions. They appear after the first task/chat.' },
|
|
239
326
|
'sessions.restore_hint': { ru: 'Восстановить: /resume-id <id> (полный id) или /resume <n> после /chats.', en: 'Restore: /resume-id <id> (full id) or /resume <n> after /chats.' },
|
|
@@ -295,6 +382,37 @@ const CATALOG = {
|
|
|
295
382
|
'cfg.f.browser_maxRateLimitRetries': { ru: 'Число повторов при лимите', en: 'Rate-limit retry count' },
|
|
296
383
|
'cfg.f.browser_deepThinking': { ru: 'Глубокое мышление (долго; размышления не выводятся)', en: 'Deep thinking (slow; reasoning is hidden)' },
|
|
297
384
|
'cfg.f.browser_webSearch': { ru: 'Умный поиск в интернете', en: 'Smart web search' },
|
|
385
|
+
'cfg.f.browser_authUsername': { ru: 'Логин DeepSeek для авто-входа', en: 'DeepSeek login for auto sign-in' },
|
|
386
|
+
'cfg.f.browser_authPassword': { ru: 'Пароль DeepSeek для авто-входа', en: 'DeepSeek password for auto sign-in' },
|
|
387
|
+
'cfg.f.browser_authSaveSession': { ru: 'Сохранять сессию входа в профиле', en: 'Persist the login session in the profile' },
|
|
388
|
+
// ---------- login / auth ----------
|
|
389
|
+
'auth.need_login': { ru: '🔐 Требуется вход в DeepSeek.', en: '🔐 DeepSeek sign-in required.' },
|
|
390
|
+
'auth.prompt_login': { ru: 'Логин DeepSeek (телефон/email): ', en: 'DeepSeek login (phone/email): ' },
|
|
391
|
+
'auth.prompt_password': { ru: 'Пароль DeepSeek: ', en: 'DeepSeek password: ' },
|
|
392
|
+
'auth.prompt_password_saved': {
|
|
393
|
+
ru: 'Пароль DeepSeek (Enter — использовать сохранённый): ',
|
|
394
|
+
en: 'DeepSeek password (Enter — use the saved one): ',
|
|
395
|
+
},
|
|
396
|
+
'auth.auto_login': { ru: 'Авто-вход в DeepSeek...', en: 'Signing in to DeepSeek...' },
|
|
397
|
+
'auth.auto_login_ok': { ru: 'Вход выполнен.', en: 'Signed in.' },
|
|
398
|
+
'auth.auto_login_failed': { ru: 'Авто-вход не удался: {v}', en: 'Auto sign-in failed: {v}' },
|
|
399
|
+
'auth.login_rejected': { ru: 'DeepSeek отклонил вход: {v}', en: 'DeepSeek rejected the sign-in: {v}' },
|
|
400
|
+
'auth.form_not_found': {
|
|
401
|
+
ru: 'Форма входа не распознана. Запустите с --headed и войдите вручную.',
|
|
402
|
+
en: 'Login form not recognized. Run with --headed and sign in manually.',
|
|
403
|
+
},
|
|
404
|
+
'auth.no_reason': {
|
|
405
|
+
ru: 'причина не определена (возможно, неверный логин/пароль или капча)',
|
|
406
|
+
en: 'no reason detected (possibly wrong login/password or a captcha)',
|
|
407
|
+
},
|
|
408
|
+
'auth.manual_hint': { ru: 'Войдите в DeepSeek в открытом браузере, затем нажмите Enter.', en: 'Sign in to DeepSeek in the open browser, then press Enter.' },
|
|
409
|
+
'auth.manual_hint_headless': {
|
|
410
|
+
ru: 'Войдите в DeepSeek: откройте https://chat.deepseek.com/ в своём браузере. Если включён headless — задайте логин/пароль через /config или запустите с --headed.',
|
|
411
|
+
en: 'Sign in to DeepSeek: open https://chat.deepseek.com/ in your browser. If headless is on — set the login/password via /config or run with --headed.',
|
|
412
|
+
},
|
|
413
|
+
'auth.session_saved': { ru: 'Сессия DeepSeek сохранена.', en: 'DeepSeek session saved.' },
|
|
414
|
+
'auth.session_restored': { ru: 'Сессия DeepSeek восстановлена.', en: 'DeepSeek session restored.' },
|
|
415
|
+
'auth.enter_to_continue': { ru: 'Нажмите Enter, чтобы продолжить...', en: 'Press Enter to continue...' },
|
|
298
416
|
'cfg.menu.title': { ru: '⚙ Настройки — выбери параметр', en: '⚙ Settings — choose a parameter' },
|
|
299
417
|
'cfg.menu.hint': { ru: '↑/↓ — выбор, Enter — изменить, q/Esc — выйти', en: '↑/↓ select, Enter edit, q/Esc quit' },
|
|
300
418
|
'cfg.menu.edit_hint': { ru: 'Введи значение и Enter (пусто — отмена)', en: 'Enter a value and press Enter (empty — cancel)' },
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
4
5
|
import { fileURLToPath } from 'url';
|
|
5
6
|
import { theme } from './theme.js';
|
|
6
7
|
import { DeepSeekBrowser } from './browser.js';
|
|
@@ -15,10 +16,22 @@ import { translate, normalizeLocale, localeDisplayName, isLocale, } from './i18n
|
|
|
15
16
|
import { Transcript } from './transcript.js';
|
|
16
17
|
import { UndoStore } from './undo.js';
|
|
17
18
|
import { selfReview, selfDiff, selfApply, selfList } from './self-review.js';
|
|
18
|
-
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';
|
|
19
21
|
import { closeWeb } from './web.js';
|
|
20
22
|
import { saveSession, loadLastSession, listSessions, sessionsDir, } from './sessions.js';
|
|
21
23
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const NL = String.fromCharCode(10);
|
|
25
|
+
// True when a DeepSeek session/credentials were stored earlier (auth.json).
|
|
26
|
+
// Used by /doctor to report that auto re-login is ready.
|
|
27
|
+
function authMarkerExists() {
|
|
28
|
+
try {
|
|
29
|
+
return existsSync(path.join(ZAMES_HOME, 'auth.json'));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
22
35
|
// ---------- CLI parsing ----------
|
|
23
36
|
const args = process.argv.slice(2);
|
|
24
37
|
function getArg(flag, fallback = null) {
|
|
@@ -50,7 +63,11 @@ let currentLocale = isLocale(config.ui?.locale)
|
|
|
50
63
|
// Translation: reads currentLocale at call time, so a language change takes
|
|
51
64
|
// effect immediately, without a restart (for lines printed afterwards).
|
|
52
65
|
const t = (key, params) => translate(currentLocale)(key, params);
|
|
53
|
-
|
|
66
|
+
// Headless is the default. --headed forces a visible window (useful for the
|
|
67
|
+
// first sign-in or debugging the DOM). --headless keeps it explicit.
|
|
68
|
+
const headless = hasFlag('--headed')
|
|
69
|
+
? false
|
|
70
|
+
: hasFlag('--headless') || config.headless;
|
|
54
71
|
const debug = hasFlag('--debug') || config.debug;
|
|
55
72
|
const calibrate = hasFlag('--calibrate');
|
|
56
73
|
const maxIterArg = getArg('--max-iter', null);
|
|
@@ -189,6 +206,7 @@ ${theme.bold(t('help.options'))}
|
|
|
189
206
|
--resend-prompt ${t('help.opt.resend_prompt')}
|
|
190
207
|
--max-iter <n> ${t('help.opt.max_iter', { n: config.maxIterations })}
|
|
191
208
|
--headless ${t('help.opt.headless')}
|
|
209
|
+
--headed ${t('help.opt.headed')}
|
|
192
210
|
--debug ${t('help.opt.debug')}
|
|
193
211
|
--calibrate ${t('help.opt.calibrate')}
|
|
194
212
|
--dev ${t('help.opt.dev')}
|
|
@@ -772,6 +790,68 @@ async function resolveWorkdir() {
|
|
|
772
790
|
}
|
|
773
791
|
return dir;
|
|
774
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
|
+
}
|
|
775
855
|
// ---------- task runner ----------
|
|
776
856
|
async function runTask(browser, tools, taskText, workdir, opts, attachments = []) {
|
|
777
857
|
const { transcript, freshChat, sendSystemPrompt, queue = [], ui: editor, onChatReady, } = opts;
|
|
@@ -828,6 +908,7 @@ async function runTask(browser, tools, taskText, workdir, opts, attachments = []
|
|
|
828
908
|
attachments: next.attachments || [],
|
|
829
909
|
transcript,
|
|
830
910
|
onThinking: () => ui.thinking(),
|
|
911
|
+
onSendPause: (seconds) => ui.sendPause(seconds),
|
|
831
912
|
onToolCall: (name, toolArgs) => ui.toolCall(name, toolArgs),
|
|
832
913
|
onToolResult: (result) => ui.toolResult(result),
|
|
833
914
|
onAssistantMessage: (msg) => ui.assistant(msg),
|
|
@@ -881,9 +962,10 @@ async function runTask(browser, tools, taskText, workdir, opts, attachments = []
|
|
|
881
962
|
finally {
|
|
882
963
|
stopWatching();
|
|
883
964
|
ui.stop();
|
|
884
|
-
// Detach the send
|
|
965
|
+
// Detach the send hooks so a later browser.ask() outside this task cannot
|
|
885
966
|
// start a stale spinner.
|
|
886
967
|
browser.onSendStart = null;
|
|
968
|
+
browser.onSendPause = null;
|
|
887
969
|
}
|
|
888
970
|
}
|
|
889
971
|
// ---------- main ----------
|
|
@@ -944,6 +1026,18 @@ async function main() {
|
|
|
944
1026
|
debug,
|
|
945
1027
|
channel: config.browserChannel,
|
|
946
1028
|
...config.browser,
|
|
1029
|
+
locale: currentLocale,
|
|
1030
|
+
// Persist credentials the operator typed in the terminal so the next
|
|
1031
|
+
// launch can re-login silently after a logout.
|
|
1032
|
+
onAuthSave: (username, password) => {
|
|
1033
|
+
config.browser.auth.username = username;
|
|
1034
|
+
config.browser.auth.password = password;
|
|
1035
|
+
try {
|
|
1036
|
+
writeConfigValue('home', 'browser.auth.username', username);
|
|
1037
|
+
writeConfigValue('home', 'browser.auth.password', password);
|
|
1038
|
+
}
|
|
1039
|
+
catch { }
|
|
1040
|
+
},
|
|
947
1041
|
});
|
|
948
1042
|
const bootSpinner = mod.createSpinner(currentLocale);
|
|
949
1043
|
bootSpinner.thinking();
|
|
@@ -1061,6 +1155,7 @@ async function main() {
|
|
|
1061
1155
|
sendSystemPromptNext = resendPrompt;
|
|
1062
1156
|
saveLastChat(resumeId, currentWorkdir);
|
|
1063
1157
|
console.log(theme.system(t('msg.chat_opened') + ' ' + resumeId + String.fromCharCode(10)));
|
|
1158
|
+
await printRestoredHistory(browser, null, resumeId);
|
|
1064
1159
|
}
|
|
1065
1160
|
catch (e) {
|
|
1066
1161
|
console.error(theme.error(t('msg.open_chat_error', { v: e.message })));
|
|
@@ -1096,6 +1191,7 @@ async function main() {
|
|
|
1096
1191
|
const ed = new LineEditor({
|
|
1097
1192
|
prompt: buildPrompt(),
|
|
1098
1193
|
commands: buildSlashCommands(),
|
|
1194
|
+
locale: currentLocale,
|
|
1099
1195
|
});
|
|
1100
1196
|
editor = ed;
|
|
1101
1197
|
ed.setTmpDir(TMP_DIR);
|
|
@@ -1259,6 +1355,7 @@ async function main() {
|
|
|
1259
1355
|
currentLocale = value;
|
|
1260
1356
|
config.ui.locale = value;
|
|
1261
1357
|
if (editor) {
|
|
1358
|
+
editor.setLocale(value);
|
|
1262
1359
|
editor.setCommands(buildSlashCommands());
|
|
1263
1360
|
editor.setPrompt(buildPrompt());
|
|
1264
1361
|
}
|
|
@@ -1304,7 +1401,9 @@ async function main() {
|
|
|
1304
1401
|
? cur
|
|
1305
1402
|
? t('common.on')
|
|
1306
1403
|
: t('common.off')
|
|
1307
|
-
:
|
|
1404
|
+
: /password/i.test(f.path) && String(cur).length > 0
|
|
1405
|
+
? '********'
|
|
1406
|
+
: JSON.stringify(cur);
|
|
1308
1407
|
const extra = f.values ? ' [' + f.values.join('|') + ']' : '';
|
|
1309
1408
|
console.log(' ' +
|
|
1310
1409
|
theme.user(f.path) +
|
|
@@ -1395,7 +1494,11 @@ async function main() {
|
|
|
1395
1494
|
const cur = getByPath(config, key);
|
|
1396
1495
|
console.log(theme.system(t('cfg.value', {
|
|
1397
1496
|
v: key,
|
|
1398
|
-
value: cur === undefined
|
|
1497
|
+
value: cur === undefined
|
|
1498
|
+
? t('cfg.menu.default')
|
|
1499
|
+
: /password/i.test(key) && String(cur).length > 0
|
|
1500
|
+
? '********'
|
|
1501
|
+
: JSON.stringify(cur),
|
|
1399
1502
|
})));
|
|
1400
1503
|
return;
|
|
1401
1504
|
}
|
|
@@ -1736,6 +1839,7 @@ async function main() {
|
|
|
1736
1839
|
theme.system(resendPrompt
|
|
1737
1840
|
? ' Системный промпт будет переслан на следующей задаче.\n'
|
|
1738
1841
|
: ' Контекст чата сохранён. Системный промпт не пересылается (--resend-prompt чтобы дослать).\n'));
|
|
1842
|
+
await printRestoredHistory(browser, editor, pick.id);
|
|
1739
1843
|
}
|
|
1740
1844
|
catch (e) {
|
|
1741
1845
|
console.error(theme.error(t('msg.open_chat_error', { v: '' })), e.message);
|
|
@@ -1793,6 +1897,7 @@ async function main() {
|
|
|
1793
1897
|
transcript.log('resume_chat', { id });
|
|
1794
1898
|
console.log(theme.assistant('Чат открыт.') +
|
|
1795
1899
|
theme.system(String.fromCharCode(10)));
|
|
1900
|
+
await printRestoredHistory(browser, editor, id);
|
|
1796
1901
|
}
|
|
1797
1902
|
catch (e) {
|
|
1798
1903
|
console.error(theme.error(t('msg.open_chat_error', { v: '' })), e.message);
|
|
@@ -2101,6 +2206,7 @@ async function main() {
|
|
|
2101
2206
|
mcpServers: mcpStatus.servers.length,
|
|
2102
2207
|
mcpTools: mcpStatus.toolCount,
|
|
2103
2208
|
transcriptOk: !!transcript.file,
|
|
2209
|
+
authSaved: authMarkerExists(),
|
|
2104
2210
|
})));
|
|
2105
2211
|
continue;
|
|
2106
2212
|
}
|