zames_pro 1.0.2 → 1.1.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 +26 -7
- package/package.json +16 -4
- package/src/agent-loop.js +5 -1
- package/src/browser.js +150 -13
- package/src/confirm.js +10 -10
- package/src/diff.js +21 -113
- package/src/index.js +352 -114
- package/src/markdown.js +49 -0
- package/src/self-review.js +47 -32
- package/src/spinner.js +15 -22
- package/src/theme.js +34 -0
package/src/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import fs from 'fs/promises'
|
|
4
|
-
import chalk from 'chalk'
|
|
5
4
|
import { fileURLToPath } from 'url'
|
|
6
5
|
import * as readlinePromises from 'readline/promises'
|
|
6
|
+
import { theme } from './theme.js'
|
|
7
7
|
|
|
8
8
|
import { DeepSeekBrowser } from './browser.js'
|
|
9
9
|
import { createTools } from './tools.js'
|
|
@@ -56,25 +56,152 @@ const maxIter =
|
|
|
56
56
|
const positional = getPositional()
|
|
57
57
|
const task = getArg('--task', positional.join(' ').trim() || null)
|
|
58
58
|
const chatIdArg = getArg('--chat', null)
|
|
59
|
+
// При возобновлении существующего чата system-prompt по умолчанию НЕ
|
|
60
|
+
// переотправляется (он уже есть в начале чата). Флаг --resend-prompt
|
|
61
|
+
// заставляет дослать его заново — например, если промпт обновился.
|
|
62
|
+
const resendPrompt = hasFlag('--resend-prompt')
|
|
63
|
+
// --new-chat: намеренно начать с чистого чата, игнорируя сохранённый
|
|
64
|
+
// --resume-last: при старте вернуться в последний сохранённый чат.
|
|
65
|
+
// По умолчанию НЕ восстанавливаем — открывается новый чат.
|
|
66
|
+
// (Флаг --new-chat сохранён для совместимости и ничего не меняет.)
|
|
67
|
+
const resumeLastFlag = hasFlag('--resume-last')
|
|
68
|
+
|
|
69
|
+
// ---------- last chat persistence ----------
|
|
70
|
+
// Запоминаем последний открытый chat id, чтобы после перезапуска процесса
|
|
71
|
+
// (в т.ч. автоперезапуска через `node --watch`) автоматически вернуться
|
|
72
|
+
// в ту же сессию, а не создавать новый чат.
|
|
73
|
+
const LAST_CHAT_FILE = path.join(ZAMES_HOME, 'last-chat.json')
|
|
74
|
+
|
|
75
|
+
async function saveLastChat(id) {
|
|
76
|
+
if (!id) return
|
|
77
|
+
try {
|
|
78
|
+
await fs.mkdir(ZAMES_HOME, { recursive: true })
|
|
79
|
+
await fs.writeFile(
|
|
80
|
+
LAST_CHAT_FILE,
|
|
81
|
+
JSON.stringify({ id, updatedAt: new Date().toISOString() }, null, 2),
|
|
82
|
+
'utf-8',
|
|
83
|
+
)
|
|
84
|
+
} catch (e) {
|
|
85
|
+
if (debug) console.error('last-chat: не удалось сохранить:', e.message)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function loadLastChat() {
|
|
90
|
+
try {
|
|
91
|
+
const raw = await fs.readFile(LAST_CHAT_FILE, 'utf-8')
|
|
92
|
+
const data = JSON.parse(raw)
|
|
93
|
+
return data && typeof data.id === 'string' ? data.id : null
|
|
94
|
+
} catch {
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
// ---------- hot reload ----------
|
|
101
|
+
// Держим ссылки на модули логики в объекте mod. Команда /reload перечитывает
|
|
102
|
+
// их через динамический import с timestamp-query — Node кэширует ESM по URL,
|
|
103
|
+
// поэтому такой import вернёт свежую версию модуля. Браузер, чат и текущее
|
|
104
|
+
// состояние НЕ перезапускаются: обновляется только логика.
|
|
105
|
+
const RELOADABLE = [
|
|
106
|
+
'./tools.js',
|
|
107
|
+
'./agent-loop.js',
|
|
108
|
+
'./system-prompt.js',
|
|
109
|
+
'./gitTools.js',
|
|
110
|
+
'./web.js',
|
|
111
|
+
'./self-review.js',
|
|
112
|
+
'./diff.js',
|
|
113
|
+
'./undo.js',
|
|
114
|
+
'./confirm.js',
|
|
115
|
+
'./transcript.js',
|
|
116
|
+
'./spinner.js',
|
|
117
|
+
'./config.js',
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
const mod = {
|
|
121
|
+
createTools,
|
|
122
|
+
runAgentLoop,
|
|
123
|
+
buildSystemPrompt: null, // подгрузим ниже
|
|
124
|
+
selfReview,
|
|
125
|
+
selfDiff,
|
|
126
|
+
selfApply,
|
|
127
|
+
selfList,
|
|
128
|
+
closeWeb,
|
|
129
|
+
createSpinner,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function reloadModules() {
|
|
133
|
+
const stamp = Date.now()
|
|
134
|
+
const loaded = new Map()
|
|
135
|
+
const errors = []
|
|
136
|
+
for (const rel of RELOADABLE) {
|
|
137
|
+
try {
|
|
138
|
+
const url = new URL(rel, import.meta.url)
|
|
139
|
+
url.searchParams.set('t', String(stamp))
|
|
140
|
+
const m = await import(url.href)
|
|
141
|
+
loaded.set(rel, m)
|
|
142
|
+
} catch (e) {
|
|
143
|
+
errors.push(`${rel}: ${e.message}`)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const pick = (rel, name) => loaded.get(rel)?.[name]
|
|
148
|
+
|
|
149
|
+
if (pick('./tools.js', 'createTools')) mod.createTools = pick('./tools.js', 'createTools')
|
|
150
|
+
if (pick('./agent-loop.js', 'runAgentLoop')) mod.runAgentLoop = pick('./agent-loop.js', 'runAgentLoop')
|
|
151
|
+
if (pick('./system-prompt.js', 'buildSystemPrompt')) mod.buildSystemPrompt = pick('./system-prompt.js', 'buildSystemPrompt')
|
|
152
|
+
if (pick('./self-review.js', 'selfReview')) mod.selfReview = pick('./self-review.js', 'selfReview')
|
|
153
|
+
if (pick('./self-review.js', 'selfDiff')) mod.selfDiff = pick('./self-review.js', 'selfDiff')
|
|
154
|
+
if (pick('./self-review.js', 'selfApply')) mod.selfApply = pick('./self-review.js', 'selfApply')
|
|
155
|
+
if (pick('./self-review.js', 'selfList')) mod.selfList = pick('./self-review.js', 'selfList')
|
|
156
|
+
if (pick('./web.js', 'closeWeb')) mod.closeWeb = pick('./web.js', 'closeWeb')
|
|
157
|
+
if (pick('./spinner.js', 'createSpinner')) mod.createSpinner = pick('./spinner.js', 'createSpinner')
|
|
158
|
+
|
|
159
|
+
return { count: loaded.size, errors }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Первичная загрузка, чтобы mod.buildSystemPrompt и остальные были заполнены.
|
|
163
|
+
await reloadModules()
|
|
164
|
+
|
|
165
|
+
// Dev-режим: авто-перечитывание модулей логики перед каждой задачей.
|
|
166
|
+
// Включается флагом --dev (его ставит `npm run dev`) или config.hotReload === true.
|
|
167
|
+
// В обычном режиме (npm start, глобальный zames) авто-reload выключен.
|
|
168
|
+
const devMode = hasFlag('--dev') || config.hotReload === true
|
|
169
|
+
|
|
170
|
+
// Безопасный авто-reload: при ошибке загрузки оставляем прошлые рабочие модули.
|
|
171
|
+
async function autoReload() {
|
|
172
|
+
if (!devMode) return
|
|
173
|
+
const { errors } = await reloadModules()
|
|
174
|
+
if (errors.length) {
|
|
175
|
+
console.error(
|
|
176
|
+
theme.warn('⚠ авто-reload: часть модулей не загрузилась, работаю на прежней версии:'),
|
|
177
|
+
)
|
|
178
|
+
for (const e of errors) console.error(theme.warn(' ' + e))
|
|
179
|
+
}
|
|
180
|
+
}
|
|
59
181
|
|
|
60
182
|
|
|
61
183
|
// ---------- helpers ----------
|
|
62
184
|
|
|
63
185
|
function printHelp() {
|
|
64
186
|
console.log(`
|
|
65
|
-
${
|
|
187
|
+
${theme.bold('zames')} — агент поверх chat.deepseek.com через Playwright
|
|
66
188
|
|
|
67
|
-
${
|
|
189
|
+
${theme.bold('Опции CLI:')}
|
|
68
190
|
--dir <path> рабочая директория агента
|
|
69
191
|
--task <text> задача одной строкой
|
|
70
192
|
--chat <id> продолжить существующий чат по id
|
|
193
|
+
--resume-last вернуться в последний сохранённый чат
|
|
194
|
+
--new-chat начать новый чат (по умолчанию и так новый)
|
|
195
|
+
--resend-prompt дослать system-prompt в существующий чат
|
|
71
196
|
--max-iter <n> лимит итераций (по умолчанию ${config.maxIterations})
|
|
72
197
|
--headless браузер без UI
|
|
73
198
|
--debug подробный лог
|
|
74
199
|
--calibrate режим калибровки селекторов
|
|
200
|
+
--dev режим разработки: авто-перечитывание модулей
|
|
201
|
+
--version, -v показать версию
|
|
75
202
|
--help, -h эта справка
|
|
76
203
|
|
|
77
|
-
${
|
|
204
|
+
${theme.bold('Обычные команды:')}
|
|
78
205
|
/new, /clear новый чат (сброс контекста)
|
|
79
206
|
/chats список последних чатов DeepSeek
|
|
80
207
|
/resume <n> открыть чат №n из /chats
|
|
@@ -82,6 +209,7 @@ ${chalk.bold('Обычные команды:')}
|
|
|
82
209
|
/cd <path> сменить рабочую директорию
|
|
83
210
|
/pwd текущая директория
|
|
84
211
|
/status состояние сессии
|
|
212
|
+
/reload перечитать модули логики без перезапуска
|
|
85
213
|
/undo откатить последнюю запись/правку
|
|
86
214
|
/undo-list список того, что можно откатить
|
|
87
215
|
/transcript путь к файлу транскрипта
|
|
@@ -90,7 +218,7 @@ ${chalk.bold('Обычные команды:')}
|
|
|
90
218
|
/help, help справка
|
|
91
219
|
/exit, /quit, exit выход
|
|
92
220
|
|
|
93
|
-
${
|
|
221
|
+
${theme.bold('Самообзор (отладка агента):')}
|
|
94
222
|
/self-review [фокус] снять снапшот src/ и запустить ревью
|
|
95
223
|
после этой команды ты остаёшься В СНАПШОТЕ
|
|
96
224
|
и можешь писать «исправь ошибки» и т.п.
|
|
@@ -100,11 +228,12 @@ ${chalk.bold('Самообзор (отладка агента):')}
|
|
|
100
228
|
/self-diff <name> различия между текущим src/ и снапшотом
|
|
101
229
|
/self-apply <name> применить снапшот к src/ (с бэкапом)
|
|
102
230
|
|
|
103
|
-
${
|
|
231
|
+
${theme.bold('Файлы:')}
|
|
104
232
|
Логи: ${config.transcript.dir}
|
|
105
233
|
Undo: ~/.zames/undo
|
|
106
234
|
Профиль: ~/.zames/profile
|
|
107
235
|
Снапшоты: ~/.zames/snapshots
|
|
236
|
+
Временные: <проект>/tmp (в .gitignore, чистится при запуске)
|
|
108
237
|
Конфиг: ${CONFIG_PATHS.HOME_CONFIG}
|
|
109
238
|
${CONFIG_PATHS.PROJECT_CONFIG}
|
|
110
239
|
`)
|
|
@@ -114,6 +243,19 @@ function dirLabel(p) {
|
|
|
114
243
|
return path.basename(p) || p
|
|
115
244
|
}
|
|
116
245
|
|
|
246
|
+
// Временные файлы агента (одноразовые скрипты и т.п.) складываем в
|
|
247
|
+
// <проект>/tmp — эта папка в .gitignore и очищается при каждом запуске.
|
|
248
|
+
const TMP_DIR = path.join(__dirname, '..', 'tmp')
|
|
249
|
+
|
|
250
|
+
async function cleanTmpDir() {
|
|
251
|
+
try {
|
|
252
|
+
await fs.rm(TMP_DIR, { recursive: true, force: true })
|
|
253
|
+
await fs.mkdir(TMP_DIR, { recursive: true })
|
|
254
|
+
} catch (e) {
|
|
255
|
+
if (debug) console.error('tmp: не удалось очистить:', e.message)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
117
259
|
async function promptOnce(question) {
|
|
118
260
|
process.stdin.resume()
|
|
119
261
|
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
@@ -130,6 +272,26 @@ async function promptOnce(question) {
|
|
|
130
272
|
}
|
|
131
273
|
}
|
|
132
274
|
|
|
275
|
+
// Слежение за клавиатурой во время работы агента.
|
|
276
|
+
// Esc — прервать текущую генерацию (клик Stop в браузере).
|
|
277
|
+
function watchEscape(onEscape) {
|
|
278
|
+
const stdin = process.stdin
|
|
279
|
+
if (!stdin.isTTY) return () => {}
|
|
280
|
+
const wasRaw = stdin.isRaw
|
|
281
|
+
if (stdin.setRawMode) stdin.setRawMode(true)
|
|
282
|
+
stdin.resume()
|
|
283
|
+
const handler = (buf) => {
|
|
284
|
+
if (buf.length === 1 && buf[0] === 27) {
|
|
285
|
+
onEscape()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
stdin.on('data', handler)
|
|
289
|
+
return () => {
|
|
290
|
+
stdin.removeListener('data', handler)
|
|
291
|
+
if (stdin.setRawMode) stdin.setRawMode(wasRaw || false)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
133
295
|
// ---------- workdir resolution ----------
|
|
134
296
|
|
|
135
297
|
// ---------- workdir resolution ----------
|
|
@@ -151,14 +313,20 @@ async function resolveWorkdir() {
|
|
|
151
313
|
async function runTask(browser, tools, taskText, workdir, opts) {
|
|
152
314
|
const { transcript, freshChat, sendSystemPrompt } = opts
|
|
153
315
|
|
|
154
|
-
const ui = createSpinner()
|
|
155
|
-
ui.taskHeader(taskText)
|
|
316
|
+
const ui = mod.createSpinner()
|
|
156
317
|
ui.thinking()
|
|
157
318
|
|
|
319
|
+
// Esc во время работы — прервать генерацию.
|
|
320
|
+
const stopWatching = watchEscape(() => {
|
|
321
|
+
ui.stop()
|
|
322
|
+
console.error(theme.warn('⏹ Esc — прерываю генерацию...'))
|
|
323
|
+
browser.stopGeneration().catch(() => {})
|
|
324
|
+
})
|
|
325
|
+
|
|
158
326
|
let finished = false
|
|
159
327
|
|
|
160
328
|
try {
|
|
161
|
-
await runAgentLoop({
|
|
329
|
+
await mod.runAgentLoop({
|
|
162
330
|
browser,
|
|
163
331
|
tools,
|
|
164
332
|
task: taskText,
|
|
@@ -174,13 +342,15 @@ async function runTask(browser, tools, taskText, workdir, opts) {
|
|
|
174
342
|
ui.assistant(msg)
|
|
175
343
|
finished = true
|
|
176
344
|
},
|
|
345
|
+
debugLog: debug,
|
|
177
346
|
})
|
|
178
347
|
} catch (e) {
|
|
179
348
|
ui.stop()
|
|
180
|
-
console.error(
|
|
349
|
+
console.error(theme.error('\n✖ Ошибка агента:'), e.message)
|
|
181
350
|
if (debug) console.error(e.stack)
|
|
182
351
|
transcript?.log('agent_error', { error: e.message })
|
|
183
352
|
} finally {
|
|
353
|
+
stopWatching()
|
|
184
354
|
if (!finished) ui.stop()
|
|
185
355
|
}
|
|
186
356
|
}
|
|
@@ -188,15 +358,25 @@ async function runTask(browser, tools, taskText, workdir, opts) {
|
|
|
188
358
|
// ---------- main ----------
|
|
189
359
|
|
|
190
360
|
async function main() {
|
|
361
|
+
if (hasFlag('--version') || hasFlag('-v')) {
|
|
362
|
+
const pkg = JSON.parse(
|
|
363
|
+
await fs.readFile(path.join(__dirname, '..', 'package.json'), 'utf-8'),
|
|
364
|
+
)
|
|
365
|
+
console.log(pkg.version)
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
|
|
191
369
|
if (hasFlag('--help') || hasFlag('-h')) {
|
|
192
370
|
printHelp()
|
|
193
371
|
return
|
|
194
372
|
}
|
|
195
373
|
|
|
196
374
|
if (calibrate) {
|
|
197
|
-
console.log(
|
|
375
|
+
console.log(theme.warn('\n🔧 Режим калибровки селекторов\n'))
|
|
198
376
|
}
|
|
199
377
|
|
|
378
|
+
await cleanTmpDir()
|
|
379
|
+
|
|
200
380
|
let currentWorkdir
|
|
201
381
|
try {
|
|
202
382
|
currentWorkdir = await resolveWorkdir()
|
|
@@ -204,13 +384,13 @@ async function main() {
|
|
|
204
384
|
const sandboxRoot = currentWorkdir
|
|
205
385
|
} catch (e) {
|
|
206
386
|
console.error(
|
|
207
|
-
|
|
387
|
+
theme.error('Не удалось определить рабочую директорию:'),
|
|
208
388
|
e.message,
|
|
209
389
|
)
|
|
210
390
|
process.exit(1)
|
|
211
391
|
}
|
|
212
392
|
|
|
213
|
-
console.log(
|
|
393
|
+
console.log(theme.system(`Рабочая директория: ${currentWorkdir}`))
|
|
214
394
|
|
|
215
395
|
const transcript = new Transcript({
|
|
216
396
|
dir: config.transcript.dir,
|
|
@@ -218,7 +398,7 @@ async function main() {
|
|
|
218
398
|
sessionName: dirLabel(currentWorkdir),
|
|
219
399
|
})
|
|
220
400
|
if (transcript.file) {
|
|
221
|
-
console.log(
|
|
401
|
+
console.log(theme.system(`Транскрипт: ${transcript.file}`))
|
|
222
402
|
}
|
|
223
403
|
|
|
224
404
|
const undo = new UndoStore(config.undo)
|
|
@@ -230,7 +410,7 @@ async function main() {
|
|
|
230
410
|
...config.browser,
|
|
231
411
|
})
|
|
232
412
|
|
|
233
|
-
const bootSpinner = createSpinner()
|
|
413
|
+
const bootSpinner = mod.createSpinner()
|
|
234
414
|
bootSpinner.thinking()
|
|
235
415
|
|
|
236
416
|
try {
|
|
@@ -239,7 +419,7 @@ async function main() {
|
|
|
239
419
|
await browser.waitForLogin()
|
|
240
420
|
} catch (e) {
|
|
241
421
|
bootSpinner.stop()
|
|
242
|
-
console.error(
|
|
422
|
+
console.error(theme.error('Не удалось запустить браузер:'), e.message)
|
|
243
423
|
if (debug) console.error(e.stack)
|
|
244
424
|
await browser.close().catch(() => {})
|
|
245
425
|
transcript.close()
|
|
@@ -248,35 +428,46 @@ async function main() {
|
|
|
248
428
|
|
|
249
429
|
// Разовый режим
|
|
250
430
|
if (task) {
|
|
251
|
-
const tools = createTools(currentWorkdir, { undo })
|
|
431
|
+
const tools = mod.createTools(currentWorkdir, { undo })
|
|
252
432
|
|
|
253
433
|
let freshChat = true
|
|
254
434
|
let sendSystemPrompt = true
|
|
255
435
|
|
|
256
|
-
|
|
436
|
+
// По умолчанию — новый чат. Продолжить прошлый можно явно:
|
|
437
|
+
// --chat <id> открыть конкретный чат
|
|
438
|
+
// --resume-last вернуться в последний сохранённый чат
|
|
439
|
+
let resumeId = chatIdArg
|
|
440
|
+
if (!resumeId && resumeLastFlag) {
|
|
441
|
+
resumeId = await loadLastChat()
|
|
442
|
+
if (resumeId) console.log(theme.system(`Восстанавливаю чат ${resumeId}...`))
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (resumeId) {
|
|
257
446
|
try {
|
|
258
|
-
console.log(
|
|
259
|
-
await browser.openChat(
|
|
447
|
+
console.log(theme.system(`Открываю чат ${resumeId}...`))
|
|
448
|
+
await browser.openChat(resumeId)
|
|
260
449
|
freshChat = false
|
|
261
|
-
sendSystemPrompt =
|
|
450
|
+
sendSystemPrompt = resendPrompt
|
|
262
451
|
} catch (e) {
|
|
263
|
-
console.error(
|
|
452
|
+
console.error(theme.error(`Не удалось открыть чат: ${e.message}`))
|
|
264
453
|
}
|
|
265
454
|
}
|
|
266
455
|
|
|
456
|
+
await autoReload()
|
|
457
|
+
|
|
267
458
|
await runTask(browser, tools, task, currentWorkdir, {
|
|
268
459
|
transcript,
|
|
269
460
|
freshChat,
|
|
270
461
|
sendSystemPrompt,
|
|
271
462
|
})
|
|
272
463
|
await browser.close()
|
|
273
|
-
await closeWeb().catch(() => {})
|
|
464
|
+
await mod.closeWeb().catch(() => {})
|
|
274
465
|
transcript.close()
|
|
275
466
|
return
|
|
276
467
|
}
|
|
277
468
|
|
|
278
469
|
console.log(
|
|
279
|
-
|
|
470
|
+
theme.system(
|
|
280
471
|
'Интерактивный режим. Введите задачу. Команды — /help. Выход — /exit.\n',
|
|
281
472
|
),
|
|
282
473
|
)
|
|
@@ -298,37 +489,47 @@ async function main() {
|
|
|
298
489
|
await browser.close().catch(() => {})
|
|
299
490
|
// Закрываем ленивый headless-браузер из web.js, иначе он останется
|
|
300
491
|
// висеть отдельным процессом после выхода агента.
|
|
301
|
-
await closeWeb().catch(() => {})
|
|
492
|
+
await mod.closeWeb().catch(() => {})
|
|
302
493
|
transcript.close()
|
|
303
|
-
console.log(
|
|
494
|
+
console.log(theme.system('\nВыход.'))
|
|
304
495
|
process.exit(0)
|
|
305
496
|
})
|
|
306
497
|
|
|
307
|
-
|
|
498
|
+
// По умолчанию — новый чат. Продолжить прошлый можно явно:
|
|
499
|
+
// --chat <id> открыть конкретный чат
|
|
500
|
+
// --resume-last вернуться в последний сохранённый чат
|
|
501
|
+
let resumeId = chatIdArg
|
|
502
|
+
if (!resumeId && resumeLastFlag) {
|
|
503
|
+
resumeId = await loadLastChat()
|
|
504
|
+
if (resumeId) console.log(theme.system(`Восстанавливаю чат ${resumeId}...`))
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (resumeId) {
|
|
308
508
|
try {
|
|
309
|
-
console.log(
|
|
310
|
-
await browser.openChat(
|
|
311
|
-
currentChatId =
|
|
509
|
+
console.log(theme.system(`Открываю чат ${resumeId}...`))
|
|
510
|
+
await browser.openChat(resumeId)
|
|
511
|
+
currentChatId = resumeId
|
|
312
512
|
freshChatNext = false
|
|
313
|
-
sendSystemPromptNext =
|
|
314
|
-
|
|
513
|
+
sendSystemPromptNext = resendPrompt
|
|
514
|
+
await saveLastChat(resumeId)
|
|
515
|
+
console.log(theme.system(`Чат открыт: ${resumeId}\n`))
|
|
315
516
|
} catch (e) {
|
|
316
|
-
console.error(
|
|
517
|
+
console.error(theme.error(`Не удалось открыть чат: ${e.message}`))
|
|
317
518
|
}
|
|
318
519
|
}
|
|
319
520
|
|
|
320
521
|
while (running) {
|
|
321
522
|
let input
|
|
322
523
|
try {
|
|
323
|
-
|
|
524
|
+
// Компактное приглашение: золотая стрелка + светло-голубая директория.
|
|
525
|
+
let tail
|
|
324
526
|
if (reviewMode) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
label = `zames[${dirLabel(currentWorkdir)}|${currentChatId.slice(0, 6)}]> `
|
|
527
|
+
tail =
|
|
528
|
+
theme.warn('REVIEW') + theme.dim(':') + theme.dir(reviewMode.snapName)
|
|
328
529
|
} else {
|
|
329
|
-
|
|
530
|
+
tail = theme.dir(dirLabel(currentWorkdir))
|
|
330
531
|
}
|
|
331
|
-
input = await promptOnce(
|
|
532
|
+
input = await promptOnce(theme.prompt('❯ ') + tail + theme.dim(' › '))
|
|
332
533
|
} catch {
|
|
333
534
|
break
|
|
334
535
|
}
|
|
@@ -346,16 +547,17 @@ async function main() {
|
|
|
346
547
|
}
|
|
347
548
|
|
|
348
549
|
if (['/new', '/clear', 'new'].includes(lower)) {
|
|
349
|
-
console.log(
|
|
550
|
+
console.log(theme.system('Создаю новый чат...'))
|
|
350
551
|
try {
|
|
351
552
|
await browser.newChat()
|
|
352
553
|
freshChatNext = false
|
|
353
554
|
sendSystemPromptNext = true
|
|
354
555
|
currentChatId = await browser.getCurrentChatId()
|
|
556
|
+
await saveLastChat(currentChatId)
|
|
355
557
|
transcript.log('new_chat')
|
|
356
|
-
console.log(
|
|
558
|
+
console.log(theme.system('Новый чат.\n'))
|
|
357
559
|
} catch (e) {
|
|
358
|
-
console.error(
|
|
560
|
+
console.error(theme.error('Не удалось создать новый чат:'), e.message)
|
|
359
561
|
}
|
|
360
562
|
continue
|
|
361
563
|
}
|
|
@@ -371,7 +573,7 @@ async function main() {
|
|
|
371
573
|
: currentWorkdir
|
|
372
574
|
|
|
373
575
|
try {
|
|
374
|
-
const result = await selfReview({
|
|
576
|
+
const result = await mod.selfReview({
|
|
375
577
|
browser,
|
|
376
578
|
config,
|
|
377
579
|
focus: focus || null,
|
|
@@ -391,9 +593,10 @@ async function main() {
|
|
|
391
593
|
freshChatNext = false
|
|
392
594
|
sendSystemPromptNext = false
|
|
393
595
|
currentChatId = await browser.getCurrentChatId()
|
|
596
|
+
await saveLastChat(currentChatId)
|
|
394
597
|
|
|
395
598
|
console.log(
|
|
396
|
-
|
|
599
|
+
theme.user(
|
|
397
600
|
'\n💡 Теперь ты в режиме ревью. Просто пиши агенту, например:\n' +
|
|
398
601
|
' «исправь ошибки»\n' +
|
|
399
602
|
' «доработай обработку ошибок в ask()»\n' +
|
|
@@ -404,7 +607,7 @@ async function main() {
|
|
|
404
607
|
),
|
|
405
608
|
)
|
|
406
609
|
} catch (e) {
|
|
407
|
-
console.error(
|
|
610
|
+
console.error(theme.error('Самообзор провалился:'), e.message)
|
|
408
611
|
if (debug) console.error(e.stack)
|
|
409
612
|
}
|
|
410
613
|
continue
|
|
@@ -413,7 +616,7 @@ async function main() {
|
|
|
413
616
|
if (lower === '/self-fix' || lower.startsWith('/self-fix ')) {
|
|
414
617
|
const rest = trimmed.slice('/self-fix'.length).trim()
|
|
415
618
|
if (!rest) {
|
|
416
|
-
console.error(
|
|
619
|
+
console.error(theme.error('Использование: /self-fix <name> [фокус]'))
|
|
417
620
|
continue
|
|
418
621
|
}
|
|
419
622
|
const sp = rest.indexOf(' ')
|
|
@@ -423,7 +626,7 @@ async function main() {
|
|
|
423
626
|
const snapRoot = path.join(ZAMES_HOME, 'snapshots', name)
|
|
424
627
|
const stat = await fs.stat(snapRoot).catch(() => null)
|
|
425
628
|
if (!stat || !stat.isDirectory()) {
|
|
426
|
-
console.error(
|
|
629
|
+
console.error(theme.error(`Снапшот не найден: ${snapRoot}`))
|
|
427
630
|
continue
|
|
428
631
|
}
|
|
429
632
|
|
|
@@ -435,18 +638,18 @@ async function main() {
|
|
|
435
638
|
try {
|
|
436
639
|
const { runAgentLoop: ral } = await import('./agent-loop.js')
|
|
437
640
|
const { buildSystemPrompt } = await import('./system-prompt.js')
|
|
438
|
-
const tools = createTools(snapRoot, { undo: null })
|
|
641
|
+
const tools = mod.createTools(snapRoot, { undo: null })
|
|
439
642
|
|
|
440
643
|
await browser.newChat()
|
|
441
|
-
const sysPrompt = buildSystemPrompt({
|
|
644
|
+
const sysPrompt = mod.buildSystemPrompt({
|
|
442
645
|
workdir: snapRoot,
|
|
443
646
|
tools,
|
|
444
647
|
})
|
|
445
|
-
console.log(
|
|
648
|
+
console.log(theme.system('Инициализирую review-чат для снапшота...'))
|
|
446
649
|
await browser.ask(sysPrompt, { timeout: 60_000 })
|
|
447
650
|
|
|
448
651
|
if (focus) {
|
|
449
|
-
const ui = createSpinner()
|
|
652
|
+
const ui = mod.createSpinner()
|
|
450
653
|
ui.thinking()
|
|
451
654
|
await ral({
|
|
452
655
|
browser,
|
|
@@ -460,7 +663,7 @@ async function main() {
|
|
|
460
663
|
onThinking: () => ui.thinking(),
|
|
461
664
|
onAssistantThought: (text) => {
|
|
462
665
|
ui.stop()
|
|
463
|
-
console.log(
|
|
666
|
+
console.log(theme.system('\n💭 ' + text.slice(0, 1200) + '\n'))
|
|
464
667
|
},
|
|
465
668
|
onToolCall: (name, toolArgs) => ui.toolCall(name, toolArgs),
|
|
466
669
|
onToolResult: (r) => ui.toolResult(r),
|
|
@@ -479,21 +682,22 @@ async function main() {
|
|
|
479
682
|
freshChatNext = false
|
|
480
683
|
sendSystemPromptNext = false
|
|
481
684
|
currentChatId = await browser.getCurrentChatId()
|
|
685
|
+
await saveLastChat(currentChatId)
|
|
482
686
|
|
|
483
687
|
console.log(
|
|
484
|
-
|
|
688
|
+
theme.user(
|
|
485
689
|
`\n💡 Режим ревью по снапшоту ${name}. Пиши агенту задачу или /self-done.\n`,
|
|
486
690
|
),
|
|
487
691
|
)
|
|
488
692
|
} catch (e) {
|
|
489
|
-
console.error(
|
|
693
|
+
console.error(theme.error('Не удалось войти в снапшот:'), e.message)
|
|
490
694
|
}
|
|
491
695
|
continue
|
|
492
696
|
}
|
|
493
697
|
|
|
494
698
|
if (lower === '/self-done') {
|
|
495
699
|
if (!reviewMode) {
|
|
496
|
-
console.log(
|
|
700
|
+
console.log(theme.system('Ты и так не в режиме ревью.'))
|
|
497
701
|
continue
|
|
498
702
|
}
|
|
499
703
|
const back = reviewMode.originalWorkdir
|
|
@@ -503,16 +707,16 @@ async function main() {
|
|
|
503
707
|
freshChatNext = true
|
|
504
708
|
sendSystemPromptNext = true
|
|
505
709
|
console.log(
|
|
506
|
-
|
|
710
|
+
theme.system(`Вернулся в ${back}. Следующая задача начнёт новый чат.\n`),
|
|
507
711
|
)
|
|
508
712
|
continue
|
|
509
713
|
}
|
|
510
714
|
|
|
511
715
|
if (lower === '/self-list') {
|
|
512
716
|
try {
|
|
513
|
-
await selfList({ config })
|
|
717
|
+
await mod.selfList({ config })
|
|
514
718
|
} catch (e) {
|
|
515
|
-
console.error(
|
|
719
|
+
console.error(theme.error('Ошибка:'), e.message)
|
|
516
720
|
}
|
|
517
721
|
continue
|
|
518
722
|
}
|
|
@@ -520,13 +724,13 @@ async function main() {
|
|
|
520
724
|
if (lower === '/self-diff' || lower.startsWith('/self-diff ')) {
|
|
521
725
|
const name = trimmed.slice('/self-diff'.length).trim()
|
|
522
726
|
if (!name) {
|
|
523
|
-
console.error(
|
|
727
|
+
console.error(theme.error('Использование: /self-diff <name>'))
|
|
524
728
|
continue
|
|
525
729
|
}
|
|
526
730
|
try {
|
|
527
|
-
await selfDiff({ config, name })
|
|
731
|
+
await mod.selfDiff({ config, name })
|
|
528
732
|
} catch (e) {
|
|
529
|
-
console.error(
|
|
733
|
+
console.error(theme.error('Ошибка:'), e.message)
|
|
530
734
|
}
|
|
531
735
|
continue
|
|
532
736
|
}
|
|
@@ -534,13 +738,13 @@ async function main() {
|
|
|
534
738
|
if (lower === '/self-apply' || lower.startsWith('/self-apply ')) {
|
|
535
739
|
const name = trimmed.slice('/self-apply'.length).trim()
|
|
536
740
|
if (!name) {
|
|
537
|
-
console.error(
|
|
741
|
+
console.error(theme.error('Использование: /self-apply <name>'))
|
|
538
742
|
continue
|
|
539
743
|
}
|
|
540
744
|
try {
|
|
541
|
-
await selfApply({ config, name })
|
|
745
|
+
await mod.selfApply({ config, name })
|
|
542
746
|
} catch (e) {
|
|
543
|
-
console.error(
|
|
747
|
+
console.error(theme.error('Ошибка:'), e.message)
|
|
544
748
|
}
|
|
545
749
|
continue
|
|
546
750
|
}
|
|
@@ -548,30 +752,30 @@ async function main() {
|
|
|
548
752
|
// ---------- Обычные команды ----------
|
|
549
753
|
|
|
550
754
|
if (lower === '/chats') {
|
|
551
|
-
const spin = createSpinner()
|
|
755
|
+
const spin = mod.createSpinner()
|
|
552
756
|
spin.thinking()
|
|
553
757
|
try {
|
|
554
758
|
lastChats = await browser.listChats(30)
|
|
555
759
|
spin.stop()
|
|
556
760
|
if (!lastChats.length) {
|
|
557
761
|
console.log(
|
|
558
|
-
|
|
762
|
+
theme.system(
|
|
559
763
|
'Чатов не найдено. Возможно, сайдбар свёрнут или селекторы устарели.',
|
|
560
764
|
),
|
|
561
765
|
)
|
|
562
766
|
} else {
|
|
563
|
-
console.log(
|
|
767
|
+
console.log(theme.system('Последние чаты DeepSeek:'))
|
|
564
768
|
lastChats.forEach((c, i) => {
|
|
565
769
|
const n = String(i + 1).padStart(2, ' ')
|
|
566
770
|
console.log(
|
|
567
|
-
` ${
|
|
771
|
+
` ${theme.user(n)}. ${c.title} ${theme.system('(' + c.id.slice(0, 8) + '…)')}`,
|
|
568
772
|
)
|
|
569
773
|
})
|
|
570
|
-
console.log(
|
|
774
|
+
console.log(theme.system('\nИспользуй /resume <n> для продолжения.\n'))
|
|
571
775
|
}
|
|
572
776
|
} catch (e) {
|
|
573
777
|
spin.stop()
|
|
574
|
-
console.error(
|
|
778
|
+
console.error(theme.error('Не удалось получить список:'), e.message)
|
|
575
779
|
}
|
|
576
780
|
continue
|
|
577
781
|
}
|
|
@@ -580,92 +784,122 @@ async function main() {
|
|
|
580
784
|
const arg = trimmed.slice(7).trim()
|
|
581
785
|
if (!arg) {
|
|
582
786
|
console.error(
|
|
583
|
-
|
|
787
|
+
theme.error('Использование: /resume <n> (или /chats для списка)'),
|
|
584
788
|
)
|
|
585
789
|
continue
|
|
586
790
|
}
|
|
587
791
|
const n = Number(arg)
|
|
588
792
|
if (!Number.isFinite(n) || n < 1) {
|
|
589
|
-
console.error(
|
|
793
|
+
console.error(theme.error('Нужен номер из /chats.'))
|
|
590
794
|
continue
|
|
591
795
|
}
|
|
592
796
|
if (!lastChats.length) {
|
|
593
|
-
console.error(
|
|
797
|
+
console.error(theme.error('Сначала выполни /chats.'))
|
|
594
798
|
continue
|
|
595
799
|
}
|
|
596
800
|
const pick = lastChats[n - 1]
|
|
597
801
|
if (!pick) {
|
|
598
|
-
console.error(
|
|
802
|
+
console.error(theme.error(`Нет чата №${n}. Всего: ${lastChats.length}.`))
|
|
599
803
|
continue
|
|
600
804
|
}
|
|
601
805
|
|
|
602
|
-
console.log(
|
|
806
|
+
console.log(theme.system(`Открываю: ${pick.title}`))
|
|
603
807
|
try {
|
|
604
808
|
await browser.openChat(pick.id)
|
|
605
809
|
currentChatId = pick.id
|
|
606
810
|
freshChatNext = false
|
|
607
|
-
sendSystemPromptNext =
|
|
811
|
+
sendSystemPromptNext = resendPrompt
|
|
812
|
+
await saveLastChat(pick.id)
|
|
608
813
|
transcript.log('resume_chat', { id: pick.id, title: pick.title })
|
|
609
814
|
console.log(
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
' Системный промпт будет переслан на следующей задаче.\n',
|
|
815
|
+
theme.assistant(`Чат открыт.`) +
|
|
816
|
+
theme.system(
|
|
817
|
+
resendPrompt ? ' Системный промпт будет переслан на следующей задаче.\n' : ' Контекст чата сохранён. Системный промпт не пересылается (--resend-prompt чтобы дослать).\n',
|
|
613
818
|
),
|
|
614
819
|
)
|
|
615
820
|
} catch (e) {
|
|
616
|
-
console.error(
|
|
821
|
+
console.error(theme.error('Не удалось открыть чат:'), e.message)
|
|
617
822
|
}
|
|
618
823
|
continue
|
|
619
824
|
}
|
|
620
825
|
|
|
621
826
|
if (lower === '/chat') {
|
|
622
827
|
if (currentChatId) {
|
|
623
|
-
console.log(
|
|
828
|
+
console.log(theme.system(`Текущий chat id: ${currentChatId}`))
|
|
624
829
|
console.log(
|
|
625
|
-
|
|
830
|
+
theme.system(
|
|
626
831
|
`URL: https://chat.deepseek.com/a/chat/s/${currentChatId}`,
|
|
627
832
|
),
|
|
628
833
|
)
|
|
629
834
|
} else {
|
|
630
835
|
const id = await browser.getCurrentChatId()
|
|
631
836
|
console.log(
|
|
632
|
-
|
|
837
|
+
theme.system(id ? `Текущий chat id: ${id}` : 'Чат ещё не создан.'),
|
|
633
838
|
)
|
|
634
839
|
}
|
|
635
840
|
continue
|
|
636
841
|
}
|
|
637
842
|
|
|
638
843
|
if (lower === '/pwd') {
|
|
639
|
-
console.log(
|
|
844
|
+
console.log(theme.system(currentWorkdir))
|
|
845
|
+
continue
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (lower === '/reload') {
|
|
849
|
+
console.log(theme.system('Перечитываю модули логики...'))
|
|
850
|
+
try {
|
|
851
|
+
const { count, errors } = await reloadModules()
|
|
852
|
+
if (errors.length) {
|
|
853
|
+
console.error(theme.error('Часть модулей не перезагрузилась:'))
|
|
854
|
+
for (const e of errors) console.error(theme.error(' ' + e))
|
|
855
|
+
} else {
|
|
856
|
+
console.log(
|
|
857
|
+
theme.assistant(
|
|
858
|
+
`Перезагружено модулей: ${count}. Браузер и чат не тронуты.`,
|
|
859
|
+
),
|
|
860
|
+
)
|
|
861
|
+
}
|
|
862
|
+
} catch (e) {
|
|
863
|
+
console.error(theme.error('Ошибка reload:'), e.message)
|
|
864
|
+
}
|
|
640
865
|
continue
|
|
641
866
|
}
|
|
642
867
|
|
|
643
868
|
if (lower === '/status') {
|
|
644
|
-
console.log(
|
|
869
|
+
console.log(theme.system(`Рабочая директория: ${currentWorkdir}`))
|
|
645
870
|
console.log(
|
|
646
|
-
|
|
871
|
+
theme.system(`Режим ревью: ${reviewMode ? reviewMode.snapName : 'нет'}`),
|
|
647
872
|
)
|
|
648
873
|
if (reviewMode) {
|
|
649
874
|
console.log(
|
|
650
|
-
|
|
875
|
+
theme.system(`Исходная директория: ${reviewMode.originalWorkdir}`),
|
|
651
876
|
)
|
|
652
877
|
}
|
|
653
|
-
console.log(
|
|
878
|
+
console.log(theme.system(`Текущий чат: ${currentChatId || '(нет)'}`))
|
|
654
879
|
console.log(
|
|
655
|
-
|
|
880
|
+
theme.system(
|
|
656
881
|
`Fresh chat на след. задаче: ${freshChatNext ? 'да' : 'нет'}`,
|
|
657
882
|
),
|
|
658
883
|
)
|
|
659
884
|
console.log(
|
|
660
|
-
|
|
885
|
+
theme.system(
|
|
661
886
|
`System prompt на след. задаче: ${sendSystemPromptNext ? 'да' : 'нет'}`,
|
|
662
887
|
),
|
|
663
888
|
)
|
|
664
|
-
console.log(
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
console.log(
|
|
668
|
-
|
|
889
|
+
console.log(
|
|
890
|
+
theme.system(`Resend prompt (--resend-prompt): ${resendPrompt ? 'да' : 'нет'}`),
|
|
891
|
+
)
|
|
892
|
+
console.log(
|
|
893
|
+
theme.system(`Last chat: ${(await loadLastChat()) || 'нет'}`),
|
|
894
|
+
)
|
|
895
|
+
console.log(
|
|
896
|
+
theme.system(`Dev mode (auto-reload): ${devMode ? 'вкл' : 'выкл'}`),
|
|
897
|
+
)
|
|
898
|
+
console.log(theme.system(`Лимит итераций: ${maxIter}`))
|
|
899
|
+
console.log(theme.system(`Headless: ${headless ? 'да' : 'нет'}`))
|
|
900
|
+
console.log(theme.system(`Debug: ${debug ? 'да' : 'нет'}`))
|
|
901
|
+
console.log(theme.system(`Undo: ${config.undo.enabled ? 'вкл' : 'выкл'}`))
|
|
902
|
+
console.log(theme.system(`Транскрипт: ${transcript.file || 'выкл'}`))
|
|
669
903
|
continue
|
|
670
904
|
}
|
|
671
905
|
|
|
@@ -675,7 +909,7 @@ async function main() {
|
|
|
675
909
|
}
|
|
676
910
|
|
|
677
911
|
if (lower === '/transcript') {
|
|
678
|
-
console.log(
|
|
912
|
+
console.log(theme.system(transcript.file || '(выключен)'))
|
|
679
913
|
continue
|
|
680
914
|
}
|
|
681
915
|
|
|
@@ -683,14 +917,14 @@ async function main() {
|
|
|
683
917
|
const result = await undo.undoLast()
|
|
684
918
|
if (result.ok) {
|
|
685
919
|
console.log(
|
|
686
|
-
|
|
687
|
-
|
|
920
|
+
theme.assistant(`↶ Откатили: ${result.record.originalPath}`) +
|
|
921
|
+
theme.system(
|
|
688
922
|
result.record.existed ? ' (восстановлено)' : ' (удалено)',
|
|
689
923
|
),
|
|
690
924
|
)
|
|
691
925
|
transcript.log('undo', { path: result.record.originalPath })
|
|
692
926
|
} else {
|
|
693
|
-
console.error(
|
|
927
|
+
console.error(theme.error(`Не удалось откатить: ${result.reason}`))
|
|
694
928
|
}
|
|
695
929
|
continue
|
|
696
930
|
}
|
|
@@ -698,12 +932,12 @@ async function main() {
|
|
|
698
932
|
if (lower === '/undo-list' || lower === '/history') {
|
|
699
933
|
const list = await undo.list(10)
|
|
700
934
|
if (!list.length) {
|
|
701
|
-
console.log(
|
|
935
|
+
console.log(theme.system('История пуста.'))
|
|
702
936
|
} else {
|
|
703
937
|
for (const r of list) {
|
|
704
938
|
const stamp = new Date(r.stamp).toLocaleString()
|
|
705
939
|
const flag = r.existed ? 'изменён' : 'создан'
|
|
706
|
-
console.log(
|
|
940
|
+
console.log(theme.system(`${stamp} [${flag}] ${r.originalPath}`))
|
|
707
941
|
}
|
|
708
942
|
}
|
|
709
943
|
continue
|
|
@@ -714,11 +948,11 @@ async function main() {
|
|
|
714
948
|
const file = path.join(config.transcript.dir, `dom-${stamp}.html`)
|
|
715
949
|
try {
|
|
716
950
|
const result = await browser.dumpDom(file)
|
|
717
|
-
console.log(
|
|
718
|
-
console.log(
|
|
951
|
+
console.log(theme.assistant(`HTML сохранён: ${result.file}`))
|
|
952
|
+
console.log(theme.system('Селекторы:'))
|
|
719
953
|
console.log(JSON.stringify(result.selectors, null, 2))
|
|
720
954
|
} catch (e) {
|
|
721
|
-
console.error(
|
|
955
|
+
console.error(theme.error('Не удалось сохранить DOM:'), e.message)
|
|
722
956
|
}
|
|
723
957
|
continue
|
|
724
958
|
}
|
|
@@ -731,42 +965,45 @@ async function main() {
|
|
|
731
965
|
: sandboxRoot
|
|
732
966
|
const rel = path.relative(sandboxRoot, newDir)
|
|
733
967
|
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
734
|
-
console.error(
|
|
968
|
+
console.error(theme.error('Нельзя выйти за пределы: ' + sandboxRoot))
|
|
735
969
|
continue
|
|
736
970
|
}
|
|
737
971
|
const stat = await fs.stat(newDir).catch(() => null)
|
|
738
972
|
if (!stat || !stat.isDirectory()) {
|
|
739
|
-
console.error(
|
|
973
|
+
console.error(theme.error('Не директория: ' + newDir))
|
|
740
974
|
continue
|
|
741
975
|
}
|
|
742
976
|
if (newDir === currentWorkdir) {
|
|
743
|
-
console.log(
|
|
977
|
+
console.log(theme.system('Уже здесь.'))
|
|
744
978
|
continue
|
|
745
979
|
}
|
|
746
980
|
if (reviewMode) {
|
|
747
|
-
console.log(
|
|
981
|
+
console.log(theme.system('Вышел из режима ревью (/cd).'))
|
|
748
982
|
reviewMode = null
|
|
749
983
|
}
|
|
750
984
|
currentWorkdir = newDir
|
|
751
985
|
freshChatNext = true
|
|
752
986
|
sendSystemPromptNext = true
|
|
753
|
-
console.log(
|
|
987
|
+
console.log(theme.system('Рабочая директория: ' + newDir))
|
|
754
988
|
} catch (e) {
|
|
755
|
-
console.error(
|
|
989
|
+
console.error(theme.error('Не удалось перейти: ' + e.message))
|
|
756
990
|
}
|
|
757
991
|
continue
|
|
758
992
|
}
|
|
759
993
|
|
|
760
994
|
if (lower.startsWith('/')) {
|
|
761
|
-
console.error(
|
|
995
|
+
console.error(theme.error(`Неизвестная команда: ${trimmed}. Набери /help.`))
|
|
762
996
|
continue
|
|
763
997
|
}
|
|
764
998
|
|
|
765
999
|
// ---- Обычная задача (в том числе в review-режиме) ----
|
|
766
1000
|
|
|
1001
|
+
// Dev-режим: подхватываем свежие модули логики перед задачей.
|
|
1002
|
+
await autoReload()
|
|
1003
|
+
|
|
767
1004
|
transcript.log('user_task', { task: trimmed, workdir: currentWorkdir })
|
|
768
1005
|
|
|
769
|
-
const tools = createTools(currentWorkdir, { undo })
|
|
1006
|
+
const tools = mod.createTools(currentWorkdir, { undo })
|
|
770
1007
|
await runTask(browser, tools, trimmed, currentWorkdir, {
|
|
771
1008
|
transcript,
|
|
772
1009
|
freshChat: freshChatNext,
|
|
@@ -778,15 +1015,16 @@ async function main() {
|
|
|
778
1015
|
if (!currentChatId) {
|
|
779
1016
|
currentChatId = await browser.getCurrentChatId()
|
|
780
1017
|
}
|
|
1018
|
+
await saveLastChat(currentChatId)
|
|
781
1019
|
}
|
|
782
1020
|
|
|
783
1021
|
await browser.close().catch(() => {})
|
|
784
|
-
await closeWeb().catch(() => {})
|
|
1022
|
+
await mod.closeWeb().catch(() => {})
|
|
785
1023
|
transcript.close()
|
|
786
1024
|
}
|
|
787
1025
|
|
|
788
1026
|
main().catch((e) => {
|
|
789
|
-
console.error(
|
|
1027
|
+
console.error(theme.error('Критическая ошибка:'), e.message)
|
|
790
1028
|
if (debug) console.error(e.stack)
|
|
791
1029
|
process.exit(1)
|
|
792
1030
|
})
|