zames_pro 2.47.0 → 2.48.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.47.0",
3
+ "version": "2.48.0",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  "prepublishOnly": "npm run build",
26
26
  "test": "tsx --test test/*.test.ts",
27
27
  "test:watch": "tsx --test --watch test/*.test.ts",
28
+ "self-smoke": "tsx scripts/self-smoke.mjs",
28
29
  "postinstall": "node scripts/postinstall.mjs",
29
30
  "prepare": "husky"
30
31
  },
@@ -0,0 +1,174 @@
1
+ // self-smoke: launch a REAL isolated zames instance and exercise the core
2
+ // functionality end-to-end (login, tools, attachments), WITHOUT touching the
3
+ // operator's ~/.zames profile. Used by the agent to check its own build.
4
+ //
5
+ // Isolation: the script points HOME at a throwaway dir BEFORE importing any
6
+ // project module (they compute ~/.zames paths at load time), copies the real
7
+ // config.json (so auto-login works) and symlinks the Playwright browser cache.
8
+ // Nothing in the operator's profile is written.
9
+ //
10
+ // Usage: npm run self-smoke (headless)
11
+ // npm run self-smoke -- --headed
12
+ // Exit code 0 = all scenarios passed.
13
+
14
+ import fs from 'fs'
15
+ import path from 'path'
16
+ import os from 'os'
17
+
18
+ const HEADED = process.argv.includes('--headed')
19
+
20
+ // --- isolate HOME before importing project modules -------------------------
21
+ const REAL_HOME = os.homedir()
22
+ if (!process.env.ZAMES_SMOKE_HOME) {
23
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'zames-smoke-'))
24
+ process.env.HOME = tmp
25
+ process.env.ZAMES_SMOKE_HOME = tmp
26
+ fs.mkdirSync(path.join(tmp, '.zames'), { recursive: true })
27
+ fs.mkdirSync(path.join(tmp, '.cache'), { recursive: true })
28
+ // Credentials for auto-login (copied, never written back to the real home).
29
+ const realCfg = path.join(REAL_HOME, '.zames', 'config.json')
30
+ if (fs.existsSync(realCfg)) {
31
+ fs.copyFileSync(realCfg, path.join(tmp, '.zames', 'config.json'))
32
+ }
33
+ // Playwright browsers live in the real cache; link it read-only.
34
+ const realPw = path.join(REAL_HOME, '.cache', 'ms-playwright')
35
+ if (fs.existsSync(realPw)) {
36
+ fs.symlinkSync(realPw, path.join(tmp, '.cache', 'ms-playwright'))
37
+ }
38
+ }
39
+ const SMOKE_HOME = process.env.ZAMES_SMOKE_HOME
40
+ const WORK = path.join(SMOKE_HOME, 'work')
41
+ fs.mkdirSync(WORK, { recursive: true })
42
+
43
+ // --- project modules (dynamic: they read HOME at import) --------------------
44
+ const { DeepSeekBrowser } = await import('../src/browser.ts')
45
+ const { createTools } = await import('../src/tools.ts')
46
+ const { runAgentLoop } = await import('../src/agent-loop.ts')
47
+ const { loadConfig } = await import('../src/config.ts')
48
+ const { Transcript } = await import('../src/transcript.ts')
49
+
50
+ const results = []
51
+ const record = (name, ok, note) => {
52
+ results.push({ name, ok, note })
53
+ console.log((ok ? 'PASS ' : 'FAIL ') + name + (note ? ' — ' + note : ''))
54
+ }
55
+
56
+ const cfg = loadConfig()
57
+ const transcript = new Transcript({
58
+ dir: path.join(SMOKE_HOME, '.zames', 'logs'),
59
+ enabled: true,
60
+ sessionName: 'self-smoke',
61
+ })
62
+
63
+ const browser = new DeepSeekBrowser({
64
+ headless: !HEADED,
65
+ debug: false,
66
+ channel: cfg.browserChannel,
67
+ auth: cfg.browser.auth,
68
+ locale: 'ru',
69
+ minSendIntervalMs: cfg.browser.minSendIntervalMs,
70
+ })
71
+
72
+ async function main() {
73
+ console.log('self-smoke: HOME=' + SMOKE_HOME + ' headed=' + HEADED)
74
+ await browser.launch()
75
+ await browser.waitForLogin()
76
+ record('login', true, 'DeepSeek session is active')
77
+
78
+ const tools = createTools(WORK, {})
79
+
80
+ // Scenario A — tools (Write, Read, Bash) + respond.
81
+ const taskA =
82
+ 'Do exactly this, then report: (1) Write a file smoke.txt with the single line HELLO_SMOKE. ' +
83
+ '(2) Read it back. (3) Run the Bash command `echo bash_ok`. ' +
84
+ 'Then call respond with one short line that contains the words HELLO_SMOKE and bash_ok.'
85
+ const outA = await runAgentLoop({
86
+ browser,
87
+ tools,
88
+ task: taskA,
89
+ workdir: WORK,
90
+ maxIterations: 25,
91
+ freshChat: true,
92
+ sendSystemPrompt: true,
93
+ transcript,
94
+ onToolCall: (n) => console.log(' tool: ' + n),
95
+ onToolResult: () => {},
96
+ onAssistantMessage: (m) => console.log(' answer: ' + m),
97
+ onWarning: (m) => console.log(' warn: ' + m),
98
+ locale: 'ru',
99
+ askDeadlineMs: cfg.browser.askDeadlineMs,
100
+ maxAfterToolRetries: cfg.browser.maxAfterToolRetries,
101
+ })
102
+ const fileMade = fs.existsSync(path.join(WORK, 'smoke.txt'))
103
+ record('tools: file created', fileMade, 'smoke.txt')
104
+ record(
105
+ 'tools: agent reported',
106
+ /HELLO_SMOKE/.test(outA) && /bash_ok/.test(outA),
107
+ JSON.stringify(outA.slice(0, 120)),
108
+ )
109
+
110
+ // Scenario B — attachment: the model must read a file attached to the message.
111
+ const note = path.join(WORK, 'note.txt')
112
+ fs.writeFileSync(note, 'ATTACH_MARKER_42')
113
+ const outB = await runAgentLoop({
114
+ browser,
115
+ tools,
116
+ task: 'Reply with the exact content of the attached file, nothing else.',
117
+ workdir: WORK,
118
+ maxIterations: 12,
119
+ freshChat: true,
120
+ sendSystemPrompt: true,
121
+ transcript,
122
+ attachments: [{ path: note, name: 'note.txt', mime: 'text/plain' }],
123
+ onAssistantMessage: (m) => console.log(' answer: ' + m),
124
+ onWarning: (m) => console.log(' warn: ' + m),
125
+ locale: 'ru',
126
+ askDeadlineMs: cfg.browser.askDeadlineMs,
127
+ maxAfterToolRetries: cfg.browser.maxAfterToolRetries,
128
+ })
129
+ record(
130
+ 'attachment: model read the file',
131
+ /ATTACH_MARKER_42/.test(outB),
132
+ JSON.stringify(outB.slice(0, 120)),
133
+ )
134
+
135
+ // Scenario C — follow-up in the SAME chat (context carried over).
136
+ const outC = await runAgentLoop({
137
+ browser,
138
+ tools,
139
+ task: 'What single word did I ask you to remember in the previous message? Answer with just that word.',
140
+ workdir: WORK,
141
+ maxIterations: 8,
142
+ freshChat: false,
143
+ sendSystemPrompt: false,
144
+ transcript,
145
+ onAssistantMessage: (m) => console.log(' answer: ' + m),
146
+ onWarning: (m) => console.log(' warn: ' + m),
147
+ locale: 'ru',
148
+ askDeadlineMs: cfg.browser.askDeadlineMs,
149
+ maxAfterToolRetries: cfg.browser.maxAfterToolRetries,
150
+ })
151
+ record('follow-up answered', outC.trim().length > 0, JSON.stringify(outC.slice(0, 80)))
152
+ }
153
+
154
+ let failed = false
155
+ try {
156
+ await main()
157
+ } catch (e) {
158
+ record('run', false, e.message)
159
+ failed = true
160
+ } finally {
161
+ await browser.close().catch(() => {})
162
+ transcript.close()
163
+ }
164
+
165
+ const passed = results.filter((r) => r.ok).length
166
+ console.log('\nself-smoke: ' + passed + '/' + results.length + ' passed')
167
+ const anyFail = failed || results.some((r) => !r.ok)
168
+ if (!process.env.ZAMES_SMOKE_KEEP) {
169
+ // Clean the throwaway HOME unless the caller asked to keep it for debugging.
170
+ try {
171
+ fs.rmSync(SMOKE_HOME, { recursive: true, force: true })
172
+ } catch {}
173
+ }
174
+ process.exit(anyFail ? 1 : 0)