@linxiraos/pi-utils 1.1.5 → 1.1.7
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 +9 -0
- package/README.md +1 -0
- package/dist/types/acp/transport.d.ts +7 -0
- package/dist/types/async.d.ts +2 -0
- package/dist/types/color.d.ts +34 -0
- package/dist/types/dirs.d.ts +44 -25
- package/dist/types/index.d.ts +1 -0
- package/dist/types/math-delimiters.d.ts +45 -0
- package/dist/types/postmortem.d.ts +46 -9
- package/dist/types/ptree.d.ts +15 -1
- package/dist/types/runtime-install.d.ts +8 -1
- package/dist/types/sqlite.d.ts +3 -0
- package/package.json +2 -2
- package/src/acp/transport.ts +9 -0
- package/src/async.ts +20 -6
- package/src/color.ts +173 -0
- package/src/dirs.ts +121 -60
- package/src/env.ts +2 -2
- package/src/index.ts +1 -0
- package/src/math-delimiters.ts +143 -0
- package/src/postmortem.ts +212 -63
- package/src/ptree.ts +308 -39
- package/src/runtime-install.ts +77 -26
- package/src/sqlite.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.1.7] - 2026-09-01
|
|
6
|
+
|
|
7
|
+
- 同步上游 OMP v18.0.11(`b8ce33a58911c26bed1d84f0db9a5e2e727c49a2`)。
|
|
8
|
+
|
|
9
|
+
## [1.1.6] - 2026-08-30
|
|
10
|
+
|
|
11
|
+
- 同步上游 OMP v18.0.10(`33cc6b9a043a`)。
|
|
12
|
+
- 同步上游 OMP v18.0.9(`cc14e04f075d`)。
|
|
13
|
+
|
|
5
14
|
## [1.1.5] - 2026-08-26
|
|
6
15
|
|
|
7
16
|
- 同步上游 OMP v18.0.5 / v18.0.6:新增 browsers / json 工具,SHA-2/SHA-3 在 ARM64 上加速。
|
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Shared utilities for [Zeta](https://github.com/Linxira-OS/linxira-zeta) packages
|
|
|
17
17
|
| `fs-error` | Errno guards (`isEnoent` and friends) |
|
|
18
18
|
| `env` / `worker-host` | Environment plumbing and side-effect-free worker-host entry contract (`workerHostEntry`) |
|
|
19
19
|
| `abortable` / `async` | AbortSignal-aware stream/promise helpers |
|
|
20
|
+
| `math-delimiters` | LaTeX span/block delimiter grammar (offsets only) shared by the TUI and collab-web renderers |
|
|
20
21
|
| `peek-file` | Read the first N bytes of a file with pooled buffers |
|
|
21
22
|
| `frontmatter`, `glob`, `mime`, `temp`, `format`, `color`, `snowflake`, `tab-spacing`, `path-tree`, `sanitize-text` | Smaller single-purpose helpers |
|
|
22
23
|
|
|
@@ -55,6 +55,13 @@ export declare class RequestError extends Error {
|
|
|
55
55
|
static authRequired(data?: unknown, additionalMessage?: string): RequestError;
|
|
56
56
|
/** Creates a resource-not-found error. */
|
|
57
57
|
static resourceNotFound(uri?: string): RequestError;
|
|
58
|
+
/**
|
|
59
|
+
* Creates a session-busy error: the agent/session is already processing, so the
|
|
60
|
+
* request can be retried once idle (steer/follow-up/wait) instead of treating it
|
|
61
|
+
* as a fault. `message` carries the caller's user-facing wording; `data` should
|
|
62
|
+
* keep the stable discriminator shape (`reason: "session_busy"`).
|
|
63
|
+
*/
|
|
64
|
+
static sessionBusy(message: string, data?: unknown): RequestError;
|
|
58
65
|
/** Converts this error into a JSON-RPC result. */
|
|
59
66
|
toResult(): {
|
|
60
67
|
error: ErrorResponse;
|
package/dist/types/async.d.ts
CHANGED
|
@@ -17,4 +17,6 @@ export declare class AsyncDrain<T> {
|
|
|
17
17
|
constructor(delayMs?: number);
|
|
18
18
|
/** Queue `value`; `hnd` receives the whole batch when the window closes. */
|
|
19
19
|
push(value: T, hnd: (values: T[]) => Promise<void> | void): Promise<void>;
|
|
20
|
+
/** Runs the pending batch handler immediately and returns its completion promise. */
|
|
21
|
+
flush(): Promise<void>;
|
|
20
22
|
}
|
package/dist/types/color.d.ts
CHANGED
|
@@ -82,6 +82,40 @@ export declare function adjustHsv(hex: string, adj: HSVAdjustment): string;
|
|
|
82
82
|
* Convert HSL (h: 0-360, s: 0-1, l: 0-1) to a CSS hex string.
|
|
83
83
|
*/
|
|
84
84
|
export declare function hslToHex(h: number, s: number, l: number): string;
|
|
85
|
+
export interface OKLCH {
|
|
86
|
+
/** Perceptual lightness (0-1) */
|
|
87
|
+
l: number;
|
|
88
|
+
/** Chroma (0 = gray; sRGB peaks around 0.37) */
|
|
89
|
+
c: number;
|
|
90
|
+
/** Hue in degrees (0-360) */
|
|
91
|
+
h: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Convert a hex color to OKLCH (perceptual lightness/chroma/hue).
|
|
95
|
+
*
|
|
96
|
+
* Unlike HSL, equal `l`/`c` values look equally bright and colorful across
|
|
97
|
+
* hues, so carrying them between colors preserves the palette's "weight".
|
|
98
|
+
*/
|
|
99
|
+
export declare function hexToOklch(hex: string): OKLCH;
|
|
100
|
+
/**
|
|
101
|
+
* The sRGB gamut cusp for an OKLCH hue: the lightness/chroma point where the
|
|
102
|
+
* hue reaches its maximum chroma inside sRGB.
|
|
103
|
+
*
|
|
104
|
+
* The cusp lightness varies wildly per hue (yellow ≈ 0.97, blue ≈ 0.45), so
|
|
105
|
+
* transferring absolute OKLCH lightness/chroma between hues distorts
|
|
106
|
+
* vividness; normalize against the cusp instead. See `getSessionAccentHex`.
|
|
107
|
+
*/
|
|
108
|
+
export declare function oklchCusp(h: number): {
|
|
109
|
+
l: number;
|
|
110
|
+
c: number;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Convert OKLCH to a CSS hex string, gamut-mapping by chroma reduction.
|
|
114
|
+
*
|
|
115
|
+
* Out-of-gamut inputs keep their lightness and hue while chroma is bisected
|
|
116
|
+
* down until the color fits sRGB, matching CSS Color 4's recommended intent.
|
|
117
|
+
*/
|
|
118
|
+
export declare function oklchToHex(oklch: OKLCH): string;
|
|
85
119
|
/**
|
|
86
120
|
* Perceptual luma (gamma-encoded BT.709 weights over raw sRGB), normalized to 0..1.
|
|
87
121
|
*
|
package/dist/types/dirs.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Centralized path helpers for
|
|
2
|
+
* Centralized path helpers for zeta config directories.
|
|
3
3
|
*
|
|
4
4
|
* Uses PI_CONFIG_DIR (default ".zeta") for the config root and
|
|
5
5
|
* PI_CODING_AGENT_DIR to override the agent directory.
|
|
6
6
|
*
|
|
7
7
|
* On Linux, if XDG_DATA_HOME / XDG_STATE_HOME / XDG_CACHE_HOME environment
|
|
8
8
|
* variables are set, paths are redirected to XDG-compliant locations under
|
|
9
|
-
* $XDG_*_HOME/
|
|
9
|
+
* $XDG_*_HOME/zeta/. This requires running `zeta config migrate` first to
|
|
10
10
|
* move data to the new locations. No filesystem existence checks are performed
|
|
11
11
|
* — if the env var is set, omp trusts that the migration has been done.
|
|
12
12
|
*/
|
|
13
|
-
/** App name (e.g. "
|
|
13
|
+
/** App name (e.g. "omp") */
|
|
14
14
|
export declare const APP_NAME: string;
|
|
15
15
|
/** Config directory name (e.g. ".zeta") */
|
|
16
16
|
export declare const CONFIG_DIR_NAME: string;
|
|
@@ -18,7 +18,7 @@ export declare const CONFIG_DIR_NAME: string;
|
|
|
18
18
|
export declare const MAIN_CONFIG_FILENAMES: readonly ["config.yml", "config.yaml"];
|
|
19
19
|
/** Version (e.g. "1.0.0") */
|
|
20
20
|
export declare const VERSION: string;
|
|
21
|
-
/** Default User-Agent header string (e.g. "zeta/1.
|
|
21
|
+
/** Default User-Agent header string (e.g. "zeta/1.1.5") */
|
|
22
22
|
export declare const USER_AGENT: string;
|
|
23
23
|
/** Minimum Bun version */
|
|
24
24
|
export declare const MIN_BUN_VERSION: string;
|
|
@@ -48,6 +48,10 @@ export declare function relativePathWithinRoot(root: string, candidate: string):
|
|
|
48
48
|
export declare function getProjectDir(): string;
|
|
49
49
|
/** Set the project directory. */
|
|
50
50
|
export declare function setProjectDir(dir: string): void;
|
|
51
|
+
/** Reset the cached project directory (test seam). */
|
|
52
|
+
export declare function __resetProjectDirCacheForTests(): void;
|
|
53
|
+
/** Whether a path is absent or not a directory. Other stat failures return false. */
|
|
54
|
+
export declare function directoryIsMissing(dir: string): Promise<boolean>;
|
|
51
55
|
/**
|
|
52
56
|
* Whether `dir` resolves to an existing directory. Any stat failure — a deleted
|
|
53
57
|
* path (ENOENT), permission error, or a non-directory — returns `false`, so
|
|
@@ -55,16 +59,23 @@ export declare function setProjectDir(dir: string): void;
|
|
|
55
59
|
* working directory before {@link setProjectDir} throws on it.
|
|
56
60
|
*/
|
|
57
61
|
export declare function directoryExists(dir: string): Promise<boolean>;
|
|
58
|
-
/** Get the config directory name relative to home (e.g. ".zeta" or PI_CONFIG_DIR override). */
|
|
59
|
-
export declare function getConfigDirName(): string;
|
|
60
62
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* Whether `dir` both exists and can be entered. POSIX `stat` succeeds for a
|
|
64
|
+
* directory whose own search/execute permission is denied (it only needs
|
|
65
|
+
* +x on the parent chain), so existence alone does not imply `chdir` works.
|
|
66
|
+
* Callers that adopt a directory as a working directory must check this
|
|
67
|
+
* rather than {@link directoryExists} alone.
|
|
66
68
|
*/
|
|
67
|
-
export declare function
|
|
69
|
+
export declare function directoryIsEnterable(dir: string): Promise<boolean>;
|
|
70
|
+
/** Whether `dir` is enterable, synchronous variant. See {@link directoryIsEnterable}. */
|
|
71
|
+
export declare function directoryIsEnterableSync(dir: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Project directory when it is enterable, otherwise a safe fallback.
|
|
74
|
+
* Used by spawns that must preserve project-relative behavior when healthy.
|
|
75
|
+
*/
|
|
76
|
+
export declare function getSafeProjectCwd(): string;
|
|
77
|
+
/** Get the config directory name relative to home (e.g. ".zeta" or PI_CONFIG_DIR override). */
|
|
78
|
+
export declare function getConfigDirName(): string;
|
|
68
79
|
/** Get the config agent directory name relative to home (e.g. ".zeta/agent" or PI_CONFIG_DIR + "/agent"). */
|
|
69
80
|
export declare function getConfigAgentDirName(): string;
|
|
70
81
|
/**
|
|
@@ -112,7 +123,7 @@ export declare function getProjectAgentDir(cwd?: string): string;
|
|
|
112
123
|
export declare function getReportsDir(): string;
|
|
113
124
|
/** Get the logs directory (~/.zeta/logs). */
|
|
114
125
|
export declare function getLogsDir(): string;
|
|
115
|
-
/** Get this process's dated log path (~/.zeta/logs/
|
|
126
|
+
/** Get this process's dated log path (~/.zeta/logs/omp.YYYY-MM-DD.PID.log). */
|
|
116
127
|
export declare function getLogPath(date?: Date, pid?: number): string;
|
|
117
128
|
/**
|
|
118
129
|
* Get the plugins directory (~/.zeta/plugins or its XDG equivalent).
|
|
@@ -129,13 +140,13 @@ export declare function getPluginsDir(home?: string): string;
|
|
|
129
140
|
export declare function getPluginsNodeModules(home?: string): string;
|
|
130
141
|
/** Plugin manifest (~/.zeta/plugins/package.json). */
|
|
131
142
|
export declare function getPluginsPackageJson(home?: string): string;
|
|
132
|
-
/** Plugin lock file (~/.zeta/plugins/
|
|
143
|
+
/** Plugin lock file (~/.zeta/plugins/omp-plugins.lock.json). */
|
|
133
144
|
export declare function getPluginsLockfile(home?: string): string;
|
|
134
145
|
/** Get the remote mount directory (~/.zeta/remote). */
|
|
135
146
|
export declare function getRemoteDir(): string;
|
|
136
147
|
/**
|
|
137
148
|
* Relocate the base directory for agent-managed worktrees (PR checkouts, task
|
|
138
|
-
* isolation, and `
|
|
149
|
+
* isolation, and `omp worktree` cleanup all read the same base). Driven by the
|
|
139
150
|
* `worktree.base` setting in coding-agent; pass `undefined`/empty to clear and
|
|
140
151
|
* fall back to `OMP_WORKTREE_DIR` or the `~/.zeta/wt` default.
|
|
141
152
|
*
|
|
@@ -190,7 +201,7 @@ export declare function getGpuCachePath(): string;
|
|
|
190
201
|
*/
|
|
191
202
|
export declare function getGithubCacheDbPath(): string;
|
|
192
203
|
/**
|
|
193
|
-
* Get the conventional commit inference cache database path (~/.
|
|
204
|
+
* Get the conventional commit inference cache database path (~/.zeta/cache/commit-inference.db).
|
|
194
205
|
* Honors `OMP_COMMIT_CACHE_DB` so tests and operators can isolate the cache.
|
|
195
206
|
*/
|
|
196
207
|
export declare function getCommitCacheDbPath(): string;
|
|
@@ -202,10 +213,9 @@ export declare function getLegacyPiExtensionCacheDbPath(): string;
|
|
|
202
213
|
* operators can isolate or relocate the cache file.
|
|
203
214
|
*/
|
|
204
215
|
export declare function getAuthBrokerSnapshotCachePath(): string;
|
|
205
|
-
/** Get the
|
|
206
|
-
/** Get the commit-author avatar cache directory (~/.omp/cache/avatars). */
|
|
216
|
+
/** Get the commit-author avatar cache directory (~/.zeta/cache/avatars). */
|
|
207
217
|
export declare function getAvatarCacheDir(): string;
|
|
208
|
-
/** Get the local FastEmbed model cache directory (~/.
|
|
218
|
+
/** Get the local FastEmbed model cache directory (~/.zeta/cache/fastembed). */
|
|
209
219
|
export declare function getFastembedCacheDir(): string;
|
|
210
220
|
/** Get the on-demand fastembed runtime install root (~/.zeta/cache/fastembed-runtime). */
|
|
211
221
|
export declare function getFastembedRuntimeDir(): string;
|
|
@@ -237,6 +247,8 @@ export declare function getModelDbPath(agentDir?: string): string;
|
|
|
237
247
|
export declare function getTinyModelsCacheDir(agentDir?: string): string;
|
|
238
248
|
/** Get the document conversion cache directory (~/.zeta/agent/cache/document-conversions; XDG default: $XDG_CACHE_HOME/zeta/cache/document-conversions). */
|
|
239
249
|
export declare function getDocumentConversionCacheDir(agentDir?: string): string;
|
|
250
|
+
/** Get the per-project composer speculative cache directory (~/.zeta/agent/cache/composer; XDG default: $XDG_CACHE_HOME/zeta/cache/composer). */
|
|
251
|
+
export declare function getComposerCacheDir(agentDir?: string): string;
|
|
240
252
|
/** Get the sessions directory (~/.zeta/agent/sessions). */
|
|
241
253
|
export declare function getSessionsDir(agentDir?: string): string;
|
|
242
254
|
/** Get the content-addressed blob store directory (~/.zeta/agent/blobs). */
|
|
@@ -255,9 +267,9 @@ export declare function getAgentModulesDir(agentDir?: string): string;
|
|
|
255
267
|
export declare function getMemoriesDir(agentDir?: string): string;
|
|
256
268
|
/** Get the terminal sessions directory (~/.zeta/agent/terminal-sessions). */
|
|
257
269
|
export declare function getTerminalSessionsDir(agentDir?: string): string;
|
|
258
|
-
/** Get the crash log path (~/.zeta/agent/
|
|
270
|
+
/** Get the crash log path (~/.zeta/agent/omp-crash.log). */
|
|
259
271
|
export declare function getCrashLogPath(agentDir?: string): string;
|
|
260
|
-
/** Get the debug log path (~/.zeta/agent/
|
|
272
|
+
/** Get the debug log path (~/.zeta/agent/omp-debug.log). */
|
|
261
273
|
export declare function getDebugLogPath(agentDir?: string): string;
|
|
262
274
|
/** Get the secret placeholder key path (~/.zeta/agent/secret-placeholder.key; XDG default: $XDG_STATE_HOME/zeta/secret-placeholder.key). Adopts a legacy key on first XDG resolution. */
|
|
263
275
|
export declare function getSecretPlaceholderKeyPath(): string;
|
|
@@ -283,10 +295,13 @@ export declare function getProjectPluginOverridesPath(cwd?: string): string;
|
|
|
283
295
|
export declare function getMCPConfigPath(scope: "user" | "project", cwd?: string): string;
|
|
284
296
|
/** Get the SSH config file path. */
|
|
285
297
|
export declare function getSSHConfigPath(scope: "user" | "project", cwd?: string): string;
|
|
286
|
-
/**
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
298
|
+
/**
|
|
299
|
+
* Application label for usage attribution (`OMP_APP_NAME`), defaulting to
|
|
300
|
+
* `omp`. Embedders that drive omp programmatically (robomp, CI bots, …) set
|
|
301
|
+
* the env var so broker-side per-client burn tracking can answer "what did
|
|
302
|
+
* app X use" instead of folding everything into one install-wide bucket.
|
|
303
|
+
*/
|
|
304
|
+
export declare function getAppName(): string;
|
|
290
305
|
/**
|
|
291
306
|
* Persistent per-install UUID stored at `~/.zeta/install-id`.
|
|
292
307
|
*
|
|
@@ -304,3 +319,7 @@ export declare function getTrackingIndexPath(agentDir?: string): string;
|
|
|
304
319
|
export declare function getInstallId(): string;
|
|
305
320
|
/** Test-only: clear cached install id. Never call from production code. */
|
|
306
321
|
export declare function __resetInstallIdCacheForTests(): void;
|
|
322
|
+
/** Get the project-level tracking directory (<project>/.zeta/tracking). */
|
|
323
|
+
export declare function getProjectTrackingDir(cwd?: string): string;
|
|
324
|
+
/** Get the global tracking index path (~/.zeta/agent/tracking-index.json). */
|
|
325
|
+
export declare function getTrackingIndexPath(agentDir?: string): string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./json.js";
|
|
|
14
14
|
export * from "./json-parse.js";
|
|
15
15
|
export * as logger from "./logger.js";
|
|
16
16
|
export * from "./loop-phase.js";
|
|
17
|
+
export * from "./math-delimiters.js";
|
|
17
18
|
export * from "./mermaid-ascii.js";
|
|
18
19
|
export * from "./mime.js";
|
|
19
20
|
export * from "./path.js";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LaTeX delimiter grammar for agent-authored Markdown: where does a math span
|
|
3
|
+
* begin and end, in source offsets. Carries no rendering policy — what to do
|
|
4
|
+
* with an unclosed opener, whether a body is typesettable, and how it is
|
|
5
|
+
* displayed belong to the renderer (Unicode in the TUI, KaTeX in collab web).
|
|
6
|
+
*/
|
|
7
|
+
/** Opening delimiter. Each closer (`$`, `$$`, `\)`, `\]`) is as wide as its opener. */
|
|
8
|
+
export type MathOpener = "$" | "$$" | "\\(" | "\\[";
|
|
9
|
+
/** A closed math span found in the source. */
|
|
10
|
+
export interface MathSpan {
|
|
11
|
+
opener: MathOpener;
|
|
12
|
+
/** True for the display forms `$$…$$` and `\[…\]`. */
|
|
13
|
+
display: boolean;
|
|
14
|
+
/** Offset one past the closing delimiter. */
|
|
15
|
+
end: number;
|
|
16
|
+
/** Source between the delimiters, verbatim. */
|
|
17
|
+
body: string;
|
|
18
|
+
}
|
|
19
|
+
/** An own-line display block: opener and closer each alone on their line. */
|
|
20
|
+
export interface MathBlock {
|
|
21
|
+
/** Both delimiter lines, the body, and the trailing newline. */
|
|
22
|
+
raw: string;
|
|
23
|
+
body: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Leftmost offset at or after `from` where an opener could begin. A scan hint,
|
|
27
|
+
* not a decision: whether that candidate is really math — escaped, currency,
|
|
28
|
+
* unclosed — is decided by {@link mathSpanAt}.
|
|
29
|
+
*/
|
|
30
|
+
export declare function mathStartIndex(source: string, from?: number): number | undefined;
|
|
31
|
+
/** Math opener at `at`, or `undefined` when no delimiter starts there. */
|
|
32
|
+
export declare function mathOpenerAt(source: string, at: number): MathOpener | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* The span opened at `at`, or `undefined` when the run is not math — including
|
|
35
|
+
* an opener the source escaped, so `\$x$` and `\\(x\)` are literal text.
|
|
36
|
+
*
|
|
37
|
+
* `from` bounds how far back the escape scan may look. Leave it at 0 when
|
|
38
|
+
* reading raw source. Pass the offset your own walk resumed at if you have
|
|
39
|
+
* already consumed the escapes behind it, as `renderMathInText` does: after it
|
|
40
|
+
* emits the `\\` of `\\\(x\)`, the `\(` that follows is a real opener even
|
|
41
|
+
* though a backslash precedes it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function mathSpanAt(source: string, at: number, from?: number): MathSpan | undefined;
|
|
44
|
+
/** The own-line display block starting at offset 0, or `undefined`. */
|
|
45
|
+
export declare function mathBlockAt(source: string): MathBlock | undefined;
|
|
@@ -44,6 +44,19 @@ export type BrokenPipeSource = "ipc-send" | "stdio-write";
|
|
|
44
44
|
export declare function classifyBrokenPipe(err: Error): BrokenPipeSource | undefined;
|
|
45
45
|
/** Whether an EPIPE came from an IPC `send()` to an optional worker. */
|
|
46
46
|
export declare function isIpcSendEpipe(err: Error): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Whether an uncaught error is Bun's asynchronous `ERR_SOCKET_CLOSED` thrown
|
|
49
|
+
* from inside `node:net` internals with no application frames on the stack.
|
|
50
|
+
*
|
|
51
|
+
* Bun ≥1.4 can fire the close callback of an already-closed `node:net` socket
|
|
52
|
+
* on a fresh stack; the throw bypasses every callsite try/catch and surfaces
|
|
53
|
+
* here as a process-level uncaughtException. Closing an already-closed socket
|
|
54
|
+
* is inherently a no-op — the socket owner's own `error`/`close` handlers
|
|
55
|
+
* still drive recovery — so tearing the session down for it is pure loss.
|
|
56
|
+
* Only frameless internal stacks qualify: an `ERR_SOCKET_CLOSED` raised
|
|
57
|
+
* through application code keeps the fatal path.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isInternalSocketClosedError(err: unknown): boolean;
|
|
47
60
|
/**
|
|
48
61
|
* Detect Bun's advanced-serialization (structured-clone) IPC decode failure.
|
|
49
62
|
*
|
|
@@ -94,10 +107,10 @@ export declare function registerStdioDisconnectHandling(): () => void;
|
|
|
94
107
|
*/
|
|
95
108
|
export declare function markExpectedCleanupError<T extends object>(reason: T): T;
|
|
96
109
|
/**
|
|
97
|
-
* Whether `reason` (or any
|
|
98
|
-
* {@link markExpectedCleanupError}.
|
|
99
|
-
*
|
|
100
|
-
*
|
|
110
|
+
* Whether `reason` (or any object in its bounded `cause` chain) was explicitly
|
|
111
|
+
* marked via {@link markExpectedCleanupError}. Runtime error names and codes
|
|
112
|
+
* are intentionally insufficient: unmarked `AbortError` and socket failures
|
|
113
|
+
* can originate from application code and must remain fatal when unhandled.
|
|
101
114
|
*/
|
|
102
115
|
export declare function isExpectedCleanupError(reason: unknown): boolean;
|
|
103
116
|
/**
|
|
@@ -110,15 +123,33 @@ export declare function interceptUnhandledRejections(interceptor: (reason: unkno
|
|
|
110
123
|
* through an uncaught exception or unhandled rejection.
|
|
111
124
|
*/
|
|
112
125
|
export declare function registerFatalRecoveryHint(provider: FatalRecoveryHintProvider): () => void;
|
|
126
|
+
/** Controls when a registered cleanup callback participates in cleanup passes. */
|
|
127
|
+
export interface CleanupRegistrationOptions {
|
|
128
|
+
/**
|
|
129
|
+
* Run only on a real exit, never during a manual keep-alive cleanup.
|
|
130
|
+
* The registration remains armed when a keep-alive pass skips it.
|
|
131
|
+
*/
|
|
132
|
+
exitOnly?: boolean;
|
|
133
|
+
}
|
|
113
134
|
/**
|
|
114
|
-
*
|
|
135
|
+
* Registers a cleanup callback for shutdown, signals, fatal errors, and
|
|
136
|
+
* repeatable manual cleanup passes.
|
|
115
137
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
138
|
+
* Registrations persist across keep-alive {@link cleanup} passes and run at
|
|
139
|
+
* most once per pass. Set `exitOnly` for resources the continuing process still
|
|
140
|
+
* holds (open databases, cached handles): keep-alive passes skip the callback
|
|
141
|
+
* without consuming its registration, while the eventual real exit runs it.
|
|
142
|
+
*
|
|
143
|
+
* A callback registered during a running keep-alive pass joins future passes;
|
|
144
|
+
* normal callbacks also run immediately for the current pass. Registrations
|
|
145
|
+
* made during a real exit run immediately.
|
|
146
|
+
*
|
|
147
|
+
* Returns a function that permanently cancels the registration.
|
|
118
148
|
*/
|
|
119
|
-
export declare function register(id: string, callback: (reason: Reason) => void | Promise<void
|
|
149
|
+
export declare function register(id: string, callback: (reason: Reason) => void | Promise<void>, options?: CleanupRegistrationOptions): () => void;
|
|
120
150
|
/**
|
|
121
|
-
* Runs all cleanup callbacks without exiting
|
|
151
|
+
* Runs all cleanup callbacks without exiting, then re-arms the system so
|
|
152
|
+
* resources opened afterwards are still cleaned at the eventual real exit.
|
|
122
153
|
* Use this in workers or when you need to clean up but continue execution.
|
|
123
154
|
*/
|
|
124
155
|
export declare function cleanup(): Promise<void>;
|
|
@@ -127,6 +158,12 @@ export interface QuitOptions {
|
|
|
127
158
|
/** Wait for buffered stdout before exiting; disable after the terminal has disconnected. */
|
|
128
159
|
drainStdout?: boolean;
|
|
129
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Waits (bounded) for buffered stdout to reach the terminal. Used before
|
|
163
|
+
* process exit and before an exec-replace, where unflushed output would be
|
|
164
|
+
* lost with the process image.
|
|
165
|
+
*/
|
|
166
|
+
export declare function drainStdout(): Promise<void>;
|
|
130
167
|
/**
|
|
131
168
|
* Runs all cleanup callbacks and exits through the current `process.exit`.
|
|
132
169
|
*
|
package/dist/types/ptree.d.ts
CHANGED
|
@@ -10,6 +10,13 @@ import type { Spawn, Subprocess } from "bun";
|
|
|
10
10
|
type InMask = "pipe" | "ignore" | Buffer | Uint8Array | null;
|
|
11
11
|
/** A Bun subprocess with stdout/stderr always piped (stdin may vary). */
|
|
12
12
|
type PipedSubprocess<In extends InMask = InMask> = Subprocess<In, "pipe", "pipe">;
|
|
13
|
+
/**
|
|
14
|
+
* Build the Linux child-subreaper entrypoint.
|
|
15
|
+
*
|
|
16
|
+
* @internal Exported so tests can force a missing first libc soname and verify
|
|
17
|
+
* the loader continues to the next candidate.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createLinuxSubreaperScript(libcCandidates?: readonly string[]): string;
|
|
13
20
|
/**
|
|
14
21
|
* Base for all exceptions representing child process nonzero exit, killed, or
|
|
15
22
|
* cancellation.
|
|
@@ -63,7 +70,7 @@ export declare class ChildProcess<In extends InMask = InMask> {
|
|
|
63
70
|
#private;
|
|
64
71
|
readonly proc: PipedSubprocess<In>;
|
|
65
72
|
readonly exposeStderr: boolean;
|
|
66
|
-
constructor(proc: PipedSubprocess<In>, exposeStderr: boolean, retainFullStderr?: boolean);
|
|
73
|
+
constructor(proc: PipedSubprocess<In>, exposeStderr: boolean, retainFullStderr?: boolean, terminateGroup?: boolean, hardKillTree?: boolean);
|
|
67
74
|
get pid(): number;
|
|
68
75
|
get exited(): Promise<number>;
|
|
69
76
|
get exitCode(): number | null;
|
|
@@ -93,6 +100,13 @@ export declare class ChildProcess<In extends InMask = InMask> {
|
|
|
93
100
|
type ChildSpawnOptions<In extends InMask = InMask> = Omit<Spawn.SpawnOptions<In, "pipe", "pipe">, "stdout" | "stderr" | "detached"> & {
|
|
94
101
|
signal?: AbortSignal;
|
|
95
102
|
detached?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* On Linux, supervise the command from a child subreaper so descendants
|
|
105
|
+
* remain reachable after changing session and reparenting. Other platforms
|
|
106
|
+
* ignore this option. macOS process groups cannot retain a daemonized
|
|
107
|
+
* descendant that creates a new session and reparents to launchd.
|
|
108
|
+
*/
|
|
109
|
+
subreaper?: boolean;
|
|
96
110
|
/** Expose and retain complete stderr for a later `wait({ stderr: "full" })`. */
|
|
97
111
|
stderr?: "full" | null;
|
|
98
112
|
};
|
|
@@ -73,6 +73,13 @@ export interface EnsureRuntimeInstalledOptions {
|
|
|
73
73
|
export declare function writeRuntimeManifest(runtimeDir: string, install: RuntimeInstallSpec): Promise<void>;
|
|
74
74
|
/**
|
|
75
75
|
* Materialize a pinned dependency set into `runtimeDir` (idempotent,
|
|
76
|
-
* cross-process safe
|
|
76
|
+
* cross-process safe). Returns `runtimeDir`.
|
|
77
|
+
*
|
|
78
|
+
* Serialization uses the OS-backed {@link withFileLock} at
|
|
79
|
+
* `${runtimeDir}.install.lock`, which the kernel releases on process death, so
|
|
80
|
+
* a crashed installer cannot wedge later attempts (issue #10120). The path is
|
|
81
|
+
* deliberately distinct from the legacy `${runtimeDir}.lock` mkdir directory;
|
|
82
|
+
* {@link withLegacyInstallLock} atomically reserves that namespace during the
|
|
83
|
+
* new install so older processes cannot cross the migration boundary.
|
|
77
84
|
*/
|
|
78
85
|
export declare function ensureRuntimeInstalled(options: EnsureRuntimeInstalledOptions): Promise<string>;
|
package/dist/types/sqlite.d.ts
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* one implementation here prevents the classifiers from drifting between the
|
|
8
8
|
* credential store and the model cache.
|
|
9
9
|
*/
|
|
10
|
+
import type { Database } from "bun:sqlite";
|
|
11
|
+
/** Checkpoints committed WAL frames without waiting for concurrent readers. */
|
|
12
|
+
export declare function checkpointWal(db: Database): void;
|
|
10
13
|
/**
|
|
11
14
|
* SQLite's busy result-code family — base `SQLITE_BUSY` plus the extended
|
|
12
15
|
* variants `SQLITE_BUSY_RECOVERY` (concurrent WAL recovery), `SQLITE_BUSY_SNAPSHOT`,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@linxiraos/pi-utils",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.7",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
6
|
"homepage": "https://linxira-os.github.io/zeta/",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"fmt": "biome format --write ."
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@linxiraos/pi-natives": "1.1.
|
|
34
|
+
"@linxiraos/pi-natives": "1.1.7"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/bun": "^1.3.14"
|
package/src/acp/transport.ts
CHANGED
|
@@ -77,6 +77,15 @@ export class RequestError extends Error {
|
|
|
77
77
|
uri === undefined ? undefined : { uri },
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Creates a session-busy error: the agent/session is already processing, so the
|
|
82
|
+
* request can be retried once idle (steer/follow-up/wait) instead of treating it
|
|
83
|
+
* as a fault. `message` carries the caller's user-facing wording; `data` should
|
|
84
|
+
* keep the stable discriminator shape (`reason: "session_busy"`).
|
|
85
|
+
*/
|
|
86
|
+
static sessionBusy(message: string, data?: unknown): RequestError {
|
|
87
|
+
return new RequestError(-32003, message, data);
|
|
88
|
+
}
|
|
80
89
|
/** Converts this error into a JSON-RPC result. */
|
|
81
90
|
toResult(): { error: ErrorResponse } {
|
|
82
91
|
return { error: this.toErrorResponse() };
|
package/src/async.ts
CHANGED
|
@@ -59,6 +59,7 @@ export function withTimeout<T>(promise: Promise<T>, ms: number, message: string,
|
|
|
59
59
|
export class AsyncDrain<T> {
|
|
60
60
|
#queue?: T[];
|
|
61
61
|
#promise = Promise.resolve();
|
|
62
|
+
#flush?: () => void;
|
|
62
63
|
|
|
63
64
|
constructor(readonly delayMs: number = 0) {}
|
|
64
65
|
|
|
@@ -66,21 +67,28 @@ export class AsyncDrain<T> {
|
|
|
66
67
|
push(value: T, hnd: (values: T[]) => Promise<void> | void): Promise<void> {
|
|
67
68
|
let queue = this.#queue;
|
|
68
69
|
if (!queue) {
|
|
69
|
-
|
|
70
|
+
const batch: T[] = [];
|
|
71
|
+
this.#queue = batch;
|
|
72
|
+
queue = batch;
|
|
70
73
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
71
74
|
const exec = (): void => {
|
|
75
|
+
if (this.#queue !== batch) return;
|
|
76
|
+
this.#queue = undefined;
|
|
77
|
+
this.#flush = undefined;
|
|
72
78
|
try {
|
|
73
|
-
|
|
74
|
-
this.#queue = undefined;
|
|
75
|
-
}
|
|
76
|
-
resolve(hnd(queue!));
|
|
79
|
+
resolve(hnd(batch));
|
|
77
80
|
} catch (error) {
|
|
78
81
|
reject(error);
|
|
79
82
|
}
|
|
80
83
|
};
|
|
81
84
|
if (this.delayMs > 0) {
|
|
82
|
-
setTimeout(exec, this.delayMs);
|
|
85
|
+
const timer = setTimeout(exec, this.delayMs);
|
|
86
|
+
this.#flush = () => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
exec();
|
|
89
|
+
};
|
|
83
90
|
} else {
|
|
91
|
+
this.#flush = exec;
|
|
84
92
|
queueMicrotask(exec);
|
|
85
93
|
}
|
|
86
94
|
this.#promise = promise;
|
|
@@ -88,4 +96,10 @@ export class AsyncDrain<T> {
|
|
|
88
96
|
queue.push(value);
|
|
89
97
|
return this.#promise;
|
|
90
98
|
}
|
|
99
|
+
|
|
100
|
+
/** Runs the pending batch handler immediately and returns its completion promise. */
|
|
101
|
+
flush(): Promise<void> {
|
|
102
|
+
this.#flush?.();
|
|
103
|
+
return this.#promise;
|
|
104
|
+
}
|
|
91
105
|
}
|