@linxiraos/pi-utils 1.0.0 → 1.0.2
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 +41 -6
- package/package.json +56 -57
- package/src/dirs.ts +99 -91
- package/src/fetch-retry.ts +11 -2
- package/src/json-parse.ts +18 -7
- package/src/logger.ts +55 -20
- package/src/ptree.ts +2 -2
- package/src/stderr-guard.ts +1 -1
- package/src/stream.ts +27 -3
- package/src/vendor/mermaid-ascii/ascii/canvas.ts +8 -6
- package/src/vendor/mermaid-ascii/ascii/draw.ts +21 -14
- package/src/vendor/mermaid-ascii/text-metrics.ts +3 -0
- package/dist/types/abortable.d.ts +0 -32
- package/dist/types/acp/connection.d.ts +0 -118
- package/dist/types/acp/protocol.d.ts +0 -526
- package/dist/types/acp/schema.d.ts +0 -41
- package/dist/types/acp/stream.d.ts +0 -8
- package/dist/types/acp/transport.d.ts +0 -81
- package/dist/types/acp.d.ts +0 -6
- package/dist/types/async.d.ts +0 -20
- package/dist/types/binary.d.ts +0 -17
- package/dist/types/browsers.d.ts +0 -68
- package/dist/types/chalk.d.ts +0 -125
- package/dist/types/cli.d.ts +0 -134
- package/dist/types/color.d.ts +0 -102
- package/dist/types/dates.d.ts +0 -7
- package/dist/types/dirs.d.ts +0 -282
- package/dist/types/docx/converter.d.ts +0 -46
- package/dist/types/docx/xml.d.ts +0 -26
- package/dist/types/docx/zip.d.ts +0 -6
- package/dist/types/docx.d.ts +0 -11
- package/dist/types/dom/core.d.ts +0 -431
- package/dist/types/dom/parser.d.ts +0 -7
- package/dist/types/dom/selector.d.ts +0 -5
- package/dist/types/dom.d.ts +0 -5
- package/dist/types/env.d.ts +0 -124
- package/dist/types/fetch-retry.d.ts +0 -96
- package/dist/types/file-lock.d.ts +0 -23
- package/dist/types/format.d.ts +0 -37
- package/dist/types/frontmatter.d.ts +0 -46
- package/dist/types/fs-error.d.ts +0 -31
- package/dist/types/glob.d.ts +0 -28
- package/dist/types/headers.d.ts +0 -34
- package/dist/types/index.d.ts +0 -39
- package/dist/types/json-parse.d.ts +0 -74
- package/dist/types/json.d.ts +0 -14
- package/dist/types/logger/rotating-file.d.ts +0 -18
- package/dist/types/logger.d.ts +0 -96
- package/dist/types/loop-phase.d.ts +0 -10
- package/dist/types/lru.d.ts +0 -46
- package/dist/types/marked/core.d.ts +0 -445
- package/dist/types/marked.d.ts +0 -2
- package/dist/types/mermaid-ascii.d.ts +0 -11
- package/dist/types/mime.d.ts +0 -29
- package/dist/types/module-timer.d.ts +0 -1
- package/dist/types/path-tree.d.ts +0 -76
- package/dist/types/path.d.ts +0 -2
- package/dist/types/peek-file.d.ts +0 -29
- package/dist/types/postmortem.d.ts +0 -104
- package/dist/types/process-name.d.ts +0 -7
- package/dist/types/procmgr.d.ts +0 -74
- package/dist/types/prompt.d.ts +0 -18
- package/dist/types/ptree.d.ts +0 -110
- package/dist/types/readability/readability.d.ts +0 -9
- package/dist/types/readability/readerable.d.ts +0 -10
- package/dist/types/readability/types.d.ts +0 -70
- package/dist/types/readability.d.ts +0 -4
- package/dist/types/ring.d.ts +0 -93
- package/dist/types/runtime-install.d.ts +0 -78
- package/dist/types/sanitize-text.d.ts +0 -29
- package/dist/types/snowflake.d.ts +0 -25
- package/dist/types/stderr-guard.d.ts +0 -22
- package/dist/types/stream.d.ts +0 -67
- package/dist/types/tab-spacing.d.ts +0 -24
- package/dist/types/temp.d.ts +0 -17
- package/dist/types/template.d.ts +0 -62
- package/dist/types/timing-buffer.d.ts +0 -22
- package/dist/types/tls-fetch.d.ts +0 -37
- package/dist/types/turndown/gfm.d.ts +0 -11
- package/dist/types/turndown/html.d.ts +0 -5
- package/dist/types/turndown/service.d.ts +0 -21
- package/dist/types/turndown/types.d.ts +0 -70
- package/dist/types/turndown.d.ts +0 -4
- package/dist/types/type-guards.d.ts +0 -5
- package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +0 -41
- package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +0 -89
- package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +0 -12
- package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +0 -66
- package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +0 -48
- package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +0 -43
- package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +0 -56
- package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +0 -65
- package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +0 -27
- package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +0 -17
- package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +0 -34
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +0 -26
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +0 -31
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +0 -59
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +0 -17
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +0 -30
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +0 -55
- package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +0 -206
- package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +0 -51
- package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +0 -2
- package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/class/types.d.ts +0 -102
- package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/er/types.d.ts +0 -76
- package/dist/types/vendor/mermaid-ascii/index.d.ts +0 -1
- package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +0 -9
- package/dist/types/vendor/mermaid-ascii/parser.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +0 -130
- package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +0 -21
- package/dist/types/vendor/mermaid-ascii/types.d.ts +0 -114
- package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +0 -25
- package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +0 -145
- package/dist/types/version.d.ts +0 -18
- package/dist/types/vterm/buffer.d.ts +0 -99
- package/dist/types/vterm/terminal.d.ts +0 -44
- package/dist/types/vterm.d.ts +0 -8
- package/dist/types/which.d.ts +0 -37
- package/dist/types/worker-host.d.ts +0 -51
- package/dist/types/xml.d.ts +0 -31
package/src/dirs.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized path helpers for omp config directories.
|
|
3
3
|
*
|
|
4
|
-
* Uses PI_CONFIG_DIR (default ".
|
|
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/omp/. This requires running `
|
|
9
|
+
* $XDG_*_HOME/omp/. 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
|
*/
|
|
@@ -28,6 +28,9 @@ export const MAIN_CONFIG_FILENAMES = ["config.yml", "config.yaml"] as const;
|
|
|
28
28
|
/** Version (e.g. "1.0.0") */
|
|
29
29
|
export const VERSION: string = version;
|
|
30
30
|
|
|
31
|
+
/** Default User-Agent header string (e.g. "omp/17.2.12") */
|
|
32
|
+
export const USER_AGENT = `omp/${VERSION}`;
|
|
33
|
+
|
|
31
34
|
/** Minimum Bun version */
|
|
32
35
|
export const MIN_BUN_VERSION: string = engines.bun.replace(/[^0-9.]/g, "");
|
|
33
36
|
|
|
@@ -202,21 +205,34 @@ export async function directoryExists(dir: string): Promise<boolean> {
|
|
|
202
205
|
}
|
|
203
206
|
}
|
|
204
207
|
|
|
205
|
-
/** Get the config directory name relative to home (e.g. ".zeta" or PI_CONFIG_DIR override).
|
|
208
|
+
/** Get the config directory name relative to home (e.g. ".zeta" or PI_CONFIG_DIR override). */
|
|
206
209
|
export function getConfigDirName(): string {
|
|
207
210
|
if (process.env.PI_CONFIG_DIR) return process.env.PI_CONFIG_DIR;
|
|
208
|
-
|
|
211
|
+
return ".zeta";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* One-time migration of a legacy `~/.omp` config root to `~/.zeta`. Called
|
|
216
|
+
* from the CLI entrypoint before any user data is written; it only moves the
|
|
217
|
+
* directory when `.zeta` does not exist yet. Zeta keeps no `.omp`
|
|
218
|
+
* compatibility surface afterwards (see AGENTS.md), so this is the single
|
|
219
|
+
* exception that honors pre-existing installs.
|
|
220
|
+
*/
|
|
221
|
+
export function migrateLegacyOmpConfigDir(): void {
|
|
222
|
+
if (process.env.PI_CONFIG_DIR) return;
|
|
209
223
|
const home = os.homedir();
|
|
210
224
|
try {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (fs.existsSync(
|
|
215
|
-
|
|
216
|
-
|
|
225
|
+
const zetaDir = path.join(home, ".zeta");
|
|
226
|
+
if (fs.existsSync(zetaDir)) return;
|
|
227
|
+
const ompDir = path.join(home, ".omp");
|
|
228
|
+
if (!fs.existsSync(ompDir)) return;
|
|
229
|
+
fs.renameSync(ompDir, zetaDir);
|
|
230
|
+
} catch {
|
|
231
|
+
// Migration is best-effort; a failed rename must not break startup.
|
|
232
|
+
}
|
|
217
233
|
}
|
|
218
234
|
|
|
219
|
-
/** Get the config agent directory name relative to home (e.g. ".
|
|
235
|
+
/** Get the config agent directory name relative to home (e.g. ".zeta/agent" or PI_CONFIG_DIR + "/agent"). */
|
|
220
236
|
export function getConfigAgentDirName(): string {
|
|
221
237
|
const profile = getActiveProfile();
|
|
222
238
|
return profile ? path.join(getConfigDirName(), "profiles", profile, "agent") : `${getConfigDirName()}/agent`;
|
|
@@ -256,12 +272,12 @@ class DirResolver {
|
|
|
256
272
|
const isDefault = this.agentDir === defaultAgent;
|
|
257
273
|
|
|
258
274
|
// XDG is a Linux convention. On supported platforms, default profile state
|
|
259
|
-
// resolves under $XDG_*_HOME/omp once `
|
|
275
|
+
// resolves under $XDG_*_HOME/omp once `zeta config init-xdg` has migrated
|
|
260
276
|
// the user's data. Named profiles follow a stricter rule: the XDG choice
|
|
261
277
|
// is keyed on the profile-specific XDG path, never the base app root.
|
|
262
278
|
//
|
|
263
279
|
// Why: if we consulted the base app root for named profiles too, the same
|
|
264
|
-
// profile could resolve to `~/.
|
|
280
|
+
// profile could resolve to `~/.zeta/profiles/<name>` on first activation
|
|
265
281
|
// (when no $XDG_*_HOME/omp exists yet) and then silently move to
|
|
266
282
|
// `$XDG_*_HOME/omp/profiles/<name>` the moment the base appeared, orphaning
|
|
267
283
|
// the earlier state. Pinning on the profile path means a profile's location
|
|
@@ -299,7 +315,7 @@ class DirResolver {
|
|
|
299
315
|
state: xdgState ?? this.configRoot,
|
|
300
316
|
cache: xdgCache ?? this.configRoot,
|
|
301
317
|
};
|
|
302
|
-
// XDG flattens the agent/ prefix: ~/.
|
|
318
|
+
// XDG flattens the agent/ prefix: ~/.zeta/agent/sessions → $XDG_DATA_HOME/zeta/sessions
|
|
303
319
|
this.#agentDirs = {
|
|
304
320
|
data: xdgData ?? this.agentDir,
|
|
305
321
|
state: xdgState ?? this.agentDir,
|
|
@@ -339,7 +355,7 @@ class DirResolver {
|
|
|
339
355
|
* agent dir. The profile source can be the active profile or a lower-priority
|
|
340
356
|
* `PI_PROFILE` that was bypassed because `OMP_PROFILE` explicitly selected the
|
|
341
357
|
* default profile. Returns `undefined` in those cases so reset falls back to the
|
|
342
|
-
* standard `~/.
|
|
358
|
+
* standard `~/.zeta/agent`.
|
|
343
359
|
*/
|
|
344
360
|
function resolvePreProfileAgentDir(
|
|
345
361
|
profile: string | undefined,
|
|
@@ -374,7 +390,7 @@ let dirs = new DirResolver({
|
|
|
374
390
|
* unconditionally deleting the env var. Without the snapshot, a process started
|
|
375
391
|
* with `PI_CODING_AGENT_DIR=/custom` then `setProfile("work")` then
|
|
376
392
|
* `setProfile(undefined)` would silently lose `/custom` and fall back to
|
|
377
|
-
* `~/.
|
|
393
|
+
* `~/.zeta/agent`. Captured at module load — ignoring a profile-derived value
|
|
378
394
|
* inherited from a parent's `setProfile` (see {@link resolvePreProfileAgentDir})
|
|
379
395
|
* — and refreshed on `setAgentDir`, since that call is the user explicitly
|
|
380
396
|
* redefining the baseline.
|
|
@@ -411,7 +427,7 @@ export function refreshDirsFromEnv(): void {
|
|
|
411
427
|
// Root directories
|
|
412
428
|
// =============================================================================
|
|
413
429
|
|
|
414
|
-
/** Get the config root directory (~/.
|
|
430
|
+
/** Get the config root directory (~/.zeta). */
|
|
415
431
|
export function getConfigRootDir(): string {
|
|
416
432
|
return dirs.configRoot;
|
|
417
433
|
}
|
|
@@ -498,45 +514,37 @@ export function getActiveProfile(): string | undefined {
|
|
|
498
514
|
export function getProfileRootDir(profile: string | undefined): string {
|
|
499
515
|
return getProfileConfigRoot(normalizeProfileName(profile));
|
|
500
516
|
}
|
|
501
|
-
/** Get the agent config directory (~/.
|
|
517
|
+
/** Get the agent config directory (~/.zeta/agent). */
|
|
502
518
|
export function getAgentDir(): string {
|
|
503
519
|
return dirs.agentDir;
|
|
504
520
|
}
|
|
505
521
|
|
|
506
|
-
/** Get the project-local config directory (.zeta).
|
|
522
|
+
/** Get the project-local config directory (.zeta). */
|
|
507
523
|
export function getProjectAgentDir(cwd: string = getProjectDir()): string {
|
|
508
|
-
|
|
509
|
-
try {
|
|
510
|
-
if (fs.existsSync(zetaDir)) return zetaDir;
|
|
511
|
-
} catch {}
|
|
512
|
-
const ompDir = path.join(cwd, ".omp");
|
|
513
|
-
try {
|
|
514
|
-
if (fs.existsSync(ompDir)) return ompDir;
|
|
515
|
-
} catch {}
|
|
516
|
-
return zetaDir; // default for new projects
|
|
524
|
+
return path.join(cwd, ".zeta");
|
|
517
525
|
}
|
|
518
526
|
|
|
519
527
|
// =============================================================================
|
|
520
|
-
// Config-root subdirectories (~/.
|
|
528
|
+
// Config-root subdirectories (~/.zeta/*)
|
|
521
529
|
// =============================================================================
|
|
522
530
|
|
|
523
|
-
/** Get the reports directory (~/.
|
|
531
|
+
/** Get the reports directory (~/.zeta/reports). */
|
|
524
532
|
export function getReportsDir(): string {
|
|
525
533
|
return dirs.rootSubdir("reports", "state");
|
|
526
534
|
}
|
|
527
535
|
|
|
528
|
-
/** Get the logs directory (~/.
|
|
536
|
+
/** Get the logs directory (~/.zeta/logs). */
|
|
529
537
|
export function getLogsDir(): string {
|
|
530
538
|
return dirs.rootSubdir("logs", "state");
|
|
531
539
|
}
|
|
532
540
|
|
|
533
|
-
/** Get this process's dated log path (~/.
|
|
541
|
+
/** Get this process's dated log path (~/.zeta/logs/zeta.YYYY-MM-DD.PID.log). */
|
|
534
542
|
export function getLogPath(date = new Date(), pid = process.pid): string {
|
|
535
543
|
return path.join(getLogsDir(), `${APP_NAME}.${date.toISOString().slice(0, 10)}.${pid}.log`);
|
|
536
544
|
}
|
|
537
545
|
|
|
538
546
|
/**
|
|
539
|
-
* Get the plugins directory (~/.
|
|
547
|
+
* Get the plugins directory (~/.zeta/plugins or its XDG equivalent).
|
|
540
548
|
*
|
|
541
549
|
* No-arg form (production callers) goes through the XDG-aware DirResolver so
|
|
542
550
|
* reads and writes always agree. The optional `home` parameter is for test
|
|
@@ -552,22 +560,22 @@ export function getPluginsDir(home?: string): string {
|
|
|
552
560
|
return dirs.rootSubdir("plugins", "data");
|
|
553
561
|
}
|
|
554
562
|
|
|
555
|
-
/** Where npm installs packages (~/.
|
|
563
|
+
/** Where npm installs packages (~/.zeta/plugins/node_modules). */
|
|
556
564
|
export function getPluginsNodeModules(home?: string): string {
|
|
557
565
|
return path.join(getPluginsDir(home), "node_modules");
|
|
558
566
|
}
|
|
559
567
|
|
|
560
|
-
/** Plugin manifest (~/.
|
|
568
|
+
/** Plugin manifest (~/.zeta/plugins/package.json). */
|
|
561
569
|
export function getPluginsPackageJson(home?: string): string {
|
|
562
570
|
return path.join(getPluginsDir(home), "package.json");
|
|
563
571
|
}
|
|
564
572
|
|
|
565
|
-
/** Plugin lock file (~/.
|
|
573
|
+
/** Plugin lock file (~/.zeta/plugins/zeta-plugins.lock.json). */
|
|
566
574
|
export function getPluginsLockfile(home?: string): string {
|
|
567
|
-
return path.join(getPluginsDir(home), "
|
|
575
|
+
return path.join(getPluginsDir(home), "zeta-plugins.lock.json");
|
|
568
576
|
}
|
|
569
577
|
|
|
570
|
-
/** Get the remote mount directory (~/.
|
|
578
|
+
/** Get the remote mount directory (~/.zeta/remote). */
|
|
571
579
|
export function getRemoteDir(): string {
|
|
572
580
|
return dirs.rootSubdir("remote", "data");
|
|
573
581
|
}
|
|
@@ -577,7 +585,7 @@ export function getRemoteDir(): string {
|
|
|
577
585
|
* empty/whitespace input or a path that is still relative after expansion.
|
|
578
586
|
*
|
|
579
587
|
* A worktree base is process-global and consumed by both creation
|
|
580
|
-
* (PR checkout, task isolation) and cleanup (`
|
|
588
|
+
* (PR checkout, task isolation) and cleanup (`zeta worktree`). A relative value
|
|
581
589
|
* would resolve against whatever cwd happened to launch `omp`, so checkout and
|
|
582
590
|
* cleanup could disagree — we refuse it rather than silently bind it to cwd.
|
|
583
591
|
*/
|
|
@@ -594,9 +602,9 @@ let worktreesDirOverride: string | undefined;
|
|
|
594
602
|
|
|
595
603
|
/**
|
|
596
604
|
* Relocate the base directory for agent-managed worktrees (PR checkouts, task
|
|
597
|
-
* isolation, and `
|
|
605
|
+
* isolation, and `zeta worktree` cleanup all read the same base). Driven by the
|
|
598
606
|
* `worktree.base` setting in coding-agent; pass `undefined`/empty to clear and
|
|
599
|
-
* fall back to `OMP_WORKTREE_DIR` or the `~/.
|
|
607
|
+
* fall back to `OMP_WORKTREE_DIR` or the `~/.zeta/wt` default.
|
|
600
608
|
*
|
|
601
609
|
* `~` is expanded and a relative path is rejected (see {@link resolveWorktreeBase}).
|
|
602
610
|
* Returns the absolute path that took effect, or `undefined` if the input was
|
|
@@ -611,7 +619,7 @@ export function setWorktreesDir(dir: string | undefined): string | undefined {
|
|
|
611
619
|
/**
|
|
612
620
|
* Get the agent-managed worktrees directory. Resolution order: the
|
|
613
621
|
* `OMP_WORKTREE_DIR` env var, then the {@link setWorktreesDir} override (the
|
|
614
|
-
* `worktree.base` setting), then the `~/.
|
|
622
|
+
* `worktree.base` setting), then the `~/.zeta/wt` default. The env var and the
|
|
615
623
|
* override are both `~`-expanded and must be absolute; a relative value is
|
|
616
624
|
* ignored and resolution falls through.
|
|
617
625
|
*/
|
|
@@ -619,32 +627,32 @@ export function getWorktreesDir(): string {
|
|
|
619
627
|
return resolveWorktreeBase(process.env.OMP_WORKTREE_DIR) ?? worktreesDirOverride ?? dirs.rootSubdir("wt", "data");
|
|
620
628
|
}
|
|
621
629
|
|
|
622
|
-
/** Get the SSH control socket directory (~/.
|
|
630
|
+
/** Get the SSH control socket directory (~/.zeta/ssh-control). */
|
|
623
631
|
export function getSshControlDir(): string {
|
|
624
632
|
return dirs.rootSubdir("ssh-control", "state");
|
|
625
633
|
}
|
|
626
634
|
|
|
627
|
-
/** Get the remote host info directory (~/.
|
|
635
|
+
/** Get the remote host info directory (~/.zeta/remote-host). */
|
|
628
636
|
export function getRemoteHostDir(): string {
|
|
629
637
|
return dirs.rootSubdir("remote-host", "data");
|
|
630
638
|
}
|
|
631
639
|
|
|
632
|
-
/** Get the managed Python venv directory (~/.
|
|
640
|
+
/** Get the managed Python venv directory (~/.zeta/python-env). */
|
|
633
641
|
export function getPythonEnvDir(): string {
|
|
634
642
|
return dirs.rootSubdir("python-env", "data");
|
|
635
643
|
}
|
|
636
644
|
|
|
637
|
-
/** Get the shared Python gateway state directory (~/.
|
|
645
|
+
/** Get the shared Python gateway state directory (~/.zeta/agent/python-gateway; XDG default: $XDG_STATE_HOME/zeta/python-gateway). */
|
|
638
646
|
export function getPythonGatewayDir(): string {
|
|
639
647
|
return dirs.agentSubdir(undefined, "python-gateway", "state");
|
|
640
648
|
}
|
|
641
649
|
|
|
642
|
-
/** Get the puppeteer sandbox directory (~/.
|
|
650
|
+
/** Get the puppeteer sandbox directory (~/.zeta/puppeteer). */
|
|
643
651
|
export function getPuppeteerDir(): string {
|
|
644
652
|
return dirs.rootSubdir("puppeteer", "cache");
|
|
645
653
|
}
|
|
646
654
|
|
|
647
|
-
/** Get the browser relay extension install directory (~/.
|
|
655
|
+
/** Get the browser relay extension install directory (~/.zeta/browser-relay). */
|
|
648
656
|
export function getBrowserRelayDir(): string {
|
|
649
657
|
return dirs.rootSubdir("browser-relay", "data");
|
|
650
658
|
}
|
|
@@ -654,7 +662,7 @@ export function getDocsRsCacheDir(): string {
|
|
|
654
662
|
return dirs.rootSubdir("webcache", "cache");
|
|
655
663
|
}
|
|
656
664
|
|
|
657
|
-
/** Get the auto-QA grievances SQLite database path (~/.
|
|
665
|
+
/** Get the auto-QA grievances SQLite database path (~/.zeta/autoqa.db; XDG: $XDG_DATA_HOME/zeta/autoqa.db). */
|
|
658
666
|
export function getAutoQaDbPath(): string {
|
|
659
667
|
return dirs.rootSubdir("autoqa.db", "data");
|
|
660
668
|
}
|
|
@@ -662,7 +670,7 @@ export function getAutoQaDbPath(): string {
|
|
|
662
670
|
* Stable 7-character hex digest of an absolute filesystem path.
|
|
663
671
|
*
|
|
664
672
|
* Used to pack the project identity into a single short fs-safe segment
|
|
665
|
-
* (e.g. PR-checkout and task-isolation worktree dirs under `~/.
|
|
673
|
+
* (e.g. PR-checkout and task-isolation worktree dirs under `~/.zeta/wt/`).
|
|
666
674
|
* Bun.hash is non-cryptographic — collision space is ~2^28, which is fine
|
|
667
675
|
* for naming a handful of repos on a single machine. Same input on the
|
|
668
676
|
* same Bun runtime yields the same output.
|
|
@@ -671,18 +679,18 @@ export function hashPath(absPath: string): string {
|
|
|
671
679
|
return Bun.hash(path.resolve(absPath)).toString(16).padStart(16, "0").slice(-7);
|
|
672
680
|
}
|
|
673
681
|
|
|
674
|
-
/** Get the path to a single worktree directory (~/.
|
|
682
|
+
/** Get the path to a single worktree directory (~/.zeta/wt/<segment>). */
|
|
675
683
|
export function getWorktreeDir(segment: string): string {
|
|
676
684
|
return path.join(getWorktreesDir(), segment);
|
|
677
685
|
}
|
|
678
686
|
|
|
679
|
-
/** Get the GPU cache path (~/.
|
|
687
|
+
/** Get the GPU cache path (~/.zeta/gpu_cache.json). */
|
|
680
688
|
export function getGpuCachePath(): string {
|
|
681
689
|
return dirs.rootSubdir("gpu_cache.json", "cache");
|
|
682
690
|
}
|
|
683
691
|
|
|
684
692
|
/**
|
|
685
|
-
* Get the GitHub view cache database path (~/.
|
|
693
|
+
* Get the GitHub view cache database path (~/.zeta/cache/github-cache.db).
|
|
686
694
|
* Honors the `OMP_GITHUB_CACHE_DB` env var when set so tests can isolate the
|
|
687
695
|
* cache file without touching the rest of the config root.
|
|
688
696
|
*/
|
|
@@ -693,7 +701,7 @@ export function getGithubCacheDbPath(): string {
|
|
|
693
701
|
}
|
|
694
702
|
|
|
695
703
|
/**
|
|
696
|
-
* Get the encrypted auth-broker snapshot cache path (~/.
|
|
704
|
+
* Get the encrypted auth-broker snapshot cache path (~/.zeta/cache/auth-broker-snapshot.enc).
|
|
697
705
|
* Honors the `OMP_AUTH_BROKER_SNAPSHOT_CACHE` env var when set so tests and
|
|
698
706
|
* operators can isolate or relocate the cache file.
|
|
699
707
|
*/
|
|
@@ -703,58 +711,58 @@ export function getAuthBrokerSnapshotCachePath(): string {
|
|
|
703
711
|
return dirs.rootSubdir(path.join("cache", "auth-broker-snapshot.enc"), "cache");
|
|
704
712
|
}
|
|
705
713
|
|
|
706
|
-
/** Get the local FastEmbed model cache directory (~/.
|
|
714
|
+
/** Get the local FastEmbed model cache directory (~/.zeta/cache/fastembed). */
|
|
707
715
|
export function getFastembedCacheDir(): string {
|
|
708
716
|
return dirs.rootSubdir(path.join("cache", "fastembed"), "cache");
|
|
709
717
|
}
|
|
710
718
|
|
|
711
|
-
/** Get the on-demand fastembed runtime install root (~/.
|
|
719
|
+
/** Get the on-demand fastembed runtime install root (~/.zeta/cache/fastembed-runtime). */
|
|
712
720
|
export function getFastembedRuntimeDir(): string {
|
|
713
721
|
return dirs.rootSubdir(path.join("cache", "fastembed-runtime"), "cache");
|
|
714
722
|
}
|
|
715
723
|
|
|
716
|
-
/** Get the natives directory (~/.
|
|
724
|
+
/** Get the natives directory (~/.zeta/natives). */
|
|
717
725
|
export function getNativesDir(): string {
|
|
718
726
|
return dirs.rootSubdir("natives", "cache");
|
|
719
727
|
}
|
|
720
728
|
|
|
721
|
-
/** Get the stats database path (~/.
|
|
729
|
+
/** Get the stats database path (~/.zeta/stats.db). */
|
|
722
730
|
export function getStatsDbPath(): string {
|
|
723
731
|
return dirs.rootSubdir("stats.db", "data");
|
|
724
732
|
}
|
|
725
733
|
|
|
726
|
-
/** Get the autoresearch state directory (~/.
|
|
734
|
+
/** Get the autoresearch state directory (~/.zeta/autoresearch). */
|
|
727
735
|
export function getAutoresearchDir(): string {
|
|
728
736
|
return dirs.rootSubdir("autoresearch", "state");
|
|
729
737
|
}
|
|
730
738
|
|
|
731
|
-
/** Get the per-project autoresearch state directory (~/.
|
|
739
|
+
/** Get the per-project autoresearch state directory (~/.zeta/autoresearch/<encoded-project>). */
|
|
732
740
|
export function getAutoresearchProjectDir(encodedProject: string): string {
|
|
733
741
|
return path.join(getAutoresearchDir(), encodedProject);
|
|
734
742
|
}
|
|
735
743
|
|
|
736
|
-
/** Get the per-project autoresearch SQLite database path (~/.
|
|
744
|
+
/** Get the per-project autoresearch SQLite database path (~/.zeta/autoresearch/<encoded-project>.db). */
|
|
737
745
|
export function getAutoresearchDbPath(encodedProject: string): string {
|
|
738
746
|
return path.join(getAutoresearchDir(), `${encodedProject}.db`);
|
|
739
747
|
}
|
|
740
748
|
|
|
741
|
-
/** Get the per-run artifact directory (~/.
|
|
749
|
+
/** Get the per-run artifact directory (~/.zeta/autoresearch/<encoded-project>/runs/<runId>). */
|
|
742
750
|
export function getAutoresearchRunDir(encodedProject: string, runId: number): string {
|
|
743
751
|
return path.join(getAutoresearchProjectDir(encodedProject), "runs", String(runId).padStart(4, "0"));
|
|
744
752
|
}
|
|
745
753
|
|
|
746
|
-
/** Get the security-analysis state directory (~/.
|
|
754
|
+
/** Get the security-analysis state directory (~/.zeta/security). */
|
|
747
755
|
export function getSecurityDir(): string {
|
|
748
756
|
return dirs.rootSubdir("security", "state");
|
|
749
757
|
}
|
|
750
758
|
|
|
751
|
-
/** Get one project's security-analysis state directory (~/.
|
|
759
|
+
/** Get one project's security-analysis state directory (~/.zeta/security/<project-key>). */
|
|
752
760
|
export function getSecurityProjectDir(projectKey: string): string {
|
|
753
761
|
return path.join(getSecurityDir(), projectKey);
|
|
754
762
|
}
|
|
755
763
|
|
|
756
764
|
// =============================================================================
|
|
757
|
-
// Agent subdirectories (~/.
|
|
765
|
+
// Agent subdirectories (~/.zeta/agent/*)
|
|
758
766
|
// =============================================================================
|
|
759
767
|
|
|
760
768
|
/** Get the path to agent.db (SQLite database for settings and auth storage). */
|
|
@@ -762,7 +770,7 @@ export function getAgentDbPath(agentDir?: string): string {
|
|
|
762
770
|
return dirs.agentSubdir(agentDir, "agent.db", "data");
|
|
763
771
|
}
|
|
764
772
|
|
|
765
|
-
/** Get the last-seen-changelog-version marker file (~/.
|
|
773
|
+
/** Get the last-seen-changelog-version marker file (~/.zeta/agent/last-changelog-version). */
|
|
766
774
|
export function getLastChangelogVersionPath(agentDir?: string): string {
|
|
767
775
|
return dirs.agentSubdir(agentDir, "last-changelog-version", "state");
|
|
768
776
|
}
|
|
@@ -777,67 +785,67 @@ export function getModelDbPath(agentDir?: string): string {
|
|
|
777
785
|
return dirs.agentSubdir(agentDir, "models.db", "data");
|
|
778
786
|
}
|
|
779
787
|
|
|
780
|
-
/** Get the tiny title model cache directory (~/.
|
|
788
|
+
/** Get the tiny title model cache directory (~/.zeta/agent/cache/tiny-models). */
|
|
781
789
|
export function getTinyModelsCacheDir(agentDir?: string): string {
|
|
782
790
|
return dirs.agentSubdir(agentDir, path.join("cache", "tiny-models"), "cache");
|
|
783
791
|
}
|
|
784
792
|
|
|
785
|
-
/** Get the document conversion cache directory (~/.
|
|
793
|
+
/** Get the document conversion cache directory (~/.zeta/agent/cache/document-conversions; XDG default: $XDG_CACHE_HOME/zeta/cache/document-conversions). */
|
|
786
794
|
export function getDocumentConversionCacheDir(agentDir?: string): string {
|
|
787
795
|
return dirs.agentSubdir(agentDir, path.join("cache", "document-conversions"), "cache");
|
|
788
796
|
}
|
|
789
797
|
|
|
790
|
-
/** Get the sessions directory (~/.
|
|
798
|
+
/** Get the sessions directory (~/.zeta/agent/sessions). */
|
|
791
799
|
export function getSessionsDir(agentDir?: string): string {
|
|
792
800
|
return dirs.agentSubdir(agentDir, "sessions", "data");
|
|
793
801
|
}
|
|
794
802
|
|
|
795
|
-
/** Get the content-addressed blob store directory (~/.
|
|
803
|
+
/** Get the content-addressed blob store directory (~/.zeta/agent/blobs). */
|
|
796
804
|
export function getBlobsDir(agentDir?: string): string {
|
|
797
805
|
return dirs.agentSubdir(agentDir, "blobs", "data");
|
|
798
806
|
}
|
|
799
807
|
|
|
800
|
-
/** Get the custom themes directory (~/.
|
|
808
|
+
/** Get the custom themes directory (~/.zeta/agent/themes). */
|
|
801
809
|
export function getCustomThemesDir(agentDir?: string): string {
|
|
802
810
|
return dirs.agentSubdir(agentDir, "themes");
|
|
803
811
|
}
|
|
804
812
|
|
|
805
|
-
/** Get the tools directory (~/.
|
|
813
|
+
/** Get the tools directory (~/.zeta/agent/tools). */
|
|
806
814
|
export function getToolsDir(agentDir?: string): string {
|
|
807
815
|
return dirs.agentSubdir(agentDir, "tools");
|
|
808
816
|
}
|
|
809
817
|
|
|
810
|
-
/** Get the slash commands directory (~/.
|
|
818
|
+
/** Get the slash commands directory (~/.zeta/agent/commands). */
|
|
811
819
|
export function getCommandsDir(agentDir?: string): string {
|
|
812
820
|
return dirs.agentSubdir(agentDir, "commands");
|
|
813
821
|
}
|
|
814
822
|
|
|
815
|
-
/** Get the prompts directory (~/.
|
|
823
|
+
/** Get the prompts directory (~/.zeta/agent/prompts). */
|
|
816
824
|
export function getPromptsDir(agentDir?: string): string {
|
|
817
825
|
return dirs.agentSubdir(agentDir, "prompts");
|
|
818
826
|
}
|
|
819
827
|
|
|
820
|
-
/** Get the user-level Python modules directory (~/.
|
|
828
|
+
/** Get the user-level Python modules directory (~/.zeta/agent/modules). */
|
|
821
829
|
export function getAgentModulesDir(agentDir?: string): string {
|
|
822
830
|
return dirs.agentSubdir(agentDir, "modules");
|
|
823
831
|
}
|
|
824
832
|
|
|
825
|
-
/** Get the memories directory (~/.
|
|
833
|
+
/** Get the memories directory (~/.zeta/agent/memories). */
|
|
826
834
|
export function getMemoriesDir(agentDir?: string): string {
|
|
827
835
|
return dirs.agentSubdir(agentDir, "memories", "state");
|
|
828
836
|
}
|
|
829
837
|
|
|
830
|
-
/** Get the terminal sessions directory (~/.
|
|
838
|
+
/** Get the terminal sessions directory (~/.zeta/agent/terminal-sessions). */
|
|
831
839
|
export function getTerminalSessionsDir(agentDir?: string): string {
|
|
832
840
|
return dirs.agentSubdir(agentDir, "terminal-sessions", "state");
|
|
833
841
|
}
|
|
834
842
|
|
|
835
|
-
/** Get the crash log path (~/.
|
|
843
|
+
/** Get the crash log path (~/.zeta/agent/zeta-crash.log). */
|
|
836
844
|
export function getCrashLogPath(agentDir?: string): string {
|
|
837
|
-
return dirs.agentSubdir(agentDir, "
|
|
845
|
+
return dirs.agentSubdir(agentDir, "zeta-crash.log", "state");
|
|
838
846
|
}
|
|
839
847
|
|
|
840
|
-
/** Get the debug log path (~/.
|
|
848
|
+
/** Get the debug log path (~/.zeta/agent/zeta-debug.log). */
|
|
841
849
|
export function getDebugLogPath(agentDir?: string): string {
|
|
842
850
|
return dirs.agentSubdir(agentDir, `${APP_NAME}-debug.log`, "state");
|
|
843
851
|
}
|
|
@@ -861,14 +869,14 @@ function adoptLegacyFile(legacyPath: string, targetPath: string): void {
|
|
|
861
869
|
}
|
|
862
870
|
}
|
|
863
871
|
|
|
864
|
-
/** Get the secret placeholder key path (~/.
|
|
872
|
+
/** 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. */
|
|
865
873
|
export function getSecretPlaceholderKeyPath(): string {
|
|
866
874
|
const keyPath = dirs.agentSubdir(undefined, "secret-placeholder.key", "state");
|
|
867
875
|
adoptLegacyFile(path.join(dirs.agentDir, "secret-placeholder.key"), keyPath);
|
|
868
876
|
return keyPath;
|
|
869
877
|
}
|
|
870
878
|
|
|
871
|
-
/** Get the daemon runtime directory for a project (~/.
|
|
879
|
+
/** Get the daemon runtime directory for a project (~/.zeta/run/daemons/<hash>; XDG default: $XDG_STATE_HOME/zeta/run/daemons/<hash>). */
|
|
872
880
|
export function getDaemonRuntimeDir(projectDir: string): string {
|
|
873
881
|
const key = Bun.hash.wyhash(path.resolve(projectDir)).toString(16).padStart(16, "0");
|
|
874
882
|
return dirs.rootSubdir(path.join("run", "daemons", key), "state");
|
|
@@ -882,12 +890,12 @@ export function getGlobalDaemonRuntimeDir(service: string): string {
|
|
|
882
890
|
return path.join(getBaseConfigRoot(), "run", "daemons", "global", service);
|
|
883
891
|
}
|
|
884
892
|
|
|
885
|
-
/** Get the provider in-flight root directory (~/.
|
|
893
|
+
/** Get the provider in-flight root directory (~/.zeta/run/provider-inflight; XDG default: $XDG_STATE_HOME/zeta/run/provider-inflight). */
|
|
886
894
|
export function getProviderInFlightRoot(): string {
|
|
887
895
|
return dirs.rootSubdir(path.join("run", "provider-inflight"), "state");
|
|
888
896
|
}
|
|
889
897
|
|
|
890
|
-
/** Get the marketplaces registry path (~/.
|
|
898
|
+
/** Get the marketplaces registry path (~/.zeta/marketplaces.json; XDG default: $XDG_DATA_HOME/zeta/marketplaces.json). Adopts a legacy registry on first XDG resolution. */
|
|
891
899
|
export function getMarketplacesRegistryPath(): string {
|
|
892
900
|
const registryPath = dirs.rootSubdir("marketplaces.json", "data");
|
|
893
901
|
adoptLegacyFile(path.join(dirs.configRoot, "marketplaces.json"), registryPath);
|
|
@@ -895,20 +903,20 @@ export function getMarketplacesRegistryPath(): string {
|
|
|
895
903
|
}
|
|
896
904
|
|
|
897
905
|
// =============================================================================
|
|
898
|
-
// Project subdirectories (.
|
|
906
|
+
// Project subdirectories (.zeta/*)
|
|
899
907
|
// =============================================================================
|
|
900
908
|
|
|
901
|
-
/** Get the project-level Python modules directory (.
|
|
909
|
+
/** Get the project-level Python modules directory (.zeta/modules). */
|
|
902
910
|
export function getProjectModulesDir(cwd: string = getProjectDir()): string {
|
|
903
911
|
return path.join(getProjectAgentDir(cwd), "modules");
|
|
904
912
|
}
|
|
905
913
|
|
|
906
|
-
/** Get the project-level prompts directory (.
|
|
914
|
+
/** Get the project-level prompts directory (.zeta/prompts). */
|
|
907
915
|
export function getProjectPromptsDir(cwd: string = getProjectDir()): string {
|
|
908
916
|
return path.join(getProjectAgentDir(cwd), "prompts");
|
|
909
917
|
}
|
|
910
918
|
|
|
911
|
-
/** Get the project-level plugin overrides path (.
|
|
919
|
+
/** Get the project-level plugin overrides path (.zeta/plugin-overrides.json). */
|
|
912
920
|
export function getProjectPluginOverridesPath(cwd: string = getProjectDir()): string {
|
|
913
921
|
return path.join(getProjectAgentDir(cwd), "plugin-overrides.json");
|
|
914
922
|
}
|
|
@@ -957,15 +965,15 @@ const INSTALL_ID_FILE = "install-id";
|
|
|
957
965
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
958
966
|
|
|
959
967
|
/**
|
|
960
|
-
* Persistent per-install UUID stored at `~/.
|
|
968
|
+
* Persistent per-install UUID stored at `~/.zeta/install-id`.
|
|
961
969
|
*
|
|
962
970
|
* Generated lazily on first call and persisted with `O_CREAT|O_EXCL` so
|
|
963
971
|
* concurrent first-call races don't clobber each other (loser re-reads the
|
|
964
972
|
* winner's id). Survives independently of agent state: deleting
|
|
965
|
-
* `~/.
|
|
973
|
+
* `~/.zeta/agent/` does not regenerate it. Server-side dedup for grievance
|
|
966
974
|
* pushes (and similar telemetry) keys on this id.
|
|
967
975
|
*
|
|
968
|
-
* Anchored to the base config root (`~/.
|
|
976
|
+
* Anchored to the base config root (`~/.zeta/install-id`) regardless of the
|
|
969
977
|
* active profile: install identity is per-install, not per-profile, so every
|
|
970
978
|
* profile shares one id and the global cache stays correct no matter the
|
|
971
979
|
* profile / `getInstallId` call order.
|
package/src/fetch-retry.ts
CHANGED
|
@@ -220,7 +220,7 @@ export async function fetchWithRetry(
|
|
|
220
220
|
if (signal?.aborted) throw new Error("Request was aborted");
|
|
221
221
|
const wrapped = wrapNetworkError(error);
|
|
222
222
|
if (attempt + 1 >= maxAttempts) throw wrapped;
|
|
223
|
-
await
|
|
223
|
+
await waitForRetry(resolveDefaultDelay(defaultDelayMs, attempt, maxDelayMs), signal);
|
|
224
224
|
continue;
|
|
225
225
|
}
|
|
226
226
|
|
|
@@ -234,7 +234,7 @@ export async function fetchWithRetry(
|
|
|
234
234
|
if (hint !== undefined && hint > maxDelayMs) return response;
|
|
235
235
|
|
|
236
236
|
const delayMs = Math.min(hint ?? resolveDefaultDelay(defaultDelayMs, attempt, maxDelayMs), maxDelayMs);
|
|
237
|
-
await
|
|
237
|
+
await waitForRetry(delayMs, signal);
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -251,6 +251,15 @@ function mergeInit(base: RequestInit, overlay: RequestInit, timeout: number | fa
|
|
|
251
251
|
return merged;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
async function waitForRetry(delayMs: number, signal: AbortSignal | undefined): Promise<void> {
|
|
255
|
+
try {
|
|
256
|
+
await scheduler.wait(delayMs, { signal });
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (signal?.aborted) throw new Error("Request was aborted");
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
254
263
|
function wrapNetworkError(error: unknown): Error {
|
|
255
264
|
if (error instanceof Error) {
|
|
256
265
|
if (error.name === "AbortError" || error.message === "Request was aborted") {
|
package/src/json-parse.ts
CHANGED
|
@@ -579,8 +579,8 @@ export function parseStreamingJson<T = Record<string, unknown>>(partialJson: str
|
|
|
579
579
|
|
|
580
580
|
/**
|
|
581
581
|
* Default minimum byte growth before `parseStreamingJsonThrottled` will
|
|
582
|
-
* re-parse a streaming tool-call argument buffer.
|
|
583
|
-
*
|
|
582
|
+
* re-parse a streaming tool-call argument buffer. Acts as the floor of the
|
|
583
|
+
* geometric gate — see {@link parseStreamingJsonThrottled}.
|
|
584
584
|
*/
|
|
585
585
|
export const STREAMING_JSON_PARSE_MIN_GROWTH = 256;
|
|
586
586
|
|
|
@@ -589,14 +589,23 @@ export const STREAMING_JSON_PARSE_MIN_GROWTH = 256;
|
|
|
589
589
|
*
|
|
590
590
|
* Tool calls arrive as a long sequence of small deltas — calling
|
|
591
591
|
* `parseStreamingJson(buffer)` on every delta re-parses the entire buffer
|
|
592
|
-
* each time, giving O(N²) work in the total buffer length.
|
|
593
|
-
*
|
|
594
|
-
*
|
|
592
|
+
* each time, giving O(N²) work in the total buffer length. A fixed re-parse
|
|
593
|
+
* floor alone does NOT fix this: with `minGrowthBytes` constant, a buffer of
|
|
594
|
+
* length N is parsed N/minGrowthBytes times at an average cost of N/2, which
|
|
595
|
+
* is still O(N²) (the constant just shrinks). Long `write` payloads — where
|
|
596
|
+
* the buffer is the whole file — made this the dominant main-thread stall
|
|
597
|
+
* during streaming.
|
|
598
|
+
*
|
|
599
|
+
* Instead the gate scales geometrically: once the buffer is large, a re-parse
|
|
600
|
+
* requires growth proportional to the current length (`len / 32`, floored at
|
|
601
|
+
* `minGrowthBytes`). Parse points then form a geometric progression, so a
|
|
602
|
+
* buffer of length N is parsed O(log N) times for O(N log N) total work,
|
|
603
|
+
* while small buffers keep the snappy fixed-cadence updates.
|
|
595
604
|
*
|
|
596
605
|
* Each provider tracks the last parsed length on its tool-call block, so the
|
|
597
606
|
* final `toolcall_end` parse (which providers already perform unconditionally)
|
|
598
607
|
* is the authoritative full parse — the throttle only delays mid-stream UI
|
|
599
|
-
* updates by at most
|
|
608
|
+
* updates, by at most ~3% of the accumulated content for large buffers.
|
|
600
609
|
*
|
|
601
610
|
* @returns the parsed object plus the new `parsedLen` to persist; or `null`
|
|
602
611
|
* when the buffer has not grown enough to warrant a re-parse.
|
|
@@ -607,7 +616,9 @@ export function parseStreamingJsonThrottled<T = Record<string, unknown>>(
|
|
|
607
616
|
minGrowthBytes: number = STREAMING_JSON_PARSE_MIN_GROWTH,
|
|
608
617
|
): { value: T; parsedLen: number } | null {
|
|
609
618
|
const len = partialJson?.length ?? 0;
|
|
610
|
-
if (len === 0
|
|
619
|
+
if (len === 0) return null;
|
|
620
|
+
const growth = Math.max(minGrowthBytes, len >> 5);
|
|
621
|
+
if (lastParsedLen > 0 && len - lastParsedLen < growth) return null;
|
|
611
622
|
return { value: parseStreamingJson<T>(partialJson), parsedLen: len };
|
|
612
623
|
}
|
|
613
624
|
|