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.
Files changed (57) hide show
  1. package/dist/agent-loop.js +652 -0
  2. package/dist/agent-loop.js.map +1 -0
  3. package/dist/browser.js +573 -0
  4. package/dist/browser.js.map +1 -0
  5. package/dist/config.js +74 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/confirm.js +82 -0
  8. package/dist/confirm.js.map +1 -0
  9. package/dist/diff.js +38 -0
  10. package/dist/diff.js.map +1 -0
  11. package/dist/gitTools.js +219 -0
  12. package/dist/gitTools.js.map +1 -0
  13. package/dist/index.js +1365 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/input.js +524 -0
  16. package/dist/input.js.map +1 -0
  17. package/dist/markdown.js +48 -0
  18. package/dist/markdown.js.map +1 -0
  19. package/dist/self-review.js +281 -0
  20. package/dist/self-review.js.map +1 -0
  21. package/dist/sessions.js +123 -0
  22. package/dist/sessions.js.map +1 -0
  23. package/dist/spinner.js +138 -0
  24. package/dist/spinner.js.map +1 -0
  25. package/{src → dist}/system-prompt.js +10 -14
  26. package/dist/system-prompt.js.map +1 -0
  27. package/{src → dist}/theme.js +17 -19
  28. package/dist/theme.js.map +1 -0
  29. package/dist/tools.js +189 -0
  30. package/dist/tools.js.map +1 -0
  31. package/dist/transcript.js +51 -0
  32. package/dist/transcript.js.map +1 -0
  33. package/dist/types.js +4 -0
  34. package/dist/types.js.map +1 -0
  35. package/dist/undo.js +100 -0
  36. package/dist/undo.js.map +1 -0
  37. package/dist/web.js +220 -0
  38. package/dist/web.js.map +1 -0
  39. package/dist/xml-toolcall.js +68 -0
  40. package/dist/xml-toolcall.js.map +1 -0
  41. package/package.json +13 -5
  42. package/src/agent-loop.js +0 -575
  43. package/src/browser.js +0 -627
  44. package/src/config.js +0 -81
  45. package/src/confirm.js +0 -85
  46. package/src/diff.js +0 -32
  47. package/src/gitTools.js +0 -256
  48. package/src/index.js +0 -1461
  49. package/src/input.js +0 -447
  50. package/src/markdown.js +0 -49
  51. package/src/self-review.js +0 -319
  52. package/src/spinner.js +0 -144
  53. package/src/tools.js +0 -206
  54. package/src/transcript.js +0 -46
  55. package/src/undo.js +0 -101
  56. package/src/web.js +0 -266
  57. package/src/xml-toolcall.js +0 -69
