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/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 maxName = Math.max(...shown.map((c) => c.name.length));
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.name.padEnd(maxName));
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
  });
@@ -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
+ }
@@ -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.60.0",
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",
@@ -28,6 +28,8 @@ const FLOORS = {
28
28
  'tools.ts': 90,
29
29
  'system-prompt.ts': 90,
30
30
  'undo.ts': 85,
31
+ 'checkpoint.ts': 80,
32
+ 'permissions.ts': 85,
31
33
  }
32
34
 
33
35
  const run = spawnSync(