zames_pro 2.82.139 → 2.82.140
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 +7 -0
- package/dist/config-command.js +17 -1
- package/dist/config.js +63 -10
- package/dist/i18n/config.js +7 -0
- package/dist/i18n/help.js +2 -2
- package/dist/index.js +33 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.82.140] - 2026-10-25
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **scheduler:** stop the ticker on exit; correct the abort help line
|
|
15
|
+
- **config:** say when a project config overrides the home one
|
|
16
|
+
|
|
10
17
|
## [2.82.139] - 2026-10-25
|
|
11
18
|
|
|
12
19
|
### Changed
|
package/dist/config-command.js
CHANGED
|
@@ -15,8 +15,22 @@ import { configGetValue, configValueHint } from './config-commands.js';
|
|
|
15
15
|
* blindly from the main loop and from the live mid-run interception.
|
|
16
16
|
*/
|
|
17
17
|
export async function handleConfigCommand(input, deps) {
|
|
18
|
-
const { t, print, printErr, getValue, setValue, resetField, showList, openMenu, currentLocale, normalizeLocale, localeDisplayName, homeConfigPath, projectConfigPath, } = deps;
|
|
18
|
+
const { t, print, printErr, getValue, setValue, resetField, showList, openMenu, currentLocale, normalizeLocale, localeDisplayName, homeConfigPath, projectConfigPath, shadowedKeys, } = deps;
|
|
19
19
|
const schema = deps.schema;
|
|
20
|
+
// A HOME write is not always the value the next launch reads: the project
|
|
21
|
+
// `.zamesrc.json` is merged last, so a key present in both wins from the
|
|
22
|
+
// project file. Without this line the operator saw "saved" and then the OLD
|
|
23
|
+
// value after a restart (the stale `ui.locale: en` bug).
|
|
24
|
+
const warnShadowed = (key) => {
|
|
25
|
+
if (!shadowedKeys)
|
|
26
|
+
return;
|
|
27
|
+
if (!shadowedKeys().includes(key))
|
|
28
|
+
return;
|
|
29
|
+
printErr(theme.warn(t('cfg.shadowed', {
|
|
30
|
+
file: projectConfigPath,
|
|
31
|
+
keys: key,
|
|
32
|
+
})));
|
|
33
|
+
};
|
|
20
34
|
// Accept BOTH spellings: the main loop passes the whole typed line
|
|
21
35
|
// (`/config get ui.locale`), while tests and other callers pass the text
|
|
22
36
|
// after the command (`get ui.locale`). Reading parts[1] blindly made the
|
|
@@ -60,6 +74,7 @@ export async function handleConfigCommand(input, deps) {
|
|
|
60
74
|
return;
|
|
61
75
|
}
|
|
62
76
|
print(theme.assistant(t('cfg.lang_set', { v: localeDisplayName(loc) })));
|
|
77
|
+
warnShadowed('ui.locale');
|
|
63
78
|
return;
|
|
64
79
|
}
|
|
65
80
|
if (sub === 'get') {
|
|
@@ -105,6 +120,7 @@ export async function handleConfigCommand(input, deps) {
|
|
|
105
120
|
value: configGetValue(key, getValue(key), t),
|
|
106
121
|
file: homeConfigPath,
|
|
107
122
|
})));
|
|
123
|
+
warnShadowed(key);
|
|
108
124
|
return;
|
|
109
125
|
}
|
|
110
126
|
if (sub === 'reset' || sub === 'unset') {
|
package/dist/config.js
CHANGED
|
@@ -165,19 +165,72 @@ export const DEFAULTS = {
|
|
|
165
165
|
};
|
|
166
166
|
export function loadConfig() {
|
|
167
167
|
const merged = JSON.parse(JSON.stringify(DEFAULTS));
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
168
|
+
// Keys the PROJECT file overrides in the HOME file. Kept so the editor can
|
|
169
|
+
// say "your change was saved but the project file still wins" — a live
|
|
170
|
+
// symptom was `/config lang ru` answering "saved", then English after a
|
|
171
|
+
// restart, because a stale project `.zamesrc.json` pinned `ui.locale: en`.
|
|
172
|
+
shadowedKeys = [];
|
|
173
|
+
const homeData = readConfigObject(HOME_CONFIG);
|
|
174
|
+
const projectData = readConfigObject(projectConfigPath());
|
|
175
|
+
if (projectData) {
|
|
176
|
+
shadowedKeys = overriddenKeys(homeData, projectData);
|
|
177
|
+
}
|
|
178
|
+
for (const data of [homeData, projectData]) {
|
|
179
|
+
if (!data)
|
|
180
|
+
continue;
|
|
181
|
+
deepMerge(merged, data);
|
|
182
|
+
}
|
|
183
|
+
return merged;
|
|
184
|
+
}
|
|
185
|
+
/** Keys the lower-priority file also sets, from the last loadConfig(). */
|
|
186
|
+
let shadowedKeys = [];
|
|
187
|
+
export function shadowedConfigKeys() {
|
|
188
|
+
return shadowedKeys.slice();
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Every leaf path ("ui.locale", "browser.auth.username") present in BOTH
|
|
192
|
+
* objects — i.e. a value the PROJECT config overrides over the HOME one.
|
|
193
|
+
* PURE so the precedence puzzle can be tested without touching the disk.
|
|
194
|
+
*/
|
|
195
|
+
export function overriddenKeys(lower, higher) {
|
|
196
|
+
if (!lower || !higher)
|
|
197
|
+
return [];
|
|
198
|
+
const out = [];
|
|
199
|
+
const walk = (a, b, prefix) => {
|
|
200
|
+
for (const key of Object.keys(b)) {
|
|
201
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const path2 = prefix ? prefix + '.' + key : key;
|
|
205
|
+
const av = a[key];
|
|
206
|
+
const bv = b[key];
|
|
207
|
+
if (isPlainObject(av) && isPlainObject(bv)) {
|
|
208
|
+
walk(av, bv, path2);
|
|
209
|
+
}
|
|
210
|
+
else if (av !== undefined && !isPlainObject(bv)) {
|
|
211
|
+
out.push(path2);
|
|
177
212
|
}
|
|
178
213
|
}
|
|
214
|
+
};
|
|
215
|
+
walk(lower, higher, '');
|
|
216
|
+
return out.sort();
|
|
217
|
+
}
|
|
218
|
+
function isPlainObject(v) {
|
|
219
|
+
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
220
|
+
}
|
|
221
|
+
/** Parse one config file; null when it is missing or unreadable. */
|
|
222
|
+
function readConfigObject(file) {
|
|
223
|
+
try {
|
|
224
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
225
|
+
return isPlainObject(data) ? data : null;
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
const err = e;
|
|
229
|
+
if (err.code !== 'ENOENT') {
|
|
230
|
+
console.error(`config: failed to read ${file}: ${err.message}`);
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
179
233
|
}
|
|
180
|
-
return merged;
|
|
181
234
|
}
|
|
182
235
|
// The agent-loop watchdog deadline per provider. The loop default (240s) is
|
|
183
236
|
// tuned for DeepSeek; a Qwen GUEST turn regularly takes 7-8 minutes (cold
|
package/dist/i18n/config.js
CHANGED
|
@@ -251,6 +251,13 @@ export const configMessages = {
|
|
|
251
251
|
ru: 'Язык переключён на {v}.',
|
|
252
252
|
en: 'Language switched to {v}.',
|
|
253
253
|
},
|
|
254
|
+
// The project `.zamesrc.json` is merged AFTER the home config, so a key it
|
|
255
|
+
// also sets silently wins — the operator sees "saved", then the old value
|
|
256
|
+
// after a restart (a stale `ui.locale: en` looked like a lost setting).
|
|
257
|
+
'cfg.shadowed': {
|
|
258
|
+
ru: '⚠ Проектный конфиг {file} переопределяет: {keys} — после перезапуска будет его значение. Уберите эти ключи из проектного файла.',
|
|
259
|
+
en: '⚠ The project config {file} overrides: {keys} — after a restart its value wins. Remove these keys from the project file.',
|
|
260
|
+
},
|
|
254
261
|
'cfg.lang_usage': {
|
|
255
262
|
ru: 'Использование: /config lang <ru|en>',
|
|
256
263
|
en: 'Usage: /config lang <ru|en>',
|
package/dist/i18n/help.js
CHANGED
|
@@ -94,8 +94,8 @@ export const helpMessages = {
|
|
|
94
94
|
en: 'Ctrl+Shift+V image paste an image/file (saved to tmp)',
|
|
95
95
|
},
|
|
96
96
|
'help.key.esc': {
|
|
97
|
-
ru: 'Esc, Ctrl+C прервать
|
|
98
|
-
en: 'Esc, Ctrl+C abort
|
|
97
|
+
ru: 'Esc, Ctrl+C Esc — прервать генерацию; Ctrl+C — прервать инструмент, второй раз — весь прогон',
|
|
98
|
+
en: 'Esc, Ctrl+C Esc — abort generation; Ctrl+C — abort the tool, twice — the whole run',
|
|
99
99
|
},
|
|
100
100
|
'help.key.scroll': {
|
|
101
101
|
ru: 'PgUp / PgDn вверх/вниз по диалогу (Home/End — в начало/конец) [full-screen]',
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import { LineEditor } from './input.js';
|
|
|
20
20
|
import { TuiEditor } from './tui/TuiEditor.js';
|
|
21
21
|
import { promptOnce } from './prompt.js';
|
|
22
22
|
import { parseImagePaste, extForMime, saveToTemp, guessMime, formatSize, readClipboardImageDetailed, sniffMime, readWindowsClipboardFiles, hasClipboardTool, writeClipboardText, contentHash, } from './attachments.js';
|
|
23
|
-
import { loadConfig, DEFAULTS, CONFIG_PATHS, ZAMES_HOME, CONFIG_SCHEMA, getByPath, validateConfigValue, writeConfigValue, resetConfigValue, providerAskDeadlineMs, } from './config.js';
|
|
23
|
+
import { loadConfig, DEFAULTS, CONFIG_PATHS, ZAMES_HOME, CONFIG_SCHEMA, getByPath, validateConfigValue, writeConfigValue, resetConfigValue, providerAskDeadlineMs, shadowedConfigKeys, } from './config.js';
|
|
24
24
|
import { runConfigMenu } from './config-menu.js';
|
|
25
25
|
import { runConfigMenuInk } from './tui/run-config-menu.js';
|
|
26
26
|
import { formatConfigList } from './config-commands.js';
|
|
@@ -126,6 +126,17 @@ let currentLocale = isLocale(config.ui?.locale)
|
|
|
126
126
|
// Translation: reads currentLocale at call time, so a language change takes
|
|
127
127
|
// effect immediately, without a restart (for lines printed afterwards).
|
|
128
128
|
const t = (key, params) => translate(currentLocale)(key, params);
|
|
129
|
+
// A project `.zamesrc.json` wins over the HOME config, so a key it ALSO sets
|
|
130
|
+
// silently defeats `/config set` — the value looks saved and comes back stale
|
|
131
|
+
// after a restart (the reported "language resets to English" case). Say it at
|
|
132
|
+
// startup, before a task, instead of letting the operator hunt for it.
|
|
133
|
+
const shadowedAtStartup = shadowedConfigKeys();
|
|
134
|
+
if (shadowedAtStartup.length) {
|
|
135
|
+
console.error(theme.warn(t('cfg.shadowed', {
|
|
136
|
+
file: CONFIG_PATHS.PROJECT_CONFIG,
|
|
137
|
+
keys: shadowedAtStartup.join(', '),
|
|
138
|
+
})));
|
|
139
|
+
}
|
|
129
140
|
// Headless is the default. --headed forces a visible window (useful for the
|
|
130
141
|
// first sign-in or debugging the DOM). --headless keeps it explicit.
|
|
131
142
|
// DeepSeek/Qwen run headless by default; GLM CANNOT: z.ai answers a headless
|
|
@@ -1349,10 +1360,23 @@ async function main() {
|
|
|
1349
1360
|
// never bypassing the send throttle). Started lazily on the first job.
|
|
1350
1361
|
const scheduler = new Scheduler();
|
|
1351
1362
|
let scheduleTimer = null;
|
|
1363
|
+
// Ctrl+C on an idle agent EXITS the process. Without this latch a /loop job
|
|
1364
|
+
// whose task was interrupted (and every later fire) would be dropped into
|
|
1365
|
+
// the queue while nobody waits for it, and each tick printed "fired" into a
|
|
1366
|
+
// dying terminal. A loop is meant to outlive a single task, not the agent.
|
|
1367
|
+
let shuttingDown = false;
|
|
1368
|
+
const stopScheduleTicker = () => {
|
|
1369
|
+
if (scheduleTimer) {
|
|
1370
|
+
clearInterval(scheduleTimer);
|
|
1371
|
+
scheduleTimer = null;
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1352
1374
|
const startScheduleTicker = () => {
|
|
1353
1375
|
if (scheduleTimer)
|
|
1354
1376
|
return;
|
|
1355
1377
|
scheduleTimer = setInterval(() => {
|
|
1378
|
+
if (shuttingDown)
|
|
1379
|
+
return;
|
|
1356
1380
|
const due = scheduler.due(Date.now());
|
|
1357
1381
|
for (const job of due) {
|
|
1358
1382
|
// Do not pile MANY copies of the same job while the agent is busy: if
|
|
@@ -1403,6 +1427,8 @@ async function main() {
|
|
|
1403
1427
|
let reviewMode = null;
|
|
1404
1428
|
process.on('SIGINT', async () => {
|
|
1405
1429
|
running = false;
|
|
1430
|
+
shuttingDown = true;
|
|
1431
|
+
stopScheduleTicker();
|
|
1406
1432
|
// Kill any background processes so no dev-server outlives the agent.
|
|
1407
1433
|
killAllBackground();
|
|
1408
1434
|
await browser.close().catch(() => { });
|
|
@@ -1827,8 +1853,12 @@ async function main() {
|
|
|
1827
1853
|
lastCtrlCAt = now;
|
|
1828
1854
|
}
|
|
1829
1855
|
else {
|
|
1830
|
-
// Not busy — exit. We wake takeInput() so the loop finishes.
|
|
1856
|
+
// Not busy — exit. We wake takeInput() so the loop finishes. The
|
|
1857
|
+
// scheduler must stop too: a live /loop ticker keeps firing into the
|
|
1858
|
+
// queue while the process is shutting down.
|
|
1831
1859
|
running = false;
|
|
1860
|
+
shuttingDown = true;
|
|
1861
|
+
stopScheduleTicker();
|
|
1832
1862
|
if (waiter) {
|
|
1833
1863
|
const r = waiter;
|
|
1834
1864
|
waiter = null;
|
|
@@ -2160,6 +2190,7 @@ async function main() {
|
|
|
2160
2190
|
schema: CONFIG_SCHEMA,
|
|
2161
2191
|
homeConfigPath: CONFIG_PATHS.HOME_CONFIG,
|
|
2162
2192
|
projectConfigPath: CONFIG_PATHS.PROJECT_CONFIG,
|
|
2193
|
+
shadowedKeys: shadowedConfigKeys,
|
|
2163
2194
|
});
|
|
2164
2195
|
}
|
|
2165
2196
|
// Open a chat by its ChatInfo and print the restored dialogue. Shared by
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.82.
|
|
3
|
+
"version": "2.82.140",
|
|
4
4
|
"description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen and other web LLMs — no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|