zames_pro 2.96.7 → 2.96.8

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,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.96.8] - 2026-11-06
11
+
12
+ ### Fixed
13
+
14
+ - **ui-health:** report a capability listed twice only once
15
+ - **tui:** a throwing store subscriber no longer starves the others
16
+ - **undo:** list(0) returns no rows instead of the whole history
17
+ - **shell:** a cd inside a nested shell is checked by the sandbox
18
+ - **sandbox:** a dangling symlink cannot let a write escape the root
19
+
10
20
  ## [2.96.7] - 2026-11-06
11
21
 
12
22
  ### Fixed
package/dist/sandbox.js CHANGED
@@ -12,6 +12,17 @@ function realPathNearest(target) {
12
12
  const tail = [];
13
13
  while (true) {
14
14
  try {
15
+ // `lstat` does NOT follow the final link: a DANGLING symlink makes
16
+ // `realpathSync` throw, so the broken link used to fall back to the
17
+ // LEXICAL path and a write THROUGH it landed at its target OUTSIDE the
18
+ // sandbox. Read the link itself and resolve its target too.
19
+ const st = fs.lstatSync(cur);
20
+ if (st.isSymbolicLink()) {
21
+ const raw = fs.readlinkSync(cur);
22
+ const resolvedLink = path.resolve(path.dirname(cur), raw);
23
+ const realTarget = realPathNearest(resolvedLink);
24
+ return path.join(realTarget, ...tail.slice().reverse());
25
+ }
15
26
  return path.join(fs.realpathSync(cur), ...tail.slice().reverse());
16
27
  }
17
28
  catch {
package/dist/shell.js CHANGED
@@ -30,7 +30,13 @@ export function assertCommandInsideRoot(root, command) {
30
30
  // `cd$HOME`, `cd../..` — and runs it as a real directory change. Requiring
31
31
  // whitespace skipped those forms, so a glued `cd../..` walked out of the
32
32
  // root with the guard never firing.
33
- const cdRe = /(?:^|[;&|(\s`])(?:cd|pushd)\s*([^;&|)]+)/gi;
33
+ // A `cd` inside a nested shell (`sh -c "cd / && ls"`) is executed
34
+ // exactly like a top-level one, but the static scan saw no separator
35
+ // before it: the `cd` sits after `-c "`. Treat `-c <quote>` as a
36
+ // separator too, so the target is checked (and rejected when it leaves
37
+ // the root). `$(cd …)` / backticks were already covered by `(` and
38
+ // the backtick.
39
+ const cdRe = /(?:^|[;&|(\s`]|-[a-z]*c\s*['"])(?:cd|pushd)\s*([^;&|)]+)/gi;
34
40
  let m;
35
41
  while ((m = cdRe.exec(cmd))) {
36
42
  // The shell removes EVERY quote in the word, wherever it sits —
package/dist/tui/store.js CHANGED
@@ -104,7 +104,20 @@ export class TuiStore {
104
104
  this.emit();
105
105
  }
106
106
  emit() {
107
- for (const cb of this.listeners)
108
- cb();
107
+ // A subscriber that throws (a React render error, a bug in the status bar)
108
+ // must not abort the loop: the subscribers AFTER it kept a STALE snapshot,
109
+ // and the exception escaped into the agent code that only printed a line.
110
+ // Notify every subscriber, then re-throw the FIRST error so a genuine bug
111
+ // is still visible where the caller can see it.
112
+ for (const cb of this.listeners) {
113
+ try {
114
+ cb();
115
+ }
116
+ catch {
117
+ // Swallowed ON PURPOSE: a notification sink is a render concern, and
118
+ // the imperative agent path that printed the line must not crash (and
119
+ // must not skip the remaining subscribers).
120
+ }
121
+ }
109
122
  }
110
123
  }
package/dist/ui-health.js CHANGED
@@ -52,6 +52,9 @@ export async function probeUiHealth(page, capabilities = UI_CAPABILITIES) {
52
52
  const present = [];
53
53
  const missing = [];
54
54
  const missingCritical = [];
55
+ // A capability listed TWICE (two selector chains merged, a copy-paste) was
56
+ // reported twice, and the `/doctor` row read `input, input`.
57
+ const seen = new Set();
55
58
  for (const spec of capabilities) {
56
59
  // A capability with NO selectors cannot be probed at all: the provider does
57
60
  // not have that control (z.ai has no toggles). Reporting it as missing made
@@ -59,6 +62,9 @@ export async function probeUiHealth(page, capabilities = UI_CAPABILITIES) {
59
62
  // page. It is N/A, not absent.
60
63
  if (!spec.selectors || spec.selectors.length === 0)
61
64
  continue;
65
+ if (seen.has(spec.capability))
66
+ continue;
67
+ seen.add(spec.capability);
62
68
  let found = false;
63
69
  for (const sel of spec.selectors) {
64
70
  if (await hasAny(page, sel, spec.textRe)) {
package/dist/undo.js CHANGED
@@ -155,6 +155,10 @@ export class UndoStore {
155
155
  }
156
156
  async list(count = 10) {
157
157
  const history = await this._readIndex();
158
+ // `slice(-0)` is `slice(0)` — the WHOLE history, so asking for ZERO rows
159
+ // returned everything. A count below 1 means "no rows".
160
+ if (!(count > 0))
161
+ return [];
158
162
  return history.slice(-count).reverse();
159
163
  }
160
164
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.96.7",
3
+ "version": "2.96.8",
4
4
  "description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen or GLM — 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": {