zames_pro 2.60.0 → 2.63.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/CHANGELOG.md +93 -2
- package/README.md +64 -23
- package/dist/agent-loop.js +55 -2
- package/dist/backlog.js +166 -0
- package/dist/checkpoint.js +197 -0
- package/dist/commands.js +157 -0
- package/dist/compact.js +2 -2
- package/dist/config.js +20 -0
- package/dist/context.js +88 -1
- package/dist/i18n.js +121 -0
- package/dist/index.js +390 -26
- package/dist/input.js +3 -2
- package/dist/path-token.js +75 -0
- package/dist/permissions.js +98 -0
- package/dist/system-prompt.js +39 -2
- package/logo-small.jpg +0 -0
- package/package.json +3 -2
- package/scripts/coverage-gate.mjs +2 -0
package/dist/input.js
CHANGED
|
@@ -692,10 +692,11 @@ export class LineEditor {
|
|
|
692
692
|
const shown = sugg.slice(off, off + pageSize);
|
|
693
693
|
this._suggestOffset = off;
|
|
694
694
|
this._suggestCount = sugg.length;
|
|
695
|
-
const
|
|
695
|
+
const label = (c) => c.hint ? c.name + ' ' + c.hint : c.name;
|
|
696
|
+
const maxName = Math.max(...shown.map((c) => label(c).length));
|
|
696
697
|
const lines = shown.map((c, i) => {
|
|
697
698
|
const selected = off + i === this._suggestSelected && this._suggestSelected > 0;
|
|
698
|
-
const name = theme.prompt(c.
|
|
699
|
+
const name = theme.prompt(label(c).padEnd(maxName));
|
|
699
700
|
const desc = theme.dim(' ' + c.description);
|
|
700
701
|
return (selected ? theme.prompt(' ▸ ') : ' ') + name + desc;
|
|
701
702
|
});
|
package/dist/path-token.js
CHANGED
|
@@ -51,6 +51,81 @@ function stripPunct(tok) {
|
|
|
51
51
|
t = t.slice(1);
|
|
52
52
|
return t;
|
|
53
53
|
}
|
|
54
|
+
// Characters that may directly precede an '@' for it to count as a file
|
|
55
|
+
// reference rather than a decorator/email. Without this guard `user@host` and
|
|
56
|
+
// `@Component` would be misread as paths.
|
|
57
|
+
function isRefBoundary(ch) {
|
|
58
|
+
return (ch === ' ' ||
|
|
59
|
+
ch === '\t' ||
|
|
60
|
+
ch === '\n' ||
|
|
61
|
+
ch === '\r' ||
|
|
62
|
+
ch === '(' ||
|
|
63
|
+
ch === '[' ||
|
|
64
|
+
ch === '{' ||
|
|
65
|
+
ch === String.fromCharCode(34) ||
|
|
66
|
+
ch === String.fromCharCode(39) ||
|
|
67
|
+
ch === '<');
|
|
68
|
+
}
|
|
69
|
+
// End of a reference token: whitespace, a closing bracket/quote, or an
|
|
70
|
+
// angle bracket (so `@a.ts>` stops at `>`).
|
|
71
|
+
function isRefEnd(ch) {
|
|
72
|
+
return (ch === ' ' ||
|
|
73
|
+
ch === '\t' ||
|
|
74
|
+
ch === '\n' ||
|
|
75
|
+
ch === '\r' ||
|
|
76
|
+
ch === ')' ||
|
|
77
|
+
ch === ']' ||
|
|
78
|
+
ch === '}' ||
|
|
79
|
+
ch === String.fromCharCode(34) ||
|
|
80
|
+
ch === String.fromCharCode(39) ||
|
|
81
|
+
ch === '<' ||
|
|
82
|
+
ch === '>');
|
|
83
|
+
}
|
|
84
|
+
// Strip trailing punctuation a path may have picked up from prose, e.g.
|
|
85
|
+
// "see @src/a.ts, please" -> `src/a.ts`.
|
|
86
|
+
function stripRefPunct(tok) {
|
|
87
|
+
let t = tok;
|
|
88
|
+
while (t.length && ',.;:!?'.includes(t[t.length - 1]))
|
|
89
|
+
t = t.slice(0, -1);
|
|
90
|
+
return t;
|
|
91
|
+
}
|
|
92
|
+
// Find all `@path` references in a task text (B5). Returns the unique paths in
|
|
93
|
+
// order of appearance. A reference is an '@' at a word boundary followed by a
|
|
94
|
+
// token that looks like a file name/path (a known extension). Implemented
|
|
95
|
+
// without regular expressions, like extractPathToken, to keep the source free
|
|
96
|
+
// of escaping pitfalls.
|
|
97
|
+
export function extractAtFileRefs(text) {
|
|
98
|
+
const s = String(text || '');
|
|
99
|
+
const chars = Array.from(s);
|
|
100
|
+
const n = chars.length;
|
|
101
|
+
const out = [];
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
let i = 0;
|
|
104
|
+
while (i < n) {
|
|
105
|
+
if (chars[i] !== '@') {
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const prev = i > 0 ? chars[i - 1] : '';
|
|
110
|
+
if (prev && !isRefBoundary(prev)) {
|
|
111
|
+
i++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
let j = i + 1;
|
|
115
|
+
let tok = '';
|
|
116
|
+
while (j < n && !isRefEnd(chars[j])) {
|
|
117
|
+
tok += chars[j];
|
|
118
|
+
j++;
|
|
119
|
+
}
|
|
120
|
+
const cleaned = stripRefPunct(tok);
|
|
121
|
+
if (cleaned && looksLikeToken(cleaned) && !seen.has(cleaned)) {
|
|
122
|
+
seen.add(cleaned);
|
|
123
|
+
out.push(cleaned);
|
|
124
|
+
}
|
|
125
|
+
i = j > i + 1 ? j : i + 1;
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
54
129
|
function looksLikeToken(tok) {
|
|
55
130
|
const t = String(tok || '');
|
|
56
131
|
if (!t || t.length > 500)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export function permissionsPath(workdir) {
|
|
4
|
+
return path.join(workdir, '.zames', 'permissions.json');
|
|
5
|
+
}
|
|
6
|
+
const ACTIONS = ['allow', 'deny', 'ask'];
|
|
7
|
+
function normAltion(v) {
|
|
8
|
+
if (typeof v !== 'string')
|
|
9
|
+
return null;
|
|
10
|
+
const s = v.toLowerCase();
|
|
11
|
+
return ACTIONS.includes(s) ? s : null;
|
|
12
|
+
}
|
|
13
|
+
/** Parse the JSON of a permissions file into a policy, or null when empty/broken. */
|
|
14
|
+
export function parsePermissions(raw) {
|
|
15
|
+
if (!raw || typeof raw !== 'object')
|
|
16
|
+
return null;
|
|
17
|
+
const obj = raw;
|
|
18
|
+
const defAction = normAltion(obj['default']) ?? 'allow';
|
|
19
|
+
const rules = Array.isArray(obj['rules']) ? obj['rules'] : [];
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const item of rules) {
|
|
22
|
+
if (!item || typeof item !== 'object')
|
|
23
|
+
continue;
|
|
24
|
+
const e = item;
|
|
25
|
+
const action = normAltion(e['action']);
|
|
26
|
+
if (!action)
|
|
27
|
+
continue;
|
|
28
|
+
out.push({
|
|
29
|
+
action,
|
|
30
|
+
tool: typeof e['tool'] === 'string' ? e['tool'] : undefined,
|
|
31
|
+
command: typeof e['command'] === 'string' ? e['command'] : undefined,
|
|
32
|
+
path: typeof e['path'] === 'string' ? e['path'] : undefined,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (!out.length && defAction === 'allow')
|
|
36
|
+
return null;
|
|
37
|
+
return { default: defAction, rules: out };
|
|
38
|
+
}
|
|
39
|
+
/** Read `.zames/permissions.json`. Missing/broken => null (no policy). */
|
|
40
|
+
export function loadPermissions(workdir) {
|
|
41
|
+
let raw;
|
|
42
|
+
try {
|
|
43
|
+
raw = fs.readFileSync(permissionsPath(workdir), 'utf-8');
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
return parsePermissions(JSON.parse(raw));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function safeRegex(source) {
|
|
56
|
+
try {
|
|
57
|
+
return new RegExp(source);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// A file path may arrive in any of these keys depending on the tool.
|
|
64
|
+
// We collect them all and match the path regex against ANY of them.
|
|
65
|
+
const PATH_KEYS = ['path', 'file_path', 'notebook_path', 'from', 'to'];
|
|
66
|
+
export function decidePermission(policy, tool, args) {
|
|
67
|
+
if (!policy)
|
|
68
|
+
return { action: 'allow', reason: 'no policy' };
|
|
69
|
+
const a = (args ?? {});
|
|
70
|
+
const command = typeof a['command'] === 'string' ? a['command'] : '';
|
|
71
|
+
const paths = PATH_KEYS.map((k) => a[k]).filter((v) => typeof v === 'string');
|
|
72
|
+
for (const r of policy.rules) {
|
|
73
|
+
if (r.tool) {
|
|
74
|
+
const re = safeRegex(r.tool);
|
|
75
|
+
if (!re || !re.test(tool))
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (r.command) {
|
|
79
|
+
const re = safeRegex(r.command);
|
|
80
|
+
if (!re || !command || !re.test(command))
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (r.path) {
|
|
84
|
+
const re = safeRegex(r.path);
|
|
85
|
+
if (!re || !paths.some((p) => re.test(p)))
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const desc = [
|
|
89
|
+
r.tool ? 'tool=' + r.tool : '',
|
|
90
|
+
r.command ? 'command=' + r.command : '',
|
|
91
|
+
r.path ? 'path=' + r.path : '',
|
|
92
|
+
]
|
|
93
|
+
.filter(Boolean)
|
|
94
|
+
.join(' ');
|
|
95
|
+
return { action: r.action, reason: 'rule ' + desc };
|
|
96
|
+
}
|
|
97
|
+
return { action: policy.default, reason: 'default' };
|
|
98
|
+
}
|
package/dist/system-prompt.js
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import { translate } from './i18n.js';
|
|
3
|
+
// The dev-mode BACKLOG instruction. English (agent-facing) like the rest of the
|
|
4
|
+
// prompt. Deliberately narrow: one short note at most, and only when the idea
|
|
5
|
+
// is OUTSIDE the current task — otherwise the model documents trivia instead
|
|
6
|
+
// of doing the work, and BACKLOG.md grows on every run.
|
|
7
|
+
const BACKLOG_SECTION = `## Self-improvement notes (dev mode)
|
|
8
|
+
|
|
9
|
+
You are running in the operator's dev mode, so this project is zames itself
|
|
10
|
+
and you may improve it. While working, if you notice a concrete improvement
|
|
11
|
+
that is OUTSIDE the current task — a bug in another module, a cleanup, a
|
|
12
|
+
missing test — append ONE short bullet to BACKLOG.md (Write/Edit) instead of
|
|
13
|
+
silently forgetting it, then continue the task.
|
|
14
|
+
|
|
15
|
+
Rules for that note:
|
|
16
|
+
- at most one or two per task; do NOT let it distract from the task;
|
|
17
|
+
- do NOT duplicate an item already in BACKLOG.md (read it first if unsure);
|
|
18
|
+
- keep it to a single line: what, why it matters, and a one-line fix sketch;
|
|
19
|
+
- put it under the matching priority section (P0 bug/data loss, P1 noticeable
|
|
20
|
+
pain, P2 quality, P3 nice-to-have);
|
|
21
|
+
- this is the ONE exception to "do not touch unrelated files". If nothing
|
|
22
|
+
worth noting, add nothing.
|
|
23
|
+
`;
|
|
2
24
|
/** Render the AGENTS.md / MEMORY / skills / commands blocks of the prompt. */
|
|
3
25
|
export function renderContextSection(context) {
|
|
4
26
|
if (!context)
|
|
@@ -24,6 +46,20 @@ export function renderContextSection(context) {
|
|
|
24
46
|
f.content +
|
|
25
47
|
'\n';
|
|
26
48
|
}
|
|
49
|
+
// B6: nested AGENTS.md/MEMORY.md from the directories this task touches.
|
|
50
|
+
// Deliberately a SEPARATE section: these apply only to part of the tree and
|
|
51
|
+
// must not be mistaken for project-wide rules.
|
|
52
|
+
for (const f of context.scopedAgents || []) {
|
|
53
|
+
out +=
|
|
54
|
+
'\n## Scoped instructions (' +
|
|
55
|
+
f.path +
|
|
56
|
+
')\n\n' +
|
|
57
|
+
'These rules apply ONLY to files under ' +
|
|
58
|
+
path.dirname(f.path) +
|
|
59
|
+
' (the current task touches that directory). Follow them for those files, in addition to the project instructions above.\n\n' +
|
|
60
|
+
f.content +
|
|
61
|
+
'\n';
|
|
62
|
+
}
|
|
27
63
|
if (context.memory.length) {
|
|
28
64
|
out +=
|
|
29
65
|
'\nWhen you learn a durable fact about this project or the operator preferences (build quirks, ' +
|
|
@@ -62,7 +98,7 @@ export function renderContextSection(context) {
|
|
|
62
98
|
}
|
|
63
99
|
return out;
|
|
64
100
|
}
|
|
65
|
-
export function buildSystemPrompt({ workdir, tools, gitContext = null, locale = 'ru', attachments = [], context = null, }) {
|
|
101
|
+
export function buildSystemPrompt({ workdir, tools, gitContext = null, locale = 'ru', attachments = [], context = null, selfImprovement = false, }) {
|
|
66
102
|
const t = translate(locale);
|
|
67
103
|
const toolDescriptions = tools
|
|
68
104
|
.map((t2) => `### ${t2.name}\n${t2.description}\nParameters: ${JSON.stringify(t2.parameters)}`)
|
|
@@ -75,6 +111,7 @@ export function buildSystemPrompt({ workdir, tools, gitContext = null, locale =
|
|
|
75
111
|
`Each [image#N] / [file#N] marker corresponds to a file the user pasted into the terminal; the file was uploaded to the chat and also saved under <project>/tmp. Look at the images in the chat; for files, read the copy in tmp if you need the contents.\n`
|
|
76
112
|
: '';
|
|
77
113
|
const contextSection = renderContextSection(context);
|
|
114
|
+
const selfSection = selfImprovement ? '\n' + BACKLOG_SECTION + '\n' : '';
|
|
78
115
|
return `You are a coding agent running in a terminal. You help the user with software engineering tasks by reading files, writing code, running commands, and iterating until the task is done.
|
|
79
116
|
|
|
80
117
|
You work in the directory: ${workdir}
|
|
@@ -127,7 +164,7 @@ task is done (or when you must ask the operator), not between tool calls.
|
|
|
127
164
|
You have access to the following tools:
|
|
128
165
|
|
|
129
166
|
${toolDescriptions}
|
|
130
|
-
${gitSection}${attachSection}${contextSection}
|
|
167
|
+
${gitSection}${attachSection}${contextSection}${selfSection}
|
|
131
168
|
## Choosing the right tool
|
|
132
169
|
|
|
133
170
|
Prefer the most specific tool; a wrong choice wastes a turn:
|
package/logo-small.jpg
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.63.0",
|
|
4
4
|
"description": "Terminal coding agent that drives chat.deepseek.com through Playwright: reads and edits files, runs commands, commits to git.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"README.md",
|
|
13
13
|
"CHANGELOG.md",
|
|
14
14
|
"LICENSE",
|
|
15
|
-
"logo.jpg"
|
|
15
|
+
"logo.jpg",
|
|
16
|
+
"logo-small.jpg"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"start": "node ./dist/index.js",
|