zames_pro 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 zames_pro contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the Software), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # zames_pro
2
+
3
+ Terminal coding agent, работающий поверх chat.deepseek.com через Playwright.
4
+ По духу похож на Claude Code / Codex CLI: запускается в текущей директории, читает и правит файлы, запускает команды, коммитит в git.
5
+
6
+ ## Требования
7
+
8
+ - Node.js >= 18
9
+ - Google Chrome или Chromium (используется через Playwright)
10
+ - Аккаунт DeepSeek (вход выполняется вручную в открывшемся браузере при первом запуске)
11
+
12
+ ## Установка
13
+
14
+ ```bash
15
+ npm install -g zames_pro
16
+ npx playwright install chromium
17
+ ```
18
+
19
+ ## Запуск
20
+
21
+ Перейди в папку проекта и запусти:
22
+
23
+ ```bash
24
+ zames
25
+ ```
26
+
27
+ Агент работает в директории запуска и не может выходить за её пределы (sandbox).
28
+
29
+ ### Опции
30
+
31
+ ```
32
+ zames --task <текст задачи>
33
+ zames --headless
34
+ zames --debug
35
+ zames --dir <путь>
36
+ zames --help
37
+ ```
38
+
39
+ ## Конфигурация
40
+
41
+ Глобальный конфиг: `~/.zames/config.json`
42
+ Локальный (в проекте): `.zamesrc.json`
43
+
44
+ Данные агента хранятся в `~/.zames`: профиль браузера, логи, история undo, снапшоты self-review.
45
+
46
+ ## Лицензия
47
+
48
+ MIT
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "zames_pro",
3
+ "version": "1.0.0",
4
+ "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
+ "type": "module",
6
+ "bin": {
7
+ "zames": "src/index.js"
8
+ },
9
+ "files": [
10
+ "src",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "scripts": {
15
+ "start": "node ./src/index.js"
16
+ },
17
+ "keywords": [
18
+ "agent",
19
+ "ai",
20
+ "deepseek",
21
+ "cli",
22
+ "coding-agent"
23
+ ],
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "dependencies": {
29
+ "playwright": "^1.48.0",
30
+ "chalk": "^5.3.0",
31
+ "ora": "^8.0.1"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ }
36
+ }
@@ -0,0 +1,419 @@
1
+ import { buildSystemPrompt } from './system-prompt.js'
2
+ import { getGitContext, formatGitContext } from './gitTools.js'
3
+
4
+ export async function runAgentLoop({
5
+ browser,
6
+ tools,
7
+ task,
8
+ workdir,
9
+ maxIterations = 40,
10
+ freshChat = false,
11
+ sendSystemPrompt = false,
12
+ transcript = null,
13
+ onThinking = () => {},
14
+ onAssistantThought = () => {},
15
+ onToolCall = () => {},
16
+ onToolResult = () => {},
17
+ onAssistantMessage = () => {},
18
+ }) {
19
+ if (freshChat) {
20
+ await browser.newChat()
21
+ transcript?.log('new_chat')
22
+ }
23
+
24
+ if (sendSystemPrompt) {
25
+ let gitText = null
26
+ try {
27
+ const ctx = await getGitContext(workdir)
28
+ gitText = formatGitContext(ctx)
29
+ } catch (e) {
30
+ gitText = `(git context error: ${e.message})`
31
+ }
32
+
33
+ const systemPrompt = buildSystemPrompt({
34
+ workdir,
35
+ tools,
36
+ gitContext: gitText,
37
+ })
38
+ transcript?.log('system_prompt', {
39
+ length: systemPrompt.length,
40
+ gitContext: gitText,
41
+ })
42
+ onThinking()
43
+ await browser.ask(systemPrompt, { timeout: 60_000 })
44
+ }
45
+
46
+ let message = task
47
+ transcript?.log('task', { task })
48
+
49
+ for (let i = 0; i < maxIterations; i++) {
50
+ onThinking()
51
+ const rawResponse = await browser.ask(message)
52
+ transcript?.log('assistant_raw', { response: rawResponse })
53
+
54
+ const parsed = parseToolCall(rawResponse)
55
+
56
+ if (parsed) {
57
+ const thought = extractPreToolText(rawResponse)
58
+ if (thought) onAssistantThought(thought)
59
+ }
60
+
61
+ const parsedCalls = Array.isArray(parsed) ? parsed : parsed ? [parsed] : []
62
+ if (parsedCalls.some((p) => p && p._permissive)) {
63
+ transcript?.log('permissive_parse', { response: rawResponse })
64
+ console.error('внимание: tool-call распознан нестрогим парсером')
65
+ }
66
+
67
+ if (!parsed) {
68
+ onAssistantMessage(rawResponse)
69
+ transcript?.log('assistant_final', { message: rawResponse })
70
+ return rawResponse
71
+ }
72
+
73
+ const calls = Array.isArray(parsed) ? parsed : [parsed]
74
+
75
+ const respondCall = calls.find((c) => c.tool === 'respond')
76
+ if (respondCall) {
77
+ const msg =
78
+ typeof respondCall.args.message === 'string'
79
+ ? respondCall.args.message
80
+ : String(respondCall.args.message ?? '')
81
+ onAssistantMessage(msg)
82
+ transcript?.log('assistant_final', { message: msg })
83
+ return msg
84
+ }
85
+
86
+ const results = []
87
+ for (const call of calls) {
88
+ const tool = tools.find((t) => t.name === call.tool)
89
+
90
+ if (!tool) {
91
+ const err = `Неизвестный инструмент: ${call.tool}`
92
+ onToolResult(err)
93
+ transcript?.log('tool_error', { tool: call.tool, error: err })
94
+ results.push({ tool: call.tool, result: err })
95
+ continue
96
+ }
97
+
98
+ onToolCall(call.tool, call.args)
99
+ transcript?.log('tool_call', { tool: call.tool, args: call.args })
100
+
101
+ let result
102
+ try {
103
+ result = await tool.fn(call.args)
104
+ } catch (e) {
105
+ result = `Ошибка: ${e.message}`
106
+ }
107
+
108
+ onToolResult(result)
109
+ transcript?.log('tool_result', {
110
+ tool: call.tool,
111
+ result: String(result),
112
+ })
113
+ results.push({ tool: call.tool, result })
114
+ }
115
+
116
+ if (results.length === 1) {
117
+ const r = results[0]
118
+ const resultStr =
119
+ typeof r.result === 'string' ? r.result : JSON.stringify(r.result)
120
+ message = `Tool result for ${r.tool}:\n${resultStr.slice(0, 12_000)}`
121
+ } else {
122
+ message = results
123
+ .map((r) => {
124
+ const resultStr =
125
+ typeof r.result === 'string' ? r.result : JSON.stringify(r.result)
126
+ return `Tool result for ${r.tool}:\n${resultStr.slice(0, 8000)}`
127
+ })
128
+ .join('\n\n')
129
+ }
130
+ }
131
+
132
+ return 'Достигнут лимит итераций.'
133
+ }
134
+
135
+ // ============ JSON parsing ============
136
+
137
+ function tryParse(str) {
138
+ try {
139
+ const obj = JSON.parse(str)
140
+ if (
141
+ obj &&
142
+ typeof obj.tool === 'string' &&
143
+ obj.args &&
144
+ typeof obj.args === 'object' &&
145
+ !Array.isArray(obj.args)
146
+ ) {
147
+ return obj
148
+ }
149
+ return null
150
+ } catch {
151
+ return null
152
+ }
153
+ }
154
+
155
+ function tryParseArray(str) {
156
+ try {
157
+ const arr = JSON.parse(str)
158
+ if (
159
+ Array.isArray(arr) &&
160
+ arr.length > 0 &&
161
+ arr.every(
162
+ (o) =>
163
+ o &&
164
+ typeof o.tool === 'string' &&
165
+ o.args &&
166
+ typeof o.args === 'object' &&
167
+ !Array.isArray(o.args),
168
+ )
169
+ ) {
170
+ return arr
171
+ }
172
+ return null
173
+ } catch {
174
+ return null
175
+ }
176
+ }
177
+
178
+ function parseArgsPermissive(str) {
179
+ const result = {}
180
+ let i = 1
181
+ while (i < str.length) {
182
+ while (i < str.length && /[\s,]/.test(str[i])) i++
183
+ if (i >= str.length || str[i] === '}') break
184
+
185
+ if (str[i] !== '"') return null
186
+ let keyEnd = str.indexOf('"', i + 1)
187
+ if (keyEnd === -1) return null
188
+ const key = str.slice(i + 1, keyEnd)
189
+ i = keyEnd + 1
190
+
191
+ while (i < str.length && /\s/.test(str[i])) i++
192
+ if (str[i] !== ':') return null
193
+ i++
194
+ while (i < str.length && /\s/.test(str[i])) i++
195
+
196
+ if (str[i] === '"') {
197
+ let j = i + 1
198
+ let value = ''
199
+ while (j < str.length) {
200
+ const c = str[j]
201
+ if (c === '\\' && j + 1 < str.length) {
202
+ value += c + str[j + 1]
203
+ j += 2
204
+ continue
205
+ }
206
+ if (c === '"') {
207
+ let k = j + 1
208
+ while (k < str.length && /\s/.test(str[k])) k++
209
+ if (
210
+ k >= str.length ||
211
+ str[k] === ',' ||
212
+ str[k] === '}' ||
213
+ str[k] === ']'
214
+ ) {
215
+ break
216
+ }
217
+ value += '"'
218
+ j++
219
+ continue
220
+ }
221
+ value += c
222
+ j++
223
+ }
224
+ if (j >= str.length) return null
225
+ result[key] = unescapeValue(value)
226
+ i = j + 1
227
+ } else if (str[i] === '{' || str[i] === '[') {
228
+ return null
229
+ } else {
230
+ let j = i
231
+ while (j < str.length && !/[,}]/.test(str[j])) j++
232
+ const raw = str.slice(i, j).trim()
233
+ if (raw === 'true') result[key] = true
234
+ else if (raw === 'false') result[key] = false
235
+ else if (raw === 'null') result[key] = null
236
+ else {
237
+ const n = Number(raw)
238
+ result[key] = Number.isNaN(n) ? raw : n
239
+ }
240
+ i = j
241
+ }
242
+ }
243
+ return result
244
+ }
245
+
246
+ function unescapeValue(s) {
247
+ let out = ''
248
+ let i = 0
249
+ while (i < s.length) {
250
+ const c = s[i]
251
+ if (c === '\\' && i + 1 < s.length) {
252
+ const n = s[i + 1]
253
+ if (n === 'n') {
254
+ out += '\n'
255
+ i += 2
256
+ continue
257
+ }
258
+ if (n === 't') {
259
+ out += '\t'
260
+ i += 2
261
+ continue
262
+ }
263
+ if (n === 'r') {
264
+ out += '\r'
265
+ i += 2
266
+ continue
267
+ }
268
+ if (n === '"') {
269
+ out += '"'
270
+ i += 2
271
+ continue
272
+ }
273
+ if (n === '\\') {
274
+ out += '\\'
275
+ i += 2
276
+ continue
277
+ }
278
+ if (n === '/') {
279
+ out += '/'
280
+ i += 2
281
+ continue
282
+ }
283
+ if (n === 'u' && i + 5 < s.length) {
284
+ const hex = s.slice(i + 2, i + 6)
285
+ if (/^[0-9a-fA-F]{4}$/.test(hex)) {
286
+ out += String.fromCharCode(parseInt(hex, 16))
287
+ i += 6
288
+ continue
289
+ }
290
+ }
291
+ out += c + n
292
+ i += 2
293
+ continue
294
+ }
295
+ out += c
296
+ i++
297
+ }
298
+ return out
299
+ }
300
+
301
+ function parseToolCallPermissive(text) {
302
+ const toolMatch = text.match(/"tool"\s*:\s*"([A-Za-z_][A-Za-z0-9_]*)"/)
303
+ if (!toolMatch) return null
304
+ const tool = toolMatch[1]
305
+
306
+ const argsIdx = text.indexOf('"args"')
307
+ if (argsIdx === -1) return null
308
+ const openIdx = text.indexOf('{', argsIdx)
309
+ if (openIdx === -1) return null
310
+
311
+ for (let i = openIdx + 1; i < text.length; i++) {
312
+ if (text[i] !== '}') continue
313
+ const argsStr = text.slice(openIdx, i + 1)
314
+ const args = parseArgsPermissive(argsStr)
315
+ if (args) return { tool, args }
316
+ }
317
+ return null
318
+ }
319
+
320
+ function extractJsonObjects(text) {
321
+ const objects = []
322
+ let i = 0
323
+ while (i < text.length) {
324
+ if (text[i] === '{') {
325
+ const end = findMatching(text, i, '{', '}')
326
+ if (end !== -1) {
327
+ objects.push(text.slice(i, end + 1))
328
+ i = end + 1
329
+ continue
330
+ }
331
+ }
332
+ if (text[i] === '[') {
333
+ const end = findMatching(text, i, '[', ']')
334
+ if (end !== -1) {
335
+ objects.push(text.slice(i, end + 1))
336
+ i = end + 1
337
+ continue
338
+ }
339
+ }
340
+ i++
341
+ }
342
+ return objects
343
+ }
344
+
345
+ function findMatching(text, openIdx, openCh, closeCh) {
346
+ let depth = 0
347
+ let inString = false
348
+ let escape = false
349
+ for (let i = openIdx; i < text.length; i++) {
350
+ const c = text[i]
351
+ if (escape) {
352
+ escape = false
353
+ continue
354
+ }
355
+ if (c === '\\') {
356
+ escape = true
357
+ continue
358
+ }
359
+ if (c === '"') {
360
+ inString = !inString
361
+ continue
362
+ }
363
+ if (inString) continue
364
+ if (c === openCh) depth++
365
+ else if (c === closeCh) {
366
+ depth--
367
+ if (depth === 0) return i
368
+ }
369
+ }
370
+ return -1
371
+ }
372
+
373
+ function parseToolCall(text) {
374
+ if (!text || typeof text !== 'string') return null
375
+
376
+ let cleaned = text.trim()
377
+ cleaned = cleaned
378
+ .replace(/^```(?:json)?\s*/i, '')
379
+ .replace(/```$/i, '')
380
+ .trim()
381
+
382
+ const candidates = extractJsonObjects(cleaned)
383
+
384
+ for (let i = candidates.length - 1; i >= 0; i--) {
385
+ const raw = candidates[i]
386
+
387
+ const arrFirst = tryParseArray(raw)
388
+ if (arrFirst) return arrFirst
389
+
390
+ const arrRepaired = tryParseArray(
391
+ raw.replace(/\\(?!["\\/bfnrtu])/g, '\\\\'),
392
+ )
393
+ if (arrRepaired) return arrRepaired
394
+
395
+ const first = tryParse(raw)
396
+ if (first) return first
397
+
398
+ const repaired = raw.replace(/\\(?!["\\/bfnrtu])/g, '\\\\')
399
+ const second = tryParse(repaired)
400
+ if (second) return second
401
+ }
402
+
403
+ const permissive = parseToolCallPermissive(cleaned)
404
+ if (permissive) return { ...permissive, _permissive: true }
405
+
406
+ return null
407
+ }
408
+
409
+ function extractPreToolText(text) {
410
+ if (!text) return ''
411
+ const patterns = ['{"tool"', '[{"tool"', '{"tool":', '[{"tool":']
412
+ let earliest = -1
413
+ for (const p of patterns) {
414
+ const idx = text.indexOf(p)
415
+ if (idx >= 0 && (earliest === -1 || idx < earliest)) earliest = idx
416
+ }
417
+ if (earliest === -1) return ''
418
+ return text.slice(0, earliest).trim()
419
+ }