zames_pro 2.61.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 +63 -2
- package/README.md +62 -13
- package/dist/agent-loop.js +54 -2
- package/dist/checkpoint.js +197 -0
- package/dist/commands.js +135 -0
- package/dist/config.js +20 -0
- package/dist/context.js +88 -1
- package/dist/i18n.js +89 -0
- package/dist/index.js +232 -12
- package/dist/input.js +3 -2
- package/dist/path-token.js +75 -0
- package/dist/permissions.js +98 -0
- package/dist/system-prompt.js +15 -0
- package/logo-small.jpg +0 -0
- package/package.json +3 -2
- package/scripts/coverage-gate.mjs +2 -0
package/dist/config.js
CHANGED
|
@@ -22,6 +22,12 @@ export const DEFAULTS = {
|
|
|
22
22
|
enabled: true,
|
|
23
23
|
maxBackups: 200,
|
|
24
24
|
},
|
|
25
|
+
// BACKLOG B3: snapshot the whole working tree at the start of each task so
|
|
26
|
+
// /rewind can roll a risky task back in one step.
|
|
27
|
+
checkpoint: {
|
|
28
|
+
enabled: true,
|
|
29
|
+
maxBackups: 50,
|
|
30
|
+
},
|
|
25
31
|
transcript: {
|
|
26
32
|
enabled: true,
|
|
27
33
|
dir: path.join(os.homedir(), '.zames', 'logs'),
|
|
@@ -162,6 +168,20 @@ export const CONFIG_SCHEMA = [
|
|
|
162
168
|
labelKey: 'cfg.f.undo_maxBackups',
|
|
163
169
|
groupKey: 'cfg.group.undo',
|
|
164
170
|
},
|
|
171
|
+
{
|
|
172
|
+
path: 'checkpoint.enabled',
|
|
173
|
+
type: 'boolean',
|
|
174
|
+
labelKey: 'cfg.f.checkpoint_enabled',
|
|
175
|
+
groupKey: 'cfg.group.checkpoint',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
path: 'checkpoint.maxBackups',
|
|
179
|
+
type: 'number',
|
|
180
|
+
min: 1,
|
|
181
|
+
max: 1000,
|
|
182
|
+
labelKey: 'cfg.f.checkpoint_maxBackups',
|
|
183
|
+
groupKey: 'cfg.group.checkpoint',
|
|
184
|
+
},
|
|
165
185
|
{
|
|
166
186
|
path: 'transcript.enabled',
|
|
167
187
|
type: 'boolean',
|
package/dist/context.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
|
+
import { statSync } from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import os from 'os';
|
|
4
5
|
// Built-in skills, always available unless a project/global skill with the
|
|
@@ -169,11 +170,21 @@ function parseFrontmatter(raw) {
|
|
|
169
170
|
const userInvokable = typeof out['user-invokable'] === 'string'
|
|
170
171
|
? out['user-invokable'].toLowerCase() !== 'false'
|
|
171
172
|
: undefined;
|
|
173
|
+
const argumentsList = typeof out['arguments'] === 'string'
|
|
174
|
+
? out['arguments']
|
|
175
|
+
.split(/[,\s]+/)
|
|
176
|
+
.map((s) => s.trim())
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
: undefined;
|
|
172
179
|
return {
|
|
173
180
|
name: out.name,
|
|
174
181
|
description: out.description,
|
|
175
182
|
allowedTools,
|
|
176
183
|
userInvokable,
|
|
184
|
+
argumentHint: typeof out['argument-hint'] === 'string'
|
|
185
|
+
? out['argument-hint']
|
|
186
|
+
: undefined,
|
|
187
|
+
arguments: argumentsList,
|
|
177
188
|
};
|
|
178
189
|
}
|
|
179
190
|
export function skillBody(raw) {
|
|
@@ -302,6 +313,8 @@ async function loadCommandsFromDir(dir) {
|
|
|
302
313
|
description: (fm.description || '').replace(/\s+/g, ' ').trim(),
|
|
303
314
|
path: full,
|
|
304
315
|
body: skillBody(raw),
|
|
316
|
+
argumentHint: fm.argumentHint,
|
|
317
|
+
arguments: fm.arguments,
|
|
305
318
|
});
|
|
306
319
|
}
|
|
307
320
|
return out;
|
|
@@ -376,13 +389,14 @@ async function loadMemoryChain(workdir) {
|
|
|
376
389
|
}
|
|
377
390
|
return out;
|
|
378
391
|
}
|
|
379
|
-
export async function loadProjectContext(workdir) {
|
|
392
|
+
export async function loadProjectContext(workdir, touchPaths = []) {
|
|
380
393
|
const [agents, memory, skills, commands] = await Promise.all([
|
|
381
394
|
loadAgentsChain(workdir).catch(() => []),
|
|
382
395
|
loadMemoryChain(workdir).catch(() => []),
|
|
383
396
|
loadSkills(workdir).catch(() => []),
|
|
384
397
|
loadCommands(workdir).catch(() => []),
|
|
385
398
|
]);
|
|
399
|
+
const scopedAgents = await loadScopedAgents(workdir, touchPaths).catch(() => []);
|
|
386
400
|
let used = 0;
|
|
387
401
|
const withinBudget = (files) => {
|
|
388
402
|
const kept = [];
|
|
@@ -396,8 +410,81 @@ export async function loadProjectContext(workdir) {
|
|
|
396
410
|
};
|
|
397
411
|
return {
|
|
398
412
|
agents: withinBudget(agents),
|
|
413
|
+
// Scoped files are dropped silently when the total budget is already spent
|
|
414
|
+
// on the main chain: a nested AGENTS.md is an optimization, not a hard
|
|
415
|
+
// requirement, and must never push out a top-level instruction.
|
|
416
|
+
scopedAgents: withinBudget(scopedAgents),
|
|
399
417
|
memory: withinBudget(memory),
|
|
400
418
|
skills,
|
|
401
419
|
commands,
|
|
402
420
|
};
|
|
403
421
|
}
|
|
422
|
+
// A directory is "touched" by a task when the task text mentions a path inside
|
|
423
|
+
// it (a `@ref`, a quoted path, a bare `src/foo.ts`). We look for the deepest
|
|
424
|
+
// existing directories named in the text and collect AGENTS.md/MEMORY.md from
|
|
425
|
+
// them and their ancestors below `workdir`. Implemented by scanning word tokens
|
|
426
|
+
// rather than a regex over the whole text so a long prompt stays cheap.
|
|
427
|
+
function touchedDirs(workdir, text) {
|
|
428
|
+
const root = path.resolve(workdir);
|
|
429
|
+
const out = new Set();
|
|
430
|
+
const words = String(text || '')
|
|
431
|
+
.split(/\s+/)
|
|
432
|
+
.map((w) => w.replace(/^[\('"\[]+/, '').replace(/[\),.;:'"\]]+$/, ''));
|
|
433
|
+
for (const w of words) {
|
|
434
|
+
if (!w || w.length > 300)
|
|
435
|
+
continue;
|
|
436
|
+
// Only path-looking tokens (contain a separator or a known file extension).
|
|
437
|
+
if (!/[\\/]/.test(w) && !/\.[A-Za-z0-9]{1,8}$/.test(w))
|
|
438
|
+
continue;
|
|
439
|
+
const abs = path.isAbsolute(w) ? w : path.resolve(root, w);
|
|
440
|
+
if (abs !== root && !abs.startsWith(root + path.sep))
|
|
441
|
+
continue;
|
|
442
|
+
// Walk from the deepest directory that actually exists up to the root.
|
|
443
|
+
let dir = abs;
|
|
444
|
+
let st = statSyncSafe(dir);
|
|
445
|
+
while (!st && dir !== root && dir.startsWith(root + path.sep)) {
|
|
446
|
+
dir = path.dirname(dir);
|
|
447
|
+
st = statSyncSafe(dir);
|
|
448
|
+
}
|
|
449
|
+
if (!st || !st.isDirectory())
|
|
450
|
+
continue;
|
|
451
|
+
let cur = dir;
|
|
452
|
+
while (cur.startsWith(root + path.sep)) {
|
|
453
|
+
out.add(cur);
|
|
454
|
+
cur = path.dirname(cur);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return [...out];
|
|
458
|
+
}
|
|
459
|
+
function statSyncSafe(p) {
|
|
460
|
+
try {
|
|
461
|
+
return statSync(p);
|
|
462
|
+
}
|
|
463
|
+
catch {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async function loadScopedAgents(workdir, touchPaths) {
|
|
468
|
+
if (!touchPaths.length)
|
|
469
|
+
return [];
|
|
470
|
+
const root = path.resolve(workdir);
|
|
471
|
+
// Collect every directory named by the task, deepest first, so the most
|
|
472
|
+
// specific instructions come first and win the budget.
|
|
473
|
+
const dirs = touchedDirs(root, touchPaths.join(' '));
|
|
474
|
+
dirs.sort((a, b) => b.length - a.length);
|
|
475
|
+
const out = [];
|
|
476
|
+
const seen = new Set();
|
|
477
|
+
for (const dir of dirs) {
|
|
478
|
+
for (const names of [AGENT_NAMES, MEMORY_NAMES]) {
|
|
479
|
+
const p = await findNamedFile(dir, names);
|
|
480
|
+
if (!p || seen.has(p))
|
|
481
|
+
continue;
|
|
482
|
+
const f = await readIfFile(p, MAX_FILE_BASE);
|
|
483
|
+
if (!f)
|
|
484
|
+
continue;
|
|
485
|
+
seen.add(p);
|
|
486
|
+
out.push(f);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return out;
|
|
490
|
+
}
|
package/dist/i18n.js
CHANGED
|
@@ -194,6 +194,14 @@ export const CATALOG = {
|
|
|
194
194
|
ru: '/undo-list список того, что можно откатить',
|
|
195
195
|
en: '/undo-list list revertable changes',
|
|
196
196
|
},
|
|
197
|
+
'help.cmd.rewind': {
|
|
198
|
+
ru: '/rewind [n] откатить рабочее дерево к чекпойнту',
|
|
199
|
+
en: '/rewind [n] roll the working tree back to a checkpoint',
|
|
200
|
+
},
|
|
201
|
+
'help.cmd.rewind_list': {
|
|
202
|
+
ru: '/rewind-list список чекпойнтов',
|
|
203
|
+
en: '/rewind-list list checkpoints',
|
|
204
|
+
},
|
|
197
205
|
'help.cmd.transcript': {
|
|
198
206
|
ru: '/transcript путь к файлу транскрипта',
|
|
199
207
|
en: '/transcript transcript file path',
|
|
@@ -565,6 +573,7 @@ export const CATALOG = {
|
|
|
565
573
|
'help.files': { ru: 'Файлы:', en: 'Files:' },
|
|
566
574
|
'help.files.logs': { ru: 'Логи:', en: 'Logs:' },
|
|
567
575
|
'help.files.undo': { ru: 'Undo:', en: 'Undo:' },
|
|
576
|
+
'help.files.checkpoints': { ru: 'Чекпойнты:', en: 'Checkpoints:' },
|
|
568
577
|
'help.files.sessions': { ru: 'Сессии:', en: 'Sessions:' },
|
|
569
578
|
'help.files.profile': { ru: 'Профиль:', en: 'Profile:' },
|
|
570
579
|
'help.files.snapshots': { ru: 'Снапшоты:', en: 'Snapshots:' },
|
|
@@ -649,6 +658,7 @@ export const CATALOG = {
|
|
|
649
658
|
'status.headless': { ru: 'Headless: {v}', en: 'Headless: {v}' },
|
|
650
659
|
'status.debug': { ru: 'Debug: {v}', en: 'Debug: {v}' },
|
|
651
660
|
'status.undo': { ru: 'Undo: {v}', en: 'Undo: {v}' },
|
|
661
|
+
'status.checkpoint': { ru: 'Чекпойнты: {v}', en: 'Checkpoints: {v}' },
|
|
652
662
|
'status.transcript': { ru: 'Транскрипт: {v}', en: 'Transcript: {v}' },
|
|
653
663
|
'status.locale': { ru: 'Язык: {v}', en: 'Language: {v}' },
|
|
654
664
|
'status.goal': { ru: 'Цель сессии: {v}', en: 'Session goal: {v}' },
|
|
@@ -708,6 +718,26 @@ export const CATALOG = {
|
|
|
708
718
|
ru: 'Неизвестная команда: {v}. Набери /help.',
|
|
709
719
|
en: 'Unknown command: {v}. Type /help.',
|
|
710
720
|
},
|
|
721
|
+
'msg.missing_args': {
|
|
722
|
+
ru: 'Не хватает обязательных аргументов: {v}.',
|
|
723
|
+
en: 'Missing required arguments: {v}.',
|
|
724
|
+
},
|
|
725
|
+
'perm.ask': {
|
|
726
|
+
ru: '⚠ Запрос подтверждения: {tool} ({reason})',
|
|
727
|
+
en: '⚠ Approval required: {tool} ({reason})',
|
|
728
|
+
},
|
|
729
|
+
'perm.confirm': {
|
|
730
|
+
ru: 'Разрешить выполнение?',
|
|
731
|
+
en: 'Allow this call?',
|
|
732
|
+
},
|
|
733
|
+
'perm.allowed': {
|
|
734
|
+
ru: '✔ Разрешено оператором.',
|
|
735
|
+
en: '✔ Allowed by operator.',
|
|
736
|
+
},
|
|
737
|
+
'perm.denied': {
|
|
738
|
+
ru: '✖ Отклонено оператором — вызов не выполнен.',
|
|
739
|
+
en: '✖ Denied by operator — the call was not run.',
|
|
740
|
+
},
|
|
711
741
|
'msg.dev_only': {
|
|
712
742
|
ru: 'Команда {v} доступна только в dev-режиме (--dev или hotReload).',
|
|
713
743
|
en: 'The {v} command is only available in dev mode (--dev or hotReload).',
|
|
@@ -749,6 +779,11 @@ export const CATALOG = {
|
|
|
749
779
|
ru: '📎 Вложен файл {marker} {name} ({size}) — {path}',
|
|
750
780
|
en: '📎 Attached file {marker} {name} ({size}) — {path}',
|
|
751
781
|
},
|
|
782
|
+
// B5: shown when `@path` references in a task were inlined as file contents.
|
|
783
|
+
'msg.at_refs': {
|
|
784
|
+
ru: '📄 Встроено файлов по @-ссылкам: {n} —',
|
|
785
|
+
en: '📄 Inlined @-referenced files: {n} —',
|
|
786
|
+
},
|
|
752
787
|
'msg.clip_empty': {
|
|
753
788
|
ru: '⚠ В буфере обмена нет картинки (проверено: {via}).',
|
|
754
789
|
en: '⚠ No image found in the clipboard (tried: {via}).',
|
|
@@ -849,6 +884,11 @@ export const CATALOG = {
|
|
|
849
884
|
},
|
|
850
885
|
'cost.auto_compacts': { ru: ' авто-сжатий: {n}', en: ' auto-compacts: {n}' },
|
|
851
886
|
'cost.by_tool': { ru: ' по инструментам:', en: ' by tool:' },
|
|
887
|
+
// T-D3: total time spent inside tools, then a top-N per-tool breakdown.
|
|
888
|
+
'cost.by_time': {
|
|
889
|
+
ru: ' время в инструментах: {dur}; топ по времени:',
|
|
890
|
+
en: ' time in tools: {dur}; top by time:',
|
|
891
|
+
},
|
|
852
892
|
'cost.duration': { ru: ' длительность: {dur}', en: ' duration: {dur}' },
|
|
853
893
|
'cost.started': { ru: ' начало: {v}', en: ' started: {v}' },
|
|
854
894
|
'cost.transcript': { ru: ' транскрипт: {v}', en: ' transcript: {v}' },
|
|
@@ -1293,6 +1333,46 @@ export const CATALOG = {
|
|
|
1293
1333
|
ru: 'undo отключён в конфиге',
|
|
1294
1334
|
en: 'undo is disabled in the config',
|
|
1295
1335
|
},
|
|
1336
|
+
'rewind.empty': { ru: 'Чекпойнтов нет.', en: 'No checkpoints.' },
|
|
1337
|
+
'rewind.created': {
|
|
1338
|
+
ru: '📸 Чекпойнт: {v}',
|
|
1339
|
+
en: '📸 Checkpoint: {v}',
|
|
1340
|
+
},
|
|
1341
|
+
'rewind.list_title': { ru: 'Чекпойнты:', en: 'Checkpoints:' },
|
|
1342
|
+
'rewind.reverted': {
|
|
1343
|
+
ru: '↶ Откатили рабочее дерево к чекпойнту {v}',
|
|
1344
|
+
en: '↶ Rolled the working tree back to checkpoint {v}',
|
|
1345
|
+
},
|
|
1346
|
+
'rewind.backup': {
|
|
1347
|
+
ru: 'Текущее состояние сохранено как чекпойнт {v}',
|
|
1348
|
+
en: 'Current state saved as checkpoint {v}',
|
|
1349
|
+
},
|
|
1350
|
+
'rewind.failed': {
|
|
1351
|
+
ru: 'Не удалось откатить: {v}',
|
|
1352
|
+
en: 'Failed to rewind: {v}',
|
|
1353
|
+
},
|
|
1354
|
+
'rewind.reason_not_found': {
|
|
1355
|
+
ru: 'чекпойнт не найден',
|
|
1356
|
+
en: 'checkpoint not found',
|
|
1357
|
+
},
|
|
1358
|
+
'rewind.reason_archive_missing': {
|
|
1359
|
+
ru: 'архив чекпойнта отсутствует',
|
|
1360
|
+
en: 'checkpoint archive is missing',
|
|
1361
|
+
},
|
|
1362
|
+
'rewind.reason_extract_failed': {
|
|
1363
|
+
ru: 'не удалось распаковать архив',
|
|
1364
|
+
en: 'failed to unpack the archive',
|
|
1365
|
+
},
|
|
1366
|
+
'rewind.reason_disabled': {
|
|
1367
|
+
ru: 'чекпойнты отключены в конфиге',
|
|
1368
|
+
en: 'checkpoints are disabled in the config',
|
|
1369
|
+
},
|
|
1370
|
+
'rewind.confirm': {
|
|
1371
|
+
ru: 'Откатить рабочее дерево к чекпойнту {v}? Текущее состояние будет сохранено как бэкап.',
|
|
1372
|
+
en: 'Roll the working tree back to checkpoint {v}? The current state will be saved as a backup.',
|
|
1373
|
+
},
|
|
1374
|
+
'rewind.cancelled': { ru: 'Отменено.', en: 'Cancelled.' },
|
|
1375
|
+
'rewind.label_backup': { ru: 'бэкап', en: 'backup' },
|
|
1296
1376
|
'dom.saved': { ru: 'HTML сохранён: {v}', en: 'HTML saved: {v}' },
|
|
1297
1377
|
'dom.selectors': { ru: 'Селекторы:', en: 'Selectors:' },
|
|
1298
1378
|
'dom.save_error': {
|
|
@@ -1366,6 +1446,7 @@ export const CATALOG = {
|
|
|
1366
1446
|
'cfg.group.ui': { ru: 'Интерфейс', en: 'Interface' },
|
|
1367
1447
|
'cfg.group.agent': { ru: 'Агент', en: 'Agent' },
|
|
1368
1448
|
'cfg.group.undo': { ru: 'Откат (undo)', en: 'Undo' },
|
|
1449
|
+
'cfg.group.checkpoint': { ru: 'Чекпойнты', en: 'Checkpoints' },
|
|
1369
1450
|
'cfg.group.transcript': { ru: 'Транскрипт', en: 'Transcript' },
|
|
1370
1451
|
'cfg.group.browser': { ru: 'Браузер / DeepSeek', en: 'Browser / DeepSeek' },
|
|
1371
1452
|
'cfg.f.ui_contextLimit': {
|
|
@@ -1401,6 +1482,14 @@ export const CATALOG = {
|
|
|
1401
1482
|
ru: 'Сколько бэкапов хранить',
|
|
1402
1483
|
en: 'Max undo backups kept',
|
|
1403
1484
|
},
|
|
1485
|
+
'cfg.f.checkpoint_enabled': {
|
|
1486
|
+
ru: 'Чекпойнт в начале каждой задачи (для /rewind)',
|
|
1487
|
+
en: 'Checkpoint at the start of each task (for /rewind)',
|
|
1488
|
+
},
|
|
1489
|
+
'cfg.f.checkpoint_maxBackups': {
|
|
1490
|
+
ru: 'Сколько чекпойнтов хранить',
|
|
1491
|
+
en: 'How many checkpoints to keep',
|
|
1492
|
+
},
|
|
1404
1493
|
'cfg.f.transcript_enabled': {
|
|
1405
1494
|
ru: 'Вести транскрипт',
|
|
1406
1495
|
en: 'Write transcript log',
|