zames_pro 2.41.1 → 2.43.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/dist/browser.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { chromium, } from 'playwright';
2
2
  import { extractAnswer, extractTokenUsage, dumpNetBody, isFinishedWithoutAnswer, } from './net-capture.js';
3
+ import { rewriteFailedAttachments } from './commands.js';
3
4
  import path from 'path';
4
5
  import os from 'os';
5
6
  import fs from 'fs/promises';
@@ -391,6 +392,12 @@ export class DeepSeekBrowser {
391
392
  // the agent hung until the timeout.
392
393
  _netBody;
393
394
  _netHookInstalled;
395
+ // The page object the net hook was installed on. A RELAUNCH (e.g. the
396
+ // headless UA fix) creates a NEW page; comparing against this lets
397
+ // _installNetHook re-attach to the new page instead of silently skipping
398
+ // (which left the post-fix page with no capture and "no answer" in
399
+ // headless, while headed worked).
400
+ _netHookPage;
394
401
  // Fired right when a message is actually typed/sent (AFTER the send-pause
395
402
  // and attachments). Used to start the "agent is working" spinner only when
396
403
  // a generation really begins, not during the pre-send phase (chat open,
@@ -462,6 +469,7 @@ export class DeepSeekBrowser {
462
469
  this._lastHistoryError = '';
463
470
  this._netBody = '';
464
471
  this._netHookInstalled = false;
472
+ this._netHookPage = null;
465
473
  this.onSendStart = null;
466
474
  this.onSendPause = null;
467
475
  this.onSendState = null;
@@ -609,12 +617,16 @@ export class DeepSeekBrowser {
609
617
  await this.page.goto(CHAT_URL, { waitUntil: 'domcontentloaded' });
610
618
  }
611
619
  _installNetHook() {
612
- if (this._netHookInstalled)
613
- return;
614
620
  const pg = this['page'];
615
621
  if (!pg)
616
622
  return;
623
+ // Skip only when the hook is ALREADY on THIS page. After a relaunch the
624
+ // page object changed, so we must attach to the new one — otherwise the
625
+ // capture stayed dead and ask() saw "no answer".
626
+ if (this._netHookInstalled && this._netHookPage === pg)
627
+ return;
617
628
  this._netHookInstalled = true;
629
+ this._netHookPage = pg;
618
630
  pg.on('response', (resp) => {
619
631
  void this._onResponse(resp).catch(() => { });
620
632
  });
@@ -1848,7 +1860,12 @@ export class DeepSeekBrowser {
1848
1860
  // Attach files/images to the chat via the hidden <input type=file> of the
1849
1861
  // DeepSeek upload widget. The element is not visible, so we set the files
1850
1862
  // programmatically (Playwright setInputFiles works on hidden inputs too).
1863
+ // Returns the files that could NOT be attached (read error, no upload
1864
+ // widget, or a failed upload), so the caller can rewrite their path in the
1865
+ // message to [attach-failed: name]. The model must not believe a file was
1866
+ // attached when it was not.
1851
1867
  async _attachFiles(files) {
1868
+ const failed = [];
1852
1869
  const bufPayload = [];
1853
1870
  for (const f of files) {
1854
1871
  try {
@@ -1856,6 +1873,7 @@ export class DeepSeekBrowser {
1856
1873
  bufPayload.push({ name: f.name, mimeType: f.mime, buffer });
1857
1874
  }
1858
1875
  catch (e) {
1876
+ failed.push({ path: f.path, name: f.name });
1859
1877
  this._notice(theme.warn(this._t('ds.attach_read_failed', {
1860
1878
  name: f.name,
1861
1879
  error: e.message,
@@ -1863,7 +1881,7 @@ export class DeepSeekBrowser {
1863
1881
  }
1864
1882
  }
1865
1883
  if (!bufPayload.length)
1866
- return;
1884
+ return failed;
1867
1885
  // Preferred path: click the attach button and let the file chooser event
1868
1886
  // carry the files. This is how a real user attaches a file and it reliably
1869
1887
  // triggers DeepSeek's upload handler.
@@ -1889,7 +1907,7 @@ export class DeepSeekBrowser {
1889
1907
  await chooser.setFiles(bufPayload);
1890
1908
  // Wait for the upload preview instead of a fixed 2.5s sleep.
1891
1909
  await this._awaitUploadPreview(files.map((f) => f.name), 2500);
1892
- return;
1910
+ return failed;
1893
1911
  }
1894
1912
  catch { }
1895
1913
  }
@@ -1897,17 +1915,22 @@ export class DeepSeekBrowser {
1897
1915
  const input = this.page.locator('input[type="file"]').first();
1898
1916
  if ((await input.count()) === 0) {
1899
1917
  this._notice(theme.warn(this._t('ds.attach_no_input')));
1900
- return;
1918
+ for (const f of files)
1919
+ failed.push({ path: f.path, name: f.name });
1920
+ return failed;
1901
1921
  }
1902
1922
  try {
1903
1923
  await input.setInputFiles(bufPayload, { timeout: 15_000 });
1904
1924
  }
1905
1925
  catch (e) {
1906
1926
  this._notice(theme.warn(this._t('ds.attach_failed', { error: e.message })));
1907
- return;
1927
+ for (const f of files)
1928
+ failed.push({ path: f.path, name: f.name });
1929
+ return failed;
1908
1930
  }
1909
1931
  // Wait for the upload to finish (the attach preview to appear).
1910
1932
  await this._awaitUploadPreview(files.map((f) => f.name), 2000);
1933
+ return failed;
1911
1934
  }
1912
1935
  // Wait for the upload preview (the attached-file chip above the
1913
1936
  // input) to appear, instead of a fixed 2.5s/2s sleep. The signal is the
@@ -2092,10 +2115,15 @@ export class DeepSeekBrowser {
2092
2115
  await this._applyToggles();
2093
2116
  // Attach files/images FIRST (before the text): the DeepSeek upload widget
2094
2117
  // shows them above the input, and only then the message can be sent.
2118
+ // A file that FAILED to attach has its real path in the prompt text (see
2119
+ // substituteAttachmentMarkers); rewrite it to [attach-failed: name] so the
2120
+ // model does not believe the file is available.
2121
+ let finalPrompt = prompt;
2095
2122
  if (attachments.length) {
2096
- await this._attachFiles(attachments);
2123
+ const failed = await this._attachFiles(attachments);
2124
+ finalPrompt = rewriteFailedAttachments(finalPrompt, failed);
2097
2125
  }
2098
- await this._setInputText(input, prompt);
2126
+ await this._setInputText(input, finalPrompt);
2099
2127
  // Send immediately: the input is already verified by _setInputText, and a
2100
2128
  // clickable send button normally appears right away. The old code slept a
2101
2129
  // fixed 50ms here, which only added latency (the button does not need it).
package/dist/commands.js CHANGED
@@ -532,6 +532,19 @@ export function substituteAttachmentMarkers(text, attachments) {
532
532
  out = out.replace(/\[file#(\d+)\]/g, (whole, n) => filePath.get(Number(n)) ?? whole);
533
533
  return out;
534
534
  }
535
+ // Rewrite the paths of files that FAILED to attach into [attach-failed: name]
536
+ // in the task text, so the model does not believe an unavailable file was
537
+ // attached. The prompt carries real paths (substituteAttachmentMarkers), so a
538
+ // failed file's path is replaced in place. Pure, so it is unit-tested.
539
+ export function rewriteFailedAttachments(text, failed) {
540
+ let out = String(text ?? '');
541
+ for (const f of failed || []) {
542
+ if (!f || !f.path)
543
+ continue;
544
+ out = out.split(f.path).join('[attach-failed: ' + f.name + ']');
545
+ }
546
+ return out;
547
+ }
535
548
  // True when a `/config ...` line can be handled LIVE (while the agent is busy):
536
549
  // the text subcommands only touch config values, never the chat. The bare
537
550
  // `/config` and `/config menu` (which pause the editor and read keys) are NOT
package/dist/context.js CHANGED
@@ -1,6 +1,65 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
+ // Built-in skills, always available unless a project/global skill with the
5
+ // same name overrides them (real files win). They give the agent proven,
6
+ // reusable workflows in the spirit of Claude Code / Codex. English only (the
7
+ // body goes into the agent-facing task text).
8
+ const BUILTIN_SKILLS = [
9
+ {
10
+ name: 'commit',
11
+ description: 'Prepare a clean git commit from the current changes (stage, message).',
12
+ body: 'Prepare a commit from the current working-tree changes. Steps:\n' +
13
+ '1. Run `git status` and `git diff` (and `git diff --staged`) to see exactly what changed.\n' +
14
+ '2. Stage ONLY the files that belong to the intended change — never `git add -A` blindly.\n' +
15
+ '3. Write a short imperative subject line (<=72 chars) and an optional body explaining WHY.\n' +
16
+ '4. Commit with `git commit`. Do NOT push.\n' +
17
+ 'If the changes are unrelated or include junk, report that and ask before committing.',
18
+ },
19
+ {
20
+ name: 'review',
21
+ description: 'Review the current diff for bugs, risks and style issues (no code changes).',
22
+ body: 'Review the current uncommitted diff. Do NOT change any code — only report.\n' +
23
+ '1. Get the diff with `git diff` (and `git diff --staged`).\n' +
24
+ '2. Look for real problems: correctness bugs, unhandled edge cases, races, ' +
25
+ 'silent failures, security issues, broken error handling.\n' +
26
+ '3. For each finding give file:line, what is wrong and WHY it matters.\n' +
27
+ '4. End with a short verdict: safe to commit / needs fixes, and list the must-fix items first.',
28
+ },
29
+ {
30
+ name: 'test',
31
+ description: 'Run the project tests and fix failures until green.',
32
+ body: 'Run the project test suite and make it green. Steps:\n' +
33
+ '1. Find the test command (package.json scripts, Makefile, README).\n' +
34
+ '2. Run it. If it passes, report the result and stop.\n' +
35
+ '3. On failure, read the failing test and the code it exercises. Fix the ROOT cause ' +
36
+ '(do not weaken or delete the test to make it pass, unless the test itself is wrong).\n' +
37
+ '4. Re-run until it passes. Report what changed and the final result.',
38
+ },
39
+ {
40
+ name: 'debug',
41
+ description: 'Systematically diagnose and fix a bug.',
42
+ body: 'Diagnose a bug systematically. Steps:\n' +
43
+ '1. Reproduce it: find the smallest command/steps that show the failure.\n' +
44
+ '2. Form a hypothesis about the cause and find evidence in the code (read the actual code path).\n' +
45
+ '3. Narrow it down (logs, a focused test, a minimal repro) BEFORE editing.\n' +
46
+ '4. Fix the ROOT cause, not the symptom. Add a regression test that fails before and passes after.\n' +
47
+ '5. Verify the fix and report the cause, the change and the test.',
48
+ },
49
+ {
50
+ name: 'explain',
51
+ description: 'Explain how a file/module/feature works.',
52
+ body: 'Explain how the requested code works, for a developer new to it.\n' +
53
+ '1. Read the relevant files (start from the entry point and follow the calls).\n' +
54
+ '2. Give a concise overview: what it does, its inputs/outputs, the main flow.\n' +
55
+ '3. Call out non-obvious details: side effects, ordering, error handling, ' +
56
+ 'couplings, and any WHY-comments that matter.\n' +
57
+ '4. Do NOT change code. Answer in the operator language.',
58
+ },
59
+ ];
60
+ export function builtinSkills() {
61
+ return BUILTIN_SKILLS;
62
+ }
4
63
  const MAX_FILE_BASE = 60000;
5
64
  const MAX_TOTAL = 240000;
6
65
  const CRLF_RE = /\r\n/g;
@@ -190,6 +249,24 @@ export async function loadSkills(workdir) {
190
249
  }
191
250
  }
192
251
  found.sort((a, b) => a.name.localeCompare(b.name));
252
+ // Built-in skills come LAST (lowest priority): any project/global skill
253
+ // with the same name already claimed `seen` and wins. Only the not-overridden
254
+ // built-ins are appended, with an empty path and the body inline.
255
+ for (const b of BUILTIN_SKILLS) {
256
+ const key = b.name.toLowerCase();
257
+ if (seen.has(key))
258
+ continue;
259
+ seen.add(key);
260
+ found.push({
261
+ name: b.name,
262
+ description: b.description,
263
+ path: '',
264
+ dir: '',
265
+ source: 'global',
266
+ userInvokable: true,
267
+ builtinBody: b.body,
268
+ });
269
+ }
193
270
  return found;
194
271
  }
195
272
  const PROJECT_COMMAND_DIRS = [
package/dist/index.js CHANGED
@@ -454,14 +454,20 @@ async function expandSlashTarget(workdir, name, rest) {
454
454
  const skill = skills.find((s) => s.name.toLowerCase() === name.toLowerCase() && s.userInvokable);
455
455
  if (skill) {
456
456
  let body = '';
457
- try {
458
- const raw = await fs.readFile(skill.path, 'utf-8');
459
- body = skillBody(raw);
457
+ if (skill.builtinBody !== undefined) {
458
+ body = skill.builtinBody;
460
459
  }
461
- catch {
462
- body = '';
460
+ else {
461
+ try {
462
+ const raw = await fs.readFile(skill.path, 'utf-8');
463
+ body = skillBody(raw);
464
+ }
465
+ catch {
466
+ body = '';
467
+ }
463
468
  }
464
- const header = 'Follow the skill "' + skill.name + '" (from ' + skill.path + ').';
469
+ const from = skill.path ? ' (from ' + skill.path + ')' : ' (built-in)';
470
+ const header = 'Follow the skill "' + skill.name + '"' + from + '.';
465
471
  const extra = rest
466
472
  ? '\n\nAdditional instructions from the operator: ' + rest
467
473
  : '';
@@ -2537,11 +2543,13 @@ async function main() {
2537
2543
  else {
2538
2544
  console.log(theme.system(t('skills.title', { n: String(skills.length) })));
2539
2545
  for (const s of skills) {
2546
+ const src = s.path ? s.source : 'built-in';
2540
2547
  console.log(' ' +
2541
2548
  theme.user('/' + s.name) +
2542
- theme.dim(' [' + s.source + '] ') +
2549
+ theme.dim(' [' + src + '] ') +
2543
2550
  (s.description || theme.dim(t('common.none'))));
2544
- console.log(theme.dim(' ' + s.path));
2551
+ if (s.path)
2552
+ console.log(theme.dim(' ' + s.path));
2545
2553
  }
2546
2554
  }
2547
2555
  continue;
@@ -37,17 +37,18 @@ export function renderContextSection(context) {
37
37
  const tools = s.allowedTools && s.allowedTools.length
38
38
  ? ' [tools: ' + s.allowedTools.join(', ') + ']'
39
39
  : '';
40
+ // A built-in skill (no file on disk) says so instead of an empty path.
41
+ const loc = s.path ? '\n file: ' + s.path : '\n (built-in)';
40
42
  return ('- ' +
41
43
  s.name +
42
44
  tools +
43
45
  ': ' +
44
46
  (s.description || '(no description)') +
45
- '\n file: ' +
46
- s.path);
47
+ loc);
47
48
  });
48
49
  out +=
49
50
  '\n## Skills\n\n' +
50
- 'Specialized workflows available as SKILL.md files. When a task matches a skill description, read its file and follow its instructions. Skills are progressive disclosure: only read the body when the skill applies.\n\n' +
51
+ 'Specialized workflows. A skill with a `file:` is a SKILL.md on disk (read it when it applies); a `(built-in)` skill has no file — the operator can invoke it with /<name> and its instructions arrive as the task. Skills are progressive disclosure: only read the body when the skill applies.\n\n' +
51
52
  lines.join('\n') +
52
53
  '\n';
53
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.41.1",
3
+ "version": "2.43.0",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {