@linxiraos/pi-utils 1.1.20 → 1.1.23

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.
Files changed (113) hide show
  1. package/CHANGELOG.md +7 -5
  2. package/README.md +1 -1
  3. package/THIRD-PARTY-NOTICES.txt +3 -40
  4. package/dist/types/dirs.d.ts +26 -4
  5. package/dist/types/file-lock.d.ts +2 -6
  6. package/dist/types/fs-open.d.ts +16 -0
  7. package/dist/types/index.d.ts +13 -0
  8. package/dist/types/logger/rotating-file.d.ts +9 -0
  9. package/dist/types/mermaid-ascii.d.ts +12 -4
  10. package/dist/types/postmortem.d.ts +23 -0
  11. package/dist/types/procmgr.d.ts +3 -0
  12. package/dist/types/stderr-guard.d.ts +23 -4
  13. package/dist/types/stream.d.ts +11 -4
  14. package/package.json +2 -2
  15. package/src/cli.ts +1 -1
  16. package/src/dirs.ts +54 -12
  17. package/src/file-lock.ts +7 -9
  18. package/src/fs-open.ts +31 -0
  19. package/src/index.ts +40 -0
  20. package/src/logger/rotating-file.ts +22 -9
  21. package/src/logger.ts +10 -9
  22. package/src/mermaid-ascii.ts +13 -7
  23. package/src/module-timer.ts +7 -4
  24. package/src/postmortem.ts +57 -0
  25. package/src/procmgr.ts +18 -18
  26. package/src/ptree.ts +3 -3
  27. package/src/runtime-install.ts +43 -7
  28. package/src/sqlite.ts +29 -2
  29. package/src/stderr-guard.ts +53 -21
  30. package/src/stream.ts +26 -8
  31. package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +0 -41
  32. package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +0 -89
  33. package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +0 -7
  34. package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +0 -12
  35. package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +0 -66
  36. package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +0 -48
  37. package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +0 -43
  38. package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +0 -7
  39. package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +0 -56
  40. package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +0 -65
  41. package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +0 -27
  42. package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +0 -17
  43. package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +0 -7
  44. package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +0 -11
  45. package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +0 -34
  46. package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +0 -11
  47. package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +0 -11
  48. package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +0 -26
  49. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +0 -31
  50. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +0 -11
  51. package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +0 -59
  52. package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +0 -17
  53. package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +0 -30
  54. package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +0 -55
  55. package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +0 -206
  56. package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +0 -51
  57. package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +0 -2
  58. package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +0 -6
  59. package/dist/types/vendor/mermaid-ascii/class/types.d.ts +0 -102
  60. package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +0 -6
  61. package/dist/types/vendor/mermaid-ascii/er/types.d.ts +0 -76
  62. package/dist/types/vendor/mermaid-ascii/index.d.ts +0 -1
  63. package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +0 -9
  64. package/dist/types/vendor/mermaid-ascii/parser.d.ts +0 -7
  65. package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +0 -6
  66. package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +0 -130
  67. package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +0 -23
  68. package/dist/types/vendor/mermaid-ascii/types.d.ts +0 -114
  69. package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +0 -25
  70. package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +0 -6
  71. package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +0 -145
  72. package/src/vendor/mermaid-ascii/NOTICE +0 -33
  73. package/src/vendor/mermaid-ascii/ascii/ansi.ts +0 -409
  74. package/src/vendor/mermaid-ascii/ascii/canvas.ts +0 -478
  75. package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +0 -699
  76. package/src/vendor/mermaid-ascii/ascii/converter.ts +0 -271
  77. package/src/vendor/mermaid-ascii/ascii/draw.ts +0 -1389
  78. package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +0 -328
  79. package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +0 -297
  80. package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +0 -441
  81. package/src/vendor/mermaid-ascii/ascii/grid.ts +0 -593
  82. package/src/vendor/mermaid-ascii/ascii/index.ts +0 -187
  83. package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +0 -78
  84. package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +0 -277
  85. package/src/vendor/mermaid-ascii/ascii/sequence.ts +0 -460
  86. package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +0 -27
  87. package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +0 -127
  88. package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +0 -27
  89. package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +0 -27
  90. package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +0 -101
  91. package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +0 -175
  92. package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +0 -27
  93. package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +0 -296
  94. package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +0 -114
  95. package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +0 -192
  96. package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +0 -73
  97. package/src/vendor/mermaid-ascii/ascii/types.ts +0 -273
  98. package/src/vendor/mermaid-ascii/ascii/validate.ts +0 -120
  99. package/src/vendor/mermaid-ascii/ascii/xychart.ts +0 -875
  100. package/src/vendor/mermaid-ascii/class/parser.ts +0 -290
  101. package/src/vendor/mermaid-ascii/class/types.ts +0 -121
  102. package/src/vendor/mermaid-ascii/er/parser.ts +0 -181
  103. package/src/vendor/mermaid-ascii/er/types.ts +0 -91
  104. package/src/vendor/mermaid-ascii/index.ts +0 -14
  105. package/src/vendor/mermaid-ascii/multiline-utils.ts +0 -30
  106. package/src/vendor/mermaid-ascii/parser.ts +0 -645
  107. package/src/vendor/mermaid-ascii/sequence/parser.ts +0 -207
  108. package/src/vendor/mermaid-ascii/sequence/types.ts +0 -146
  109. package/src/vendor/mermaid-ascii/text-metrics.ts +0 -74
  110. package/src/vendor/mermaid-ascii/types.ts +0 -164
  111. package/src/vendor/mermaid-ascii/xychart/colors.ts +0 -140
  112. package/src/vendor/mermaid-ascii/xychart/parser.ts +0 -115
  113. package/src/vendor/mermaid-ascii/xychart/types.ts +0 -150
