zames_pro 2.81.0 → 2.81.2
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 +20 -0
- package/dist/browser-login.js +3 -1
- package/dist/config-command.js +5 -1
- package/dist/config.js +5 -1
- package/dist/glm-browser.js +14 -10
- package/dist/glm-chats.js +1 -3
- package/dist/index.js +14 -7
- package/dist/system-prompt.js +3 -3
- package/dist/tools.js +24 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.81.2] - 2026-10-21
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **tools:** read-only mode drops concatenated/camelCase MCP writers
|
|
15
|
+
|
|
16
|
+
## [2.81.1] - 2026-10-21
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- send telemetry is opt-in (on automatically in dev)
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **config:** mask a password in the /config set confirmation
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- apply prettier to the GLM provider and send runtime
|
|
29
|
+
|
|
10
30
|
## [2.81.0] - 2026-10-21
|
|
11
31
|
|
|
12
32
|
### Added
|
package/dist/browser-login.js
CHANGED
|
@@ -113,7 +113,9 @@ async function runHeadedLogin(deps) {
|
|
|
113
113
|
// may still hold the session cookies, so verify once more and accept.
|
|
114
114
|
if (deps.windowClosed()) {
|
|
115
115
|
deps.notice('login.window_closed');
|
|
116
|
-
const again = await deps
|
|
116
|
+
const again = await deps
|
|
117
|
+
.probeLogin()
|
|
118
|
+
.catch(() => 'unknown');
|
|
117
119
|
return again === 'in';
|
|
118
120
|
}
|
|
119
121
|
await deps.sleep(1000);
|
package/dist/config-command.js
CHANGED
|
@@ -90,7 +90,11 @@ export async function handleConfigCommand(input, deps) {
|
|
|
90
90
|
}
|
|
91
91
|
print(theme.assistant(t('cfg.saved', {
|
|
92
92
|
v: key,
|
|
93
|
-
|
|
93
|
+
// Mask a SECRET here too: this line goes to the terminal scrollback,
|
|
94
|
+
// the transcript and screenshots. `configGetValue` already masks the
|
|
95
|
+
// password for /config get and the list, but the save confirmation
|
|
96
|
+
// echoed it in the clear.
|
|
97
|
+
value: configGetValue(key, getValue(key), t),
|
|
94
98
|
file: homeConfigPath,
|
|
95
99
|
})));
|
|
96
100
|
return;
|
package/dist/config.js
CHANGED
|
@@ -115,7 +115,11 @@ export const DEFAULTS = {
|
|
|
115
115
|
maxSubagents: 3,
|
|
116
116
|
deepThinking: false,
|
|
117
117
|
webSearch: true,
|
|
118
|
-
|
|
118
|
+
// OFF by default: the telemetry file only serves diagnosis and manual
|
|
119
|
+
// calibration of the send pauses (the adaptive throttle needs the EVENTS,
|
|
120
|
+
// not the file, and keeps working). A regular user gains nothing from it,
|
|
121
|
+
// so it is opted into with `/config set browser.sendTelemetry true`.
|
|
122
|
+
sendTelemetry: false,
|
|
119
123
|
auth: {
|
|
120
124
|
username: '',
|
|
121
125
|
password: '',
|
package/dist/glm-browser.js
CHANGED
|
@@ -292,9 +292,7 @@ export class GlmBrowser {
|
|
|
292
292
|
// re-apply them BEFORE the first goto, or the SPA answers 401 and may wipe
|
|
293
293
|
// its own token.
|
|
294
294
|
if (this._carryCookies && this._carryCookies.length) {
|
|
295
|
-
await this.context
|
|
296
|
-
.addCookies(this._carryCookies)
|
|
297
|
-
.catch(() => { });
|
|
295
|
+
await this.context.addCookies(this._carryCookies).catch(() => { });
|
|
298
296
|
}
|
|
299
297
|
this.page = this.context.pages()[0] || (await this.context.newPage());
|
|
300
298
|
this._windowClosed = false;
|
|
@@ -366,9 +364,7 @@ export class GlmBrowser {
|
|
|
366
364
|
throw new Error('glm: the browser profile is busy (' + e.message + ')');
|
|
367
365
|
}
|
|
368
366
|
if (this._carryCookies && this._carryCookies.length) {
|
|
369
|
-
await this.context
|
|
370
|
-
.addCookies(this._carryCookies)
|
|
371
|
-
.catch(() => { });
|
|
367
|
+
await this.context.addCookies(this._carryCookies).catch(() => { });
|
|
372
368
|
}
|
|
373
369
|
this.page = this.context.pages()[0] || (await this.context.newPage());
|
|
374
370
|
this._windowClosed = false;
|
|
@@ -1110,7 +1106,7 @@ export class GlmBrowser {
|
|
|
1110
1106
|
const el = document.querySelector(sel);
|
|
1111
1107
|
if (!el)
|
|
1112
1108
|
return false;
|
|
1113
|
-
return el.disabled === true || el.getAttribute('aria-disabled') === 'true';
|
|
1109
|
+
return (el.disabled === true || el.getAttribute('aria-disabled') === 'true');
|
|
1114
1110
|
}, INPUT_SELECTORS[0])
|
|
1115
1111
|
.catch(() => false);
|
|
1116
1112
|
return !!disabled;
|
|
@@ -1297,7 +1293,11 @@ export class GlmBrowser {
|
|
|
1297
1293
|
return [
|
|
1298
1294
|
{ capability: 'input', selectors: INPUT_SELECTORS, critical: true },
|
|
1299
1295
|
{ capability: 'toggles', selectors: [], critical: false },
|
|
1300
|
-
{
|
|
1296
|
+
{
|
|
1297
|
+
capability: 'newChat',
|
|
1298
|
+
selectors: [NEW_CHAT_SELECTOR],
|
|
1299
|
+
critical: false,
|
|
1300
|
+
},
|
|
1301
1301
|
];
|
|
1302
1302
|
}
|
|
1303
1303
|
async probeUiHealth() {
|
|
@@ -1385,7 +1385,8 @@ export class GlmBrowser {
|
|
|
1385
1385
|
this.sendRuntime.onOutcome('guest_limit', { errorText: msg });
|
|
1386
1386
|
throw new Error(this._t('glm.guest_mode'));
|
|
1387
1387
|
}
|
|
1388
|
-
if (isQuotaExhaustedText(msg) ||
|
|
1388
|
+
if (isQuotaExhaustedText(msg) ||
|
|
1389
|
+
isQuotaExhaustedText(this._netBody)) {
|
|
1389
1390
|
this.sendRuntime.onOutcome('rate_limit', { errorText: 'quota' });
|
|
1390
1391
|
throw new Error(this._t('glm.quota_exhausted'));
|
|
1391
1392
|
}
|
|
@@ -1501,7 +1502,10 @@ export class GlmBrowser {
|
|
|
1501
1502
|
try {
|
|
1502
1503
|
const [chooser] = await Promise.all([
|
|
1503
1504
|
this.page.waitForEvent('filechooser', { timeout: 5000 }),
|
|
1504
|
-
this.page
|
|
1505
|
+
this.page
|
|
1506
|
+
.locator(UPLOAD_BUTTON_SELECTOR)
|
|
1507
|
+
.first()
|
|
1508
|
+
.click({ timeout: 3000 }),
|
|
1505
1509
|
]);
|
|
1506
1510
|
await chooser.setFiles(bufPayload);
|
|
1507
1511
|
await this._awaitUploadPreview(files.map((f) => f.name), 5000);
|
package/dist/glm-chats.js
CHANGED
|
@@ -31,9 +31,7 @@ export function scrapeGlmChatList(limit) {
|
|
|
31
31
|
if (seen.has(m[1]))
|
|
32
32
|
continue;
|
|
33
33
|
seen.add(m[1]);
|
|
34
|
-
const title = (a.textContent || '')
|
|
35
|
-
.trim()
|
|
36
|
-
.replace(/\s+/g, ' ');
|
|
34
|
+
const title = (a.textContent || '').trim().replace(/\s+/g, ' ');
|
|
37
35
|
out.push({ id: m[1], title });
|
|
38
36
|
if (out.length >= limit)
|
|
39
37
|
break;
|
package/dist/index.js
CHANGED
|
@@ -525,9 +525,7 @@ ${theme.bold(t('help.sec.context'))}
|
|
|
525
525
|
${t('help.cmd.lang')}
|
|
526
526
|
|
|
527
527
|
${theme.bold(t('help.sec.provider'))}
|
|
528
|
-
${t('help.cmd.provider')}${providerHasModelPicker(config.provider)
|
|
529
|
-
? '\n ' + t('help.cmd.model')
|
|
530
|
-
: ''}
|
|
528
|
+
${t('help.cmd.provider')}${providerHasModelPicker(config.provider) ? '\n ' + t('help.cmd.model') : ''}
|
|
531
529
|
|
|
532
530
|
${theme.bold(t('help.sec.files'))}
|
|
533
531
|
${t('help.cmd.transcript')}
|
|
@@ -778,7 +776,9 @@ function applyModelContextLimit(model) {
|
|
|
778
776
|
const section = providerConfigSection(config.provider);
|
|
779
777
|
if (!section)
|
|
780
778
|
return;
|
|
781
|
-
const window = config.provider === 'qwen'
|
|
779
|
+
const window = config.provider === 'qwen'
|
|
780
|
+
? qwenContextWindow(model)
|
|
781
|
+
: glmContextWindow(model);
|
|
782
782
|
if (window === null)
|
|
783
783
|
return;
|
|
784
784
|
const sec = activeProviderSection();
|
|
@@ -851,7 +851,11 @@ function createProviderBrowser(provider) {
|
|
|
851
851
|
answerTimeoutMs: config.glm.answerTimeoutMs,
|
|
852
852
|
model: config.glm.model,
|
|
853
853
|
auth: config.glm.auth,
|
|
854
|
-
|
|
854
|
+
// Dev runs (`npm run dev`, i.e. `--dev`) record the telemetry
|
|
855
|
+
// automatically: it is a diagnostic for the developer. For a regular
|
|
856
|
+
// user it stays OFF by default (see DEFAULTS in config.ts) and is
|
|
857
|
+
// opted into with `/config set browser.sendTelemetry true`.
|
|
858
|
+
sendTelemetry: config.browser.sendTelemetry || devMode,
|
|
855
859
|
logsDir: config.transcript.dir,
|
|
856
860
|
loginTimeoutMs: config.browser.loginTimeoutMs,
|
|
857
861
|
locale: currentLocale,
|
|
@@ -1151,7 +1155,8 @@ async function main() {
|
|
|
1151
1155
|
await transcript.close();
|
|
1152
1156
|
return;
|
|
1153
1157
|
}
|
|
1154
|
-
if (providerNeedsVisibleBrowser(config.provider) &&
|
|
1158
|
+
if (providerNeedsVisibleBrowser(config.provider) &&
|
|
1159
|
+
headlessFor(config.provider)) {
|
|
1155
1160
|
console.log(theme.warn(t('provider.needs_headed', { v: providerLabel(config.provider) })));
|
|
1156
1161
|
}
|
|
1157
1162
|
console.log(theme.system(t('msg.interactive')));
|
|
@@ -2735,7 +2740,9 @@ async function main() {
|
|
|
2735
2740
|
// (Qwen, GLM). DeepSeek has none, so the command is hidden from the
|
|
2736
2741
|
// hints there and only explains itself when typed by hand.
|
|
2737
2742
|
if (!providerHasModelPicker(config.provider)) {
|
|
2738
|
-
console.log(theme.system(t('model.unsupported', {
|
|
2743
|
+
console.log(theme.system(t('model.unsupported', {
|
|
2744
|
+
provider: providerLabel(config.provider),
|
|
2745
|
+
})));
|
|
2739
2746
|
continue;
|
|
2740
2747
|
}
|
|
2741
2748
|
const section = providerConfigSection(config.provider);
|
package/dist/system-prompt.js
CHANGED
|
@@ -214,8 +214,8 @@ export function buildSystemPrompt({ workdir, tools, gitContext = null, locale =
|
|
|
214
214
|
// runs: otherwise it asks for a sandbox ("Limit Sandbox Concurrency") or
|
|
215
215
|
// claims it ran something it did not.
|
|
216
216
|
const runtimeSection = provider === 'glm'
|
|
217
|
-
?
|
|
218
|
-
|
|
217
|
+
? "\n## You run on the OPERATOR'S MACHINE (GLM)\n\n" +
|
|
218
|
+
"The tools above execute on the operator's computer, in the working " +
|
|
219
219
|
'directory shown at the top. They are the ONLY way to touch files or ' +
|
|
220
220
|
'run commands.\n\n' +
|
|
221
221
|
'- There is NO cloud sandbox for you. Do NOT request or use a z.ai ' +
|
|
@@ -223,7 +223,7 @@ export function buildSystemPrompt({ workdir, tools, gitContext = null, locale =
|
|
|
223
223
|
"site's own agent mode and is NOT part of this task.\n" +
|
|
224
224
|
'- Never claim a command ran unless you emitted its tool call and ' +
|
|
225
225
|
'received a tool result. If you have not called the tool, say so.\n' +
|
|
226
|
-
|
|
226
|
+
"- Paths like `/home/z/...` or `/mnt/data` are NOT the operator's " +
|
|
227
227
|
'machine.\n' +
|
|
228
228
|
'- Do NOT use web search / WebSearch unless the task explicitly asks ' +
|
|
229
229
|
'for current web information: it is slow, it burns the account quota ' +
|
package/dist/tools.js
CHANGED
|
@@ -403,13 +403,34 @@ export function filterToolsByAllowList(tools, allowed) {
|
|
|
403
403
|
// mutating VERBS in the tool name. It is a heuristic deny-list, not a proof:
|
|
404
404
|
// an exotic server could name a mutating tool innocuously. The honest default
|
|
405
405
|
// in read-only mode is therefore to drop everything that looks like a writer.
|
|
406
|
-
const
|
|
406
|
+
const MCP_MUTATING_WORDS = new Set(('click type navigate press upload select drag fill hover evaluate key ' +
|
|
407
|
+
'screenshot write create delete update set put post patch execute exec ' +
|
|
408
|
+
'insert remove drop restart stop start install kill move rename copy ' +
|
|
409
|
+
'mkdir rmdir touch chmod chown').split(' '));
|
|
410
|
+
// Multi-word verbs are matched as a substring of the normalized name: they
|
|
411
|
+
// never stand alone as ONE `_word_`, so a word-set alone missed them.
|
|
412
|
+
const MCP_MUTATING_PHRASES = ['run_code', 'run_', 'handle_dialog'];
|
|
413
|
+
// A camelCase name is split BEFORE it is lowercased: `browserClick` used to
|
|
414
|
+
// become `browserclick` (one word, no verb) and slipped through read-only mode.
|
|
415
|
+
function splitCamelCase(s) {
|
|
416
|
+
return s.replace(/([a-z0-9])([A-Z])/g, '$1_$2');
|
|
417
|
+
}
|
|
407
418
|
export function isMutatingMcpTool(name) {
|
|
408
419
|
// Strip the server prefix and normalize separators to underscores so the
|
|
409
420
|
// verb matcher sees "browser_click" -> "click".
|
|
410
421
|
const tail = String(name).split('__').pop() || String(name);
|
|
411
|
-
const norm = tail
|
|
412
|
-
|
|
422
|
+
const norm = splitCamelCase(tail)
|
|
423
|
+
.toLowerCase()
|
|
424
|
+
.replace(/[^a-z0-9]+/g, '_');
|
|
425
|
+
if (MCP_MUTATING_PHRASES.some((p) => norm.includes(p)))
|
|
426
|
+
return true;
|
|
427
|
+
const words = norm.split('_').filter(Boolean);
|
|
428
|
+
if (words.some((w) => MCP_MUTATING_WORDS.has(w)))
|
|
429
|
+
return true;
|
|
430
|
+
// A CONCATENATED style (all lowercase, no separator: `browserclick`,
|
|
431
|
+
// `pageevaluate`) still hides a writer. A verb as a substring is safe here
|
|
432
|
+
// because every verb is a whole action word no read-only tool contains.
|
|
433
|
+
return [...MCP_MUTATING_WORDS].some((v) => norm.includes(v));
|
|
413
434
|
}
|
|
414
435
|
// Merge MCP tools into the base set. In read-only (plan) mode the MCP tools
|
|
415
436
|
// must be filtered too: an MCP server (e.g. @playwright/mcp) exposes mutating
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.81.
|
|
3
|
+
"version": "2.81.2",
|
|
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": {
|