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 +36 -8
- package/dist/commands.js +13 -0
- package/dist/context.js +77 -0
- package/dist/index.js +16 -8
- package/dist/system-prompt.js +4 -3
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
body = skillBody(raw);
|
|
457
|
+
if (skill.builtinBody !== undefined) {
|
|
458
|
+
body = skill.builtinBody;
|
|
460
459
|
}
|
|
461
|
-
|
|
462
|
-
|
|
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
|
|
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(' [' +
|
|
2549
|
+
theme.dim(' [' + src + '] ') +
|
|
2543
2550
|
(s.description || theme.dim(t('common.none'))));
|
|
2544
|
-
|
|
2551
|
+
if (s.path)
|
|
2552
|
+
console.log(theme.dim(' ' + s.path));
|
|
2545
2553
|
}
|
|
2546
2554
|
}
|
|
2547
2555
|
continue;
|
package/dist/system-prompt.js
CHANGED
|
@@ -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
|
-
|
|
46
|
-
s.path);
|
|
47
|
+
loc);
|
|
47
48
|
});
|
|
48
49
|
out +=
|
|
49
50
|
'\n## Skills\n\n' +
|
|
50
|
-
'Specialized workflows
|
|
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
|
}
|