zames_pro 2.69.0 → 2.71.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.
@@ -0,0 +1,71 @@
1
+ // UI health check — a cheap "can the agent still drive this page?" probe.
2
+ //
3
+ // WHY: every selector in deepseek-ui.ts is an assumption about a page we do
4
+ // not control. When DeepSeek ships a redesign, the FIRST symptom used to be a
5
+ // task hanging on "input field not found" (or a silently mis-clicked send),
6
+ // minutes into a run. This module turns that into an explicit answer BEFORE
7
+ // the task: it checks the capabilities the agent ALWAYS needs and reports the
8
+ // missing ones. The page object is injected (the browser owns Playwright), so
9
+ // the decision logic here stays pure and unit-testable.
10
+ //
11
+ // Only ALWAYS-PRESENT capabilities are probed. The Stop button and the answer
12
+ // nodes are TRANSIENT (Stop exists only during generation, answers only after
13
+ // a turn), so a presence probe on a healthy idle page would report false
14
+ // negatives; they are deliberately excluded.
15
+ import { INPUT_SELECTORS, SEND_SELECTORS, TOGGLE_SELECTORS, NEW_CHAT_SELECTOR, NEW_CHAT_TEXT_RE, } from './deepseek-ui.js';
16
+ // Critical: without the input the prompt cannot be typed, without send it
17
+ // cannot be submitted — a task would hang on `ds.input_missing`. Advisory:
18
+ // toggles/newChat degrade gracefully (the toggles fall back to the configured
19
+ // state, newChat falls back to navigating to the base URL).
20
+ export const UI_CAPABILITIES = [
21
+ { capability: 'input', selectors: INPUT_SELECTORS, critical: true },
22
+ { capability: 'send', selectors: SEND_SELECTORS, critical: true },
23
+ { capability: 'toggles', selectors: TOGGLE_SELECTORS, critical: false },
24
+ { capability: 'newChat', selectors: [NEW_CHAT_SELECTOR], critical: false },
25
+ ];
26
+ async function hasAny(page, sel, textRe) {
27
+ try {
28
+ const n = textRe
29
+ ? await page.countByText(sel, textRe)
30
+ : await page.count(sel);
31
+ return n > 0;
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
37
+ /**
38
+ * Probe the always-present DeepSeek controls. Never throws: a dead page is
39
+ * reported as every capability missing, which the caller surfaces to the
40
+ * operator instead of letting the next task hang.
41
+ */
42
+ export async function probeUiHealth(page) {
43
+ const present = [];
44
+ const missing = [];
45
+ const missingCritical = [];
46
+ for (const spec of UI_CAPABILITIES) {
47
+ let found = false;
48
+ // The new-chat control is matched by an EXACT text label on a broad
49
+ // element set (it is a <div> on the current build, not a <button>).
50
+ const textRe = spec.capability === 'newChat' ? NEW_CHAT_TEXT_RE : undefined;
51
+ for (const sel of spec.selectors) {
52
+ if (await hasAny(page, sel, textRe)) {
53
+ found = true;
54
+ break;
55
+ }
56
+ }
57
+ if (found)
58
+ present.push(spec.capability);
59
+ else {
60
+ missing.push(spec.capability);
61
+ if (spec.critical)
62
+ missingCritical.push(spec.capability);
63
+ }
64
+ }
65
+ return {
66
+ ok: missingCritical.length === 0,
67
+ present,
68
+ missing,
69
+ missingCritical,
70
+ };
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.69.0",
3
+ "version": "2.71.0",
4
4
  "description": "Terminal coding agent that drives chat.deepseek.com through Playwright — no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,8 +42,9 @@ fs.mkdirSync(WORK, { recursive: true })
42
42
 
43
43
  // --- project modules (dynamic: they read HOME at import) --------------------
44
44
  const { DeepSeekBrowser } = await import('../src/browser.ts')
45
- const { createTools } = await import('../src/tools.ts')
45
+ const { createTools, filterToolsForReadOnly } = await import('../src/tools.ts')
46
46
  const { runAgentLoop } = await import('../src/agent-loop.ts')
47
+ const { createSubagentRunner } = await import('../src/subagent.ts')
47
48
  const { loadConfig } = await import('../src/config.ts')
48
49
  const { Transcript } = await import('../src/transcript.ts')
49
50
 
@@ -149,6 +150,49 @@ async function main() {
149
150
  maxAfterToolRetries: cfg.browser.maxAfterToolRetries,
150
151
  })
151
152
  record('follow-up answered', outC.trim().length > 0, JSON.stringify(outC.slice(0, 80)))
153
+ const onSubagent = createSubagentRunner({
154
+ browser,
155
+ workdir: WORK,
156
+ locale: 'ru',
157
+ runAgentLoop,
158
+ buildTools: (readOnly) => {
159
+ const base = createTools(WORK, {})
160
+ return readOnly ? filterToolsForReadOnly(base) : base
161
+ },
162
+ transcript,
163
+ maxSubagents: 2,
164
+ askDeadlineMs: cfg.browser.askDeadlineMs,
165
+ maxAfterToolRetries: cfg.browser.maxAfterToolRetries,
166
+ })
167
+
168
+ // Scenario D — subagents (N36): the model delegates via Task; the runner
169
+ // opens a separate chat, returns only the report, and restores the parent
170
+ // chat. Requires browser.subagents (off by default).
171
+ const subTools = createTools(WORK, { subagents: true })
172
+ const outD = await runAgentLoop({
173
+ browser,
174
+ tools: subTools,
175
+ task:
176
+ 'Use the Task tool with subagent_type "explore" to find which word is written in the file smoke.txt. ' +
177
+ 'prompt the subagent in a self-contained way. Then, based on its report, call respond with that word.',
178
+ workdir: WORK,
179
+ maxIterations: 20,
180
+ freshChat: true,
181
+ sendSystemPrompt: true,
182
+ transcript,
183
+ onSubagent,
184
+ onToolCall: (n) => console.log(' tool: ' + n),
185
+ onAssistantMessage: (m) => console.log(' answer: ' + m),
186
+ onWarning: (m) => console.log(' warn: ' + m),
187
+ locale: 'ru',
188
+ askDeadlineMs: cfg.browser.askDeadlineMs,
189
+ maxAfterToolRetries: cfg.browser.maxAfterToolRetries,
190
+ })
191
+ record(
192
+ 'subagent: parent used the report',
193
+ /HELLO_SMOKE/i.test(outD),
194
+ JSON.stringify(outD.slice(0, 120)),
195
+ )
152
196
  }
153
197
 
154
198
  let failed = false