package/src/agent-loop.js DELETED
@@ -1,575 +0,0 @@
1
- import { buildSystemPrompt } from './system-prompt.js'
2
- import { getGitContext, formatGitContext } from './gitTools.js'
3
- import { parseXmlToolCalls } from './xml-toolcall.js'
4
-
5
- export async function runAgentLoop({
6
- browser,
7
- tools,
8
- task,
9
- workdir,
10
- maxIterations = 40,
11
- freshChat = false,
12
- sendSystemPrompt = false,
13
- transcript = null,
14
- onThinking = () => {},
15
- onAssistantThought = () => {},
16
- onToolCall = () => {},
17
- onToolResult = () => {},
18
- onAssistantMessage = () => {},
19
- debugLog = false,
20
- }) {
21
- if (freshChat) {
22
- await browser.newChat()
23
- transcript?.log('new_chat')
24
- }
25
-
26
- if (sendSystemPrompt) {
27
- let gitText = null
28
- try {
29
- const ctx = await getGitContext(workdir)
30
- gitText = formatGitContext(ctx)
31
- } catch (e) {
32
- gitText = `(git context error: ${e.message})`
33
- }
34
-
35
- const systemPrompt = buildSystemPrompt({
36
- workdir,
37
- tools,
38
- gitContext: gitText,
39
- })
40
- transcript?.log('system_prompt', {
41
- length: systemPrompt.length,
42
- gitContext: gitText,
43
- })
44
- onThinking()
45
- await browser.ask(systemPrompt, { timeout: 60_000 })
46
- }
47
-
48
- let message = task
49
- transcript?.log('task', { task })
50
-
51
- // Счётчик «ответ похож на tool-call, но не распознан». Чтобы модель,
52
- // написавшая битый JSON/XML, не останавливала агента молча, мы просим
53
- // её переотправить вызов. Ограничиваем число таких попыток.
54
- let malformedRetries = 0
55
- const MAX_MALFORMED_RETRIES = 3
56
-
57
- for (let i = 0; i < maxIterations; i++) {
58
- onThinking()
59
- const rawResponse = await browser.ask(message)
60
- transcript?.log('assistant_raw', { response: rawResponse })
61
-
62
- const parsed = parseToolCall(rawResponse)
63
-
64
- if (parsed) {
65
- const thought = extractPreToolText(rawResponse)
66
- if (thought) onAssistantThought(thought)
67
- }
68
-
69
- const parsedCalls = Array.isArray(parsed) ? parsed : parsed ? [parsed] : []
70
- if (parsedCalls.some((p) => p && p._permissive)) {
71
- // Пишем в транскрипт (для отладки), но не сыпем в консоль.
72
- transcript?.log('permissive_parse', { response: rawResponse })
73
- if (debugLog) {
74
- console.error('внимание: tool-call распознан нестрогим парсером')
75
- }
76
- }
77
-
78
- if (!parsed) {
79
- // Ответ не распознан как tool-call. Если он ПОХОЖ на попытку вызова
80
- // (есть tool/invoke/parameter, но JSON/XML битый) — это почти всегда
81
- // ошибка формата. Не считаем её финальным ответом (иначе агент молча
82
- // остановится), а просим модель переотправить вызов корректно.
83
- const looksLikeToolCall = /("tool"\s*:|\binvoke\b|\bparameter\b)/i.test(rawResponse)
84
- if (looksLikeToolCall && malformedRetries < MAX_MALFORMED_RETRIES) {
85
- malformedRetries++
86
- transcript?.log("malformed_toolcall", { attempt: malformedRetries, response: rawResponse })
87
- if (debugLog) {
88
- console.error("внимание: ответ похож на tool-call, но не распознан (попытка " + malformedRetries + "/" + MAX_MALFORMED_RETRIES + ")")
89
- }
90
- message = "Твой предыдущий ответ не распознан как вызов инструмента. " +
91
- "Ответь РОВНО одним JSON-объектом вызова инструмента, без текста до и после. " +
92
- "Например: " + String.fromCharCode(39) + "tool" + String.fromCharCode(39) + ": ..., args: {...}"
93
- continue
94
- }
95
-
96
- onAssistantMessage(rawResponse)
97
- transcript?.log('assistant_final', { message: rawResponse })
98
- return rawResponse
99
- }
100
-
101
- const calls = Array.isArray(parsed) ? parsed : [parsed]
102
-
103
- const respondCall = calls.find((c) => c.tool === 'respond')
104
- if (respondCall) {
105
- const msg =
106
- typeof respondCall.args.message === 'string'
107
- ? respondCall.args.message
108
- : String(respondCall.args.message ?? '')
109
- onAssistantMessage(msg)
110
- transcript?.log('assistant_final', { message: msg })
111
- return msg
112
- }
113
-
114
- const results = []
115
- for (const call of calls) {
116
- const tool = tools.find((t) => t.name === call.tool)
117
-
118
- if (!tool) {
119
- const err = `Неизвестный инструмент: ${call.tool}`
120
- onToolResult(err)
121
- transcript?.log('tool_error', { tool: call.tool, error: err })
122
- results.push({ tool: call.tool, result: err })
123
- continue
124
- }
125
-
126
- onToolCall(call.tool, call.args)
127
- transcript?.log('tool_call', { tool: call.tool, args: call.args })
128
-
129
- let result
130
- try {
131
- result = await tool.fn(call.args)
132
- } catch (e) {
133
- result = `Ошибка: ${e.message}`
134
- }
135
-
136
- onToolResult(result)
137
- transcript?.log('tool_result', {
138
- tool: call.tool,
139
- result: String(result),
140
- })
141
- results.push({ tool: call.tool, result })
142
- }
143
-
144
- if (results.length === 1) {
145
- const r = results[0]
146
- const resultStr =
147
- typeof r.result === 'string' ? r.result : JSON.stringify(r.result)
148
- message = `Tool result for ${r.tool}:\n${resultStr.slice(0, 12_000)}`
149
- } else {
150
- message = results
151
- .map((r) => {
152
- const resultStr =
153
- typeof r.result === 'string' ? r.result : JSON.stringify(r.result)
154
- return `Tool result for ${r.tool}:\n${resultStr.slice(0, 8000)}`
155
- })
156
- .join('\n\n')
157
- }
158
- }
159
-
160
- return 'Достигнут лимит итераций.'
161
- }
162
-
163
- // ============ JSON parsing ============
164
-
165
- function tryParse(str) {
166
- try {
167
- const obj = JSON.parse(str)
168
- if (
169
- obj &&
170
- typeof obj.tool === 'string' &&
171
- obj.args &&
172
- typeof obj.args === 'object' &&
173
- !Array.isArray(obj.args)
174
- ) {
175
- return obj
176
- }
177
- return null
178
- } catch {
179
- return null
180
- }
181
- }
182
-
183
- function tryParseArray(str) {
184
- try {
185
- const arr = JSON.parse(str)
186
- if (
187
- Array.isArray(arr) &&
188
- arr.length > 0 &&
189
- arr.every(
190
- (o) =>
191
- o &&
192
- typeof o.tool === 'string' &&
193
- o.args &&
194
- typeof o.args === 'object' &&
195
- !Array.isArray(o.args),
196
- )
197
- ) {
198
- return arr
199
- }
200
- return null
201
- } catch {
202
- return null
203
- }
204
- }
205
-
206
- // Жадный разбор args для грязного JSON (незаэкранированные кавычки в строке).
207
- function parseArgsGreedy(str) {
208
- const result = {}
209
- let i = str.indexOf('{') + 1
210
- if (i === 0) return null
211
- const skipWs = () => {
212
- while (i < str.length && /[\s,]/.test(str[i])) i++
213
- }
214
- while (i < str.length) {
215
- skipWs()
216
- if (i >= str.length || str[i] === '}') break
217
- if (str[i] !== '"') return null
218
- const keyEnd = str.indexOf('"', i + 1)
219
- if (keyEnd === -1) return null
220
- const key = str.slice(i + 1, keyEnd)
221
- i = keyEnd + 1
222
- while (i < str.length && /\s/.test(str[i])) i++
223
- if (str[i] !== ':') return null
224
- i++
225
- while (i < str.length && /\s/.test(str[i])) i++
226
- if (str[i] === '"') {
227
- let lastEnd = -1
228
- let k = i + 1
229
- while (k < str.length) {
230
- if (str.charCodeAt(k) === 92) { k += 2; continue }
231
- if (str[k] === '"') {
232
- let t = k + 1
233
- while (t < str.length && /\s/.test(str[t])) t++
234
- if (t >= str.length || str[t] === ',' || str[t] === '}') lastEnd = k
235
- }
236
- k++
237
- }
238
- if (lastEnd === -1) return null
239
- result[key] = unescapeValue(str.slice(i + 1, lastEnd))
240
- i = lastEnd + 1
241
- continue
242
- }
243
- if (str[i] === '{' || str[i] === '[') {
244
- const open = str[i]
245
- const close = open === '{' ? '}' : ']'
246
- const end = findMatching(str, i, open, close)
247
- if (end === -1) return null
248
- let parsed = null
249
- try { parsed = JSON.parse(str.slice(i, end + 1)) }
250
- catch { if (open === '{') parsed = parseArgsPermissive(str.slice(i, end + 1)) }
251
- if (parsed === null) return null
252
- result[key] = parsed
253
- i = end + 1
254
- continue
255
- }
256
- let j = i
257
- while (j < str.length && !/[,}]/.test(str[j])) j++
258
- const raw = str.slice(i, j).trim()
259
- if (raw === 'true') result[key] = true
260
- else if (raw === 'false') result[key] = false
261
- else if (raw === 'null') result[key] = null
262
- else { const n = Number(raw); result[key] = Number.isNaN(n) ? raw : n }
263
- i = j
264
- }
265
- return result
266
- }
267
-
268
- function parseArgsPermissive(str) {
269
- const result = {}
270
- let i = 1
271
- while (i < str.length) {
272
- while (i < str.length && /[\s,]/.test(str[i])) i++
273
- if (i >= str.length || str[i] === '}') break
274
-
275
- if (str[i] !== '"') return null
276
- let keyEnd = str.indexOf('"', i + 1)
277
- if (keyEnd === -1) return null
278
- const key = str.slice(i + 1, keyEnd)
279
- i = keyEnd + 1
280
-
281
- while (i < str.length && /\s/.test(str[i])) i++
282
- if (str[i] !== ':') return null
283
- i++
284
- while (i < str.length && /\s/.test(str[i])) i++
285
-
286
- if (str[i] === '"') {
287
- let j = i + 1
288
- let value = ''
289
- while (j < str.length) {
290
- const c = str[j]
291
- if (c === '\\' && j + 1 < str.length) {
292
- value += c + str[j + 1]
293
- j += 2
294
- continue
295
- }
296
- if (c === '"') {
297
- let k = j + 1
298
- while (k < str.length && /\s/.test(str[k])) k++
299
- if (
300
- k >= str.length ||
301
- str[k] === ',' ||
302
- str[k] === '}' ||
303
- str[k] === ']'
304
- ) {
305
- break
306
- }
307
- value += '"'
308
- j++
309
- continue
310
- }
311
- value += c
312
- j++
313
- }
314
- if (j >= str.length) return null
315
- result[key] = unescapeValue(value)
316
- i = j + 1
317
- } else if (str[i] === '{' || str[i] === '[') {
318
- // Вложенный объект/массив: находим сбалансированный фрагмент и
319
- // пытаемся распарсить его как JSON (или как вложенный permissive).
320
- const open = str[i]
321
- const close = open === '{' ? '}' : ']'
322
- const end = findMatching(str, i, open, close)
323
- if (end === -1) return null
324
- const frag = str.slice(i, end + 1)
325
- let parsedFrag = null
326
- try {
327
- parsedFrag = JSON.parse(frag)
328
- } catch {
329
- if (open === '{') parsedFrag = parseArgsPermissive(frag)
330
- }
331
- if (parsedFrag === null) return null
332
- result[key] = parsedFrag
333
- i = end + 1
334
- } else {
335
- let j = i
336
- while (j < str.length && !/[,}]/.test(str[j])) j++
337
- const raw = str.slice(i, j).trim()
338
- if (raw === 'true') result[key] = true
339
- else if (raw === 'false') result[key] = false
340
- else if (raw === 'null') result[key] = null
341
- else {
342
- const n = Number(raw)
343
- result[key] = Number.isNaN(n) ? raw : n
344
- }
345
- i = j
346
- }
347
- }
348
- return result
349
- }
350
-
351
- function unescapeValue(s) {
352
- let out = ''
353
- let i = 0
354
- while (i < s.length) {
355
- const c = s[i]
356
- if (c === '\\' && i + 1 < s.length) {
357
- const n = s[i + 1]
358
- if (n === 'n') {
359
- out += '\n'
360
- i += 2
361
- continue
362
- }
363
- if (n === 't') {
364
- out += '\t'
365
- i += 2
366
- continue
367
- }
368
- if (n === 'r') {
369
- out += '\r'
370
- i += 2
371
- continue
372
- }
373
- if (n === '"') {
374
- out += '"'
375
- i += 2
376
- continue
377
- }
378
- if (n === '\\') {
379
- out += '\\'
380
- i += 2
381
- continue
382
- }
383
- if (n === '/') {
384
- out += '/'
385
- i += 2
386
- continue
387
- }
388
- if (n === 'u' && i + 5 < s.length) {
389
- const hex = s.slice(i + 2, i + 6)
390
- if (/^[0-9a-fA-F]{4}$/.test(hex)) {
391
- out += String.fromCharCode(parseInt(hex, 16))
392
- i += 6
393
- continue
394
- }
395
- }
396
- out += c + n
397
- i += 2
398
- continue
399
- }
400
- out += c
401
- i++
402
- }
403
- return out
404
- }
405
-
406
- function parseToolCallPermissive(text) {
407
- const toolMatch = text.match(/"tool"\s*:\s*"([A-Za-z_][A-Za-z0-9_]*)"/)
408
- if (!toolMatch) return null
409
- const tool = toolMatch[1]
410
-
411
- const argsIdx = text.indexOf('"args"')
412
- if (argsIdx === -1) return null
413
- const openIdx = text.indexOf('{', argsIdx)
414
- if (openIdx === -1) return null
415
-
416
- // Берём сбалансированный по скобкам фрагмент args (с учётом строк),
417
- // а не первый попавшийся '}'. Иначе вложенные объекты/массивы или
418
- // фигурные скобки внутри строк ломают разбор.
419
- const endIdx = findMatching(text, openIdx, '{', '}')
420
- if (endIdx === -1) return null
421
-
422
- const argsStr = text.slice(openIdx, endIdx + 1)
423
- if (tool === 'Edit') {
424
- const e = parseEditArgs(argsStr)
425
- if (e) return { tool, args: e }
426
- }
427
- const args = parseArgsPermissive(argsStr)
428
- if (args) return { tool, args }
429
- const greedy = parseArgsGreedy(argsStr)
430
- if (greedy) return { tool, args: greedy }
431
- return null
432
- }
433
-
434
- // Специализированный разбор args для Edit: ровно три поля path/old_string/
435
- // new_string, значения могут содержать сырые кавычки и переводы строк.
436
- function parseEditArgs(str) {
437
- const keyRe = (name) => new RegExp('"' + name + '"\\s*:\\s*"')
438
- const readValue = (name, nextNames) => {
439
- const m = keyRe(name).exec(str)
440
- if (!m) return null
441
- const start = m.index + m[0].length
442
- let end = str.length
443
- for (const n of nextNames) {
444
- const mm = keyRe(n).exec(str.slice(start))
445
- if (mm) {
446
- const absIdx = start + mm.index
447
- if (absIdx < end) end = absIdx
448
- }
449
- }
450
- let val = str.slice(start, end)
451
- val = val.replace(/"\s*[,}]?\s*$/, '')
452
- return val
453
- }
454
- const path = readValue('path', ['old_string', 'new_string'])
455
- const oldStr = readValue('old_string', ['new_string'])
456
- const newStr = readValue('new_string', [])
457
- if (path === null || oldStr === null || newStr === null) return null
458
- return { path: path, old_string: oldStr, new_string: newStr }
459
- }
460
-
461
- function extractJsonObjects(text) {
462
- const objects = []
463
- let i = 0
464
- while (i < text.length) {
465
- if (text[i] === '{') {
466
- const end = findMatching(text, i, '{', '}')
467
- if (end !== -1) {
468
- objects.push(text.slice(i, end + 1))
469
- i = end + 1
470
- continue
471
- }
472
- }
473
- if (text[i] === '[') {
474
- const end = findMatching(text, i, '[', ']')
475
- if (end !== -1) {
476
- objects.push(text.slice(i, end + 1))
477
- i = end + 1
478
- continue
479
- }
480
- }
481
- i++
482
- }
483
- return objects
484
- }
485
-
486
- function findMatching(text, openIdx, openCh, closeCh) {
487
- let depth = 0
488
- let inString = false
489
- let escape = false
490
- for (let i = openIdx; i < text.length; i++) {
491
- const c = text[i]
492
- if (escape) {
493
- escape = false
494
- continue
495
- }
496
- if (c === '\\') {
497
- escape = true
498
- continue
499
- }
500
- if (c === '"') {
501
- inString = !inString
502
- continue
503
- }
504
- if (inString) continue
505
- if (c === openCh) depth++
506
- else if (c === closeCh) {
507
- depth--
508
- if (depth === 0) return i
509
- }
510
- }
511
- return -1
512
- }
513
-
514
- function parseToolCall(text) {
515
- if (!text || typeof text !== 'string') return null
516
-
517
- let cleaned = text.trim()
518
- cleaned = cleaned
519
- .replace(/^```(?:json)?\s*/i, '')
520
- .replace(/```$/i, '')
521
- .trim()
522
-
523
- const candidates = extractJsonObjects(cleaned)
524
-
525
- for (let i = candidates.length - 1; i >= 0; i--) {
526
- const raw = candidates[i]
527
-
528
- const arrFirst = tryParseArray(raw)
529
- if (arrFirst) return arrFirst
530
-
531
- const arrRepaired = tryParseArray(
532
- raw.replace(/\\(?!["\\/bfnrtu])/g, '\\\\'),
533
- )
534
- if (arrRepaired) return arrRepaired
535
-
536
- const first = tryParse(raw)
537
- if (first) return first
538
-
539
- const repaired = raw.replace(/\\(?!["\\/bfnrtu])/g, '\\\\')
540
- const second = tryParse(repaired)
541
- if (second) return second
542
- }
543
-
544
- const permissive = parseToolCallPermissive(cleaned)
545
- if (permissive) return { ...permissive, _permissive: true }
546
-
547
- // Fallback: модель могла обернуть JSON в прозу и/или добавить мусорные
548
- // теги после него. Отрезаем всё до первого '"tool"' и всё, что похоже
549
- // на XML/DSML-хвост, затем пробуем permissive-разбор ещё раз.
550
- const toolIdx = cleaned.search(/["']?tool["']?\s:/)
551
- if (toolIdx > 0) {
552
- let tail = cleaned.slice(toolIdx)
553
- tail = tail.replace(/<[^>]>.*$/s, '').trim()
554
- const tailPermissive = parseToolCallPermissive('{"' + tail)
555
- if (tailPermissive) return { ...tailPermissive, _permissive: true }
556
- }
557
-
558
- // Последний fallback: модель ответила XML/DSML-блоком вместо JSON.
559
- const xmlCalls = parseXmlToolCalls(cleaned)
560
- if (xmlCalls) return Array.isArray(xmlCalls) ? xmlCalls : [xmlCalls]
561
-
562
- return null
563
- }
564
-
565
- function extractPreToolText(text) {
566
- if (!text) return ''
567
- const patterns = ['{"tool"', '[{"tool"', '{"tool":', '[{"tool":']
568
- let earliest = -1
569
- for (const p of patterns) {
570
- const idx = text.indexOf(p)
571
- if (idx >= 0 && (earliest === -1 || idx < earliest)) earliest = idx
572
- }
573
- if (earliest === -1) return ''
574
- return text.slice(0, earliest).trim()
575
- }