zames_pro 2.82.53 → 2.82.55

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 CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.82.55] - 2026-10-23
11
+
12
+ ### Fixed
13
+
14
+ - **deepseek:** require a boundary around HTTP status codes in the busy matcher
15
+ - **toolcall:** require an attribute boundary before the tool name
16
+
17
+ ## [2.82.54] - 2026-10-23
18
+
19
+ ### Fixed
20
+
21
+ - **telemetry:** measure a rate-limit block once, at the lift
22
+ - **hooks:** load the config before firing the Stop hook
23
+ - **history:** match fragment types case-insensitively in every parser
24
+
10
25
  ## [2.82.53] - 2026-10-23
11
26
 
12
27
  ### Fixed
@@ -49,7 +49,11 @@ export function parseHistoryMessages(messages) {
49
49
  const want = role === 'assistant' ? 'RESPONSE' : 'REQUEST';
50
50
  let text = '';
51
51
  for (const fr of (raw && raw.fragments) || []) {
52
- if (!fr || fr.type !== want)
52
+ // The fragment TYPE is matched case-insensitively, like the role: a
53
+ // payload spelling it `response`/`request` (a proxy build, a JSON
54
+ // round-trip through a normalizer) skipped EVERY fragment and the
55
+ // restored dialogue came back empty although it carried the answer.
56
+ if (!fr || String(fr.type ?? '').toUpperCase() !== want)
53
57
  continue;
54
58
  if (typeof fr.content === 'string')
55
59
  text += fr.content;
@@ -242,7 +242,7 @@ export const RATE_LIMIT_RE = /(messages? too frequent|too many requests|rate lim
242
242
  // Transient server-side hiccups (DeepSeek overloaded / hiccup). Unlike the
243
243
  // rate limit, these usually clear in seconds, so we retry quickly instead of
244
244
  // waiting minutes.
245
- export const SERVER_BUSY_RE = /(server (is )?busy|server error|service (is )?unavailable|temporarily unavailable|internal server error|502|503|504|server overloaded|сервер занят|сервер перегружен|сервис недоступен|внутренняя ошибка|попробуйте позже)/i;
245
+ export const SERVER_BUSY_RE = /(server (is )?busy|server error|service (is )?unavailable|temporarily unavailable|internal server error|\b50[234]\b|server overloaded|сервер занят|сервер перегружен|сервис недоступен|внутренняя ошибка|попробуйте позже)/i;
246
246
  // The chat's CONTEXT WINDOW is exhausted. DeepSeek refuses to continue the
247
247
  // conversation and answers with a non-retryable notice instead of a model
248
248
  // turn: "Length limit reached. Please start a new chat." (the Russian UI:
package/dist/glm-chats.js CHANGED
@@ -213,7 +213,11 @@ export function parseGlmHistory(json) {
213
213
  else if (Array.isArray(m.fragments)) {
214
214
  const want = role === 'assistant' ? 'RESPONSE' : 'REQUEST';
215
215
  for (const fr of m.fragments) {
216
- if (fr && fr.type === want && typeof fr.content === 'string') {
216
+ // Case-insensitive, like the DeepSeek parser: a payload spelling the
217
+ // fragment type `response`/`request` used to contribute nothing.
218
+ if (fr &&
219
+ String(fr.type ?? '').toUpperCase() === want &&
220
+ typeof fr.content === 'string') {
217
221
  text += fr.content;
218
222
  }
219
223
  }
@@ -266,7 +266,11 @@ export function parseQwenHistory(json) {
266
266
  else if (Array.isArray(m.fragments)) {
267
267
  const want = role === 'assistant' ? 'RESPONSE' : 'REQUEST';
268
268
  for (const fr of m.fragments) {
269
- if (fr && fr.type === want && typeof fr.content === 'string') {
269
+ // Case-insensitive, like the DeepSeek parser: a payload spelling the
270
+ // fragment type `response`/`request` used to contribute nothing.
271
+ if (fr &&
272
+ String(fr.type ?? '').toUpperCase() === want &&
273
+ typeof fr.content === 'string') {
270
274
  text += fr.content;
271
275
  }
272
276
  }
package/dist/run-task.js CHANGED
@@ -573,8 +573,11 @@ export async function runTask(deps, browser, tools, taskText, workdir, opts, att
573
573
  // Stop hook (N32): the whole task (including drained queue messages) is
574
574
  // done. A cleanup/notification hook runs here. Best-effort.
575
575
  try {
576
- const { runLifecycleHooks } = await import('./hooks.js');
577
- const out = await runLifecycleHooks(undefined, 'Stop', workdir, {
576
+ // LOAD the config: `undefined` here made every Stop hook a no-op — the
577
+ // operator's `.zames/hooks.json` entry never ran, while the PreToolUse
578
+ // hooks from the SAME file worked (they are loaded separately).
579
+ const { runLifecycleHooks, loadHooks } = await import('./hooks.js');
580
+ const out = await runLifecycleHooks(loadHooks(workdir), 'Stop', workdir, {
578
581
  failure: failure ?? '',
579
582
  });
580
583
  if (out)
@@ -10,7 +10,7 @@
10
10
  // survives across tasks and a provider switch within a session (a new browser
11
11
  // object is created, but the operator's decision about the interval does not
12
12
  // silently reset mid-session — it is re-derived from this session's events).
13
- import { DEFAULT_THROTTLE, initialThrottleState, applySendEvent, nextWaitMs, withBlockDuration, } from './send-throttle.js';
13
+ import { DEFAULT_THROTTLE, initialThrottleState, applySendEvent, nextWaitMs, } from './send-throttle.js';
14
14
  import { appendSendEvent, } from './send-telemetry.js';
15
15
  /**
16
16
  * The runtime a provider keeps: the learned interval, the last send and the
@@ -72,7 +72,7 @@ export class SendRuntime {
72
72
  onOutcome(outcome, extra = {}) {
73
73
  if (!this.pending)
74
74
  return;
75
- let e = { ...this.pending, outcome };
75
+ const e = { ...this.pending, outcome };
76
76
  if (extra.errorText)
77
77
  e.errorText = String(extra.errorText).slice(0, 300);
78
78
  if (typeof extra.retryAfterMs === 'number') {
@@ -88,7 +88,14 @@ export class SendRuntime {
88
88
  this.state = { ...this.state, blocked: true, blockedAt: this.now() };
89
89
  }
90
90
  if (outcome === 'rate_limit') {
91
- e = withBlockDuration(e, this.state.blockedAt, this.now());
91
+ // The block START is only known AFTER the fold above (the adaptive path
92
+ // sets it there). The event itself carries NO duration: a rate-limit
93
+ // send is not the END of the block, so any value measured here is a
94
+ // lower bound that would then be averaged together with the real
95
+ // duration written by onBlockLifted — the same block counted TWICE.
96
+ if (!this.state.blockedAt) {
97
+ this.state = { ...this.state, blocked: true, blockedAt: this.now() };
98
+ }
92
99
  }
93
100
  if (this.opts.telemetry) {
94
101
  appendSendEvent(this.opts.logsDir, e);
@@ -14,7 +14,10 @@ function unescapeXml(s) {
14
14
  }
15
15
  function readAttr(attrs, name) {
16
16
  const Q = String.fromCharCode(34);
17
- const re = new RegExp(name + '[ ]*=[ ]*([' + Q + ']([^' + Q + ']*)[' + Q + '])');
17
+ // A boundary BEFORE the name: without it `name` matched the TAIL of
18
+ // `tool_name="Bash"`, so an invoke tag that restated the tool was dispatched
19
+ // to `Bash` and the real `name="Write"` was ignored.
20
+ const re = new RegExp('(?:^|[\\s])' + name + '[ ]*=[ ]*([' + Q + ']([^' + Q + ']*)[' + Q + '])');
18
21
  const m = attrs.match(re);
19
22
  return m ? m[2] : null;
20
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.82.53",
3
+ "version": "2.82.55",
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": {