package/CHANGELOG.md CHANGED
@@ -2,13 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## [1.1.20] - 2026-09-23
5
+ ## [1.1.23] - 2026-10-01
6
6
 
7
- - 版本线推进;`set-version.ts` 保留 manifest 原有缩进与行尾(tab+CRLF),版本 bump 不再产生全文件 diff;prerelease 版本号(semver `x.y.z-rc.N`)校验支持,拒绝空标识符/前导零/build metadata。
7
+ ## [1.1.22] - 2026-09-30
8
8
 
9
- ## [1.1.19] - 2026-09-22
10
-
11
- - 版本线推进;本版无独立用户可见变化。
9
+ - **CLI bin 命名**:新增 `CLI_BIN_NAME`(zeta-c),Usage/进程标题/补全驱动统一;配置根 `.zeta`、`ZETA_*`、`APP_NAME` 不变。
12
10
 
13
11
  ## [1.1.18] - 2026-09-22
14
12
 
@@ -17,3 +15,7 @@
17
15
  ## [1.1.16] - 2026-09-19
18
16
 
19
17
  - 上游 v18.2.5 同步:JSON 与缓存逻辑流线化重构、which 缓存改字符串键重写。
18
+ - Optimized model configuration command execution by deduplicating requests and adding failure backoff
19
+ - Prevented unnecessary credential command execution when runtime API keys are configured
20
+ - Retained `readLines()` results no longer change when later chunks reuse the internal buffer.
21
+ - Long sleeps honor elapsed time and re-arm after premature timer wakes without overflowing native timer delays.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @linxiraos/pi-utils
2
2
 
