zames_pro 1.1.0 → 1.2.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 +27 -37
- package/logo.png +0 -0
- package/package.json +1 -1
- package/src/agent-loop.js +28 -0
- package/src/browser.js +22 -0
- package/src/config.js +1 -0
- package/src/index.js +503 -72
- package/src/input.js +447 -0
- package/src/spinner.js +97 -1
- package/src/system-prompt.js +39 -5
- package/src/theme.js +1 -0
- package/src/xml-toolcall.js +69 -0
package/README.md
CHANGED
|
@@ -2,69 +2,59 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
A terminal coding agent that works on top of [chat.deepseek.com](https://chat.deepseek.com/) through Playwright.
|
|
6
|
+
In spirit it is similar to Claude Code / Codex CLI: it starts in the current
|
|
7
|
+
directory, reads and edits files, runs commands, and commits to git.
|
|
7
8
|
|
|
8
|
-
##
|
|
9
|
+
## Requirements
|
|
9
10
|
|
|
10
11
|
- Node.js >= 18
|
|
11
|
-
- Google Chrome
|
|
12
|
-
-
|
|
12
|
+
- Google Chrome or Chromium (used through Playwright)
|
|
13
|
+
- A DeepSeek account (you log in manually in the browser window that opens on first launch)
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## Installation
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
```bash
|
|
17
18
|
npm install -g zames_pro
|
|
18
19
|
npx playwright install chromium
|
|
19
|
-
|
|
20
|
+
```
|
|
20
21
|
|
|
21
|
-
##
|
|
22
|
+
## Usage
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
Go to your project folder and run:
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
```bash
|
|
26
27
|
zames
|
|
27
|
-
|
|
28
|
+
```
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
The agent works inside the directory it was started in and cannot leave it (sandbox).
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
While the agent is working you can keep typing: press Enter to queue a message
|
|
33
|
+
(it is sent right after the current task, in the same chat), or Esc / Ctrl+C to
|
|
34
|
+
abort the current generation. This mirrors typing during generation on the
|
|
35
|
+
DeepSeek website.
|
|
36
|
+
|
|
37
|
+
### Options
|
|
32
38
|
|
|
33
39
|
```
|
|
34
|
-
zames --task
|
|
40
|
+
zames --task <task text>
|
|
35
41
|
zames --chat <id>
|
|
36
42
|
zames --new-chat
|
|
37
43
|
zames --resend-prompt
|
|
38
|
-
zames --dir
|
|
44
|
+
zames --dir <path>
|
|
39
45
|
zames --headless
|
|
40
46
|
zames --debug
|
|
41
|
-
zames --dev
|
|
42
47
|
zames --version
|
|
43
48
|
zames --help
|
|
44
49
|
```
|
|
45
50
|
|
|
46
|
-
##
|
|
47
|
-
|
|
48
|
-
Глобальный конфиг: `~/.zames/config.json`
|
|
49
|
-
Локальный (в проекте): `.zamesrc.json`
|
|
50
|
-
|
|
51
|
-
Данные агента хранятся в `~/.zames`: профиль браузера, логи, история undo, снапшоты self-review.
|
|
52
|
-
|
|
53
|
-
## Разработка
|
|
54
|
-
|
|
55
|
-
Для работы над самим агентом используй dev-режим:
|
|
56
|
-
|
|
57
|
-
`bash
|
|
58
|
-
npm run dev
|
|
59
|
-
`
|
|
51
|
+
## Configuration
|
|
60
52
|
|
|
61
|
-
|
|
53
|
+
Global config: `~/.zames/config.json`
|
|
54
|
+
Local (per project): `.zamesrc.json`
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
- /reload — принудительно перечитать модули логики
|
|
65
|
-
- /self-review — снять снапшот src/ и запустить самообзор
|
|
66
|
-
- /status — состояние сессии
|
|
56
|
+
Agent data is stored in `~/.zames`: browser profile, logs, undo history, self-review snapshots.
|
|
67
57
|
|
|
68
|
-
##
|
|
58
|
+
## License
|
|
69
59
|
|
|
70
60
|
MIT
|
package/logo.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/agent-loop.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildSystemPrompt } from './system-prompt.js'
|
|
2
2
|
import { getGitContext, formatGitContext } from './gitTools.js'
|
|
3
|
+
import { parseXmlToolCalls } from './xml-toolcall.js'
|
|
3
4
|
|
|
4
5
|
export async function runAgentLoop({
|
|
5
6
|
browser,
|
|
@@ -47,6 +48,12 @@ export async function runAgentLoop({
|
|
|
47
48
|
let message = task
|
|
48
49
|
transcript?.log('task', { task })
|
|
49
50
|
|
|
51
|
+
// Счётчик «ответ похож на tool-call, но не распознан». Чтобы модель,
|
|
52
|
+
// написавшая битый JSON/XML, не останавливала агента молча, мы просим
|
|
53
|
+
// её переотправить вызов. Ограничиваем число таких попыток.
|
|
54
|
+
let malformedRetries = 0
|
|
55
|
+
const MAX_MALFORMED_RETRIES = 3
|
|
56
|
+
|
|
50
57
|
for (let i = 0; i < maxIterations; i++) {
|
|
51
58
|
onThinking()
|
|
52
59
|
const rawResponse = await browser.ask(message)
|
|
@@ -69,6 +76,23 @@ export async function runAgentLoop({
|
|
|
69
76
|
}
|
|
70
77
|
|
|
71
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
|
+
|
|
72
96
|
onAssistantMessage(rawResponse)
|
|
73
97
|
transcript?.log('assistant_final', { message: rawResponse })
|
|
74
98
|
return rawResponse
|
|
@@ -531,6 +555,10 @@ function parseToolCall(text) {
|
|
|
531
555
|
if (tailPermissive) return { ...tailPermissive, _permissive: true }
|
|
532
556
|
}
|
|
533
557
|
|
|
558
|
+
// Последний fallback: модель ответила XML/DSML-блоком вместо JSON.
|
|
559
|
+
const xmlCalls = parseXmlToolCalls(cleaned)
|
|
560
|
+
if (xmlCalls) return Array.isArray(xmlCalls) ? xmlCalls : [xmlCalls]
|
|
561
|
+
|
|
534
562
|
return null
|
|
535
563
|
}
|
|
536
564
|
|
package/src/browser.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path'
|
|
|
3
3
|
import os from 'os'
|
|
4
4
|
import fs from 'fs/promises'
|
|
5
5
|
import { execSync } from 'child_process'
|
|
6
|
+
import { theme } from './theme.js'
|
|
6
7
|
|
|
7
8
|
const USER_DATA_DIR = path.join(os.homedir(), '.zames', 'profile')
|
|
8
9
|
const CHAT_URL = 'https://chat.deepseek.com/'
|
|
@@ -78,6 +79,7 @@ export class DeepSeekBrowser {
|
|
|
78
79
|
askRetries = 3,
|
|
79
80
|
stabilityChecks = 3,
|
|
80
81
|
stabilityDelayMs = 1000,
|
|
82
|
+
minSendIntervalMs = 25000,
|
|
81
83
|
} = {}) {
|
|
82
84
|
this.headless = headless
|
|
83
85
|
this.debug = debug
|
|
@@ -86,6 +88,10 @@ export class DeepSeekBrowser {
|
|
|
86
88
|
this.askRetries = askRetries
|
|
87
89
|
this.stabilityChecks = stabilityChecks
|
|
88
90
|
this.stabilityDelayMs = stabilityDelayMs
|
|
91
|
+
// Минимальный интервал между отправками в чат: DeepSeek ограничивает
|
|
92
|
+
// частоту («Messages too frequent. Try again later.»).
|
|
93
|
+
this.minSendIntervalMs = minSendIntervalMs
|
|
94
|
+
this._lastSentAt = 0
|
|
89
95
|
this.context = null
|
|
90
96
|
this.page = null
|
|
91
97
|
}
|
|
@@ -418,6 +424,20 @@ export class DeepSeekBrowser {
|
|
|
418
424
|
}
|
|
419
425
|
}
|
|
420
426
|
|
|
427
|
+
// Ждём, пока с прошлой отправки пройдёт minSendIntervalMs. Защита от
|
|
428
|
+
// «Messages too frequent. Try again later.» на [chat.deepseek.com](https://chat.deepseek.com/).
|
|
429
|
+
async _waitForSendSlot() {
|
|
430
|
+
if (!this._lastSentAt) return // первая отправка — пауза не нужна
|
|
431
|
+
const gap = this.minSendIntervalMs - (Date.now() - this._lastSentAt)
|
|
432
|
+
if (gap <= 0) return
|
|
433
|
+
console.error(
|
|
434
|
+
theme.warn(
|
|
435
|
+
`⏳ пауза ${Math.ceil(gap / 1000)}с перед отправкой (лимит частоты DeepSeek)...`,
|
|
436
|
+
),
|
|
437
|
+
)
|
|
438
|
+
await this.page.waitForTimeout(gap)
|
|
439
|
+
}
|
|
440
|
+
|
|
421
441
|
async _askOnce(prompt, { timeout }) {
|
|
422
442
|
const input = await this._findVisible(INPUT_SELECTORS, 10_000)
|
|
423
443
|
if (!input) {
|
|
@@ -428,6 +448,7 @@ export class DeepSeekBrowser {
|
|
|
428
448
|
|
|
429
449
|
const beforeText = await this._readLastAnswerTextClean().catch(() => '')
|
|
430
450
|
|
|
451
|
+
await this._waitForSendSlot()
|
|
431
452
|
await this._setInputText(input, prompt)
|
|
432
453
|
await this.page.waitForTimeout(200)
|
|
433
454
|
|
|
@@ -440,6 +461,7 @@ export class DeepSeekBrowser {
|
|
|
440
461
|
} catch {
|
|
441
462
|
await this.page.keyboard.press('Enter')
|
|
442
463
|
}
|
|
464
|
+
this._lastSentAt = Date.now()
|
|
443
465
|
|
|
444
466
|
const startDeadline = Date.now() + 15_000
|
|
445
467
|
let started = false
|