zames_pro 2.35.0 → 2.37.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/commands.js +8 -0
- package/dist/i18n.js +1 -0
- package/dist/index.js +51 -21
- package/dist/input.js +4 -8
- package/dist/spinner.js +14 -13
- package/package.json +1 -1
package/dist/commands.js
CHANGED
|
@@ -444,6 +444,14 @@ export function buildCompactCarryover(summary, task) {
|
|
|
444
444
|
export function ctrlCEscalation(sinceLastMs, windowMs = 2000) {
|
|
445
445
|
return sinceLastMs < windowMs ? 'run' : 'tool';
|
|
446
446
|
}
|
|
447
|
+
// True when a scheduled job already has a message waiting in the queue. The
|
|
448
|
+
// scheduler ticker uses this to avoid piling many copies of the SAME job while
|
|
449
|
+
// the agent is busy (a 1-minute loop would otherwise queue a task every minute
|
|
450
|
+
// during one long task, and they would all run back-to-back). Pure, so the
|
|
451
|
+
// guard is unit-tested without a live scheduler.
|
|
452
|
+
export function hasQueuedJob(queue, jobId) {
|
|
453
|
+
return queue.some((m) => m.jobId === jobId);
|
|
454
|
+
}
|
|
447
455
|
// True when the queued message is a slash-command. It must NOT be merged into
|
|
448
456
|
// a task: the main loop has to execute it (e.g. /compact, /new).
|
|
449
457
|
export function isSlashCommand(text) {
|
package/dist/i18n.js
CHANGED
|
@@ -491,6 +491,7 @@ const CATALOG = {
|
|
|
491
491
|
'status.undo': { ru: 'Undo: {v}', en: 'Undo: {v}' },
|
|
492
492
|
'status.transcript': { ru: 'Транскрипт: {v}', en: 'Transcript: {v}' },
|
|
493
493
|
'status.locale': { ru: 'Язык: {v}', en: 'Language: {v}' },
|
|
494
|
+
'status.goal': { ru: 'Цель сессии: {v}', en: 'Session goal: {v}' },
|
|
494
495
|
// ---------- messages ----------
|
|
495
496
|
'msg.working_dir': {
|
|
496
497
|
ru: 'Рабочая директория: {v}',
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { translate, normalizeLocale, localeDisplayName, isLocale, } from './i18n
|
|
|
16
16
|
import { Transcript } from './transcript.js';
|
|
17
17
|
import { UndoStore } from './undo.js';
|
|
18
18
|
import { selfReview, selfDiff, selfApply, selfList } from './self-review.js';
|
|
19
|
-
import { formatDiff, diffGitArgs, parseTranscript, summarizeTranscript, renderCost, formatExport, defaultExportPath, renderDoctor, renderPermissions, resolveExtraDir, buildReviewPrompt, formatDuration, trimRestoredMessages, RESTORED_HISTORY_LIMIT, mergeMessages, isSlashCommand, parseQueueCommand, parseLiveToggle, parseGoalCommand, isLiveConfigCommand, withGoal, formatQueueList, ctrlCEscalation, } from './commands.js';
|
|
19
|
+
import { formatDiff, diffGitArgs, parseTranscript, summarizeTranscript, renderCost, formatExport, defaultExportPath, renderDoctor, renderPermissions, resolveExtraDir, buildReviewPrompt, formatDuration, trimRestoredMessages, RESTORED_HISTORY_LIMIT, mergeMessages, hasQueuedJob, isSlashCommand, parseQueueCommand, parseLiveToggle, parseGoalCommand, isLiveConfigCommand, withGoal, formatQueueList, ctrlCEscalation, } from './commands.js';
|
|
20
20
|
import { performCompact } from './compact.js';
|
|
21
21
|
import { Scheduler, parseInterval, formatInterval, parseCron, } from './scheduler.js';
|
|
22
22
|
import { renderMarkdown } from './markdown.js';
|
|
@@ -1343,7 +1343,18 @@ async function main() {
|
|
|
1343
1343
|
scheduleTimer = setInterval(() => {
|
|
1344
1344
|
const due = scheduler.due(Date.now());
|
|
1345
1345
|
for (const job of due) {
|
|
1346
|
-
|
|
1346
|
+
// Do not pile MANY copies of the same job while the agent is busy: if
|
|
1347
|
+
// this job already has a pending message in the queue, skip this fire.
|
|
1348
|
+
// Otherwise a 1-minute loop would add a new task every minute while a
|
|
1349
|
+
// long task is in flight, and they would all run back-to-back.
|
|
1350
|
+
if (hasQueuedJob(pendingQueue, job.id)) {
|
|
1351
|
+
transcript.log('scheduled_skip_queued', {
|
|
1352
|
+
id: job.id,
|
|
1353
|
+
task: job.task,
|
|
1354
|
+
});
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
pendingQueue.push({ text: job.task, jobId: job.id });
|
|
1347
1358
|
transcript.log('scheduled_fire', { id: job.id, task: job.task });
|
|
1348
1359
|
if (editor) {
|
|
1349
1360
|
editor.printAbove(theme.system(t('sched.fired', { id: String(job.id), task: job.task })));
|
|
@@ -1609,6 +1620,11 @@ async function main() {
|
|
|
1609
1620
|
void handleConfigCommand(text);
|
|
1610
1621
|
return;
|
|
1611
1622
|
}
|
|
1623
|
+
// `/goal` is safe mid-run: it only sets a variable and writes a file.
|
|
1624
|
+
if (/^\/goal(\s|$)/i.test(text.trim())) {
|
|
1625
|
+
void handleGoal(text);
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1612
1628
|
}
|
|
1613
1629
|
const attachments = (items || []).map((a) => ({
|
|
1614
1630
|
path: a.path,
|
|
@@ -1778,6 +1794,36 @@ async function main() {
|
|
|
1778
1794
|
else
|
|
1779
1795
|
console.log(theme.system(line));
|
|
1780
1796
|
}
|
|
1797
|
+
// Handle a `/goal` command shared by the main loop and the live interception
|
|
1798
|
+
// (so a goal can be set even while the agent is busy — it only touches a
|
|
1799
|
+
// variable and a file, never the chat). Output goes above the input line
|
|
1800
|
+
// when the editor is active, otherwise to stdout.
|
|
1801
|
+
async function handleGoal(input) {
|
|
1802
|
+
const g = parseGoalCommand(input);
|
|
1803
|
+
if (!g)
|
|
1804
|
+
return;
|
|
1805
|
+
const emit = (line) => {
|
|
1806
|
+
if (editor)
|
|
1807
|
+
editor.printAbove(line);
|
|
1808
|
+
else
|
|
1809
|
+
console.log(line);
|
|
1810
|
+
};
|
|
1811
|
+
if (g.sub === 'clear') {
|
|
1812
|
+
sessionGoal = null;
|
|
1813
|
+
await saveGoal(currentWorkdir, null);
|
|
1814
|
+
emit(theme.system(t('goal.cleared')));
|
|
1815
|
+
}
|
|
1816
|
+
else if (g.sub === 'show') {
|
|
1817
|
+
emit(sessionGoal
|
|
1818
|
+
? theme.system(t('goal.current', { goal: sessionGoal }))
|
|
1819
|
+
: theme.dim(t('goal.none')));
|
|
1820
|
+
}
|
|
1821
|
+
else {
|
|
1822
|
+
sessionGoal = g.goal;
|
|
1823
|
+
await saveGoal(currentWorkdir, g.goal);
|
|
1824
|
+
emit(theme.system(t('goal.set', { goal: g.goal })));
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1781
1827
|
function configSetRaw(field, raw) {
|
|
1782
1828
|
const value = validateConfigValue(field, raw);
|
|
1783
1829
|
writeConfigValue('home', field.path, raw);
|
|
@@ -2328,25 +2374,8 @@ async function main() {
|
|
|
2328
2374
|
continue;
|
|
2329
2375
|
}
|
|
2330
2376
|
if (lower === '/goal' || lower.startsWith('/goal ')) {
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
if (g.sub === 'clear') {
|
|
2334
|
-
sessionGoal = null;
|
|
2335
|
-
await saveGoal(currentWorkdir, null);
|
|
2336
|
-
console.log(theme.system(t('goal.cleared')));
|
|
2337
|
-
}
|
|
2338
|
-
else if (g.sub === 'show') {
|
|
2339
|
-
console.log(sessionGoal
|
|
2340
|
-
? theme.system(t('goal.current', { goal: sessionGoal }))
|
|
2341
|
-
: theme.dim(t('goal.none')));
|
|
2342
|
-
}
|
|
2343
|
-
else {
|
|
2344
|
-
sessionGoal = g.goal;
|
|
2345
|
-
await saveGoal(currentWorkdir, g.goal);
|
|
2346
|
-
console.log(theme.system(t('goal.set', { goal: g.goal })));
|
|
2347
|
-
}
|
|
2348
|
-
continue;
|
|
2349
|
-
}
|
|
2377
|
+
await handleGoal(trimmed);
|
|
2378
|
+
continue;
|
|
2350
2379
|
}
|
|
2351
2380
|
if (lower === '/loop' || lower.startsWith('/loop ')) {
|
|
2352
2381
|
const rest = trimmed.slice('/loop'.length).trim();
|
|
@@ -2667,6 +2696,7 @@ async function main() {
|
|
|
2667
2696
|
console.log(theme.system(t('status.transcript', {
|
|
2668
2697
|
v: transcript.file || t('common.off'),
|
|
2669
2698
|
})));
|
|
2699
|
+
console.log(theme.system(t('status.goal', { v: sessionGoal || t('common.none') })));
|
|
2670
2700
|
console.log(theme.system(t('status.locale', { v: localeDisplayName(currentLocale) })));
|
|
2671
2701
|
console.log(theme.system(t('status.mcp', {
|
|
2672
2702
|
v: mcpPool ? String(mcpPool.status().toolCount) : t('common.none'),
|
package/dist/input.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { theme } from './theme.js';
|
|
2
2
|
import { renderMarkdown } from './markdown.js';
|
|
3
|
-
import { randomThinkingPhrase, stripEllipsis } from './spinner.js';
|
|
3
|
+
import { randomThinkingPhrase, stripEllipsis, DOTS, renderDots, } from './spinner.js';
|
|
4
4
|
import { translate } from './i18n.js';
|
|
5
5
|
import { AttachmentStore, parseImagePaste, looksLikeFilePath, } from './attachments.js';
|
|
6
6
|
// A permanent input line at the bottom of the terminal + a status/output area above it.
|
|
@@ -75,10 +75,8 @@ export function expandPastes(pastes, text) {
|
|
|
75
75
|
}
|
|
76
76
|
return out;
|
|
77
77
|
}
|
|
78
|
-
// Dot animation
|
|
79
|
-
//
|
|
80
|
-
const DOTS = ['', '.', '..', '...'];
|
|
81
|
-
const DOTS_PAD = ' ';
|
|
78
|
+
// Dot animation is shared with the ora spinner (see src/spinner.ts): DOTS and
|
|
79
|
+
// renderDots live there so both status lines animate identically.
|
|
82
80
|
function safeJson(v) {
|
|
83
81
|
try {
|
|
84
82
|
return JSON.stringify(v);
|
|
@@ -921,9 +919,7 @@ export class LineEditor {
|
|
|
921
919
|
this._animating = false;
|
|
922
920
|
}
|
|
923
921
|
_dots(n) {
|
|
924
|
-
|
|
925
|
-
// the hint on the right "jumps" when the animation phase changes.
|
|
926
|
-
return theme.brown(DOTS[n] + DOTS_PAD.slice(DOTS[n].length));
|
|
922
|
+
return renderDots(n);
|
|
927
923
|
}
|
|
928
924
|
// Status-line hint ("Esc — stop"), localized.
|
|
929
925
|
_hint() {
|
package/dist/spinner.js
CHANGED
|
@@ -15,6 +15,17 @@ export function randomThinkingPhrase(locale = 'ru') {
|
|
|
15
15
|
export function stripEllipsis(phrase) {
|
|
16
16
|
return phrase.replace(/[.…]+\s*$/, '');
|
|
17
17
|
}
|
|
18
|
+
// Dot animation shared by BOTH UIs (the ora spinner and the LineEditor): start
|
|
19
|
+
// from an empty string (0 dots), then grow. The width is padded to the maximum
|
|
20
|
+
// (3 dots) so the trailing hint does not "jump" when the phase changes. Kept in
|
|
21
|
+
// one place so the two status lines animate identically.
|
|
22
|
+
export const DOTS = ['', '.', '..', '...'];
|
|
23
|
+
export const DOTS_PAD = ' ';
|
|
24
|
+
// The colored dot string for a phase (brown, matching the base text).
|
|
25
|
+
export function renderDots(n) {
|
|
26
|
+
const d = DOTS[n] ?? '';
|
|
27
|
+
return theme.brown(d + DOTS_PAD.slice(d.length));
|
|
28
|
+
}
|
|
18
29
|
export function createSpinner(locale = 'ru') {
|
|
19
30
|
let spinner = null;
|
|
20
31
|
let dotTimer = null;
|
|
@@ -26,10 +37,6 @@ export function createSpinner(locale = 'ru') {
|
|
|
26
37
|
// zero every second and look frozen on a single dot.
|
|
27
38
|
let animBase = '';
|
|
28
39
|
let animating = false;
|
|
29
|
-
// Dot animation: start from an empty string (0 dots), then grow.
|
|
30
|
-
// We align the width to the maximum (3) so the hint doesn't shift.
|
|
31
|
-
const DOTS = ['', '.', '..', '...'];
|
|
32
|
-
const DOTS_PAD = ' ';
|
|
33
40
|
const start = (text) => {
|
|
34
41
|
if (!spinner)
|
|
35
42
|
spinner = ora(text).start();
|
|
@@ -59,10 +66,7 @@ export function createSpinner(locale = 'ru') {
|
|
|
59
66
|
const startAnimated = (baseText, showHint = true) => {
|
|
60
67
|
animBase = theme.brown(stripEllipsis(baseText));
|
|
61
68
|
const hint = showHint ? HINT : '';
|
|
62
|
-
|
|
63
|
-
// the hint on the right "jumps" when the animation phase changes.
|
|
64
|
-
const dots = (n) => theme.brown(DOTS[n] + DOTS_PAD.slice(DOTS[n].length));
|
|
65
|
-
start(animBase + dots(0) + hint);
|
|
69
|
+
start(animBase + renderDots(0) + hint);
|
|
66
70
|
dotPhase = 0;
|
|
67
71
|
if (dotTimer)
|
|
68
72
|
clearInterval(dotTimer);
|
|
@@ -70,7 +74,7 @@ export function createSpinner(locale = 'ru') {
|
|
|
70
74
|
if (!spinner)
|
|
71
75
|
return;
|
|
72
76
|
dotPhase = (dotPhase + 1) % DOTS.length;
|
|
73
|
-
spinner.text = animBase +
|
|
77
|
+
spinner.text = animBase + renderDots(dotPhase) + hint;
|
|
74
78
|
}, 400);
|
|
75
79
|
animating = true;
|
|
76
80
|
if (dotTimer.unref)
|
|
@@ -113,10 +117,7 @@ export function createSpinner(locale = 'ru') {
|
|
|
113
117
|
// so the countdown refreshes without resetting the dots.
|
|
114
118
|
if (animating && dotTimer && spinner) {
|
|
115
119
|
animBase = theme.brown(stripEllipsis(label));
|
|
116
|
-
spinner.text =
|
|
117
|
-
animBase +
|
|
118
|
-
theme.brown(DOTS[dotPhase] + DOTS_PAD.slice(DOTS[dotPhase].length)) +
|
|
119
|
-
HINT;
|
|
120
|
+
spinner.text = animBase + renderDots(dotPhase) + HINT;
|
|
120
121
|
return;
|
|
121
122
|
}
|
|
122
123
|
startAnimated(label);
|