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/markdown.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { render } from 'markdansi'
|
|
2
|
+
import { highlight } from 'cli-highlight'
|
|
3
|
+
|
|
4
|
+
// Рендер Markdown-ответов модели в терминал через markdansi.
|
|
5
|
+
// GFM (заголовки, списки, таблицы, цитаты, task lists) + подсветка кода.
|
|
6
|
+
// Тема собрана из нашей спокойной палитры, без кислотных цветов.
|
|
7
|
+
|
|
8
|
+
const theme = {
|
|
9
|
+
heading: { color: '#9fb8d8', bold: true },
|
|
10
|
+
strong: { color: '#e6e0cc', bold: true },
|
|
11
|
+
emph: { color: '#cfc9b0', italic: true },
|
|
12
|
+
inlineCode: { color: '#9ecb9e' },
|
|
13
|
+
blockCode: { color: '#c9d1c9' },
|
|
14
|
+
code: { color: '#9ecb9e' },
|
|
15
|
+
link: { color: '#8fb8d8', underline: true },
|
|
16
|
+
quote: { color: '#8a8f9a', italic: true },
|
|
17
|
+
hr: { color: '#5b616e' },
|
|
18
|
+
listMarker: { color: '#8a9bb5' },
|
|
19
|
+
tableHeader: { color: '#9fb8d8', bold: true },
|
|
20
|
+
tableCell: { color: '#d0d0d0' },
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Подсветка кода через cli-highlight (транзитивно уже есть у нас).
|
|
24
|
+
function highlighter(code, lang) {
|
|
25
|
+
try {
|
|
26
|
+
return highlight(code, { language: lang || 'plaintext', ignoreIllegals: true })
|
|
27
|
+
} catch {
|
|
28
|
+
return code
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function renderMarkdown(text) {
|
|
33
|
+
if (!text) return ''
|
|
34
|
+
try {
|
|
35
|
+
return render(String(text), {
|
|
36
|
+
width: Math.min(process.stdout.columns || 80, 100),
|
|
37
|
+
theme,
|
|
38
|
+
highlighter,
|
|
39
|
+
codeBox: true,
|
|
40
|
+
tableBorder: 'unicode',
|
|
41
|
+
listIndent: 2,
|
|
42
|
+
hyperlinks: false,
|
|
43
|
+
})
|
|
44
|
+
} catch {
|
|
45
|
+
return String(text)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default renderMarkdown
|
package/src/self-review.js
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import fs from 'fs/promises'
|
|
3
|
-
import
|
|
3
|
+
import { theme } from './theme.js'
|
|
4
4
|
import { fileURLToPath } from 'url'
|
|
5
5
|
|
|
6
|
-
import { createTools } from './tools.js'
|
|
7
|
-
import { runAgentLoop } from './agent-loop.js'
|
|
8
6
|
import { ZAMES_HOME } from './config.js'
|
|
9
7
|
|
|
10
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
11
9
|
const SRC_DIR = __dirname
|
|
12
10
|
const SNAP_ROOT = path.join(ZAMES_HOME, 'snapshots')
|
|
13
11
|
|
|
12
|
+
// Динамический импорт логики с timestamp — чтобы при /reload (или авто-reload)
|
|
13
|
+
// self-review использовал СВЕЖИЕ tools/agent-loop, а не закэшированные при
|
|
14
|
+
// первой загрузке. Иначе reload не доходил бы до зависимостей self-review.
|
|
15
|
+
async function loadFresh() {
|
|
16
|
+
const stamp = Date.now()
|
|
17
|
+
const toolsUrl = new URL('./tools.js', import.meta.url)
|
|
18
|
+
toolsUrl.searchParams.set('t', String(stamp))
|
|
19
|
+
const loopUrl = new URL('./agent-loop.js', import.meta.url)
|
|
20
|
+
loopUrl.searchParams.set('t', String(stamp))
|
|
21
|
+
const [{ createTools }, { runAgentLoop }] = await Promise.all([
|
|
22
|
+
import(toolsUrl.href),
|
|
23
|
+
import(loopUrl.href),
|
|
24
|
+
])
|
|
25
|
+
return { createTools, runAgentLoop }
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
async function ensureDir(p) {
|
|
15
29
|
await fs.mkdir(p, { recursive: true })
|
|
16
30
|
}
|
|
@@ -42,7 +56,7 @@ export async function selfReview({ browser, config, focus, transcript }) {
|
|
|
42
56
|
|
|
43
57
|
if (copied.length === 0) {
|
|
44
58
|
console.error(
|
|
45
|
-
|
|
59
|
+
theme.error(
|
|
46
60
|
`\n✖ Самообзор отменён: в ${SRC_DIR} нет .js файлов.\n` +
|
|
47
61
|
`Проверь, что src/ не пуст и ты запускаешь агента из корня проекта.\n`,
|
|
48
62
|
),
|
|
@@ -59,12 +73,13 @@ export async function selfReview({ browser, config, focus, transcript }) {
|
|
|
59
73
|
await fs.writeFile(path.join(snapDir, 'package.json'), pkg, 'utf-8')
|
|
60
74
|
} catch {}
|
|
61
75
|
|
|
62
|
-
console.log(
|
|
63
|
-
console.log(
|
|
64
|
-
console.log(
|
|
76
|
+
console.log(theme.system(`\n📸 Снапшот: ${snapDir}`))
|
|
77
|
+
console.log(theme.system(`Файлов: ${copied.length} — ${copied.join(', ')}`))
|
|
78
|
+
console.log(theme.system('Начинаю самообзор...\n'))
|
|
65
79
|
|
|
66
80
|
const taskPrompt = buildReviewPrompt({ focus, snapDir })
|
|
67
81
|
|
|
82
|
+
const { createTools, runAgentLoop } = await loadFresh()
|
|
68
83
|
const tools = createTools(snapDir, { undo: null })
|
|
69
84
|
let finalMessage = ''
|
|
70
85
|
|
|
@@ -80,15 +95,15 @@ export async function selfReview({ browser, config, focus, transcript }) {
|
|
|
80
95
|
onThinking: () => {},
|
|
81
96
|
onToolCall: (name, args) => {
|
|
82
97
|
const preview = JSON.stringify(args).slice(0, 120)
|
|
83
|
-
console.log(
|
|
98
|
+
console.log(theme.warn(`🔧 ${name}`), theme.system(preview))
|
|
84
99
|
},
|
|
85
100
|
onToolResult: (r) => {
|
|
86
101
|
const t = typeof r === 'string' ? r : JSON.stringify(r)
|
|
87
|
-
console.log(
|
|
102
|
+
console.log(theme.system(` → ${t.slice(0, 200).replace(/\n/g, ' ↵ ')}\n`))
|
|
88
103
|
},
|
|
89
104
|
onAssistantMessage: (msg) => {
|
|
90
105
|
finalMessage = msg
|
|
91
|
-
console.log(
|
|
106
|
+
console.log(theme.assistant('\n📋 Отчёт:\n'))
|
|
92
107
|
console.log(msg)
|
|
93
108
|
console.log()
|
|
94
109
|
},
|
|
@@ -101,26 +116,26 @@ export async function selfReview({ browser, config, focus, transcript }) {
|
|
|
101
116
|
// Считаем, что изменилось
|
|
102
117
|
const changed = await diffFiles(SRC_DIR, snapDir)
|
|
103
118
|
|
|
104
|
-
console.log(
|
|
105
|
-
console.log(
|
|
106
|
-
console.log(
|
|
119
|
+
console.log(theme.system('─'.repeat(60)))
|
|
120
|
+
console.log(theme.system(`Отчёт: ${reportPath}`))
|
|
121
|
+
console.log(theme.system(`Снапшот: ${snapDir}`))
|
|
107
122
|
if (changed.length) {
|
|
108
123
|
console.log(
|
|
109
|
-
|
|
124
|
+
theme.assistant(
|
|
110
125
|
`Изменено: ${changed.length} файл(ов): ${changed.join(', ')}`,
|
|
111
126
|
),
|
|
112
127
|
)
|
|
113
128
|
} else {
|
|
114
|
-
console.log(
|
|
129
|
+
console.log(theme.system('Изменено: (ничего — только отчёт)'))
|
|
115
130
|
}
|
|
116
|
-
console.log(
|
|
131
|
+
console.log(theme.user(`\nДальше:`))
|
|
117
132
|
console.log(
|
|
118
|
-
|
|
133
|
+
theme.user(
|
|
119
134
|
` /self-diff ${path.basename(snapDir)} — посмотреть различия`,
|
|
120
135
|
),
|
|
121
136
|
)
|
|
122
137
|
console.log(
|
|
123
|
-
|
|
138
|
+
theme.user(
|
|
124
139
|
` /self-apply ${path.basename(snapDir)} — применить к живому src/`,
|
|
125
140
|
),
|
|
126
141
|
)
|
|
@@ -151,13 +166,13 @@ export async function selfDiff({ config, name }) {
|
|
|
151
166
|
if (orig === next) continue
|
|
152
167
|
|
|
153
168
|
anyDiff = true
|
|
154
|
-
console.log(
|
|
169
|
+
console.log(theme.bold(`\n=== src/${f} ===`))
|
|
155
170
|
const diff = unifiedDiff(orig, next, { label: f })
|
|
156
|
-
console.log(colorDiff(diff
|
|
171
|
+
console.log(colorDiff(diff))
|
|
157
172
|
}
|
|
158
173
|
|
|
159
174
|
if (!anyDiff) {
|
|
160
|
-
console.log(
|
|
175
|
+
console.log(theme.system('Различий нет.'))
|
|
161
176
|
}
|
|
162
177
|
}
|
|
163
178
|
|
|
@@ -192,11 +207,11 @@ export async function selfApply({ config, name }) {
|
|
|
192
207
|
applied.push(f)
|
|
193
208
|
}
|
|
194
209
|
|
|
195
|
-
console.log(
|
|
196
|
-
console.log(
|
|
197
|
-
console.log(
|
|
210
|
+
console.log(theme.assistant(`\n✅ Применено: ${applied.length} файл(ов)`))
|
|
211
|
+
console.log(theme.system(applied.join(', ')))
|
|
212
|
+
console.log(theme.system(`Бэкап: ${backupDir}`))
|
|
198
213
|
console.log(
|
|
199
|
-
|
|
214
|
+
theme.warn(`\nПерезапусти агента, чтобы изменения вступили в силу.\n`),
|
|
200
215
|
)
|
|
201
216
|
}
|
|
202
217
|
|
|
@@ -208,17 +223,17 @@ export async function selfList({ config }) {
|
|
|
208
223
|
try {
|
|
209
224
|
entries = await fs.readdir(snapRoot)
|
|
210
225
|
} catch {
|
|
211
|
-
console.log(
|
|
226
|
+
console.log(theme.system('Снапшотов нет.'))
|
|
212
227
|
return
|
|
213
228
|
}
|
|
214
229
|
|
|
215
230
|
if (!entries.length) {
|
|
216
|
-
console.log(
|
|
231
|
+
console.log(theme.system('Снапшотов нет.'))
|
|
217
232
|
return
|
|
218
233
|
}
|
|
219
234
|
|
|
220
235
|
entries.sort()
|
|
221
|
-
console.log(
|
|
236
|
+
console.log(theme.system('Снапшоты самообзора:'))
|
|
222
237
|
for (const e of entries) {
|
|
223
238
|
const full = path.join(snapRoot, e)
|
|
224
239
|
const stat = await fs.stat(full).catch(() => null)
|
|
@@ -230,13 +245,13 @@ export async function selfList({ config }) {
|
|
|
230
245
|
.catch(() => false)
|
|
231
246
|
const changedCount = (await diffFiles(SRC_DIR, full)).length
|
|
232
247
|
const tag = changedCount
|
|
233
|
-
?
|
|
234
|
-
:
|
|
235
|
-
const rep = hasReport ?
|
|
248
|
+
? theme.assistant(`[${changedCount} изменено]`)
|
|
249
|
+
: theme.system('[без правок]')
|
|
250
|
+
const rep = hasReport ? theme.user('📋') : ' '
|
|
236
251
|
console.log(` ${rep} ${e} ${tag}`)
|
|
237
252
|
}
|
|
238
253
|
console.log()
|
|
239
|
-
console.log(
|
|
254
|
+
console.log(theme.system('Команды: /self-diff <name>, /self-apply <name>\n'))
|
|
240
255
|
}
|
|
241
256
|
|
|
242
257
|
// ---------- helpers ----------
|
package/src/spinner.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ora from 'ora'
|
|
2
|
-
import
|
|
2
|
+
import { theme } from './theme.js'
|
|
3
|
+
import { renderMarkdown } from './markdown.js'
|
|
3
4
|
|
|
4
5
|
export function createSpinner() {
|
|
5
6
|
let spinner = null
|
|
@@ -13,43 +14,35 @@ export function createSpinner() {
|
|
|
13
14
|
if (spinner) spinner.stop()
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const succeed = (text) => {
|
|
17
|
-
if (spinner) spinner.succeed(text)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const fail = (text) => {
|
|
21
|
-
if (spinner) spinner.fail(text)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
17
|
return {
|
|
25
|
-
|
|
26
|
-
thinking: () => start(chalk.gray('Думаю...')),
|
|
18
|
+
thinking: () => start(theme.system('Думаю...')),
|
|
27
19
|
|
|
28
|
-
// Оба метода только останавливают и печатают — не перезапускают
|
|
29
20
|
toolCall: (name, args) => {
|
|
30
21
|
stop()
|
|
31
22
|
const preview = JSON.stringify(args).slice(0, 120)
|
|
32
|
-
console.log(
|
|
23
|
+
console.log(theme.tool('🔧 ' + name), theme.dim(preview))
|
|
33
24
|
},
|
|
34
25
|
|
|
35
26
|
toolResult: (result) => {
|
|
36
27
|
stop()
|
|
37
28
|
const text = typeof result === 'string' ? result : JSON.stringify(result)
|
|
38
|
-
const preview = text.slice(0, 200).
|
|
39
|
-
console.log(
|
|
29
|
+
const preview = text.slice(0, 200).split(String.fromCharCode(10)).join(' ↵ ')
|
|
30
|
+
console.log(theme.toolResult(' → ' + preview + String.fromCharCode(10)))
|
|
40
31
|
},
|
|
41
32
|
|
|
42
33
|
assistant: (msg) => {
|
|
43
34
|
stop()
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
console.log(
|
|
35
|
+
const NL = String.fromCharCode(10)
|
|
36
|
+
const rendered = renderMarkdown(msg)
|
|
37
|
+
// Маркер ответа модели: помогает визуально отделить его от ввода
|
|
38
|
+
// пользователя (который подсвечен приглашением с золотой стрелкой).
|
|
39
|
+
console.log(NL + theme.assistant('● Ответ') + NL)
|
|
40
|
+
console.log(rendered)
|
|
41
|
+
console.log(theme.dim('─'.repeat(60)) + NL)
|
|
49
42
|
},
|
|
50
43
|
|
|
51
44
|
stop,
|
|
52
|
-
succeed,
|
|
53
|
-
fail,
|
|
45
|
+
succeed: () => {},
|
|
46
|
+
fail: () => {},
|
|
54
47
|
}
|
|
55
48
|
}
|
package/src/theme.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
// Спокойная палитра для тёмного терминала.
|
|
4
|
+
// Приглушённые, слегка «выцветшие» тона без высокой насыщенности
|
|
5
|
+
// (в духе Tokyo Night / Nord). Задача — комфортное чтение длинных
|
|
6
|
+
// ответов, а не максимальная контрастность.
|
|
7
|
+
//
|
|
8
|
+
// Роли:
|
|
9
|
+
// - user — мягкий лавандово-серый (служебные сообщения)
|
|
10
|
+
// - prompt — тёплое золотистое (приглашение ввода, заметное)
|
|
11
|
+
// - dir — светло-голубое (имя рабочей директории)
|
|
12
|
+
// - assistant — тёплый песочный (ответы модели)
|
|
13
|
+
// - tool — приглушённый янтарный
|
|
14
|
+
// - toolResult — серо-голубой
|
|
15
|
+
// - system — нейтральный серый
|
|
16
|
+
// - warn — спокойный охра
|
|
17
|
+
// - error — приглушённый терракот
|
|
18
|
+
|
|
19
|
+
export const theme = {
|
|
20
|
+
user: chalk.hex('#b4b8d0'), // мягкий лавандово-серый
|
|
21
|
+
prompt: chalk.hex('#c8b06a').bold, // приглашение ввода — тёплое золотистое, заметное
|
|
22
|
+
dir: chalk.hex('#7fc4f0'), // имя рабочей директории — заметно голубое
|
|
23
|
+
assistant: chalk.hex('#cfc9b0'), // тёплый песочный
|
|
24
|
+
tool: chalk.hex('#c6a97e'), // приглушённый янтарный
|
|
25
|
+
toolResult: chalk.hex('#8a9bb5'), // серо-голубой
|
|
26
|
+
system: chalk.hex('#808896'), // нейтральный серый
|
|
27
|
+
dim: chalk.hex('#5b616e'), // тёмно-серый
|
|
28
|
+
warn: chalk.hex('#c9a86a'), // спокойный охра
|
|
29
|
+
error: chalk.hex('#c98a80'), // приглушённый терракот
|
|
30
|
+
success: chalk.hex('#a9c08c'), // мягкий шалфейный
|
|
31
|
+
bold: chalk.bold,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default theme
|