3
- Shared utilities for Zeta packages. Zero ceremony, Bun-first.
3
+ Shared utilities for [omp](https://github.com/can1357/oh-my-pi) packages. Zero ceremony, Bun-first.
4
4
 
5
5
  ## Notable modules
6
6
 
@@ -78,8 +78,8 @@ below.
78
78
  uutils coreutils (https://github.com/uutils/coreutils), MIT
79
79
  --------------------------------------------------------------------------------
80
80
  Covers: base32, base64, basename, cat, cksum (shared checksum machinery),
81
- b2sum, md5sum, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, comm, cut,
82
- date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv, nproc, paste,
81
+ b2sum, md5sum, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, comm, cp,
82
+ cut, date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv, nproc, paste,
83
83
  printenv, readlink, realpath, rm, seq, sort, stat, tac, tail, tee, touch, tr,
84
84
  truncate, uname, uniq, wc, whoami, yes.
85
85
 
@@ -293,7 +293,7 @@ MIT License
293
293
  Copyright (c) 2026 Sander Land
294
294
  (measured tokenizer vocabulary data, reconstruction model, and reference
295
295
  implementation: https://github.com/sanderland/ctok)
296
- Copyright (c) 2026 Can Bölük and the Oh My Pi contributors
296
+ Copyright (c) 2026 Can Bölük and the omp contributors
297
297
  Copyright (c) 2026 Stencil Labs, Inc.
298
298
  (Rust implementation and the compact binary vocabulary encoding in
299
299
  crates/pi-natives/src/utok/claude)
@@ -335,43 +335,6 @@ This license allows the work and adaptations of it to be shared and used
335
335
  commercially, as long as it is attributed to Poppy Works. The font is bundled
336
336
  here (crates/pi-natives/src/fonts/Silver.ttf) as a CJK/Unicode bitmap fallback.
337
337
 
338
- -------------------------------------------------------------------------------
339
- packages/utils/src/vendor/mermaid-ascii/NOTICE
340
-
341
- This directory contains an in-house Mermaid-diagram-to-ASCII renderer adapted
342
- from beautiful-mermaid (https://github.com/lukilabs/beautiful-mermaid), used
343
- under the MIT License.
344
-
345
- Copyright (c) 2026 Craft Docs
346
-
347
- Only the ASCII rendering pipeline is ported (flowchart/state, sequence, class,
348
- ER, and xychart diagrams); the SVG renderer and its `elkjs` graph-layout
349
- dependency, the browser entry point, and the SVG theme/style modules were
350
- dropped. Terminal display width is reimplemented on `Bun.stringWidth`, and
351
- inline label formatting (HTML tags, markdown emphasis) is reduced to plain text
352
- for ASCII output. Layout and edge-routing logic is preserved faithfully so
353
- ASCII output matches the upstream package.
354
-
355
- MIT License
356
-
357
- Permission is hereby granted, free of charge, to any person obtaining a copy
358
- of this software and associated documentation files (the "Software"), to deal
359
- in the Software without restriction, including without limitation the rights
360
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
361
- copies of the Software, and to permit persons to whom the Software is
362
- furnished to do so, subject to the following conditions:
363
-
364
- The above copyright notice and this permission notice shall be included in all
365
- copies or substantial portions of the Software.
366
-
367
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
368
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
369
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
370
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
371
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
372
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
373
- SOFTWARE.
374
-
375
338
  -------------------------------------------------------------------------------
376
339
  packages/coding-agent/src/markit/NOTICE
377
340
 
@@ -10,8 +10,16 @@
10
10
  * move data to the new locations. No filesystem existence checks are performed
11
11
  * — if the env var is set, zeta trusts that the migration has been done.
12
12
  */
13
- /** App name (e.g. "zeta") */
13
+ /** App name (e.g. "zeta") — product-level identity: config-dir root, log file
14
+ * names, provider attribution headers, splash wordmark. NOT the CLI command. */
14
15
  export declare const APP_NAME: string;
16
+ /** CLI binary name (`npm i -g @linxiraos/zeta` installs `zeta-c`, with
17
+ * `zeta-cli` and `zetacode` as aliases). Every user-facing "run this command"
18
+ * string and process title uses this; the bare `zeta` name belongs to the
19
+ * @linxiraos/main workspace product, not this package. */
20
+ export declare const CLI_BIN_NAME: string;
21
+ /** Public homepage that inference gateways (OpenRouter, Vercel AI Gateway) credit our traffic to. */
22
+ export declare const APP_URL: string;
15
23
  /** Config directory name (e.g. ".zeta") */
16
24
  export declare const CONFIG_DIR_NAME: string;
17
25
  /** Ordered main settings filenames: canonical write target first, legacy-compatible YAML fallback second. */
@@ -131,7 +139,16 @@ export declare function getProjectAgentDir(cwd?: string): string;
131
139
  export declare function getReportsDir(): string;
132
140
  /** Get the logs directory (~/.zeta/logs). */
133
141
  export declare function getLogsDir(): string;
134
- /** Get this process's dated log path (~/.zeta/logs/zeta.YYYY-MM-DD.PID.log). */
142
+ /**
143
+ * Local-timezone `YYYY-MM-DD` day key (zero-padded), formatted exactly like
144
+ * the rotating log sink's file naming: log files are named `omp.<day>.<pid>.log`
145
+ * with the LOCAL day, not the UTC day `toISOString()` yields. Anything that
146
+ * computes "today's" log path or matches same-day log files by name must use
147
+ * this key, or between local midnight and UTC midnight it points at files that
148
+ * do not exist (e.g. 00:00–08:00 in UTC+8).
149
+ */
150
+ export declare function localDay(date: Date): string;
151
+ /** Get this process's dated log path (~/.zeta/logs/zeta.YYYY-MM-DD.PID.log, local-day named like the rotating sink). */
135
152
  export declare function getLogPath(date?: Date, pid?: number): string;
136
153
  /**
137
154
  * Get the plugins directory (~/.zeta/plugins or its XDG equivalent).
@@ -202,8 +219,6 @@ export declare function getAutoQaDbPath(): string;
202
219
  export declare function hashPath(absPath: string): string;
203
220
  /** Get the path to a single worktree directory (~/.zeta/wt/<segment>). */
204
221
  export declare function getWorktreeDir(segment: string): string;
205
- /** Get the GPU cache path (~/.zeta/gpu_cache.json). */
206
- export declare function getGpuCachePath(): string;
207
222
  /**
208
223
  * Get the GitHub view cache database path (~/.zeta/cache/github-cache.db).
209
224
  * Honors the `OMP_GITHUB_CACHE_DB` env var when set so tests can isolate the
@@ -215,6 +230,11 @@ export declare function getGithubCacheDbPath(): string;
215
230
  * Honors `OMP_COMMIT_CACHE_DB` so tests and operators can isolate the cache.
216
231
  */
217
232
  export declare function getCommitCacheDbPath(): string;
233
+ /**
234
+ * Get the judgment answer cache database path (~/.zeta/cache/judgment-cache.db).
235
+ * Honors `OMP_JUDGMENT_CACHE_DB` so tests and operators can isolate the cache.
236
+ */
237
+ export declare function getJudgmentCacheDbPath(): string;
218
238
  /** Get the legacy Pi extension parse cache database path. */
219
239
  export declare function getLegacyPiExtensionCacheDbPath(): string;
220
240
  /**
@@ -259,6 +279,8 @@ export declare function getTinyModelsCacheDir(agentDir?: string): string;
259
279
  export declare function getDocumentConversionCacheDir(agentDir?: string): string;
260
280
  /** Get the per-project composer speculative cache directory (~/.zeta/agent/cache/composer; XDG default: $XDG_CACHE_HOME/zeta/cache/composer). */
261
281
  export declare function getComposerCacheDir(agentDir?: string): string;
282
+ /** Get the composer speculative cache database (~/.zeta/agent/cache/composer.db; XDG default: $XDG_CACHE_HOME/zeta/cache/composer.db). */
283
+ export declare function getComposerCacheDbPath(agentDir?: string): string;
262
284
  /** Get the sessions directory (~/.zeta/agent/sessions). */
263
285
  export declare function getSessionsDir(agentDir?: string): string;
264
286
  /** Get the content-addressed blob store directory (~/.zeta/agent/blobs). */
@@ -5,12 +5,8 @@ export interface FileLockOptions {
5
5
  retries?: number;
6
6
  /** Delay between acquisition attempts. */
7
7
  retryDelayMs?: number;
8
- /** Maximum age of the lock before it is considered stale and can be broken. */
9
- staleMs?: number;
10
- }
11
- /** An exclusive OS-backed lease. Releasing an already released handle is safe. */
12
- export interface FileLockHandle {
13
- release(): void;
8
+ /** Cancel acquisition while waiting for another process to release the resource. */
9
+ signal?: AbortSignal;
14
10
  }
15
11
  /** An exclusive OS-backed lease. Releasing an already released handle is safe. */
16
12
  export interface FileLockHandle {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `fs.openSync` with close-on-exec, matching Node's default.
3
+ *
4
+ * libuv adds `O_CLOEXEC` inside `uv__fs_open`, so on Node every `fs` descriptor
5
+ * dies at `exec`. Bun's `fs.open*` does not, and descriptors it returns survive
6
+ * into any child spawned through a path that does not close strays itself — the
7
+ * bash tool's shell `fork`/`exec`s user commands and hands them the lot. Use
8
+ * this for every descriptor kept open past such a spawn (session transcripts,
9
+ * logs, spools); a child must never hold a writable handle to them (#13224).
10
+ *
11
+ * Flags are numeric only: the bit cannot be OR-ed into `"a"`/`"w+"` strings.
12
+ *
13
+ * @example
14
+ * const fd = openCloexecSync(logPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_APPEND);
15
+ */
16
+ export declare function openCloexecSync(filePath: string, flags: number, mode?: number): number;
@@ -10,6 +10,7 @@ export * from "./file-lock.js";
10
10
  export * from "./format.js";
11
11
  export * from "./frontmatter.js";
12
12
  export * from "./fs-error.js";
13
+ export * from "./fs-open.js";
13
14
  export * from "./incoming-json.js";
14
15
  export * from "./json.js";
15
16
  export * from "./json-parse.js";
@@ -42,3 +43,15 @@ export * from "./version.js";
42
43
  export * from "./which.js";
43
44
  export * from "./yaml-config.js";
44
45
  export declare function structuredCloneJSON<T>(value: T): T;
46
+ /**
47
+ * Deep-copies a JSON-shaped tree (arrays and plain or null-prototype objects)
48
+ * so later mutation of the source can never reach the copy. Strings and other
49
+ * primitives are immutable and shared, so the cost is O(containers) instead of
50
+ * `structuredClone`'s O(bytes) — the difference matters for hot paths that
51
+ * re-copy large string payloads (e.g. streamed tool-call arguments) per delta.
52
+ *
53
+ * Copies own enumerable string keys only. Any other object (class instance,
54
+ * Date, Map, inherited-prototype object) is copied with {@link structuredCloneJSON}.
55
+ * Not cycle-safe: callers MUST pass acyclic data, as any JSON-serializable value is.
56
+ */
57
+ export declare function cloneJsonTree<T>(value: T): T;
@@ -6,6 +6,15 @@ export interface RotatingFileOptions {
6
6
  readonly auditFile: string;
7
7
  readonly maxBytes: number;
8
8
  readonly maxFiles: number;
9
+ /**
10
+ * Called with the new active file path whenever the sink opens a different
11
+ * file — construction, local-day rotation, and size rotation alike.
12
+ *
13
+ * Consumers that hold their own descriptor on the active log (the macOS
14
+ * stderr guard dup2s it onto fd 2) use this to follow the sink instead of
15
+ * staying pinned to a file the sink later prunes.
16
+ */
17
+ readonly onRotate?: (filePath: string) => void;
9
18
  }
10
19
  /** Synchronous append sink with local-day and size rotation plus bounded retention. */
11
20
  export declare class RotatingFileSink {
@@ -1,7 +1,15 @@
1
- import { type AsciiRenderOptions } from "./vendor/mermaid-ascii/index.js";
2
- export type { AsciiRenderOptions as MermaidAsciiRenderOptions };
3
- export declare function renderMermaidAscii(source: string, options?: AsciiRenderOptions): string;
4
- export declare function renderMermaidAsciiSafe(source: string, options?: AsciiRenderOptions): string | null;
1
+ import { type MermaidRenderOptions, renderMermaidAscii } from "@linxiraos/pi-natives";
2
+ /**
3
+ * Native Mermaid → ASCII/Unicode renderer (flowchart, state, sequence, class,
4
+ * ER, xychart). Synchronous because callers render inside the compositor;
5
+ * throws on empty input, an unknown flowchart header, or an invalid
6
+ * `direction`/`colorMode` value.
7
+ */
8
+ export { renderMermaidAscii };
9
+ /** Options for {@link renderMermaidAscii}; every field optional. */
10
+ export type MermaidAsciiRenderOptions = MermaidRenderOptions;
11
+ /** {@link renderMermaidAscii}, returning `null` instead of throwing. */
12
+ export declare function renderMermaidAsciiSafe(source: string, options?: MermaidAsciiRenderOptions): string | null;
5
13
  /**
6
14
  * Extract mermaid code blocks from markdown text.
7
15
  */
@@ -119,6 +119,29 @@ export declare function registerWorkerIpcFaultHandler(handler: (err: Error) => v
119
119
  * when the last registrant unregisters.
120
120
  */
121
121
  export declare function registerStdioDisconnectHandling(): () => void;
122
+ /**
123
+ * Fail a process entry whose event loop drains while `work` is still pending.
124
+ *
125
+ * The CLI starts from a floating `runCli()` call because top-level await breaks
126
+ * `--bytecode` builds. When a one-shot command awaits a promise that never
127
+ * settles and holds no live handle, the loop drains and Bun exits 0 without
128
+ * output: an unfinished command reported as success. `beforeExit` fires in
129
+ * that state; explicit exits (`process.exit`, {@link quit},
130
+ * {@link exitProcess}, the signal handlers) never emit it.
131
+ *
132
+ * `beforeExit` alone is not the verdict: on Windows Bun emits it while I/O is
133
+ * still in flight and then keeps running the loop, so a command that goes on
134
+ * to complete must keep its own exit code (#13470). The verdict is taken at
135
+ * `exit` instead, and only when the last `beforeExit` was final: an unref'd
136
+ * timer armed there runs only if the loop turns again, clearing the mark
137
+ * before a later explicit exit. Any later drain emits `beforeExit` again and
138
+ * re-arms it, so other `beforeExit` listeners (LSP shutdown) still run before
139
+ * the process exits 1.
140
+ *
141
+ * `describe` names what was running (e.g. the resolved subcommand) at report
142
+ * time; it must return only a command name, never user arguments.
143
+ */
144
+ export declare function reportUnsettledEntry(work: Promise<unknown>, describe?: () => string | undefined): void;
122
145
  /**
123
146
  * Mark an error as expected cleanup fallout so the global fatal handlers
124
147
  * downgrade it to a log line instead of tearing down the process. Use for
@@ -65,6 +65,9 @@ export declare function resolveWindowsShell(env?: Record<string, string | undefi
65
65
  * {@link resolveWindowsShell}) — never fails
66
66
  * 3. On Unix: $SHELL if bash/zsh, then fallback paths
67
67
  * 4. Fallback: sh
68
+ *
69
+ * Custom and auto-resolved configs are cached separately, so clearing
70
+ * `shellPath` mid-process immediately falls back to the platform default.
68
71
  */
69
72
  export declare function getShellConfig(customShellPath?: string, options?: ShellConfigOptions): ShellConfig;
70
73
  /**
@@ -1,16 +1,35 @@
1
1
  export interface SuppressTerminalStderrOptions {
2
- /** Redirect target path; defaults to today's omp log file, then /dev/null. */
2
+ /**
3
+ * Redirect target path; defaults to the log file the rotating sink last
4
+ * reported (see {@link setStderrRedirectTarget}), then today's omp log
5
+ * file, then /dev/null.
6
+ */
3
7
  redirectPath?: string;
4
- /** Bypass the macOS + same-terminal gate. Tests only. */
8
+ /** Bypass the same-terminal gate. Tests only. */
5
9
  force?: boolean;
6
10
  }
7
11
  /**
8
12
  * Redirect fd 2 away from the terminal while the TUI owns the viewport.
9
13
  * Returns true when suppression is (already) active. No-op — returning
10
- * false — off macOS, when stderr does not target the stdout terminal, or
11
- * when the libc fd ops are unavailable.
14
+ * false — when stderr does not target the stdout terminal or when libc fd
15
+ * operations are unavailable.
12
16
  */
13
17
  export declare function suppressTerminalStderr(options?: SuppressTerminalStderrOptions): boolean;
18
+ /**
19
+ * Point fd 2 at `logPath`, the log file the rotating sink is currently
20
+ * writing.
21
+ *
22
+ * The logger calls this on every sink rotation. Without it fd 2 keeps the
23
+ * descriptor opened at TUI start, so after local midnight raw stderr — this
24
+ * process's and every child that inherited fd 2 — keeps landing in the
25
+ * previous day's file, which the sink eventually prunes out from under it.
26
+ *
27
+ * The path is recorded even while suppression is inactive, so a later
28
+ * {@link suppressTerminalStderr} redirects to the live file instead of
29
+ * re-deriving a dated name. Best-effort: if the new file cannot be opened,
30
+ * fd 2 keeps its current target.
31
+ */
32
+ export declare function setStderrRedirectTarget(logPath: string): void;
14
33
  /**
15
34
  * Re-point fd 2 at the saved terminal stderr. Safe to call unconditionally:
16
35
  * no-op when suppression is not active. Called at every terminal-ownership
@@ -74,7 +74,13 @@ export declare function readSseJson<T>(stream: ReadableStream<Uint8Array>, signa
74
74
  * which is the safe direction — each is classified as text rather than trusted as
75
75
  * an event object.
76
76
  */
77
- export declare function readSseJsonOrText<T>(stream: ReadableStream<Uint8Array>, signal?: AbortSignal, onEvent?: SseEventObserver): AsyncGenerator<T | string>;
77
+ export declare function readSseJsonOrText<T>(stream: ReadableStream<Uint8Array>, signal?: AbortSignal, onEvent?: SseEventObserver,
78
+ /**
79
+ * Called when iteration ends on the OpenAI `[DONE]` sentinel. Lets a
80
+ * consumer tell a server-agreed end from a bare EOF without attaching an
81
+ * `onEvent` observer (which turns on per-line raw capture).
82
+ */
83
+ onDone?: () => void): AsyncGenerator<T | string>;
78
84
  /**
79
85
  * A single Server-Sent Event dispatched on a blank-line boundary.
80
86
  *
@@ -95,12 +101,13 @@ export interface ServerSentEvent {
95
101
  * Decoded wire lines for this event (`event:`/`data:`/etc.), for the
96
102
  * diagnostic pipeline. Populated only when the reader opts in via
97
103
  * {@link ReadSseEventsOptions.captureRaw} (or attaches an `onSseEvent`
98
- * observer to the JSON readers, which opt in automatically); otherwise
99
- * `[]`. Direct `readSseEvents` callers that need wire text must pass
104
+ * observer to the JSON readers, which opt in automatically); otherwise a
105
+ * shared, frozen empty array — copy or reassign it, never mutate it in
106
+ * place. Direct `readSseEvents` callers that need wire text must pass
100
107
  * `{ captureRaw: true }` — the field is allocation-free by default so
101
108
  * the token path pays no per-frame array/slice cost.
102
109
  */
103
- raw: string[];
110
+ raw: readonly string[];
104
111
  id?: string;
105
112
  retry?: number;
106
113
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linxiraos/pi-utils",
3
- "version": "1.1.20",
3
+ "version": "1.1.23",
4
4
  "description": "Shared utilities for pi packages",
5
5
  "keywords": [
6
6
  "utilities",
@@ -54,7 +54,7 @@
54
54
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
55
55
  },
56
56
  "dependencies": {
57
- "@linxiraos/pi-natives": "1.1.20"
57
+ "@linxiraos/pi-natives": "1.1.23"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/bun": "^1.3.14"
package/src/cli.ts CHANGED
@@ -463,7 +463,7 @@ export async function run(opts: RunOptions): Promise<void> {
463
463
  }
464
464
 
465
465
  // Per-command help: load only the requested command. Loading the full
466
- // command table here would make `omp <cmd> --help` hang or crash whenever
466
+ // command table here would make `zeta <cmd> --help` hang or crash whenever
467
467
  // any *unrelated* command module misbehaves at import time.
468
468
  if (commandArgv.includes("--help") || commandArgv.includes("-h")) {
469
469
  const entry = findEntry(opts.commands, commandId);
package/src/dirs.ts CHANGED
@@ -14,12 +14,23 @@
14
14
  import * as fs from "node:fs";
15
15
  import * as os from "node:os";
16
16
  import * as path from "node:path";
17
+ import { expandWindowsLongPath } from "@linxiraos/pi-natives/path";
17
18
  import { engines, version } from "../package.json" with { type: "json" };
18
19
  import { isEnoent, isEnotdir } from "./fs-error";
19
20
 
20
- /** App name (e.g. "zeta") */
21
+ /** App name (e.g. "zeta") — product-level identity: config-dir root, log file
22
+ * names, provider attribution headers, splash wordmark. NOT the CLI command. */
21
23
  export const APP_NAME: string = "zeta";
22
24
 
25
+ /** CLI binary name (`npm i -g @linxiraos/zeta` installs `zeta-c`, with
26
+ * `zeta-cli` and `zetacode` as aliases). Every user-facing "run this command"
27
+ * string and process title uses this; the bare `zeta` name belongs to the
28
+ * @linxiraos/main workspace product, not this package. */
29
+ export const CLI_BIN_NAME: string = "zeta-c";
30
+
31
+ /** Public homepage that inference gateways (OpenRouter, Vercel AI Gateway) credit our traffic to. */
32
+ export const APP_URL: string = "https://linxira-os.github.io/zeta/";
33
+
23
34
  /** Config directory name (e.g. ".zeta") */
24
35
  export const CONFIG_DIR_NAME: string = ".zeta";
25
36
 
@@ -140,6 +151,11 @@ function standardizeMacOSPath(p: string): string {
140
151
  return p;
141
152
  }
142
153
 
154
+ /** Keep the current directory's spelling while expanding Windows 8.3 aliases. */
155
+ function standardizeProjectPath(p: string): string {
156
+ return process.platform === "win32" ? expandWindowsLongPath(p) : standardizeMacOSPath(p);
157
+ }
158
+
143
159
  export function resolveEquivalentPath(inputPath: string): string {
144
160
  const resolvedPath = path.resolve(inputPath);
145
161
  try {
@@ -186,29 +202,33 @@ let projectDir: string | undefined;
186
202
  /** Get the project directory. */
187
203
  export function getProjectDir(): string {
188
204
  if (projectDir === undefined) {
205
+ let cwd: string | undefined;
189
206
  try {
190
- projectDir = standardizeMacOSPath(process.cwd());
207
+ cwd = process.cwd();
191
208
  } catch {
192
209
  const candidates = [process.env.PWD, os.homedir(), os.tmpdir()];
193
210
  for (const candidate of candidates) {
194
211
  if (!candidate || !path.isAbsolute(candidate)) continue;
195
212
  try {
196
213
  process.chdir(candidate);
197
- projectDir = standardizeMacOSPath(candidate);
214
+ cwd = candidate;
198
215
  break;
199
216
  } catch {}
200
217
  }
201
- if (projectDir === undefined) {
218
+ if (cwd === undefined) {
202
219
  throw new Error("Unable to determine an accessible working directory");
203
220
  }
204
221
  }
222
+ // Normalize outside the fallback: a native-addon failure is not an inaccessible cwd,
223
+ // and must surface as itself instead of relocating the process.
224
+ projectDir = standardizeProjectPath(cwd);
205
225
  }
206
226
  return projectDir;
207
227
  }
208
228
 
209
229
  /** Set the project directory. */
210
230
  export function setProjectDir(dir: string): void {
211
- const resolved = standardizeMacOSPath(path.resolve(dir));
231
+ const resolved = standardizeProjectPath(path.resolve(dir));
212
232
  process.chdir(resolved);
213
233
  projectDir = resolved;
214
234
  }
@@ -581,9 +601,21 @@ export function getLogsDir(): string {
581
601
  return dirs.rootSubdir("logs", "state");
582
602
  }
583
603
 
584
- /** Get this process's dated log path (~/.zeta/logs/zeta.YYYY-MM-DD.PID.log). */
604
+ /**
605
+ * Local-timezone `YYYY-MM-DD` day key (zero-padded), formatted exactly like
606
+ * the rotating log sink's file naming: log files are named `omp.<day>.<pid>.log`
607
+ * with the LOCAL day, not the UTC day `toISOString()` yields. Anything that
608
+ * computes "today's" log path or matches same-day log files by name must use
609
+ * this key, or between local midnight and UTC midnight it points at files that
610
+ * do not exist (e.g. 00:00–08:00 in UTC+8).
611
+ */
612
+ export function localDay(date: Date): string {
613
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
614
+ }
615
+
616
+ /** Get this process's dated log path (~/.zeta/logs/zeta.YYYY-MM-DD.PID.log, local-day named like the rotating sink). */
585
617
  export function getLogPath(date = new Date(), pid = process.pid): string {
586
- return path.join(getLogsDir(), `${APP_NAME}.${date.toISOString().slice(0, 10)}.${pid}.log`);
618
+ return path.join(getLogsDir(), `${APP_NAME}.${localDay(date)}.${pid}.log`);
587
619
  }
588
620
 
589
621
  /**
@@ -732,11 +764,6 @@ export function getWorktreeDir(segment: string): string {
732
764
  return path.join(getWorktreesDir(), segment);
733
765
  }
734
766
 
735
- /** Get the GPU cache path (~/.zeta/gpu_cache.json). */
736
- export function getGpuCachePath(): string {
737
- return dirs.rootSubdir("gpu_cache.json", "cache");
738
- }
739
-
740
767
  /**
741
768
  * Get the GitHub view cache database path (~/.zeta/cache/github-cache.db).
742
769
  * Honors the `OMP_GITHUB_CACHE_DB` env var when set so tests can isolate the
@@ -757,6 +784,16 @@ export function getCommitCacheDbPath(): string {
757
784
  return dirs.rootSubdir(path.join("cache", "commit-inference.db"), "cache");
758
785
  }
759
786
 
787
+ /**
788
+ * Get the judgment answer cache database path (~/.zeta/cache/judgment-cache.db).
789
+ * Honors `OMP_JUDGMENT_CACHE_DB` so tests and operators can isolate the cache.
790
+ */
791
+ export function getJudgmentCacheDbPath(): string {
792
+ const override = process.env.OMP_JUDGMENT_CACHE_DB;
793
+ if (override) return override;
794
+ return dirs.rootSubdir(path.join("cache", "judgment-cache.db"), "cache");
795
+ }
796
+
760
797
  /** Get the legacy Pi extension parse cache database path. */
761
798
  export function getLegacyPiExtensionCacheDbPath(): string {
762
799
  return dirs.rootSubdir(path.join("cache", "legacy-pi-extension-cache.db"), "cache");
@@ -866,6 +903,11 @@ export function getComposerCacheDir(agentDir?: string): string {
866
903
  return dirs.agentSubdir(agentDir, path.join("cache", "composer"), "cache");
867
904
  }
868
905
 
906
+ /** Get the composer speculative cache database (~/.zeta/agent/cache/composer.db; XDG default: $XDG_CACHE_HOME/zeta/cache/composer.db). */
907
+ export function getComposerCacheDbPath(agentDir?: string): string {
908
+ return dirs.agentSubdir(agentDir, path.join("cache", "composer.db"), "cache");
909
+ }
910
+
869
911
  /** Get the sessions directory (~/.zeta/agent/sessions). */
870
912
  export function getSessionsDir(agentDir?: string): string {
871
913
  return dirs.agentSubdir(agentDir, "sessions", "data");
package/src/file-lock.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * mutexes, and other Unix platforms use `flock(2)` on `${filePath}.lock`.
6
6
  */
7
7
  import * as path from "node:path";
8
+ import { scheduler } from "node:timers/promises";
8
9
  import { FileLock as NativeFileLock } from "@linxiraos/pi-natives";
9
10
 
10
11
  /** Controls bounded waiting when an advisory file lock is contended. */
@@ -13,8 +14,8 @@ export interface FileLockOptions {
13
14
  retries?: number;
14
15
  /** Delay between acquisition attempts. */
15
16
  retryDelayMs?: number;
16
- /** Maximum age of the lock before it is considered stale and can be broken. */
17
- staleMs?: number;
17
+ /** Cancel acquisition while waiting for another process to release the resource. */
18
+ signal?: AbortSignal;
18
19
  }
19
20
 
20
21
  /** An exclusive OS-backed lease. Releasing an already released handle is safe. */
@@ -22,12 +23,7 @@ export interface FileLockHandle {
22
23
  release(): void;
23
24
  }
24
25
 
25
- /** An exclusive OS-backed lease. Releasing an already released handle is safe. */
26
- export interface FileLockHandle {
27
- release(): void;
28
- }
29
-
30
- const DEFAULT_OPTIONS: Required<FileLockOptions> = {
26
+ const DEFAULT_OPTIONS = {
31
27
  retries: 50,
32
28
  retryDelayMs: 100,
33
29
  staleMs: 0,
@@ -48,9 +44,10 @@ export async function acquireFileLock(filePath: string, options: FileLockOptions
48
44
  const lockPath = getLockPath(filePath);
49
45
 
50
46
  for (let attempt = 0; attempt < opts.retries; attempt++) {
47
+ opts.signal?.throwIfAborted();
51
48
  const lock = tryAcquireLock(lockPath);
52
49
  if (lock) return lock;
53
- if (attempt + 1 < opts.retries) await Bun.sleep(opts.retryDelayMs);
50
+ if (attempt + 1 < opts.retries) await scheduler.wait(opts.retryDelayMs, { signal: opts.signal });
54
51
  }
55
52
 
56
53
  throw new Error(`Failed to acquire lock for ${filePath} after ${opts.retries} attempts`);
@@ -61,6 +58,7 @@ function acquireLockSync(filePath: string, options: FileLockOptions = {}): Nativ
61
58
  const lockPath = getLockPath(filePath);
62
59
 
63
60
  for (let attempt = 0; attempt < opts.retries; attempt++) {
61
+ opts.signal?.throwIfAborted();
64
62
  const lock = tryAcquireLock(lockPath);
65
63
  if (lock) return lock;
66
64
  if (attempt + 1 < opts.retries && opts.retryDelayMs > 0) Bun.sleepSync(opts.retryDelayMs);
package/src/fs-open.ts ADDED
@@ -0,0 +1,31 @@
1
+ /** Close-on-exec file opens for descriptors held past a child spawn. */
2
+ import * as fs from "node:fs";
3
+
4
+ /**
5
+ * `O_CLOEXEC`, which neither Node nor Bun exposes in `fs.constants`.
6
+ *
7
+ * Kernel ABI values: `0o2000000` on Linux, `0x0100_0000` on Darwin. Windows has
8
+ * no `exec` and its CRT handles are inherited only when a spawn explicitly asks
9
+ * for it, so the bit is 0 there — as it is on any other platform, where adding
10
+ * an unknown flag bit would be worse than leaving the descriptor inheritable.
11
+ */
12
+ const O_CLOEXEC = process.platform === "linux" ? 0o2000000 : process.platform === "darwin" ? 0x0100_0000 : 0;
13
+
14
+ /**
15
+ * `fs.openSync` with close-on-exec, matching Node's default.
16
+ *
17
+ * libuv adds `O_CLOEXEC` inside `uv__fs_open`, so on Node every `fs` descriptor
18
+ * dies at `exec`. Bun's `fs.open*` does not, and descriptors it returns survive
19
+ * into any child spawned through a path that does not close strays itself — the
20
+ * bash tool's shell `fork`/`exec`s user commands and hands them the lot. Use
21
+ * this for every descriptor kept open past such a spawn (session transcripts,
22
+ * logs, spools); a child must never hold a writable handle to them (#13224).
23
+ *
24
+ * Flags are numeric only: the bit cannot be OR-ed into `"a"`/`"w+"` strings.
25
+ *
26
+ * @example
27
+ * const fd = openCloexecSync(logPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_APPEND);
28
+ */
29
+ export function openCloexecSync(filePath: string, flags: number, mode?: number): number {
30
+ return fs.openSync(filePath, flags | O_CLOEXEC, mode);
31
+ }