zames_pro 2.32.0 → 2.35.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/dist/agent-loop.js +4 -1
- package/dist/browser.js +32 -1
- package/dist/commands.js +114 -4
- package/dist/i18n.js +96 -5
- package/dist/index.js +348 -30
- package/dist/input.js +98 -41
- package/dist/scheduler.js +185 -0
- package/dist/theme.js +1 -0
- package/package.json +1 -1
package/dist/agent-loop.js
CHANGED
|
@@ -4,7 +4,7 @@ import { getGitContext, formatGitContext } from './gitTools.js';
|
|
|
4
4
|
import { parseXmlToolCalls } from './xml-toolcall.js';
|
|
5
5
|
import { translate } from './i18n.js';
|
|
6
6
|
import { normText } from './browser.js';
|
|
7
|
-
export async function runAgentLoop({ browser, tools, task, workdir, maxIterations = 0, freshChat = false, sendSystemPrompt = false, transcript = null, attachments = [], onThinking = () => { }, onSendPause = () => { }, onNotice = () => { }, onAssistantThought = () => { }, onToolCall = () => { }, onToolResult = () => { }, onAssistantMessage = () => { }, onChatReady = () => { }, onWarning = () => { }, debugLog = false, locale = 'ru', askDeadlineMs = 240_000, maxAfterToolRetries = 6, onAutoCompact = null, autoCompactPct = 95, contextLimit = 1_000_000, getTokenUsage = null, }) {
|
|
7
|
+
export async function runAgentLoop({ browser, tools, task, workdir, maxIterations = 0, freshChat = false, sendSystemPrompt = false, transcript = null, attachments = [], onThinking = () => { }, onSendPause = () => { }, onSendState = () => { }, onNotice = () => { }, onAssistantThought = () => { }, onToolCall = () => { }, onToolResult = () => { }, onAssistantMessage = () => { }, onChatReady = () => { }, onWarning = () => { }, debugLog = false, locale = 'ru', askDeadlineMs = 240_000, maxAfterToolRetries = 6, onAutoCompact = null, autoCompactPct = 95, contextLimit = 1_000_000, getTokenUsage = null, }) {
|
|
8
8
|
// UI callbacks must NEVER break the agent loop. A rendering error (a huge
|
|
9
9
|
// tool result, a broken markdown frame, a closed terminal) used to throw
|
|
10
10
|
// out of the loop right after a tool call — the session looked "stopped
|
|
@@ -30,6 +30,9 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
30
30
|
// seconds, the UI shows an animated status instead of a frozen line.
|
|
31
31
|
const safeSendPause = safe(onSendPause);
|
|
32
32
|
browser.onSendPause = safeSendPause;
|
|
33
|
+
// Coarse lifecycle of the send (generating/paused/settled) for the status
|
|
34
|
+
// line. Same best-effort wiring as the other hooks.
|
|
35
|
+
browser.onSendState = safe(onSendState);
|
|
33
36
|
// Service notices (rate limit, server busy, resend) must reach the OPERATOR.
|
|
34
37
|
// While a LineEditor is active a raw console.error is overwritten by the
|
|
35
38
|
// editor's repaint, so the operator only saw the spinner. `onNotice` prints
|
package/dist/browser.js
CHANGED
|
@@ -400,6 +400,12 @@ export class DeepSeekBrowser {
|
|
|
400
400
|
// remaining seconds. Lets the UI animate the pause status instead of
|
|
401
401
|
// printing a static line (the dots used to be frozen during the pause).
|
|
402
402
|
onSendPause;
|
|
403
|
+
// Coarse lifecycle of one send: 'generating' right before the message is
|
|
404
|
+
// typed, 'paused' while waiting out a throttle/Continue gap, 'settled'
|
|
405
|
+
// once the answer stopped changing. Lets the UI show an explicit phase
|
|
406
|
+
// instead of only an on/off spinner (a long reasoning turn and a rate-limit
|
|
407
|
+
// wait looked identical). Firing this hook does not alter any send timing.
|
|
408
|
+
onSendState;
|
|
403
409
|
// A service notice for the OPERATOR (rate limit, server busy, resend, …).
|
|
404
410
|
// IMPORTANT: these must NOT go through `console.error` directly — while a
|
|
405
411
|
// LineEditor is active it repaints its own status line and the raw stderr
|
|
@@ -458,6 +464,7 @@ export class DeepSeekBrowser {
|
|
|
458
464
|
this._netHookInstalled = false;
|
|
459
465
|
this.onSendStart = null;
|
|
460
466
|
this.onSendPause = null;
|
|
467
|
+
this.onSendState = null;
|
|
461
468
|
this.onNotice = null;
|
|
462
469
|
// An explicit UA (config/options) is respected as-is. Otherwise it stays
|
|
463
470
|
// empty here and is derived from the real engine UA after launch — see
|
|
@@ -482,6 +489,16 @@ export class DeepSeekBrowser {
|
|
|
482
489
|
}
|
|
483
490
|
console.error(text);
|
|
484
491
|
}
|
|
492
|
+
// Report the coarse lifecycle of one send to the UI hook (if wired).
|
|
493
|
+
// Best-effort: a broken UI callback must never break the send loop.
|
|
494
|
+
_emitSendState(state) {
|
|
495
|
+
if (this.onSendState) {
|
|
496
|
+
try {
|
|
497
|
+
this.onSendState(state);
|
|
498
|
+
}
|
|
499
|
+
catch { }
|
|
500
|
+
}
|
|
501
|
+
}
|
|
485
502
|
async launch() {
|
|
486
503
|
// NOTE: we deliberately do NOT kill stray chrome processes here. Per the
|
|
487
504
|
// project rules, an agent must never kill a browser it did not start —
|
|
@@ -1606,7 +1623,18 @@ export class DeepSeekBrowser {
|
|
|
1606
1623
|
// at the start burns the whole askRetries budget and ask() gives up early.
|
|
1607
1624
|
while (attempt < this.askRetries) {
|
|
1608
1625
|
try {
|
|
1609
|
-
|
|
1626
|
+
const answer = await this._askOnce(prompt, {
|
|
1627
|
+
timeout,
|
|
1628
|
+
agent,
|
|
1629
|
+
attachments,
|
|
1630
|
+
});
|
|
1631
|
+
// The answer has settled (the finish loop returned it): mark the
|
|
1632
|
+
// lifecycle state so the status line shows "settled" instead of
|
|
1633
|
+
// still looking like generation.
|
|
1634
|
+
if (answer !== '(прервано пользователем)') {
|
|
1635
|
+
this._emitSendState('settled');
|
|
1636
|
+
}
|
|
1637
|
+
return answer;
|
|
1610
1638
|
}
|
|
1611
1639
|
catch (e) {
|
|
1612
1640
|
lastErr = e;
|
|
@@ -1892,6 +1920,7 @@ export class DeepSeekBrowser {
|
|
|
1892
1920
|
catch { }
|
|
1893
1921
|
}
|
|
1894
1922
|
};
|
|
1923
|
+
this._emitSendState('paused');
|
|
1895
1924
|
tick(totalSec * 1000);
|
|
1896
1925
|
return await this._sleepInterruptible(totalSec * 1000, tick);
|
|
1897
1926
|
}
|
|
@@ -1937,6 +1966,7 @@ export class DeepSeekBrowser {
|
|
|
1937
1966
|
}
|
|
1938
1967
|
};
|
|
1939
1968
|
report(gap);
|
|
1969
|
+
this._emitSendState('paused');
|
|
1940
1970
|
await this._sleepInterruptible(gap, report);
|
|
1941
1971
|
}
|
|
1942
1972
|
// Sleep in small slices so Esc/Ctrl+C can cancel the wait promptly.
|
|
@@ -2022,6 +2052,7 @@ export class DeepSeekBrowser {
|
|
|
2022
2052
|
}
|
|
2023
2053
|
catch { }
|
|
2024
2054
|
}
|
|
2055
|
+
this._emitSendState('generating');
|
|
2025
2056
|
this._netCapture = '';
|
|
2026
2057
|
this._netCaptureAt = 0;
|
|
2027
2058
|
this._netNoAnswer = false;
|
package/dist/commands.js
CHANGED
|
@@ -35,12 +35,15 @@ export function summarizeTranscript(entries) {
|
|
|
35
35
|
toolCounts: {},
|
|
36
36
|
durationMs: 0,
|
|
37
37
|
startedAt: null,
|
|
38
|
+
autoCompacts: 0,
|
|
38
39
|
};
|
|
39
40
|
for (const e of entries) {
|
|
40
41
|
if (!e || typeof e !== 'object')
|
|
41
42
|
continue;
|
|
42
43
|
if (e.type === 'user_task')
|
|
43
44
|
stats.turns++;
|
|
45
|
+
if (e.type === 'auto_compact_done')
|
|
46
|
+
stats.autoCompacts++;
|
|
44
47
|
if (e.type === 'tool_call') {
|
|
45
48
|
stats.toolCalls++;
|
|
46
49
|
const tool = String(e.tool ?? '?');
|
|
@@ -71,17 +74,20 @@ export function parseTranscript(body) {
|
|
|
71
74
|
}
|
|
72
75
|
return out;
|
|
73
76
|
}
|
|
74
|
-
export
|
|
77
|
+
export const EN_DURATION_UNITS = { h: 'h', m: 'm', s: 's' };
|
|
78
|
+
// Human-readable duration: "8s", "5m 12s", "1h 01m 01s". Seconds are padded
|
|
79
|
+
// only when a larger unit is present, so a lone "45s" is not "45s" padded.
|
|
80
|
+
export function formatDuration(ms, units = EN_DURATION_UNITS) {
|
|
75
81
|
const total = Math.max(0, Math.round(ms / 1000));
|
|
76
82
|
const h = Math.floor(total / 3600);
|
|
77
83
|
const m = Math.floor((total % 3600) / 60);
|
|
78
84
|
const s = total % 60;
|
|
79
85
|
const pad = (n) => String(n).padStart(2, '0');
|
|
80
86
|
if (h > 0)
|
|
81
|
-
return h +
|
|
87
|
+
return h + units.h + ' ' + pad(m) + units.m + ' ' + pad(s) + units.s;
|
|
82
88
|
if (m > 0)
|
|
83
|
-
return m +
|
|
84
|
-
return s +
|
|
89
|
+
return m + units.m + ' ' + pad(s) + units.s;
|
|
90
|
+
return s + units.s;
|
|
85
91
|
}
|
|
86
92
|
export function renderCost(stats, transcriptFile, tokenUsage = null) {
|
|
87
93
|
const lines = [];
|
|
@@ -94,6 +100,9 @@ export function renderCost(stats, transcriptFile, tokenUsage = null) {
|
|
|
94
100
|
}
|
|
95
101
|
lines.push(' tasks: ' + stats.turns);
|
|
96
102
|
lines.push(' tool calls: ' + stats.toolCalls);
|
|
103
|
+
if (stats.autoCompacts > 0) {
|
|
104
|
+
lines.push(' auto-compacts: ' + stats.autoCompacts);
|
|
105
|
+
}
|
|
97
106
|
const top = Object.entries(stats.toolCounts).sort((a, b) => b[1] - a[1]);
|
|
98
107
|
if (top.length) {
|
|
99
108
|
lines.push(' by tool:');
|
|
@@ -116,6 +125,15 @@ export function formatExport(entries, meta = {}) {
|
|
|
116
125
|
if (meta.chatId)
|
|
117
126
|
out.push('- chat: ' + meta.chatId);
|
|
118
127
|
out.push('- exported: ' + new Date().toISOString());
|
|
128
|
+
// A self-contained summary, so the exported Markdown stands on its own as a
|
|
129
|
+
// report without opening the transcript or a /cost run.
|
|
130
|
+
const stats = summarizeTranscript(entries);
|
|
131
|
+
out.push('- tasks: ' + stats.turns);
|
|
132
|
+
out.push('- tool calls: ' + stats.toolCalls);
|
|
133
|
+
out.push('- duration: ' + formatDuration(stats.durationMs));
|
|
134
|
+
if (stats.autoCompacts > 0) {
|
|
135
|
+
out.push('- auto-compacts: ' + stats.autoCompacts);
|
|
136
|
+
}
|
|
119
137
|
out.push('');
|
|
120
138
|
for (const e of entries) {
|
|
121
139
|
const type = String(e.type ?? '?');
|
|
@@ -178,6 +196,9 @@ export function renderDoctor(d) {
|
|
|
178
196
|
if (typeof d.minSendIntervalMs === 'number') {
|
|
179
197
|
row(d.minSendIntervalMs > 0, 'send pause', Math.round(d.minSendIntervalMs / 1000) + 's between agent sends');
|
|
180
198
|
}
|
|
199
|
+
if (typeof d.sshRemote === 'boolean') {
|
|
200
|
+
row(true, 'session', d.sshRemote ? 'SSH remote' : 'local terminal');
|
|
201
|
+
}
|
|
181
202
|
return 'Doctor:' + NL + rows.join(NL);
|
|
182
203
|
}
|
|
183
204
|
export function renderPermissions(p) {
|
|
@@ -430,6 +451,95 @@ export function isSlashCommand(text) {
|
|
|
430
451
|
.trim()
|
|
431
452
|
.startsWith('/');
|
|
432
453
|
}
|
|
454
|
+
export function parseLiveToggle(text) {
|
|
455
|
+
const m = /^\/(thinking|web|websearch|search)(?:\s+(\S+))?\s*$/i.exec(String(text ?? '').trim());
|
|
456
|
+
if (!m)
|
|
457
|
+
return null;
|
|
458
|
+
const name = m[1].toLowerCase();
|
|
459
|
+
const kind = name === 'thinking' ? 'thinking' : 'search';
|
|
460
|
+
const arg = (m[2] || '').toLowerCase();
|
|
461
|
+
let mode = 'toggle';
|
|
462
|
+
if (arg === 'on' || arg === '1' || arg === 'true' || arg === 'вкл')
|
|
463
|
+
mode = 'on';
|
|
464
|
+
else if (arg === 'off' || arg === '0' || arg === 'false' || arg === 'выкл')
|
|
465
|
+
mode = 'off';
|
|
466
|
+
else if (arg !== '')
|
|
467
|
+
return null;
|
|
468
|
+
return { kind, mode };
|
|
469
|
+
}
|
|
470
|
+
// Prepend a long-lived session goal to a task message, so the model keeps the
|
|
471
|
+
// big picture even when the goal was set many turns ago. The task is appended
|
|
472
|
+
// AFTER the goal; when there is no goal the task is returned unchanged. The
|
|
473
|
+
// text is agent-facing (English), like the system prompt.
|
|
474
|
+
export function withGoal(goal, task) {
|
|
475
|
+
const g = String(goal ?? '').trim();
|
|
476
|
+
if (!g)
|
|
477
|
+
return task;
|
|
478
|
+
const NL = String.fromCharCode(10);
|
|
479
|
+
return ('Session goal (long-lived — keep it in mind for this and every following task):' +
|
|
480
|
+
NL +
|
|
481
|
+
g +
|
|
482
|
+
NL +
|
|
483
|
+
NL +
|
|
484
|
+
'Current task:' +
|
|
485
|
+
NL +
|
|
486
|
+
task);
|
|
487
|
+
}
|
|
488
|
+
// Parse a `/goal` invocation. Subcommands: 'set' (with text), 'show', 'clear'.
|
|
489
|
+
// Returns null when the text is not a /goal command. Kept pure so the main loop
|
|
490
|
+
// and any future interception share one parser.
|
|
491
|
+
export function parseGoalCommand(text) {
|
|
492
|
+
const t = String(text ?? '').trim();
|
|
493
|
+
const m = /^\/goal(?:\s+([\s\S]*))?$/.exec(t);
|
|
494
|
+
if (!m)
|
|
495
|
+
return null;
|
|
496
|
+
const rest = (m[1] ?? '').trim();
|
|
497
|
+
if (rest === '')
|
|
498
|
+
return { sub: 'show' };
|
|
499
|
+
if (rest.toLowerCase() === 'clear' || rest.toLowerCase() === 'off')
|
|
500
|
+
return { sub: 'clear' };
|
|
501
|
+
return { sub: 'set', goal: rest };
|
|
502
|
+
}
|
|
503
|
+
// True when a `/config ...` line can be handled LIVE (while the agent is busy):
|
|
504
|
+
// the text subcommands only touch config values, never the chat. The bare
|
|
505
|
+
// `/config` and `/config menu` (which pause the editor and read keys) are NOT
|
|
506
|
+
// live — they must go through the main loop, so a mid-run menu never fights
|
|
507
|
+
// the running task. Pure, so the interception is unit-tested.
|
|
508
|
+
export function isLiveConfigCommand(text) {
|
|
509
|
+
return /^\/config\s+(?!menu\b|ui\b)\S+/i.test(String(text ?? '').trim());
|
|
510
|
+
}
|
|
511
|
+
// Parse a `/queue` invocation. Returns the subcommand ('list' | 'clear') or
|
|
512
|
+
// null when the text is not a /queue command at all. This runs WHILE the agent
|
|
513
|
+
// is busy (intercepted in the input handler), because the main command loop is
|
|
514
|
+
// blocked on runTask() and a queued /queue would only run after the task —
|
|
515
|
+
// when inspecting/clearing the queue is pointless.
|
|
516
|
+
export function parseQueueCommand(text) {
|
|
517
|
+
const t = String(text ?? '').trim();
|
|
518
|
+
const m = /^\/queue(?:\s+(\S+))?\s*$/.exec(t);
|
|
519
|
+
if (!m)
|
|
520
|
+
return null;
|
|
521
|
+
const arg = (m[1] || '').toLowerCase();
|
|
522
|
+
if (arg === '' || arg === 'list' || arg === 'ls')
|
|
523
|
+
return { sub: 'list' };
|
|
524
|
+
if (arg === 'clear' || arg === 'c' || arg === 'clean')
|
|
525
|
+
return { sub: 'clear' };
|
|
526
|
+
// An unrecognized subcommand falls through to the normal main-loop handler,
|
|
527
|
+
// which prints the usage hint.
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
// Render the queued messages for the operator (one line per message, text
|
|
531
|
+
// truncated). Pure, so the /queue interception and the main-loop handler share
|
|
532
|
+
// the EXACT same output instead of duplicating the formatting.
|
|
533
|
+
export function formatQueueList(msgs) {
|
|
534
|
+
return (msgs || []).map((m, i) => {
|
|
535
|
+
const one = String(m.text || '')
|
|
536
|
+
.replace(/\s+/g, ' ')
|
|
537
|
+
.trim();
|
|
538
|
+
const shown = one.length > 80 ? one.slice(0, 80) + ' …' : one;
|
|
539
|
+
const att = m.attachments?.length ? ' [+' + m.attachments.length + ']' : '';
|
|
540
|
+
return ' ' + (i + 1) + '. ' + shown + att;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
433
543
|
function queuedIsImage(a) {
|
|
434
544
|
return (String(a.mime || '').startsWith('image/') ||
|
|
435
545
|
isImageName(String(a.name || '')));
|
package/dist/i18n.js
CHANGED
|
@@ -205,6 +205,78 @@ const CATALOG = {
|
|
|
205
205
|
ru: '/queue [clear] показать/очистить очередь сообщений',
|
|
206
206
|
en: '/queue [clear] list/clear the pending message queue',
|
|
207
207
|
},
|
|
208
|
+
'help.cmd.thinking': {
|
|
209
|
+
ru: '/thinking [on|off] включить/выключить режим размышления (можно во время работы)',
|
|
210
|
+
en: '/thinking [on|off] toggle Deep thinking (works while the agent is busy)',
|
|
211
|
+
},
|
|
212
|
+
'help.cmd.web': {
|
|
213
|
+
ru: '/web [on|off] включить/выключить поиск в интернете (можно во время работы)',
|
|
214
|
+
en: '/web [on|off] toggle Smart search (works while the agent is busy)',
|
|
215
|
+
},
|
|
216
|
+
'help.cmd.goal': {
|
|
217
|
+
ru: '/goal [текст|clear] задать долгоживущую цель сессии',
|
|
218
|
+
en: '/goal [text|clear] set a long-lived session goal',
|
|
219
|
+
},
|
|
220
|
+
'help.cmd.loop': {
|
|
221
|
+
ru: '/loop <интервал> <задача> периодически повторять задачу (напр. 10m)',
|
|
222
|
+
en: '/loop <interval> <task> repeat a task periodically (e.g. 10m)',
|
|
223
|
+
},
|
|
224
|
+
'help.cmd.cron': {
|
|
225
|
+
ru: '/cron <выражение> <задача> запускать по расписанию (5 полей)',
|
|
226
|
+
en: '/cron <expr> <task> schedule a task (5-field cron)',
|
|
227
|
+
},
|
|
228
|
+
'help.cmd.jobs': {
|
|
229
|
+
ru: '/jobs [rm <id>|clear] список/удаление запланированных задач',
|
|
230
|
+
en: '/jobs [rm <id>|clear] list/remove scheduled tasks',
|
|
231
|
+
},
|
|
232
|
+
'sched.loop_usage': {
|
|
233
|
+
ru: 'Использование: /loop <интервал> <задача>, например /loop 10m проверь тесты',
|
|
234
|
+
en: 'Usage: /loop <interval> <task>, e.g. /loop 10m check the tests',
|
|
235
|
+
},
|
|
236
|
+
'sched.cron_usage': {
|
|
237
|
+
ru: 'Использование: /cron "<мин> <час> <день> <мес> <день недели>" <задача>',
|
|
238
|
+
en: 'Usage: /cron "<min> <hour> <dom> <month> <dow>" <task>',
|
|
239
|
+
},
|
|
240
|
+
'sched.added': {
|
|
241
|
+
ru: '⏰ Задача #{id} добавлена ({when}): {task}',
|
|
242
|
+
en: '⏰ Job #{id} added ({when}): {task}',
|
|
243
|
+
},
|
|
244
|
+
'sched.title': { ru: 'Запланированные задачи:', en: 'Scheduled jobs:' },
|
|
245
|
+
'sched.none': { ru: 'Запланированных задач нет.', en: 'No scheduled jobs.' },
|
|
246
|
+
'sched.fired': {
|
|
247
|
+
ru: '⏰ Задача #{id} сработала: {task}',
|
|
248
|
+
en: '⏰ Job #{id} fired: {task}',
|
|
249
|
+
},
|
|
250
|
+
'sched.cleared': { ru: 'Задачи очищены: {n}', en: 'Jobs cleared: {n}' },
|
|
251
|
+
'sched.removed': { ru: 'Задача #{id} удалена.', en: 'Job #{id} removed.' },
|
|
252
|
+
'sched.rm_usage': {
|
|
253
|
+
ru: 'Использование: /jobs rm <id>',
|
|
254
|
+
en: 'Usage: /jobs rm <id>',
|
|
255
|
+
},
|
|
256
|
+
'sched.remove_hint': {
|
|
257
|
+
ru: 'Удалить: /jobs rm <id> · очистить всё: /jobs clear',
|
|
258
|
+
en: 'Remove: /jobs rm <id> · clear all: /jobs clear',
|
|
259
|
+
},
|
|
260
|
+
'goal.set': { ru: '🎯 Цель сессии: {goal}', en: '🎯 Session goal: {goal}' },
|
|
261
|
+
'goal.loaded': {
|
|
262
|
+
ru: '🎯 Восстановлена цель сессии: {goal}',
|
|
263
|
+
en: '🎯 Restored session goal: {goal}',
|
|
264
|
+
},
|
|
265
|
+
'goal.current': {
|
|
266
|
+
ru: '🎯 Цель сессии: {goal}',
|
|
267
|
+
en: '🎯 Session goal: {goal}',
|
|
268
|
+
},
|
|
269
|
+
'goal.none': {
|
|
270
|
+
ru: 'Цель сессии не задана. /goal <текст>, /goal clear.',
|
|
271
|
+
en: 'No session goal. /goal <text>, /goal clear.',
|
|
272
|
+
},
|
|
273
|
+
'goal.cleared': {
|
|
274
|
+
ru: '🎯 Цель сессии очищена.',
|
|
275
|
+
en: '🎯 Session goal cleared.',
|
|
276
|
+
},
|
|
277
|
+
'toggle.thinking': { ru: 'Размышление', en: 'Deep thinking' },
|
|
278
|
+
'toggle.search': { ru: 'Поиск в интернете', en: 'Smart search' },
|
|
279
|
+
'toggle.set': { ru: '{name}: {state}', en: '{name}: {state}' },
|
|
208
280
|
'compact.auto_trigger': {
|
|
209
281
|
ru: '🗜️ Контекст заполнен на {pct}% ({tokens} токенов) — сжимаю историю и открываю новый чат...',
|
|
210
282
|
en: '🗜️ Context at {pct}% ({tokens} tokens) — compacting the history and opening a fresh chat...',
|
|
@@ -374,6 +446,10 @@ const CATALOG = {
|
|
|
374
446
|
ru: '⚙ выполняю {name}',
|
|
375
447
|
en: '⚙ running {name}',
|
|
376
448
|
},
|
|
449
|
+
// Send/generation lifecycle indicator in the status line. Unlike the
|
|
450
|
+
// animated thinking phrase, it says explicitly that a generation is in
|
|
451
|
+
// flight. 'paused'/'settled' have no label (see LineEditor._stateLabel).
|
|
452
|
+
'state.generating': { ru: '▶ генерация', en: '▶ generating' },
|
|
377
453
|
'editor.more': { ru: '…ещё {n}', en: '…{n} more' },
|
|
378
454
|
'editor.answer': { ru: '● Ответ', en: '● Answer' },
|
|
379
455
|
// ---------- status ----------
|
|
@@ -462,6 +538,13 @@ const CATALOG = {
|
|
|
462
538
|
en: '⏹ Ctrl+C — aborting generation...',
|
|
463
539
|
},
|
|
464
540
|
'msg.queued': { ru: '📨 В очередь ({n}): ', en: '📨 Queued ({n}): ' },
|
|
541
|
+
// Shown ONCE per session, the first time a message is queued while the
|
|
542
|
+
// agent is working. It explains what happens (sent after the current task)
|
|
543
|
+
// and how to inspect/clear the queue, so the operator knows nothing is lost.
|
|
544
|
+
'msg.queued_hint': {
|
|
545
|
+
ru: 'Сообщение в очереди — уйдёт после текущей задачи; /queue покажет и очистит.',
|
|
546
|
+
en: 'Message queued — it will be sent after the current task; /queue lists or clears it.',
|
|
547
|
+
},
|
|
465
548
|
'msg.attached_image': {
|
|
466
549
|
ru: '🖼 Вложено изображение {marker} ({size}) — {path}',
|
|
467
550
|
en: '🖼 Attached image {marker} ({size}) — {path}',
|
|
@@ -482,10 +565,15 @@ const CATALOG = {
|
|
|
482
565
|
ru: ' Похоже, вы в контейнере/Dev Container: клипборд Windows отсюда недоступен. Вставьте путь к файлу (например tmp/pic.png) — он приложится к сообщению.',
|
|
483
566
|
en: ' Looks like a container/Dev Container: the Windows clipboard is not reachable from here. Paste a path to a file (e.g. tmp/pic.png) — it will be attached to the message.',
|
|
484
567
|
},
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
},
|
|
568
|
+
// Per-task summary parts (joined with "·" by index.ts). Split into separate
|
|
569
|
+
// keys so the tokens part can be omitted when the counter is unknown.
|
|
570
|
+
'task_sum.dur': { ru: 'длительность: {dur}', en: 'duration: {dur}' },
|
|
571
|
+
'task_sum.tools': { ru: 'инструментов: {n}', en: 'tools: {n}' },
|
|
572
|
+
'task_sum.tokens': { ru: 'токенов: {n}', en: 'tokens: {n}' },
|
|
573
|
+
// Unit labels for the localized task duration (see formatDuration).
|
|
574
|
+
'dur.h': { ru: 'ч', en: 'h' },
|
|
575
|
+
'dur.m': { ru: 'м', en: 'm' },
|
|
576
|
+
'dur.s': { ru: 'с', en: 's' },
|
|
489
577
|
'msg.from_queue': { ru: '▶ Из очереди: ', en: '▶ From queue: ' },
|
|
490
578
|
'msg.from_queue_batch': {
|
|
491
579
|
ru: '▶ Из очереди ({n} сообщ.): ',
|
|
@@ -498,7 +586,6 @@ const CATALOG = {
|
|
|
498
586
|
'msg.queue_title': { ru: 'Очередь сообщений:', en: 'Pending messages:' },
|
|
499
587
|
'msg.queue_empty': { ru: 'Очередь пуста.', en: 'The queue is empty.' },
|
|
500
588
|
'msg.queue_cleared': { ru: 'Очередь очищена.', en: 'Queue cleared.' },
|
|
501
|
-
'msg.queue_item': { ru: '{n}. {text}', en: '{n}. {text}' },
|
|
502
589
|
'msg.queue_usage': {
|
|
503
590
|
ru: 'Использование: /queue [clear]',
|
|
504
591
|
en: 'Usage: /queue [clear]',
|
|
@@ -876,6 +963,10 @@ const CATALOG = {
|
|
|
876
963
|
},
|
|
877
964
|
'mcp.status_error': { ru: '(ошибка: {v})', en: '(error: {v})' },
|
|
878
965
|
'status.mcp': { ru: 'MCP-инструменты: {v}', en: 'MCP tools: {v}' },
|
|
966
|
+
'status.toggles': {
|
|
967
|
+
ru: 'Размышление: {think} · Поиск: {search}',
|
|
968
|
+
en: 'Deep thinking: {think} · Search: {search}',
|
|
969
|
+
},
|
|
879
970
|
'status.tokens': {
|
|
880
971
|
ru: 'Контекст (токенов): {v}',
|
|
881
972
|
en: 'Context (tokens): {v}',
|