zames_pro 2.82.48 → 2.82.50
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 +45 -0
- package/dist/agent-loop.js +11 -1
- package/dist/attach-flow.js +7 -3
- package/dist/attach-refs.js +8 -0
- package/dist/attachments.js +9 -0
- package/dist/browser-chats.js +8 -1
- package/dist/browser-pure.js +8 -2
- package/dist/commands.js +5 -0
- package/dist/config-commands.js +4 -1
- package/dist/config-live.js +10 -0
- package/dist/config.js +12 -0
- package/dist/context-command.js +6 -0
- package/dist/context.js +8 -1
- package/dist/diff.js +52 -9
- package/dist/glm-chats.js +18 -1
- package/dist/glm-net-capture.js +23 -5
- package/dist/goal.js +6 -1
- package/dist/i18n/self-review.js +4 -0
- package/dist/i18n.js +7 -1
- package/dist/index.js +87 -98
- package/dist/input/layout.js +32 -7
- package/dist/limits-command.js +42 -0
- package/dist/mcp.js +15 -2
- package/dist/model-command.js +21 -0
- package/dist/path-token.js +35 -8
- package/dist/permissions.js +32 -3
- package/dist/provider-command.js +29 -0
- package/dist/provider-handoff.js +18 -1
- package/dist/queue-command.js +15 -0
- package/dist/qwen-chats.js +13 -6
- package/dist/qwen-net-capture.js +13 -6
- package/dist/resume-command.js +38 -0
- package/dist/send-telemetry.js +15 -3
- package/dist/send-throttle.js +3 -1
- package/dist/status-command.js +68 -0
- package/dist/tool-summary.js +8 -1
- package/dist/tools.js +4 -1
- package/dist/tui/log-window.js +41 -4
- package/dist/undo.js +11 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.82.50] - 2026-10-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **i18n:** a prototype member is not a translation
|
|
15
|
+
- **path-token:** split semicolon-separated @file lists
|
|
16
|
+
- **tui:** keep a tall log entry inside the viewport when scrolling
|
|
17
|
+
- **resume:** refuse a session owned by another provider
|
|
18
|
+
- **tui:** skip OSC sequences when measuring log rows
|
|
19
|
+
- **layout:** wrapToWidth keeps every line within the width
|
|
20
|
+
- **tools:** trim allowed-tools entries before matching
|
|
21
|
+
- **config:** getByPath must not walk the prototype chain
|
|
22
|
+
- **context:** strip a BOM before parsing the frontmatter
|
|
23
|
+
- **telemetry:** a provider id must not escape the log directory
|
|
24
|
+
- **throttle:** a non-finite retryAfterMs must not block forever
|
|
25
|
+
- **auth:** do not migrate a legacy GLM marker to deepseek
|
|
26
|
+
- **qwen:** accept a bare array under chat.history
|
|
27
|
+
- **qwen:** a quota_limit mention inside an answer is not a rate limit
|
|
28
|
+
- **glm:** a quota phrase inside an answer is not a quota error
|
|
29
|
+
- **glm:** order the restored dialogue by an ISO timestamp too
|
|
30
|
+
- **attach:** a throwing preview lookup must not abort the attach
|
|
31
|
+
- **handoff:** clear the side handoff files too
|
|
32
|
+
- **loop:** resolve a padded or upper-cased tool name
|
|
33
|
+
- **mcp:** honour a profile flag given inside the command
|
|
34
|
+
- **config:** re-derive the ask deadline when the answer timeout changes live
|
|
35
|
+
- **diff:** compare lines with an LCS instead of by position
|
|
36
|
+
- **goal:** a verdict must not match the head of a word
|
|
37
|
+
- **telemetry:** ignore a send event dated in the future
|
|
38
|
+
- **tui:** show a sandbox refusal as a failed tool call
|
|
39
|
+
- **config:** the list does not mask a whitespace-only password
|
|
40
|
+
- **attach:** never treat a URL as a local file path
|
|
41
|
+
- **chats:** reject a negative accumulated_token_usage on restore
|
|
42
|
+
- **qwen:** never return a thinking-phase object as the answer
|
|
43
|
+
- **glm:** reject a negative token usage
|
|
44
|
+
- **undo:** tolerate a corrupt undo index
|
|
45
|
+
- **permissions:** match a command rule in nested MCP args
|
|
46
|
+
- **status:** fall back to the default context window on an invalid limit
|
|
47
|
+
- **input:** count blank lines at the end of a paste
|
|
48
|
+
|
|
49
|
+
## [2.82.49] - 2026-10-22
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- **goal:** /goal on no longer overwrites the goal text
|
|
54
|
+
|
|
10
55
|
## [2.82.48] - 2026-10-22
|
|
11
56
|
|
|
12
57
|
### Fixed
|
package/dist/agent-loop.js
CHANGED
|
@@ -958,7 +958,17 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
958
958
|
}
|
|
959
959
|
continue;
|
|
960
960
|
}
|
|
961
|
-
|
|
961
|
+
// Resolve the tool name TOLERANTLY: the model pads it with spaces or
|
|
962
|
+
// a newline (`"tool": " Bash "`) and sometimes changes its case
|
|
963
|
+
// (`bash`). An exact match reported `Unknown tool: Bash ` and the model
|
|
964
|
+
// simply repeated the same call, burning the retry budget. The first
|
|
965
|
+
// exact match still wins, so a name that really is distinct is not
|
|
966
|
+
// merged.
|
|
967
|
+
const wantName = String(call.tool ?? '')
|
|
968
|
+
.trim()
|
|
969
|
+
.toLowerCase();
|
|
970
|
+
const tool = tools.find((t) => t.name === call.tool) ??
|
|
971
|
+
tools.find((t) => t.name.trim().toLowerCase() === wantName);
|
|
962
972
|
if (!tool) {
|
|
963
973
|
const err = `Unknown tool: ${call.tool}`;
|
|
964
974
|
safeToolResult(err);
|
package/dist/attach-flow.js
CHANGED
|
@@ -36,9 +36,13 @@ export async function awaitUploadPreview(page, names, timeoutMs) {
|
|
|
36
36
|
const deadline = Date.now() + timeoutMs;
|
|
37
37
|
while (Date.now() < deadline) {
|
|
38
38
|
for (const name of clean) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
// The lookup itself can THROW synchronously (Playwright: "Execution
|
|
40
|
+
// context was destroyed" while the page navigates). `.catch` handles a
|
|
41
|
+
// rejected promise, not a synchronous throw — without the try/catch the
|
|
42
|
+
// error escaped into `_attachFiles` and the whole attach failed. A
|
|
43
|
+
// lookup error means "not visible yet", exactly like a failed count.
|
|
44
|
+
const n = await Promise.resolve()
|
|
45
|
+
.then(() => page.getByText(name, { exact: false }).count())
|
|
42
46
|
.catch(() => 0);
|
|
43
47
|
if (n > 0)
|
|
44
48
|
return;
|
package/dist/attach-refs.js
CHANGED
|
@@ -27,6 +27,14 @@ export async function resolveAttachPath(workdir, raw) {
|
|
|
27
27
|
s = s.slice(1, -1);
|
|
28
28
|
}
|
|
29
29
|
s = s.replace(/\\ /g, ' ');
|
|
30
|
+
// A URL is never a local file. `isImageName` alone would still accept
|
|
31
|
+
// `https://example.com/a.png` (it only looks at the extension), and the
|
|
32
|
+
// RELATIVE candidate `path.resolve(workdir, 'https://…')` then becomes
|
|
33
|
+
// `https:/…` — a project that happens to have such a directory silently
|
|
34
|
+
// attached the URL as a file. A URL preceded by prose words is covered too
|
|
35
|
+
// (the same rule as looksLikeFilePath).
|
|
36
|
+
if (/(?:^|\s)[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(s))
|
|
37
|
+
return null;
|
|
30
38
|
if (!looksLikeFilePath(s) && !isImageName(s))
|
|
31
39
|
return null;
|
|
32
40
|
const candidates = path.isAbsolute(s)
|
package/dist/attachments.js
CHANGED
|
@@ -186,6 +186,15 @@ export function looksLikeFilePath(text) {
|
|
|
186
186
|
return false;
|
|
187
187
|
if (/[\r\n]/.test(s))
|
|
188
188
|
return false;
|
|
189
|
+
// A URL is not a file path. It contains `/` and ends in a short extension,
|
|
190
|
+
// so it used to pass, and resolveAttachPath then resolved it RELATIVELY
|
|
191
|
+
// (`https://…` -> `https:/…`): a project that happened to contain such a
|
|
192
|
+
// directory silently attached the URL as a local file instead of keeping
|
|
193
|
+
// the text. Any `scheme://` head is rejected here — INCLUDING one that sits
|
|
194
|
+
// after prose words (`see https://x/a.png`), since the whole trimmed text is
|
|
195
|
+
// handed to this check.
|
|
196
|
+
if (/(?:^|\s)[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(s))
|
|
197
|
+
return false;
|
|
189
198
|
// A bare file name with an image/known extension.
|
|
190
199
|
if (looksLikeFileName(s))
|
|
191
200
|
return true;
|
package/dist/browser-chats.js
CHANGED
|
@@ -27,7 +27,14 @@ export function parseHistoryMessages(messages) {
|
|
|
27
27
|
const list = [];
|
|
28
28
|
let usage = null;
|
|
29
29
|
for (const raw of messages) {
|
|
30
|
-
|
|
30
|
+
// A token counter is a FINITE NON-NEGATIVE number. A negative value is a
|
|
31
|
+
// nonsense payload; it used to become browser._lastTokenUsage as-is, so
|
|
32
|
+
// the context badge vanished (tokenStatusLevel rejects a negative count)
|
|
33
|
+
// and the auto-compact percentage went negative and never fired.
|
|
34
|
+
if (raw &&
|
|
35
|
+
typeof raw.accumulated_token_usage === 'number' &&
|
|
36
|
+
Number.isFinite(raw.accumulated_token_usage) &&
|
|
37
|
+
raw.accumulated_token_usage >= 0) {
|
|
31
38
|
usage = raw.accumulated_token_usage;
|
|
32
39
|
}
|
|
33
40
|
// Roles are matched case-insensitively, and a turn that is neither the
|
package/dist/browser-pure.js
CHANGED
|
@@ -245,8 +245,14 @@ export function mergeAuthMarker(raw, provider, at) {
|
|
|
245
245
|
providers[k] = v || {};
|
|
246
246
|
}
|
|
247
247
|
else if (j && typeof j === 'object') {
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
// A legacy record carries ONE provider name. Only Qwen and GLM ever wrote
|
|
249
|
+
// one (`{provider:'qwen'|'glm', at}`); a BARE `{at}` was DeepSeek. Treating
|
|
250
|
+
// every non-qwen name as DeepSeek filed a legacy GLM session under
|
|
251
|
+
// `deepseek`, so /doctor reported a DeepSeek session and the GLM one
|
|
252
|
+
// vanished.
|
|
253
|
+
const p = typeof j.provider === 'string' ? j.provider : '';
|
|
254
|
+
if (p === 'qwen' || p === 'glm')
|
|
255
|
+
providers[p] = { at: j.at };
|
|
250
256
|
else if (typeof j.at === 'string')
|
|
251
257
|
providers.deepseek = { at: j.at };
|
|
252
258
|
}
|
package/dist/commands.js
CHANGED
|
@@ -708,6 +708,11 @@ export function parseGoalCommand(text) {
|
|
|
708
708
|
return { sub: 'show' };
|
|
709
709
|
if (rest.toLowerCase() === 'clear' || rest.toLowerCase() === 'off')
|
|
710
710
|
return { sub: 'clear' };
|
|
711
|
+
// `/goal on` is the natural way to RE-ENABLE a goal after `/goal clear`;
|
|
712
|
+
// treating it as new goal text replaced the goal with the literal word
|
|
713
|
+
// `on`, which no work can satisfy. With no goal set it is a no-op.
|
|
714
|
+
if (rest.toLowerCase() === 'on')
|
|
715
|
+
return { sub: 'show' };
|
|
711
716
|
return { sub: 'set', goal: rest };
|
|
712
717
|
}
|
|
713
718
|
// Replace [image#N] / [file#N] markers in a task with the REAL file path, so
|
package/dist/config-commands.js
CHANGED
|
@@ -14,7 +14,10 @@ export function configListValue(field, value, t) {
|
|
|
14
14
|
return t('cfg.menu.default');
|
|
15
15
|
if (typeof value === 'boolean')
|
|
16
16
|
return value ? t('common.on') : t('common.off');
|
|
17
|
-
|
|
17
|
+
// Mask a real secret, but NOT a whitespace-only one — the same rule as
|
|
18
|
+
// configGetValue(): a blank password cannot log in, and masking it makes
|
|
19
|
+
// the list claim a password is configured while auto-login silently fails.
|
|
20
|
+
if (/password/i.test(field.path) && String(value).trim().length > 0)
|
|
18
21
|
return '********';
|
|
19
22
|
return JSON.stringify(value);
|
|
20
23
|
}
|
package/dist/config-live.js
CHANGED
|
@@ -31,6 +31,16 @@ export function applyBrowserLiveConfig(path, value, browser) {
|
|
|
31
31
|
if (!field || !(field in browser))
|
|
32
32
|
return false;
|
|
33
33
|
browser[field] = value;
|
|
34
|
+
// `answerTimeoutMs` is DERIVED into the watchdog (`askDeadlineMs` = the
|
|
35
|
+
// answer timeout + 60s) by the Qwen/GLM constructors. Pushing only the field
|
|
36
|
+
// left the deadline at its old value, so a LARGER timeout made the agent
|
|
37
|
+
// give up before the browser's own timeout ever fired. Re-derive it the same
|
|
38
|
+
// way the constructor does.
|
|
39
|
+
if (field === 'answerTimeoutMs' && 'askDeadlineMs' in browser) {
|
|
40
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
41
|
+
browser.askDeadlineMs = value + 60_000;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
34
44
|
return true;
|
|
35
45
|
}
|
|
36
46
|
/** Push undo.* / checkpoint.* changes onto the live stores. The stores copy
|
package/dist/config.js
CHANGED
|
@@ -601,11 +601,23 @@ export function getConfigField(path) {
|
|
|
601
601
|
}
|
|
602
602
|
export function getByPath(obj, path) {
|
|
603
603
|
const parts = path.split('.');
|
|
604
|
+
// Same guard as setByPath: a dotted path must never reach the PROTOTYPE
|
|
605
|
+
// chain. `__proto__` used to return Object.prototype (an object), and
|
|
606
|
+
// `constructor` / `toString` the corresponding FUNCTION — the /config list
|
|
607
|
+
// then rendered `{}` for a field whose path did not exist instead of the
|
|
608
|
+
// default marker. Reading those keys is never meaningful for a config path.
|
|
609
|
+
// `toString`, `valueOf`, `hasOwnProperty`, ... are inherited METHODS, not
|
|
610
|
+
// config values: reading them through a dotted path returned a FUNCTION.
|
|
611
|
+
// Only an OWN property counts.
|
|
612
|
+
if (parts.some((p) => UNSAFE_PATH_KEYS.has(p)))
|
|
613
|
+
return undefined;
|
|
604
614
|
let cur = obj;
|
|
605
615
|
for (const p of parts) {
|
|
606
616
|
if (cur === null || cur === undefined || typeof cur !== 'object') {
|
|
607
617
|
return undefined;
|
|
608
618
|
}
|
|
619
|
+
if (!Object.prototype.hasOwnProperty.call(cur, p))
|
|
620
|
+
return undefined;
|
|
609
621
|
cur = cur[p];
|
|
610
622
|
}
|
|
611
623
|
return cur;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { formatContextSources } from './commands.js';
|
|
2
|
+
import { translate } from './i18n.js';
|
|
3
|
+
/** Render the `/context` report for already-loaded sources. */
|
|
4
|
+
export function renderContextReport(src, opts = {}, t = translate('en')) {
|
|
5
|
+
return formatContextSources(src.agents || [], src.memory || [], src.skills || [], src.commands || [], opts, t);
|
|
6
|
+
}
|
package/dist/context.js
CHANGED
|
@@ -245,7 +245,14 @@ const PROJECT_SKILL_DIRS = [
|
|
|
245
245
|
'skills',
|
|
246
246
|
];
|
|
247
247
|
function parseFrontmatter(raw) {
|
|
248
|
-
|
|
248
|
+
// A leading BOM must be dropped BEFORE the `---` check: Windows editors and
|
|
249
|
+
// generators write one, and without this the frontmatter was not recognised
|
|
250
|
+
// at all (description / argument-hint / arguments silently lost).
|
|
251
|
+
// `skillBody` already strips it; the two must agree.
|
|
252
|
+
const bom = String.fromCharCode(0xfeff);
|
|
253
|
+
const norm = String(raw ?? '')
|
|
254
|
+
.replace(new RegExp('^' + bom), '')
|
|
255
|
+
.replace(CRLF_RE, NL);
|
|
249
256
|
if (!norm.startsWith('---'))
|
|
250
257
|
return {};
|
|
251
258
|
const end = norm.indexOf(NL + '---', 3);
|
package/dist/diff.js
CHANGED
|
@@ -2,22 +2,65 @@ import { theme } from './theme.js';
|
|
|
2
2
|
export function unifiedDiff(a, b, { label = '' } = {}) {
|
|
3
3
|
const la = a.split(String.fromCharCode(10));
|
|
4
4
|
const lb = b.split(String.fromCharCode(10));
|
|
5
|
-
const n = Math.max(la.length, lb.length);
|
|
6
5
|
const out = [];
|
|
7
6
|
if (label)
|
|
8
7
|
out.push('... ' + label);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
// A LINE diff, not a positional compare: an insertion at the top of a
|
|
9
|
+
// file shifts every following line, and comparing line i with line i
|
|
10
|
+
// reported the WHOLE file as removed AND added (a 20-line file with one
|
|
11
|
+
// new first line produced 41 diff lines). The undo/`/diff` preview became
|
|
12
|
+
// unreadable and the model was told everything changed. A longest-common-
|
|
13
|
+
// subsequence walk emits only the lines that really differ.
|
|
14
|
+
for (const op of diffOps(la, lb)) {
|
|
15
|
+
if (op.kind === 'same')
|
|
13
16
|
continue;
|
|
14
|
-
|
|
15
|
-
out.push('-' + x);
|
|
16
|
-
if (y !== undefined)
|
|
17
|
-
out.push('+' + y);
|
|
17
|
+
out.push((op.kind === 'del' ? '-' : '+') + op.line);
|
|
18
18
|
}
|
|
19
19
|
return out.join(String.fromCharCode(10));
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Longest-common-subsequence line diff (Hunt–Szymanski style table, O(n*m)
|
|
23
|
+
* memory). Files here are a handful of lines (a single edit preview), so the
|
|
24
|
+
* simple dynamic-programming table is fine and keeps the module dependency-
|
|
25
|
+
* free. A change pair is emitted as `-old` then `+new`, like a real diff.
|
|
26
|
+
*/
|
|
27
|
+
function diffOps(a, b) {
|
|
28
|
+
const n = a.length;
|
|
29
|
+
const m = b.length;
|
|
30
|
+
// lcs[i][j] = length of the LCS of a[i..] and b[j..].
|
|
31
|
+
const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
32
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
33
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
34
|
+
lcs[i][j] =
|
|
35
|
+
a[i] === b[j]
|
|
36
|
+
? lcs[i + 1][j + 1] + 1
|
|
37
|
+
: Math.max(lcs[i + 1][j], lcs[i][j + 1]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const ops = [];
|
|
41
|
+
let i = 0;
|
|
42
|
+
let j = 0;
|
|
43
|
+
while (i < n && j < m) {
|
|
44
|
+
if (a[i] === b[j]) {
|
|
45
|
+
ops.push({ kind: 'same', line: a[i] });
|
|
46
|
+
i++;
|
|
47
|
+
j++;
|
|
48
|
+
}
|
|
49
|
+
else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
50
|
+
ops.push({ kind: 'del', line: a[i] });
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
ops.push({ kind: 'add', line: b[j] });
|
|
55
|
+
j++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
while (i < n)
|
|
59
|
+
ops.push({ kind: 'del', line: a[i++] });
|
|
60
|
+
while (j < m)
|
|
61
|
+
ops.push({ kind: 'add', line: b[j++] });
|
|
62
|
+
return ops;
|
|
63
|
+
}
|
|
21
64
|
export function colorDiff(diffText) {
|
|
22
65
|
return diffText
|
|
23
66
|
.split(String.fromCharCode(10))
|
package/dist/glm-chats.js
CHANGED
|
@@ -157,9 +157,26 @@ export function parseGlmHistory(json) {
|
|
|
157
157
|
: null;
|
|
158
158
|
if (!arr)
|
|
159
159
|
return [];
|
|
160
|
+
// The map is UNORDERED and `timestamp` restores the dialogue order. The
|
|
161
|
+
// value is a NUMBER or a numeric string in the builds seen so far, but an
|
|
162
|
+
// ISO stamp (`2026-10-23T10:00:00Z`) makes `Number(...)` NaN, and a NaN
|
|
163
|
+
// comparator keeps the INSERTION order — a scrambled dialogue. Fall back to
|
|
164
|
+
// Date.parse for a non-numeric string.
|
|
165
|
+
const stampOf = (m) => {
|
|
166
|
+
const raw = m.timestamp;
|
|
167
|
+
const n = Number(raw);
|
|
168
|
+
if (Number.isFinite(n) && n !== 0)
|
|
169
|
+
return n;
|
|
170
|
+
if (typeof raw === 'string') {
|
|
171
|
+
const t = Date.parse(raw);
|
|
172
|
+
if (Number.isFinite(t))
|
|
173
|
+
return t;
|
|
174
|
+
}
|
|
175
|
+
return 0;
|
|
176
|
+
};
|
|
160
177
|
const sorted = arr
|
|
161
178
|
.filter((x) => !!x && typeof x === 'object')
|
|
162
|
-
.sort((a, b) => (
|
|
179
|
+
.sort((a, b) => stampOf(a) - stampOf(b));
|
|
163
180
|
const out = [];
|
|
164
181
|
for (const m of sorted) {
|
|
165
182
|
// The role comes from `role` (confirmed by the live fixture). Only the
|
package/dist/glm-net-capture.js
CHANGED
|
@@ -172,6 +172,10 @@ export function extractAnswer(body) {
|
|
|
172
172
|
return sse;
|
|
173
173
|
return extractFromJson(body);
|
|
174
174
|
}
|
|
175
|
+
/** A usable token count: a FINITE NON-NEGATIVE number. */
|
|
176
|
+
function isUsageCount(v) {
|
|
177
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 0;
|
|
178
|
+
}
|
|
175
179
|
/**
|
|
176
180
|
* The CONTEXT size (tokens) from `usage.prompt_tokens` - the prompt size for
|
|
177
181
|
* the turn, the same idea as DeepSeek's accumulated_token_usage. Falls back to
|
|
@@ -186,9 +190,14 @@ export function extractTokenUsage(body) {
|
|
|
186
190
|
if (!usage || typeof usage !== 'object')
|
|
187
191
|
continue;
|
|
188
192
|
const u = usage;
|
|
189
|
-
|
|
193
|
+
// A token count is a FINITE NON-NEGATIVE number. A negative payload (a
|
|
194
|
+
// nonsense reply, or a wrapped `int` in a proxy build) used to become
|
|
195
|
+
// `browser._lastTokenUsage` as-is: the context badge disappeared
|
|
196
|
+
// (tokenStatusLevel rejects it) while the auto-compact percentage went
|
|
197
|
+
// negative and never fired. Treat it as "no usage", like a non-number.
|
|
198
|
+
if (isUsageCount(u.prompt_tokens))
|
|
190
199
|
lastPrompt = u.prompt_tokens;
|
|
191
|
-
if (
|
|
200
|
+
if (isUsageCount(u.total_tokens))
|
|
192
201
|
lastTotal = u.total_tokens;
|
|
193
202
|
}
|
|
194
203
|
if (lastPrompt !== null)
|
|
@@ -205,9 +214,9 @@ export function extractTokenUsage(body) {
|
|
|
205
214
|
: null;
|
|
206
215
|
const usage = ((envelope ? envelope.usage : undefined) ?? obj?.usage);
|
|
207
216
|
if (usage) {
|
|
208
|
-
if (
|
|
217
|
+
if (isUsageCount(usage.prompt_tokens))
|
|
209
218
|
return usage.prompt_tokens;
|
|
210
|
-
if (
|
|
219
|
+
if (isUsageCount(usage.total_tokens))
|
|
211
220
|
return usage.total_tokens;
|
|
212
221
|
}
|
|
213
222
|
}
|
|
@@ -279,7 +288,16 @@ const ERROR_PAYLOAD_RE = /"type"\s*:\s*"error"|"error"\s*:|\{\s*"code"\s*:/i;
|
|
|
279
288
|
* message instead of retrying for the whole budget.
|
|
280
289
|
*/
|
|
281
290
|
export function isQuotaExhaustedText(body) {
|
|
282
|
-
|
|
291
|
+
const b = String(body || '');
|
|
292
|
+
if (!b)
|
|
293
|
+
return false;
|
|
294
|
+
// Same guard as every other z.ai classifier: a normal ANSWER may legitimately
|
|
295
|
+
// discuss `insufficient_quota` / "exceeded your current quota" (the operator
|
|
296
|
+
// asked how to handle that API error). Without this the answer was taken for
|
|
297
|
+
// a refusal and a good turn was abandoned with `glm.quota_exhausted`.
|
|
298
|
+
if (!ERROR_PAYLOAD_RE.test(b) && hasAnswerContent(b))
|
|
299
|
+
return false;
|
|
300
|
+
return /insufficient_quota|quota (?:exceeded|exhausted)|exceeded your current quota/i.test(b);
|
|
283
301
|
}
|
|
284
302
|
export function isRateLimitText(body) {
|
|
285
303
|
const b = String(body || '');
|
package/dist/goal.js
CHANGED
|
@@ -96,7 +96,12 @@ export function parseGoalVerdict(text) {
|
|
|
96
96
|
// separator is checked on the character FOLLOWING the matched value (a
|
|
97
97
|
// lookahead INSIDE the value pattern was useless: the value alternatives
|
|
98
98
|
// always end on a word boundary of their own).
|
|
99
|
-
|
|
99
|
+
// A right boundary is REQUIRED: without it `VERDICT: METS` /
|
|
100
|
+
// `METADATA` parsed as MET, and MET CLEARS the goal — an unfinished task
|
|
101
|
+
// looked done. `\b` alone is not enough: in `MET-ish` the hyphen is a word
|
|
102
|
+
// boundary, so a hyphen/underscore directly after the value is rejected
|
|
103
|
+
// explicitly (a hyphen is only legal INSIDE `NOT-MET`).
|
|
104
|
+
const label = /VERDICT\s*[*_`]*\s*:\s*[*_`]*\s*(IMPOSSIBLE|NOT[_ -]?MET|MET)(?![\w-])/gi;
|
|
100
105
|
let m;
|
|
101
106
|
let status = null;
|
|
102
107
|
while ((m = label.exec(t)) !== null) {
|
package/dist/i18n/self-review.js
CHANGED
|
@@ -183,4 +183,8 @@ export const selfReviewMessages = {
|
|
|
183
183
|
ru: 'Использование: /resume-id <chat id>',
|
|
184
184
|
en: 'Usage: /resume-id <chat id>',
|
|
185
185
|
},
|
|
186
|
+
'sessions.provider_mismatch': {
|
|
187
|
+
ru: 'Сессия принадлежит провайдеру {owner}, а активен {active}: чат не откроется. Переключись командой /provider {owner} и повтори.',
|
|
188
|
+
en: 'This session belongs to {owner}, but {active} is active: the chat will not open. Switch with /provider {owner} and retry.',
|
|
189
|
+
},
|
|
186
190
|
};
|
package/dist/i18n.js
CHANGED
|
@@ -55,7 +55,13 @@ export const CATALOG = {
|
|
|
55
55
|
export function translate(locale) {
|
|
56
56
|
const loc = isLocale(locale) ? locale : DEFAULT_LOCALE;
|
|
57
57
|
return (key, params) => {
|
|
58
|
-
|
|
58
|
+
// A plain-object lookup ALSO resolves Object.prototype members, so a key
|
|
59
|
+
// like 'toString' returned the inherited FUNCTION: the caller got
|
|
60
|
+
// `undefined` (no params) or a TypeError (`.replace is not a function`).
|
|
61
|
+
// Only an OWN entry is a translation.
|
|
62
|
+
const entry = Object.prototype.hasOwnProperty.call(CATALOG, key)
|
|
63
|
+
? CATALOG[key]
|
|
64
|
+
: undefined;
|
|
59
65
|
const template = entry ? entry[loc] || entry[DEFAULT_LOCALE] : key;
|
|
60
66
|
if (!params)
|
|
61
67
|
return template;
|