zames_pro 1.2.0 → 2.0.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 +652 -0
- package/dist/agent-loop.js.map +1 -0
- package/dist/browser.js +573 -0
- package/dist/browser.js.map +1 -0
- package/dist/config.js +74 -0
- package/dist/config.js.map +1 -0
- package/dist/confirm.js +82 -0
- package/dist/confirm.js.map +1 -0
- package/dist/diff.js +38 -0
- package/dist/diff.js.map +1 -0
- package/dist/gitTools.js +219 -0
- package/dist/gitTools.js.map +1 -0
- package/dist/index.js +1365 -0
- package/dist/index.js.map +1 -0
- package/dist/input.js +524 -0
- package/dist/input.js.map +1 -0
- package/dist/markdown.js +48 -0
- package/dist/markdown.js.map +1 -0
- package/dist/self-review.js +281 -0
- package/dist/self-review.js.map +1 -0
- package/dist/sessions.js +123 -0
- package/dist/sessions.js.map +1 -0
- package/dist/spinner.js +138 -0
- package/dist/spinner.js.map +1 -0
- package/{src → dist}/system-prompt.js +10 -14
- package/dist/system-prompt.js.map +1 -0
- package/{src → dist}/theme.js +17 -19
- package/dist/theme.js.map +1 -0
- package/dist/tools.js +189 -0
- package/dist/tools.js.map +1 -0
- package/dist/transcript.js +51 -0
- package/dist/transcript.js.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/undo.js +100 -0
- package/dist/undo.js.map +1 -0
- package/dist/web.js +220 -0
- package/dist/web.js.map +1 -0
- package/dist/xml-toolcall.js +68 -0
- package/dist/xml-toolcall.js.map +1 -0
- package/package.json +13 -5
- package/src/agent-loop.js +0 -575
- package/src/browser.js +0 -627
- package/src/config.js +0 -81
- package/src/confirm.js +0 -85
- package/src/diff.js +0 -32
- package/src/gitTools.js +0 -256
- package/src/index.js +0 -1461
- package/src/input.js +0 -447
- package/src/markdown.js +0 -49
- package/src/self-review.js +0 -319
- package/src/spinner.js +0 -144
- package/src/tools.js +0 -206
- package/src/transcript.js +0 -46
- package/src/undo.js +0 -101
- package/src/web.js +0 -266
- package/src/xml-toolcall.js +0 -69
package/src/confirm.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import * as readlinePromises from 'readline/promises'
|
|
2
|
-
import { theme } from './theme.js'
|
|
3
|
-
import { unifiedDiff, colorDiff } from './diff.js'
|
|
4
|
-
|
|
5
|
-
export class ConfirmManager {
|
|
6
|
-
constructor({ config }) {
|
|
7
|
-
const c = config || {}
|
|
8
|
-
this.settings = {
|
|
9
|
-
write: c.write !== false,
|
|
10
|
-
edit: c.edit !== false,
|
|
11
|
-
bash: c.bash !== false,
|
|
12
|
-
}
|
|
13
|
-
this.alwaysConfirm = (c.alwaysConfirm || []).map((p) => new RegExp(p, 'i'))
|
|
14
|
-
this.allowedForSession = new Set()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
_matchesAlwaysConfirm(command) {
|
|
18
|
-
return this.alwaysConfirm.some((re) => re.test(command))
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
shouldAsk(kind, target = null) {
|
|
22
|
-
if (kind === 'bash' && target && this._matchesAlwaysConfirm(target)) {
|
|
23
|
-
return true
|
|
24
|
-
}
|
|
25
|
-
if (!this.settings[kind]) return false
|
|
26
|
-
if (this.allowedForSession.has(kind)) return false
|
|
27
|
-
return true
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async ask({ kind, target, preview = null, message }) {
|
|
31
|
-
if (!this.shouldAsk(kind, target)) return true
|
|
32
|
-
|
|
33
|
-
if (preview) {
|
|
34
|
-
console.log(preview)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const label = message || `Разрешить ${kind}?`
|
|
38
|
-
const question =
|
|
39
|
-
theme.warn(`${label} `) +
|
|
40
|
-
theme.system('[') +
|
|
41
|
-
theme.assistant('y') +
|
|
42
|
-
theme.system(' = да, ') +
|
|
43
|
-
theme.error('n') +
|
|
44
|
-
theme.system(' = нет, ') +
|
|
45
|
-
theme.user('a') +
|
|
46
|
-
theme.system(' = всегда для этого типа] ')
|
|
47
|
-
|
|
48
|
-
const answer = await this._prompt(question)
|
|
49
|
-
|
|
50
|
-
if (answer === 'a') {
|
|
51
|
-
if (kind !== 'bash' || !this._matchesAlwaysConfirm(target)) {
|
|
52
|
-
this.allowedForSession.add(kind)
|
|
53
|
-
}
|
|
54
|
-
return true
|
|
55
|
-
}
|
|
56
|
-
return answer === 'y'
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async _prompt(question) {
|
|
60
|
-
process.stdin.resume()
|
|
61
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
62
|
-
process.stdin.setRawMode(false)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const rl = readlinePromises.createInterface({
|
|
66
|
-
input: process.stdin,
|
|
67
|
-
output: process.stdout,
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
const ans = await rl.question(question)
|
|
72
|
-
const v = (ans || '').trim().toLowerCase()
|
|
73
|
-
if (v === 'a' || v === 'always' || v === 'в') return 'a'
|
|
74
|
-
if (v === 'y' || v === 'yes' || v === 'д' || v === 'да') return 'y'
|
|
75
|
-
return 'n'
|
|
76
|
-
} finally {
|
|
77
|
-
rl.close()
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function formatDiffPreview(oldText, newText, { label } = {}) {
|
|
83
|
-
const diff = unifiedDiff(oldText, newText, { label })
|
|
84
|
-
return colorDiff(diff)
|
|
85
|
-
}
|
package/src/diff.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { theme } from './theme.js'
|
|
2
|
-
|
|
3
|
-
export function unifiedDiff(a, b, { label = '' } = {}) {
|
|
4
|
-
const la = a.split(String.fromCharCode(10))
|
|
5
|
-
const lb = b.split(String.fromCharCode(10))
|
|
6
|
-
const n = Math.max(la.length, lb.length)
|
|
7
|
-
const out = []
|
|
8
|
-
if (label) out.push('... ' + label)
|
|
9
|
-
for (let i = 0; i < n; i++) {
|
|
10
|
-
const x = la[i]
|
|
11
|
-
const y = lb[i]
|
|
12
|
-
if (x === y) continue
|
|
13
|
-
if (x !== undefined) out.push('-' + x)
|
|
14
|
-
if (y !== undefined) out.push('+' + y)
|
|
15
|
-
}
|
|
16
|
-
return out.join(String.fromCharCode(10))
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function colorDiff(diffText) {
|
|
20
|
-
return diffText
|
|
21
|
-
.split(String.fromCharCode(10))
|
|
22
|
-
.map((line) => {
|
|
23
|
-
if (line.startsWith('+++') || line.startsWith('---')) return theme.bold(line)
|
|
24
|
-
if (line.startsWith('+')) return theme.assistant(line)
|
|
25
|
-
if (line.startsWith('-')) return theme.error(line)
|
|
26
|
-
if (line.startsWith('...')) return theme.system(line)
|
|
27
|
-
return line
|
|
28
|
-
})
|
|
29
|
-
.join(String.fromCharCode(10))
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export default { unifiedDiff, colorDiff }
|
package/src/gitTools.js
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { exec } from 'child_process'
|
|
2
|
-
|
|
3
|
-
function runGit(cmd, cwd, timeout = 15_000) {
|
|
4
|
-
return new Promise((resolve) => {
|
|
5
|
-
exec(
|
|
6
|
-
cmd,
|
|
7
|
-
{
|
|
8
|
-
cwd,
|
|
9
|
-
timeout,
|
|
10
|
-
windowsHide: true,
|
|
11
|
-
maxBuffer: 1024 * 1024 * 8,
|
|
12
|
-
env: {
|
|
13
|
-
...process.env,
|
|
14
|
-
GIT_TERMINAL_PROMPT: '0',
|
|
15
|
-
GIT_PAGER: 'cat',
|
|
16
|
-
PAGER: 'cat',
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
(err, stdout, stderr) => {
|
|
20
|
-
const out = (stdout || '').toString()
|
|
21
|
-
const errStr = (stderr || '').toString()
|
|
22
|
-
|
|
23
|
-
if (!err) {
|
|
24
|
-
const combined = (out + errStr).trim()
|
|
25
|
-
resolve(combined || '(команда выполнена, вывода нет)')
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const parts = []
|
|
30
|
-
if (err.killed) parts.push(`⏱ Таймаут ${timeout}ms`)
|
|
31
|
-
else if (err.code !== undefined && err.code !== null)
|
|
32
|
-
parts.push(`Exit code: ${err.code}`)
|
|
33
|
-
else parts.push(`Ошибка: ${err.message}`)
|
|
34
|
-
if (out.trim()) parts.push(out.trim())
|
|
35
|
-
if (errStr.trim()) parts.push(errStr.trim())
|
|
36
|
-
resolve(parts.join('\n'))
|
|
37
|
-
},
|
|
38
|
-
)
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Безопасно квотит пути/аргументы для передачи в shell.
|
|
43
|
-
// Строка или массив -> строка с двойными кавычками и экранированием.
|
|
44
|
-
function quoteArgs(input) {
|
|
45
|
-
const list = Array.isArray(input) ? input : [input]
|
|
46
|
-
return list
|
|
47
|
-
.map((v) => String(v).trim())
|
|
48
|
-
.filter(Boolean)
|
|
49
|
-
.map((v) => JSON.stringify(v))
|
|
50
|
-
.join(' ')
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Проверяет, является ли директория git-репозиторием, и собирает контекст.
|
|
54
|
-
export async function getGitContext(workdir) {
|
|
55
|
-
const probe = await runGit(
|
|
56
|
-
'git rev-parse --is-inside-work-tree',
|
|
57
|
-
workdir,
|
|
58
|
-
5000,
|
|
59
|
-
)
|
|
60
|
-
if (probe.trim() !== 'true') return null
|
|
61
|
-
|
|
62
|
-
const branchR = await runGit('git branch --show-current', workdir, 5000)
|
|
63
|
-
const statusR = await runGit('git status --porcelain', workdir, 5000)
|
|
64
|
-
const remoteR = await runGit('git remote', workdir, 5000)
|
|
65
|
-
const aheadR = await runGit(
|
|
66
|
-
'git rev-list --left-right --count @{upstream}...HEAD',
|
|
67
|
-
workdir,
|
|
68
|
-
5000,
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
const branch = branchR.trim() || '(detached HEAD)'
|
|
72
|
-
const statusLines = statusR
|
|
73
|
-
.split('\n')
|
|
74
|
-
.map((l) => l.trimEnd())
|
|
75
|
-
.filter(Boolean)
|
|
76
|
-
const remotes = remoteR
|
|
77
|
-
.split('\n')
|
|
78
|
-
.map((l) => l.trim())
|
|
79
|
-
.filter(Boolean)
|
|
80
|
-
const hasOrigin = remotes.includes('origin')
|
|
81
|
-
|
|
82
|
-
let ahead = 0
|
|
83
|
-
let behind = 0
|
|
84
|
-
const m = aheadR.match(/^(\d+)\s+(\d+)/)
|
|
85
|
-
if (m) {
|
|
86
|
-
behind = Number(m[1])
|
|
87
|
-
ahead = Number(m[2])
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
branch,
|
|
92
|
-
changedFiles: statusLines.length,
|
|
93
|
-
statusPreview: statusLines.slice(0, 30).join('\n'),
|
|
94
|
-
hasOrigin,
|
|
95
|
-
ahead,
|
|
96
|
-
behind,
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function formatGitContext(ctx) {
|
|
101
|
-
if (!ctx) return 'Not a git repository (or git is not installed).'
|
|
102
|
-
|
|
103
|
-
let s = `- Branch: \`${ctx.branch}\``
|
|
104
|
-
if (ctx.hasOrigin) {
|
|
105
|
-
s += `\n- Remote: origin (ahead ${ctx.ahead}, behind ${ctx.behind})`
|
|
106
|
-
} else {
|
|
107
|
-
s += '\n- Remote: (none)'
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (ctx.changedFiles > 0) {
|
|
111
|
-
s += `\n- Uncommitted changes: ${ctx.changedFiles} file(s)\n`
|
|
112
|
-
s += '```\n' + ctx.statusPreview + '\n```'
|
|
113
|
-
} else {
|
|
114
|
-
s += '\n- Working tree: clean'
|
|
115
|
-
}
|
|
116
|
-
return s
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function createGitTools(workdir) {
|
|
120
|
-
const ensureRepo = async () => {
|
|
121
|
-
const probe = await runGit(
|
|
122
|
-
'git rev-parse --is-inside-work-tree',
|
|
123
|
-
workdir,
|
|
124
|
-
5000,
|
|
125
|
-
)
|
|
126
|
-
if (probe.trim() !== 'true') {
|
|
127
|
-
return (
|
|
128
|
-
`Not a git repository: ${workdir}\n` +
|
|
129
|
-
`Hint: run \`git init\` here, or cd into an existing repo.\n` +
|
|
130
|
-
`(raw probe output: ${JSON.stringify(probe.trim())})`
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
return null
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return [
|
|
137
|
-
{
|
|
138
|
-
name: 'GitStatus',
|
|
139
|
-
description:
|
|
140
|
-
'Показать git status рабочей директории. Возвращает ошибку, если это не git-репозиторий.',
|
|
141
|
-
parameters: {},
|
|
142
|
-
fn: async () => {
|
|
143
|
-
const err = await ensureRepo()
|
|
144
|
-
if (err) return err
|
|
145
|
-
return runGit('git status', workdir, 10_000)
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
{
|
|
150
|
-
name: 'GitDiff',
|
|
151
|
-
description:
|
|
152
|
-
'Показать git diff. По умолчанию незастейдженные изменения. Можно ограничить path и указать staged=true.',
|
|
153
|
-
parameters: { path: 'string?', staged: 'boolean?' },
|
|
154
|
-
fn: async ({ path: p, staged }) => {
|
|
155
|
-
const err = await ensureRepo()
|
|
156
|
-
if (err) return err
|
|
157
|
-
const parts = ['git diff']
|
|
158
|
-
if (staged) parts.push('--staged')
|
|
159
|
-
if (p) parts.push('--', JSON.stringify(p))
|
|
160
|
-
return runGit(parts.join(' '), workdir, 20_000)
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
|
|
164
|
-
{
|
|
165
|
-
name: 'GitLog',
|
|
166
|
-
description: 'Показать последние N коммитов (по умолчанию 10).',
|
|
167
|
-
parameters: { count: 'number?' },
|
|
168
|
-
fn: async ({ count }) => {
|
|
169
|
-
const err = await ensureRepo()
|
|
170
|
-
if (err) return err
|
|
171
|
-
return runGit(
|
|
172
|
-
`git log --oneline --decorate -n ${Number(count) || 10}`,
|
|
173
|
-
workdir,
|
|
174
|
-
10_000,
|
|
175
|
-
)
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
{
|
|
180
|
-
name: 'GitAdd',
|
|
181
|
-
description:
|
|
182
|
-
'Добавить файлы в индекс. Если paths не задан — git add -A (всё).',
|
|
183
|
-
parameters: { paths: 'string?' },
|
|
184
|
-
fn: async ({ paths }) => {
|
|
185
|
-
const err = await ensureRepo()
|
|
186
|
-
if (err) return err
|
|
187
|
-
return runGit(
|
|
188
|
-
paths && String(paths).trim()
|
|
189
|
-
? 'git add -- ' + quoteArgs(paths)
|
|
190
|
-
: 'git add -A',
|
|
191
|
-
workdir,
|
|
192
|
-
20_000,
|
|
193
|
-
)
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
|
|
197
|
-
{
|
|
198
|
-
name: 'GitCommit',
|
|
199
|
-
description:
|
|
200
|
-
'Закоммитить застейдженное (если ничего не застейджено — сначала git add -A). Не пушит.',
|
|
201
|
-
parameters: { message: 'string' },
|
|
202
|
-
fn: async ({ message }) => {
|
|
203
|
-
const err = await ensureRepo()
|
|
204
|
-
if (err) return err
|
|
205
|
-
|
|
206
|
-
if (!message || !message.trim()) {
|
|
207
|
-
return 'Ошибка: message пустой.'
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Индексируем всё только если в индексе пусто (как обещает описание).
|
|
211
|
-
const staged = await runGit('git diff --cached --name-only', workdir, 10_000)
|
|
212
|
-
const hasStaged = !/^\s*$/.test(staged) && staged.trim() !== '(команда выполнена, вывода нет)'
|
|
213
|
-
const addResult = hasStaged
|
|
214
|
-
? '(индекс уже не пуст — add -A пропущен)'
|
|
215
|
-
: await runGit('git add -A', workdir, 20_000)
|
|
216
|
-
|
|
217
|
-
const fs = await import('fs/promises')
|
|
218
|
-
const path = await import('path')
|
|
219
|
-
const os = await import('os')
|
|
220
|
-
const tmp = path.join(os.tmpdir(), `dsa-commit-${Date.now()}.txt`)
|
|
221
|
-
await fs.writeFile(tmp, message, 'utf-8')
|
|
222
|
-
try {
|
|
223
|
-
const result = await runGit(
|
|
224
|
-
`git commit -F ${JSON.stringify(tmp)}`,
|
|
225
|
-
workdir,
|
|
226
|
-
30_000,
|
|
227
|
-
)
|
|
228
|
-
return `add: ${addResult}\ncommit: ${result}`
|
|
229
|
-
} finally {
|
|
230
|
-
await fs.unlink(tmp).catch(() => {})
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
{
|
|
236
|
-
name: 'GitPush',
|
|
237
|
-
description:
|
|
238
|
-
'Запушить ветку в origin. Требует настроенных креденшелов (SSH или Windows Credential Manager).',
|
|
239
|
-
parameters: { branch: 'string?', setUpstream: 'boolean?' },
|
|
240
|
-
fn: async ({ branch, setUpstream }) => {
|
|
241
|
-
const err = await ensureRepo()
|
|
242
|
-
if (err) return err
|
|
243
|
-
|
|
244
|
-
const b =
|
|
245
|
-
branch && branch.trim()
|
|
246
|
-
? branch.trim()
|
|
247
|
-
: (await runGit('git branch --show-current', workdir, 5000)).trim()
|
|
248
|
-
if (!b) return 'Ошибка: не удалось определить ветку для push.'
|
|
249
|
-
|
|
250
|
-
const flag = setUpstream ? '-u ' : ''
|
|
251
|
-
const target = quoteArgs(b)
|
|
252
|
-
return runGit(`git push ${flag}origin ${target}`, workdir, 120_000)
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
]
|
|
256
|
-
}
|