@oh-my-pi/pi-utils 18.9.1 → 18.10.0
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/types/dirs.d.ts +2 -0
- package/package.json +2 -2
- package/src/dirs.ts +5 -0
- package/src/json-lexer.ts +23 -5
- package/src/ptree.ts +32 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.10.0] - 2026-10-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `getMnemopiEmbedRuntimeDir()` for the shared mnemopi embedding server endpoints and logs.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed `ChildProcess.kill()` silently leaving the child running when the native process handle could not be resolved or used; it now signals the child directly and escalates to SIGKILL after the grace period.
|
|
14
|
+
|
|
15
|
+
## [18.9.2] - 2026-10-11
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Improved streaming JSON parsing performance for tool arguments containing many string values.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Fixed parsing of long strings that contain closing delimiters other than double or single quotes.
|
|
24
|
+
|
|
5
25
|
## [18.9.0] - 2026-10-11
|
|
6
26
|
|
|
7
27
|
### Breaking Changes
|
package/dist/types/dirs.d.ts
CHANGED
|
@@ -320,6 +320,8 @@ export declare function getDebugLogPath(agentDir?: string): string;
|
|
|
320
320
|
export declare function getSecretPlaceholderKeyPath(): string;
|
|
321
321
|
/** Directory holding the per-model tiny-worker sockets and logs (~/.omp/run/tiny; XDG default: $XDG_STATE_HOME/omp/run/tiny). */
|
|
322
322
|
export declare function getTinyWorkerRuntimeDir(): string;
|
|
323
|
+
/** Directory holding the per-model mnemopi embedding server endpoints and logs (~/.omp/run/mnemopi; XDG default: $XDG_STATE_HOME/omp/run/mnemopi). */
|
|
324
|
+
export declare function getMnemopiEmbedRuntimeDir(): string;
|
|
323
325
|
/** Root directory containing every per-project daemon runtime scope (~/.omp/run/daemons; XDG default: $XDG_STATE_HOME/omp/run/daemons). */
|
|
324
326
|
export declare function getDaemonRuntimeRoot(): string;
|
|
325
327
|
/** Get the daemon runtime directory for a project (~/.omp/run/daemons/<hash>; XDG default: $XDG_STATE_HOME/omp/run/daemons/<hash>). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-utils",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.10.0",
|
|
4
4
|
"description": "Shared utilities for pi packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@oh-my-pi/pi-natives": "18.
|
|
60
|
+
"@oh-my-pi/pi-natives": "18.10.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/bun": "^1.3.14"
|
package/src/dirs.ts
CHANGED
|
@@ -1082,6 +1082,11 @@ export function getTinyWorkerRuntimeDir(): string {
|
|
|
1082
1082
|
return dirs.rootSubdir(path.join("run", "tiny"), "state");
|
|
1083
1083
|
}
|
|
1084
1084
|
|
|
1085
|
+
/** Directory holding the per-model mnemopi embedding server endpoints and logs (~/.omp/run/mnemopi; XDG default: $XDG_STATE_HOME/omp/run/mnemopi). */
|
|
1086
|
+
export function getMnemopiEmbedRuntimeDir(): string {
|
|
1087
|
+
return dirs.rootSubdir(path.join("run", "mnemopi"), "state");
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1085
1090
|
/** Root directory containing every per-project daemon runtime scope (~/.omp/run/daemons; XDG default: $XDG_STATE_HOME/omp/run/daemons). */
|
|
1086
1091
|
export function getDaemonRuntimeRoot(): string {
|
|
1087
1092
|
return dirs.rootSubdir(path.join("run", "daemons"), "state");
|
package/src/json-lexer.ts
CHANGED
|
@@ -186,6 +186,17 @@ const enum QuoteLook {
|
|
|
186
186
|
*/
|
|
187
187
|
export class JsonLexer {
|
|
188
188
|
pos: number;
|
|
189
|
+
/**
|
|
190
|
+
* Shared across {@link string} calls: `src` has no backslash in
|
|
191
|
+
* `[#backslashFrom, #backslashAt)` and `#backslashAt` is a backslash or
|
|
192
|
+
* `src.length`. An escape-free run can extend past its string's closing
|
|
193
|
+
* quote to the end of the document, so searching per string would rescan
|
|
194
|
+
* the same tail for every sibling string (quadratic on objects with many
|
|
195
|
+
* short values). `src` is immutable, so the range never goes stale; a
|
|
196
|
+
* cursor outside it (e.g. a rollback) re-searches.
|
|
197
|
+
*/
|
|
198
|
+
#backslashFrom = 0;
|
|
199
|
+
#backslashAt = -1;
|
|
189
200
|
|
|
190
201
|
constructor(
|
|
191
202
|
readonly src: string,
|
|
@@ -228,13 +239,16 @@ export class JsonLexer {
|
|
|
228
239
|
// malformed structure fails loudly instead of silently swallowing
|
|
229
240
|
// commas/colons or sibling members.
|
|
230
241
|
const lenient = quote === SQUOTE || this.mode === "streaming";
|
|
231
|
-
|
|
242
|
+
// Any delimiter code unit, not just `"`/`'`; a value above 0xFFFF matches
|
|
243
|
+
// no code unit, so such a string never closes.
|
|
244
|
+
const quoteChar = String.fromCharCode(quote);
|
|
232
245
|
// Next quote / backslash at or after the cursor, cached across iterations
|
|
233
246
|
// and re-searched only once the cursor passes them, so every character is
|
|
234
247
|
// scanned at most once per kind and the bulk path stays linear even on
|
|
235
248
|
// escape-dense input (re-searching the far closing quote after each
|
|
236
|
-
// escape would be quadratic). `n` means "none before end of input".
|
|
237
|
-
|
|
249
|
+
// escape would be quadratic). `n` means "none before end of input". The
|
|
250
|
+
// backslash search result is shared across calls via `#backslashAt`.
|
|
251
|
+
let nextQuote = quoteChar.charCodeAt(0) === quote ? -1 : n;
|
|
238
252
|
let nextBackslash = -1;
|
|
239
253
|
while (i < n) {
|
|
240
254
|
// Bulk-skip a run of ordinary characters: jump to the nearest quote or
|
|
@@ -249,8 +263,12 @@ export class JsonLexer {
|
|
|
249
263
|
nextQuote = q === -1 ? n : q;
|
|
250
264
|
}
|
|
251
265
|
if (nextBackslash < i) {
|
|
252
|
-
|
|
253
|
-
|
|
266
|
+
if (i < this.#backslashFrom || i > this.#backslashAt) {
|
|
267
|
+
const b = s.indexOf("\\", i);
|
|
268
|
+
this.#backslashFrom = i;
|
|
269
|
+
this.#backslashAt = b === -1 ? n : b;
|
|
270
|
+
}
|
|
271
|
+
nextBackslash = this.#backslashAt;
|
|
254
272
|
}
|
|
255
273
|
i = nextQuote < nextBackslash ? nextQuote : nextBackslash;
|
|
256
274
|
if (i >= n) break;
|
package/src/ptree.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Convenience helpers: captureText / execText, AbortSignal, timeouts.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { Process } from "@oh-my-pi/pi-natives";
|
|
10
|
+
import { Process, type ProcessTerminateOptions } from "@oh-my-pi/pi-natives";
|
|
11
11
|
import type { Spawn, Subprocess } from "bun";
|
|
12
12
|
|
|
13
13
|
type InMask = "pipe" | "ignore" | Buffer | Uint8Array | null;
|
|
@@ -387,12 +387,40 @@ export class ChildProcess<In extends InMask = InMask> {
|
|
|
387
387
|
? { group: true }
|
|
388
388
|
: undefined
|
|
389
389
|
: { gracefulMs, group: this.#terminateGroup };
|
|
390
|
-
this.#terminating =
|
|
391
|
-
?.terminate(options)
|
|
392
|
-
?.catch(e => void e);
|
|
390
|
+
this.#terminating = this.#terminateTree(options);
|
|
393
391
|
}
|
|
394
392
|
}
|
|
395
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Native tree termination. A native handle that cannot be resolved or used
|
|
396
|
+
* — the PID left the process table, or a `bun test --isolate` realm whose
|
|
397
|
+
* napi classes another realm superseded ("Value is not an instance of class
|
|
398
|
+
* `Process`") — must not leave the child running, so signal it directly.
|
|
399
|
+
*/
|
|
400
|
+
#terminateTree(options: ProcessTerminateOptions | undefined): Promise<boolean | void> {
|
|
401
|
+
const gracefulMs = options?.gracefulMs ?? 1000;
|
|
402
|
+
try {
|
|
403
|
+
const root = this.#windowsRootProcess ?? Process.fromPid(this.proc.pid);
|
|
404
|
+
if (root) return root.terminate(options).catch(() => this.#signalChild(gracefulMs));
|
|
405
|
+
} catch {}
|
|
406
|
+
return this.#signalChild(gracefulMs);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** SIGTERM the child (its group when it leads one), then SIGKILL if it outlives `gracefulMs`. */
|
|
410
|
+
async #signalChild(gracefulMs: number): Promise<void> {
|
|
411
|
+
const signal = (name: NodeJS.Signals) => {
|
|
412
|
+
try {
|
|
413
|
+
if (this.#terminateGroup && process.platform !== "win32") process.kill(-this.proc.pid, name);
|
|
414
|
+
else this.proc.kill(name);
|
|
415
|
+
} catch {}
|
|
416
|
+
};
|
|
417
|
+
if (gracefulMs >= 0) {
|
|
418
|
+
signal("SIGTERM");
|
|
419
|
+
await Promise.race([this.proc.exited, Bun.sleep(gracefulMs)]);
|
|
420
|
+
}
|
|
421
|
+
if (this.proc.exitCode === null && this.proc.signalCode === null) signal("SIGKILL");
|
|
422
|
+
}
|
|
423
|
+
|
|
396
424
|
async #hardKillSubreaperTree(root: Process): Promise<void> {
|
|
397
425
|
try {
|
|
398
426
|
const deadline = Date.now() + SUBREAPER_KILL_WINDOW_MS;
|