@oh-my-pi/pi-utils 15.13.0 → 15.13.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 +82 -28
- package/README.md +1 -1
- package/dist/types/dirs.d.ts +49 -0
- package/dist/types/env.d.ts +1 -4
- package/dist/types/worker-host.d.ts +4 -0
- package/package.json +2 -2
- package/src/dirs.ts +267 -15
- package/src/env.ts +10 -21
- package/src/runtime-install.ts +20 -0
- package/src/worker-host.ts +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,24 +2,71 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.13.1] - 2026-06-15
|
|
6
|
+
|
|
5
7
|
### Added
|
|
6
8
|
|
|
9
|
+
- Added profile-aware directory helpers and isolated profile state roots, while keeping the install ID shared across profiles.
|
|
10
|
+
- Added a named-profile API to the `dirs` module — `setProfile()`, `getActiveProfile()`, `getProfileRootDir()`, and `normalizeProfileName()` — plus `resolveProfileEnv()`, which selects the active profile from `OMP_PROFILE` (canonical; takes precedence) then `PI_PROFILE` (legacy fallback, consulted only when `OMP_PROFILE` is unset).
|
|
7
11
|
- Added support for a runtime `overrides` map in `RuntimeInstallSpec`, which is now written into generated runtime `package.json` manifests to force dependency pins (including transitive ones) across the runtime tree
|
|
8
12
|
- Added a lightweight loop-phase breadcrumb stack (`pushLoopPhase`/`popLoopPhase`/`currentLoopPhase`, plus `takeRecentLoopPhase` which returns the live phase or the most recently popped one and clears it) so the TUI event-loop watchdog can attribute a main-thread block to the phase that caused it — including a synchronous phase already popped before the watchdog's delayed tick runs ([#2485](https://github.com/can1357/oh-my-pi/issues/2485))
|
|
9
13
|
- Added `FetchWithRetryOptions.timeout` (forwarded to the underlying `fetch` call). `false` disables Bun's native ~300s pre-response timeout; a positive number overrides the ceiling. Bare browser/Node fetch ignores it ([#2422](https://github.com/can1357/oh-my-pi/issues/2422))
|
|
14
|
+
- Added the side-effect-free `@oh-my-pi/pi-utils/worker-host` module (`declareWorkerHostEntry()` / `workerHostEntry()`), extracted from `env` (still re-exported there) so worker spawn sites can resolve the self-dispatching CLI host entry without importing `env`'s side-effecting module graph.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Fixed profile directory isolation when a profile's agent `.env` customizes directory roots: directory-affecting keys (`XDG_DATA_HOME`/`XDG_STATE_HOME`/`XDG_CACHE_HOME`, and a default-mode `PI_CODING_AGENT_DIR`) are now honored. The `env` loader rebuilds the `dirs` resolver after applying `.env` files (`refreshDirsFromEnv()`), so a profile `.env` that points XDG roots elsewhere no longer leaks state into the home-based config dir.
|
|
19
|
+
- Made `TempDir` cleanup retry transient Windows `EBUSY`/`EPERM`/`ENOTEMPTY` removal failures so tests are less likely to fail when deleting just-used temp directories.
|
|
20
|
+
- Fixed `installRuntimeModuleResolver()` to keep bare requests from runtime-cache modules inside that registered runtime before falling back to host/workspace packages.
|
|
21
|
+
|
|
22
|
+
## [15.12.4] - 2026-06-13
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Fixed abortable stream wrappers to cancel the source stream on abort, so timeout watchdogs release upstream HTTP bodies instead of only stopping the local reader.
|
|
27
|
+
|
|
28
|
+
## [15.12.0] - 2026-06-12
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
10
32
|
- Added `runtime-install`: shared on-demand runtime dependency support — `ensureRuntimeInstalled()` (locked, idempotent `bun install` of a pinned dependency set into a cache dir) and a multi-root `installRuntimeModuleResolver()`/`resolveRuntimeModule()` for loading those graphs inside compiled binaries (Bun #1763). Extracted from the coding-agent tiny-model worker; now also backs Mnemopi's on-demand fastembed runtime ([#2389](https://github.com/can1357/oh-my-pi/issues/2389))
|
|
11
33
|
- Added `getFastembedRuntimeDir()` (~/.omp/cache/fastembed-runtime) alongside `getFastembedCacheDir()`
|
|
34
|
+
|
|
35
|
+
## [15.11.4] - 2026-06-12
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- Added `getEditorConfigFormatting(file)`: returns the `.editorconfig`-pinned `tabSize`/`insertSpaces` (both optional, no fallback) so LSP-format callers can layer per-file defaults under it without paving over silence with the renderer's display tab width ([#2329](https://github.com/can1357/oh-my-pi/issues/2329)).
|
|
40
|
+
|
|
41
|
+
## [15.11.3] - 2026-06-11
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
12
45
|
- Added `getEditorConfigFormatting(file)`: returns the `.editorconfig`-pinned `tabSize`/`insertSpaces` (both optional, no fallback) so LSP-format callers can layer per-file defaults under it without paving over silence with the renderer's display tab width ([#2329](https://github.com/can1357/oh-my-pi/issues/2329)).
|
|
46
|
+
|
|
47
|
+
## [15.11.1] - 2026-06-11
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- Fixed cleanup reentry noise during fatal shutdown: recursive cleanup requests now no-op idempotently instead of logging repeated `Cleanup invoked recursively` errors ([#2284](https://github.com/can1357/oh-my-pi/issues/2284)).
|
|
52
|
+
|
|
53
|
+
## [15.11.0] - 2026-06-10
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
13
57
|
- Added the `path-tree` module (`buildPathTree`, `walkPathTree`, `formatGroupedPaths`, `isUrlLikePath`), moved from the coding agent's grouped file output so compaction file lists can share the same prefix-folded directory-tree rendering; `formatGroupedPaths` gains an optional `annotate` callback for per-file suffixes
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- Fixed the `{{join}}` prompt helper joining with a literal two-character `\n` when templates pass `"\n"` as the separator — Handlebars string literals carry no escape processing. The separator now unescapes `\n`/`\t`, matching the `{{#list}}` helper's documented convention (visible as literal `\n` between paths in compaction `<read-files>` lists).
|
|
62
|
+
|
|
63
|
+
## [15.10.11] - 2026-06-10
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
14
67
|
- Restored `PI_DEBUG_STARTUP` streaming startup markers: `logger.time` now writes a synchronous `[startup] <op>:start` / `:done` / `:fail` stderr line per phase (independent of `PI_TIMING`), so a startup that hangs hard still names the phase it is stuck in — the `PI_TIMING` tree only prints after startup completes and is structurally unable to diagnose a hang. The CLI runner emits `cli:load:<name>` markers around each lazily-imported command module for the same reason.
|
|
15
68
|
- Added `logger.openSpanPath()`: ops of the currently-open timing-span chain (root → deepest), used by the coding agent's startup watchdog to name the in-flight phase of a stalled startup.
|
|
16
69
|
- Added `declareWorkerHostEntry()` / `workerHostEntry()` (env): self-dispatching CLI entrypoints declare `Bun.main` as the worker host so worker spawn sites can re-enter the single entry module with `WorkerOptions.argv` selectors across source, npm-bundle, and compiled distributions
|
|
17
|
-
- Added `getAuthBrokerSnapshotCachePath()` with `OMP_AUTH_BROKER_SNAPSHOT_CACHE` override support for isolating the encrypted broker snapshot cache.
|
|
18
|
-
- Added color helpers `colorLuma` (perceptual luma), `relativeLuminance` (WCAG, linearized sRGB), and `hslToHex` to the color utilities. The luminance helpers parse `#rgb`/`#rrggbb` hex and 256-color palette indices, returning `undefined` for unparseable values.
|
|
19
|
-
- Added `peekFileEnds`, a single-open head-and-tail file peek helper that reuses the head bytes for the tail when the file fits the head window.
|
|
20
|
-
- Added `peekFileTail`, the tail mirror of `peekFile`: reads up to the last `maxBytes` of a file ending at EOF, reusing the same pooled-buffer strategy (no per-call allocation for small reads).
|
|
21
|
-
- Added `getFastembedCacheDir` to return the FastEmbed model cache directory under ~/.omp/cache/fastembed
|
|
22
|
-
- Added an XDG-aware tiny-title model cache directory helper for coding-agent local title models.
|
|
23
70
|
|
|
24
71
|
### Changed
|
|
25
72
|
|
|
@@ -27,52 +74,59 @@
|
|
|
27
74
|
- `Snowflake.formatParts` packs the id as a single 64-bit BigInt hex format instead of stitching four 16-bit segments (simpler and ~1.7x faster), and `getTimestamp` extracts via exact double arithmetic instead of a BigInt round-trip. Output is bit-identical.
|
|
28
75
|
- Logger initialization is lazy: the winston logger, file transport, and log-directory creation now happen on first log emission instead of at module import (the import previously cost ~8ms of fs work on the CLI startup path); the in-memory timing infrastructure never touches winston
|
|
29
76
|
- `prompt.format()` post-processing got cheap per-line guards and a single-pass ASCII-symbol replacement (was 7 chained regex passes per line), roughly halving render post-processing cost; output is byte-identical
|
|
30
|
-
- `logger.printTimings()` (the `PI_TIMING` startup tree) now surfaces two previously-invisible regions: a `(before instrumentation)` line for runtime init / uncaptured pre-marker work, and an `(unattributed self)` line for the root span's own untimed work so the gap between visible top-level spans and `Total` is no longer swallowed. `Total` is now labelled `(since first marker)` to make the window explicit. The restored `module-timer.ts` preload can feed module spans into the report: each module records `onLoad` → final top-level marker as `total`, a prepended body marker → final marker as `body/TLA`, and resolved static imports as a bounded dependency tree so the report separates graph wait from actual top-level module work.
|
|
31
77
|
|
|
32
78
|
### Fixed
|
|
33
79
|
|
|
34
|
-
- Made `TempDir` cleanup retry transient Windows `EBUSY`/`EPERM`/`ENOTEMPTY` removal failures so tests are less likely to fail when deleting just-used temp directories.
|
|
35
|
-
- Fixed abortable stream wrappers to cancel the source stream on abort, so timeout watchdogs release upstream HTTP bodies instead of only stopping the local reader.
|
|
36
|
-
- Fixed cleanup reentry noise during fatal shutdown: recursive cleanup requests now no-op idempotently instead of logging repeated `Cleanup invoked recursively` errors ([#2284](https://github.com/can1357/oh-my-pi/issues/2284)).
|
|
37
|
-
- Fixed the `{{join}}` prompt helper joining with a literal two-character `\n` when templates pass `"\n"` as the separator — Handlebars string literals carry no escape processing. The separator now unescapes `\n`/`\t`, matching the `{{#list}}` helper's documented convention (visible as literal `\n` between paths in compaction `<read-files>` lists).
|
|
38
80
|
- Fixed `prompt.format()` so ASCII symbol replacements such as `-->` and `!=` still run on lines containing a closing HTML comment token when not inside a comment
|
|
39
81
|
- `isCompiledBinary()` now also honors a define-folded `process.env.PI_COMPILED` (only `Bun.env` was checked), so builds that constant-fold `process.env` keep compiled-binary detection without relying on `import.meta.url` bunfs markers
|
|
40
82
|
- `omp <cmd> --help` now loads only the requested command module instead of the entire command table, so an unrelated command whose import graph hangs or crashes can no longer take down every per-command help invocation.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- Fixed `$flag` environment parsing to accept lowercase truthy values such as `y`, `true`, `yes`, and `on`
|
|
83
|
+
|
|
84
|
+
## [15.10.8] - 2026-06-09
|
|
44
85
|
|
|
45
86
|
### Removed
|
|
46
87
|
|
|
47
88
|
- Removed the exported `hookFetch` API, which previously intercepted `globalThis.fetch` via middleware handlers
|
|
48
89
|
- Removed `hookFetch` from the package entrypoint, so imports from `@.../utils` no longer provide this fetch interception helper
|
|
49
90
|
|
|
50
|
-
## [15.
|
|
51
|
-
|
|
52
|
-
## [15.12.4] - 2026-06-13
|
|
91
|
+
## [15.10.0] - 2026-06-06
|
|
53
92
|
|
|
54
|
-
|
|
93
|
+
### Changed
|
|
55
94
|
|
|
56
|
-
|
|
95
|
+
- `logger.printTimings()` (the `PI_TIMING` startup tree) now surfaces two previously-invisible regions: a `(before instrumentation)` line for runtime init / uncaptured pre-marker work, and an `(unattributed self)` line for the root span's own untimed work so the gap between visible top-level spans and `Total` is no longer swallowed. `Total` is now labelled `(since first marker)` to make the window explicit. The restored `module-timer.ts` preload can feed module spans into the report: each module records `onLoad` → final top-level marker as `total`, a prepended body marker → final marker as `body/TLA`, and resolved static imports as a bounded dependency tree so the report separates graph wait from actual top-level module work.
|
|
57
96
|
|
|
58
|
-
## [15.
|
|
97
|
+
## [15.9.2] - 2026-06-05
|
|
59
98
|
|
|
60
|
-
|
|
99
|
+
### Added
|
|
61
100
|
|
|
62
|
-
|
|
101
|
+
- Added `getAuthBrokerSnapshotCachePath()` with `OMP_AUTH_BROKER_SNAPSHOT_CACHE` override support for isolating the encrypted broker snapshot cache.
|
|
63
102
|
|
|
64
|
-
## [15.
|
|
103
|
+
## [15.9.1] - 2026-06-04
|
|
65
104
|
|
|
66
|
-
|
|
105
|
+
### Fixed
|
|
67
106
|
|
|
68
|
-
|
|
107
|
+
- Hardened `getIndentation` against malformed paths: any filesystem error from the `.editorconfig` probe (e.g. `ENAMETOOLONG` on oversized garbage path segments) is now swallowed and cached as a miss instead of escaping and crashing the TUI mid-render ([#1871](https://github.com/can1357/oh-my-pi/issues/1871)).
|
|
108
|
+
- Fixed `getIndentation` (and the edit renderer's `replaceTabs` callers) crashing with `ENAMETOOLONG`/`ENOTDIR`/etc. when handed a path with an overlong component or a non-directory in its parent chain. Editorconfig discovery now short-circuits to the default tab width on any path component above `NAME_MAX` (255 bytes) and absorbs any `FsError` while walking the editorconfig chain — best-effort discovery must never escape as an uncaught exception ([#1872](https://github.com/can1357/oh-my-pi/issues/1872)).
|
|
69
109
|
|
|
70
|
-
## [15.9.
|
|
110
|
+
## [15.9.0] - 2026-06-04
|
|
71
111
|
|
|
72
|
-
|
|
112
|
+
### Added
|
|
73
113
|
|
|
74
|
-
|
|
114
|
+
- Added color helpers `colorLuma` (perceptual luma), `relativeLuminance` (WCAG, linearized sRGB), and `hslToHex` to the color utilities. The luminance helpers parse `#rgb`/`#rrggbb` hex and 256-color palette indices, returning `undefined` for unparseable values.
|
|
115
|
+
- Added `peekFileEnds`, a single-open head-and-tail file peek helper that reuses the head bytes for the tail when the file fits the head window.
|
|
116
|
+
- Added `peekFileTail`, the tail mirror of `peekFile`: reads up to the last `maxBytes` of a file ending at EOF, reusing the same pooled-buffer strategy (no per-call allocation for small reads).
|
|
75
117
|
|
|
76
118
|
## [15.7.3] - 2026-05-31
|
|
77
119
|
|
|
120
|
+
### Added
|
|
121
|
+
|
|
122
|
+
- Added `getFastembedCacheDir` to return the FastEmbed model cache directory under ~/.omp/cache/fastembed
|
|
123
|
+
|
|
124
|
+
### Fixed
|
|
125
|
+
|
|
126
|
+
- Fixed `$flag` environment parsing to accept lowercase truthy values such as `y`, `true`, `yes`, and `on`
|
|
127
|
+
|
|
78
128
|
## [15.6.0] - 2026-05-30
|
|
129
|
+
|
|
130
|
+
### Added
|
|
131
|
+
|
|
132
|
+
- Added an XDG-aware tiny-title model cache directory helper for coding-agent local title models.
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Shared utilities for [oh-my-pi](https://github.com/can1357/oh-my-pi) packages. Z
|
|
|
15
15
|
| `which` | `$which()` binary lookup with caching |
|
|
16
16
|
| `fetch-retry` | `fetch` with retry/backoff policies |
|
|
17
17
|
| `fs-error` | Errno guards (`isEnoent` and friends) |
|
|
18
|
-
| `env` | Environment plumbing
|
|
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
20
|
| `peek-file` | Read the first N bytes of a file with pooled buffers |
|
|
21
21
|
| `frontmatter`, `glob`, `mime`, `temp`, `format`, `color`, `snowflake`, `tab-spacing`, `path-tree`, `sanitize-text` | Smaller single-purpose helpers |
|
package/dist/types/dirs.d.ts
CHANGED
|
@@ -18,6 +18,24 @@ export declare const CONFIG_DIR_NAME: string;
|
|
|
18
18
|
export declare const VERSION: string;
|
|
19
19
|
/** Minimum Bun version */
|
|
20
20
|
export declare const MIN_BUN_VERSION: string;
|
|
21
|
+
/**
|
|
22
|
+
* Normalize and validate a profile name. Returns `undefined` for the implicit
|
|
23
|
+
* default (empty string, whitespace, or the explicit "default" sentinel) and
|
|
24
|
+
* throws for syntactically invalid or platform-reserved names.
|
|
25
|
+
*
|
|
26
|
+
* Exported so consumers of `@oh-my-pi/pi-utils/dirs` (CLI bootstrap, tests,
|
|
27
|
+
* downstream tools) can validate user input without re-deriving the rules.
|
|
28
|
+
*/
|
|
29
|
+
export declare function normalizeProfileName(profile: string | undefined): string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the active profile from the two profile env vars. `OMP_PROFILE` is the
|
|
32
|
+
* canonical variable and takes precedence; `PI_PROFILE` is the legacy
|
|
33
|
+
* compatibility fallback, consulted only when `OMP_PROFILE` is undefined. An
|
|
34
|
+
* explicitly-empty `OMP_PROFILE` therefore selects the default profile rather
|
|
35
|
+
* than silently inheriting `PI_PROFILE`. Delegates validation/normalization to
|
|
36
|
+
* {@link normalizeProfileName} (which throws on a syntactically invalid value).
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveProfileEnv(omp: string | undefined, pi: string | undefined): string | undefined;
|
|
21
39
|
export declare function resolveEquivalentPath(inputPath: string): string;
|
|
22
40
|
export declare function normalizePathForComparison(inputPath: string): string;
|
|
23
41
|
export declare function pathIsWithin(root: string, candidate: string): boolean;
|
|
@@ -30,10 +48,36 @@ export declare function setProjectDir(dir: string): void;
|
|
|
30
48
|
export declare function getConfigDirName(): string;
|
|
31
49
|
/** Get the config agent directory name relative to home (e.g. ".omp/agent" or PI_CONFIG_DIR + "/agent"). */
|
|
32
50
|
export declare function getConfigAgentDirName(): string;
|
|
51
|
+
/**
|
|
52
|
+
* Rebuild the dirs resolver from the current environment, reusing the profile
|
|
53
|
+
* resolved at module load. Directory-affecting keys (XDG_*_HOME and, in default
|
|
54
|
+
* mode, `PI_CODING_AGENT_DIR`) loaded from a profile/agent `.env` only reach
|
|
55
|
+
* `process.env` *after* this module froze the resolver at import time, so
|
|
56
|
+
* `env.ts` calls this once after applying its `.env` files. The agent `.env`
|
|
57
|
+
* location derives from the profile name + home before this runs, so the
|
|
58
|
+
* rebuild re-reads only the directory vars, never the profile selection. The
|
|
59
|
+
* `preProfileAgentDirEnv` snapshot is intentionally left untouched.
|
|
60
|
+
*/
|
|
61
|
+
export declare function refreshDirsFromEnv(): void;
|
|
33
62
|
/** Get the config root directory (~/.omp). */
|
|
34
63
|
export declare function getConfigRootDir(): string;
|
|
35
64
|
/** Set the coding agent directory. Creates a fresh resolver, invalidating all cached paths. */
|
|
36
65
|
export declare function setAgentDir(dir: string): void;
|
|
66
|
+
/**
|
|
67
|
+
* Test-only: reset the pre-profile `PI_CODING_AGENT_DIR` snapshot to whatever
|
|
68
|
+
* the current environment looks like. Cross-suite test pollution can otherwise
|
|
69
|
+
* leak a stale snapshot through `setAgentDir` and corrupt `setProfile(undefined)`
|
|
70
|
+
* restore semantics. Production code MUST NOT call this — the snapshot's
|
|
71
|
+
* lifecycle is owned by `setAgentDir` / `setProfile` and a runtime caller has
|
|
72
|
+
* no business clearing it.
|
|
73
|
+
*/
|
|
74
|
+
export declare function __resetProfileSnapshotForTests(): void;
|
|
75
|
+
/** Activate a named profile. Passing undefined or "default" returns to the default profile. */
|
|
76
|
+
export declare function setProfile(profile: string | undefined): void;
|
|
77
|
+
/** Get the active named profile. Undefined means the default profile. */
|
|
78
|
+
export declare function getActiveProfile(): string | undefined;
|
|
79
|
+
/** Resolve the config root that backs a profile without activating it. */
|
|
80
|
+
export declare function getProfileRootDir(profile: string | undefined): string;
|
|
37
81
|
/** Get the agent config directory (~/.omp/agent). */
|
|
38
82
|
export declare function getAgentDir(): string;
|
|
39
83
|
/** Get the project-local config directory (.omp). */
|
|
@@ -167,6 +211,11 @@ export declare function getSSHConfigPath(scope: "user" | "project", cwd?: string
|
|
|
167
211
|
* winner's id). Survives independently of agent state: deleting
|
|
168
212
|
* `~/.omp/agent/` does not regenerate it. Server-side dedup for grievance
|
|
169
213
|
* pushes (and similar telemetry) keys on this id.
|
|
214
|
+
*
|
|
215
|
+
* Anchored to the base config root (`~/.omp/install-id`) regardless of the
|
|
216
|
+
* active profile: install identity is per-install, not per-profile, so every
|
|
217
|
+
* profile shares one id and the global cache stays correct no matter the
|
|
218
|
+
* profile / `getInstallId` call order.
|
|
170
219
|
*/
|
|
171
220
|
export declare function getInstallId(): string;
|
|
172
221
|
/** Test-only: clear cached install id. Never call from production code. */
|
package/dist/types/env.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from "./worker-host";
|
|
1
2
|
/**
|
|
2
3
|
* Strict shell-identifier shape. Used for dotenv keys we accept into
|
|
3
4
|
* `Bun.env` — those should be referenceable as `$NAME` from POSIX shells,
|
|
@@ -53,8 +54,4 @@ export declare function isBunTestRuntime(): boolean;
|
|
|
53
54
|
* first for cheap fast-path detection.
|
|
54
55
|
*/
|
|
55
56
|
export declare function isCompiledBinary(): boolean;
|
|
56
|
-
/** Called by CLI entrypoints whose main module dispatches worker argv selectors. */
|
|
57
|
-
export declare function declareWorkerHostEntry(): void;
|
|
58
|
-
/** Main-module path of the self-dispatching CLI host, or null outside it. */
|
|
59
|
-
export declare function workerHostEntry(): string | null;
|
|
60
57
|
export declare function $flag(name: string, def?: boolean): boolean;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Called by CLI entrypoints whose main module dispatches worker argv selectors. */
|
|
2
|
+
export declare function declareWorkerHostEntry(): void;
|
|
3
|
+
/** Main-module path of the self-dispatching CLI host, or null outside it. */
|
|
4
|
+
export declare function workerHostEntry(): string | null;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-utils",
|
|
4
|
-
"version": "15.13.
|
|
4
|
+
"version": "15.13.2",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"fmt": "biome format --write ."
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@oh-my-pi/pi-natives": "15.13.
|
|
34
|
+
"@oh-my-pi/pi-natives": "15.13.2",
|
|
35
35
|
"beautiful-mermaid": "^1.1.3",
|
|
36
36
|
"handlebars": "^4.7.9",
|
|
37
37
|
"winston": "^3.19.0",
|
package/src/dirs.ts
CHANGED
|
@@ -28,6 +28,102 @@ export const VERSION: string = version;
|
|
|
28
28
|
/** Minimum Bun version */
|
|
29
29
|
export const MIN_BUN_VERSION: string = engines.bun.replace(/[^0-9.]/g, "");
|
|
30
30
|
|
|
31
|
+
const PROFILE_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
32
|
+
const PROFILE_ENV_KEYS = ["OMP_PROFILE", "PI_PROFILE"] as const;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Names Windows treats as reserved device aliases. Matches the basename
|
|
36
|
+
* itself as well as any `BASENAME.<anything>` form, because Windows reserves
|
|
37
|
+
* `CON.foo`/`PRN.txt`/etc. too — using them as a profile name would let
|
|
38
|
+
* `setProfile` accept the input only for directory creation to fail later
|
|
39
|
+
* with a confusing `ENOENT`/`EINVAL`. Case-insensitive: NTFS treats `CON`
|
|
40
|
+
* and `con` identically.
|
|
41
|
+
*/
|
|
42
|
+
const WINDOWS_RESERVED_BASENAME_RE = /^(?:CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])(?:\..*)?$/i;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Normalize and validate a profile name. Returns `undefined` for the implicit
|
|
46
|
+
* default (empty string, whitespace, or the explicit "default" sentinel) and
|
|
47
|
+
* throws for syntactically invalid or platform-reserved names.
|
|
48
|
+
*
|
|
49
|
+
* Exported so consumers of `@oh-my-pi/pi-utils/dirs` (CLI bootstrap, tests,
|
|
50
|
+
* downstream tools) can validate user input without re-deriving the rules.
|
|
51
|
+
*/
|
|
52
|
+
export function normalizeProfileName(profile: string | undefined): string | undefined {
|
|
53
|
+
const normalized = profile?.trim();
|
|
54
|
+
if (!normalized || normalized === "default") return undefined;
|
|
55
|
+
if (
|
|
56
|
+
normalized === "." ||
|
|
57
|
+
normalized === ".." ||
|
|
58
|
+
normalized.endsWith(".") ||
|
|
59
|
+
!PROFILE_NAME_RE.test(normalized) ||
|
|
60
|
+
WINDOWS_RESERVED_BASENAME_RE.test(normalized)
|
|
61
|
+
) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Invalid OMP profile "${profile}". Profile names must match ${PROFILE_NAME_RE.source}, ` +
|
|
64
|
+
`cannot be "." or "..", cannot end with ".", and cannot be a Windows reserved device name ` +
|
|
65
|
+
`(CON, PRN, AUX, NUL, COM0-9, LPT0-9, or any of those with an extension).`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve the active profile from the two profile env vars. `OMP_PROFILE` is the
|
|
73
|
+
* canonical variable and takes precedence; `PI_PROFILE` is the legacy
|
|
74
|
+
* compatibility fallback, consulted only when `OMP_PROFILE` is undefined. An
|
|
75
|
+
* explicitly-empty `OMP_PROFILE` therefore selects the default profile rather
|
|
76
|
+
* than silently inheriting `PI_PROFILE`. Delegates validation/normalization to
|
|
77
|
+
* {@link normalizeProfileName} (which throws on a syntactically invalid value).
|
|
78
|
+
*/
|
|
79
|
+
export function resolveProfileEnv(omp: string | undefined, pi: string | undefined): string | undefined {
|
|
80
|
+
return normalizeProfileName(omp !== undefined ? omp : pi);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getProfileFromEnv(): string | undefined {
|
|
84
|
+
return resolveProfileEnv(process.env.OMP_PROFILE, process.env.PI_PROFILE);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Module-load profile resolution. Unlike {@link getProfileFromEnv}, an invalid
|
|
89
|
+
* OMP_PROFILE/PI_PROFILE value does NOT throw here — a bad env var must not
|
|
90
|
+
* crash a bare `import` of this module with an uncaught stack trace before the
|
|
91
|
+
* CLI's error handling is in scope. The default profile is used instead; the
|
|
92
|
+
* CLI re-validates the env (see `runCli` in coding-agent/src/cli.ts) so the
|
|
93
|
+
* user still gets a clean "Invalid OMP profile" message.
|
|
94
|
+
*/
|
|
95
|
+
function readProfileFromEnvSafe(): string | undefined {
|
|
96
|
+
try {
|
|
97
|
+
return getProfileFromEnv();
|
|
98
|
+
} catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getBaseConfigRoot(): string {
|
|
104
|
+
return path.join(os.homedir(), getConfigDirName());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getProfileConfigRoot(profile: string | undefined): string {
|
|
108
|
+
const root = getBaseConfigRoot();
|
|
109
|
+
return profile ? path.join(root, "profiles", profile) : root;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readPiProfileFromEnvSafe(): string | undefined {
|
|
113
|
+
try {
|
|
114
|
+
return normalizeProfileName(process.env.PI_PROFILE);
|
|
115
|
+
} catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getProfileAgentDir(profile: string): string {
|
|
121
|
+
return path.join(getProfileConfigRoot(profile), "agent");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isProfileDerivedAgentDir(profile: string | undefined, agentDirEnv: string | undefined): boolean {
|
|
125
|
+
return profile !== undefined && agentDirEnv === getProfileAgentDir(profile);
|
|
126
|
+
}
|
|
31
127
|
// =============================================================================
|
|
32
128
|
// Project directory
|
|
33
129
|
// =============================================================================
|
|
@@ -96,7 +192,8 @@ export function getConfigDirName(): string {
|
|
|
96
192
|
|
|
97
193
|
/** Get the config agent directory name relative to home (e.g. ".omp/agent" or PI_CONFIG_DIR + "/agent"). */
|
|
98
194
|
export function getConfigAgentDirName(): string {
|
|
99
|
-
|
|
195
|
+
const profile = getActiveProfile();
|
|
196
|
+
return profile ? path.join(getConfigDirName(), "profiles", profile, "agent") : `${getConfigDirName()}/agent`;
|
|
100
197
|
}
|
|
101
198
|
|
|
102
199
|
// =============================================================================
|
|
@@ -123,29 +220,47 @@ class DirResolver {
|
|
|
123
220
|
readonly #rootCache = new Map<string, string>();
|
|
124
221
|
readonly #agentCache = new Map<string, string>();
|
|
125
222
|
|
|
126
|
-
constructor(agentDirOverride?: string) {
|
|
127
|
-
|
|
223
|
+
constructor(options: { agentDirOverride?: string; profile?: string } = {}) {
|
|
224
|
+
const profile = normalizeProfileName(options.profile);
|
|
225
|
+
this.configRoot = getProfileConfigRoot(profile);
|
|
128
226
|
|
|
129
227
|
const defaultAgent = path.join(this.configRoot, "agent");
|
|
228
|
+
const agentDirOverride = profile ? undefined : options.agentDirOverride;
|
|
130
229
|
this.agentDir = agentDirOverride ? path.resolve(agentDirOverride) : defaultAgent;
|
|
131
230
|
const isDefault = this.agentDir === defaultAgent;
|
|
132
231
|
|
|
133
|
-
// XDG is a Linux convention. On
|
|
134
|
-
//
|
|
232
|
+
// XDG is a Linux convention. On supported platforms, default profile state
|
|
233
|
+
// resolves under $XDG_*_HOME/omp once `omp config init-xdg` has migrated
|
|
234
|
+
// the user's data. Named profiles follow a stricter rule: the XDG choice
|
|
235
|
+
// is keyed on the profile-specific XDG path, never the base app root.
|
|
236
|
+
//
|
|
237
|
+
// Why: if we consulted the base app root for named profiles too, the same
|
|
238
|
+
// profile could resolve to `~/.omp/profiles/<name>` on first activation
|
|
239
|
+
// (when no $XDG_*_HOME/omp exists yet) and then silently move to
|
|
240
|
+
// `$XDG_*_HOME/omp/profiles/<name>` the moment the base appeared, orphaning
|
|
241
|
+
// the earlier state. Pinning on the profile path means a profile's location
|
|
242
|
+
// is decided at first activation and stays put until the user explicitly
|
|
243
|
+
// migrates it (e.g. by mkdir'ing the XDG profile dir).
|
|
135
244
|
let xdgData: string | undefined;
|
|
136
245
|
let xdgState: string | undefined;
|
|
137
246
|
let xdgCache: string | undefined;
|
|
138
247
|
if ((process.platform === "linux" || process.platform === "darwin") && isDefault) {
|
|
139
248
|
const resolveIf = (envVar: string) => {
|
|
140
249
|
const value = process.env[envVar];
|
|
141
|
-
if (value)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
250
|
+
if (!value) return undefined;
|
|
251
|
+
try {
|
|
252
|
+
const appRoot = path.join(value, APP_NAME);
|
|
253
|
+
if (profile) {
|
|
254
|
+
const profilePath = path.join(appRoot, "profiles", profile);
|
|
255
|
+
if (fs.existsSync(profilePath)) {
|
|
256
|
+
return profilePath;
|
|
146
257
|
}
|
|
147
|
-
|
|
148
|
-
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
if (fs.existsSync(appRoot)) {
|
|
261
|
+
return appRoot;
|
|
262
|
+
}
|
|
263
|
+
} catch {}
|
|
149
264
|
return undefined;
|
|
150
265
|
};
|
|
151
266
|
xdgData = resolveIf("XDG_DATA_HOME");
|
|
@@ -190,14 +305,82 @@ class DirResolver {
|
|
|
190
305
|
}
|
|
191
306
|
}
|
|
192
307
|
|
|
193
|
-
|
|
308
|
+
/**
|
|
309
|
+
* Decide which `PI_CODING_AGENT_DIR` value to capture as the pre-profile
|
|
310
|
+
* baseline. A value equal to a profile's derived agent dir is profile-derived
|
|
311
|
+
* (propagated by a parent's `setProfile`), so it must NOT be snapshotted as the
|
|
312
|
+
* default-mode baseline — otherwise default mode would resolve to the profile's
|
|
313
|
+
* agent dir. The profile source can be the active profile or a lower-priority
|
|
314
|
+
* `PI_PROFILE` that was bypassed because `OMP_PROFILE` explicitly selected the
|
|
315
|
+
* default profile. Returns `undefined` in those cases so reset falls back to the
|
|
316
|
+
* standard `~/.omp/agent`.
|
|
317
|
+
*/
|
|
318
|
+
function resolvePreProfileAgentDir(
|
|
319
|
+
profile: string | undefined,
|
|
320
|
+
agentDirEnv: string | undefined,
|
|
321
|
+
profileAgentDirSource: string | undefined = profile,
|
|
322
|
+
): string | undefined {
|
|
323
|
+
return isProfileDerivedAgentDir(profile ?? profileAgentDirSource, agentDirEnv) ? undefined : agentDirEnv;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let activeProfile = readProfileFromEnvSafe();
|
|
194
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Resolve the agent-dir override for the current `activeProfile` from the live
|
|
330
|
+
* environment. A named profile derives its own agent dir (no override); default
|
|
331
|
+
* mode honors a non-profile `PI_CODING_AGENT_DIR` (see
|
|
332
|
+
* {@link resolvePreProfileAgentDir}). Shared by the module-load resolver and
|
|
333
|
+
* {@link refreshDirsFromEnv} so both apply identical logic.
|
|
334
|
+
*/
|
|
335
|
+
function resolveActiveAgentDirOverride(): string | undefined {
|
|
336
|
+
return activeProfile
|
|
337
|
+
? undefined
|
|
338
|
+
: resolvePreProfileAgentDir(undefined, process.env.PI_CODING_AGENT_DIR, readPiProfileFromEnvSafe());
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let dirs = new DirResolver({
|
|
342
|
+
agentDirOverride: resolveActiveAgentDirOverride(),
|
|
343
|
+
profile: activeProfile,
|
|
344
|
+
});
|
|
345
|
+
/**
|
|
346
|
+
* Snapshot of `PI_CODING_AGENT_DIR` from before the first named-profile
|
|
347
|
+
* activation. Reset paths restore this value (or its absence) instead of
|
|
348
|
+
* unconditionally deleting the env var. Without the snapshot, a process started
|
|
349
|
+
* with `PI_CODING_AGENT_DIR=/custom` then `setProfile("work")` then
|
|
350
|
+
* `setProfile(undefined)` would silently lose `/custom` and fall back to
|
|
351
|
+
* `~/.omp/agent`. Captured at module load — ignoring a profile-derived value
|
|
352
|
+
* inherited from a parent's `setProfile` (see {@link resolvePreProfileAgentDir})
|
|
353
|
+
* — and refreshed on `setAgentDir`, since that call is the user explicitly
|
|
354
|
+
* redefining the baseline.
|
|
355
|
+
*/
|
|
356
|
+
let preProfileAgentDirEnv: string | undefined = resolvePreProfileAgentDir(
|
|
357
|
+
activeProfile,
|
|
358
|
+
process.env.PI_CODING_AGENT_DIR,
|
|
359
|
+
activeProfile ?? readPiProfileFromEnvSafe(),
|
|
360
|
+
);
|
|
195
361
|
// Anchor home for the resolver. Captured at module load to stay stable across
|
|
196
362
|
// test mocks of `os.homedir()`. `getPluginsDir(home)` compares against this so
|
|
197
363
|
// production callers (`home === RESOLVER_HOME`) hit the XDG-aware resolver while
|
|
198
364
|
// tests passing a temp HOME short-circuit to a deterministic path.
|
|
199
365
|
const RESOLVER_HOME = os.homedir();
|
|
200
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Rebuild the dirs resolver from the current environment, reusing the profile
|
|
369
|
+
* resolved at module load. Directory-affecting keys (XDG_*_HOME and, in default
|
|
370
|
+
* mode, `PI_CODING_AGENT_DIR`) loaded from a profile/agent `.env` only reach
|
|
371
|
+
* `process.env` *after* this module froze the resolver at import time, so
|
|
372
|
+
* `env.ts` calls this once after applying its `.env` files. The agent `.env`
|
|
373
|
+
* location derives from the profile name + home before this runs, so the
|
|
374
|
+
* rebuild re-reads only the directory vars, never the profile selection. The
|
|
375
|
+
* `preProfileAgentDirEnv` snapshot is intentionally left untouched.
|
|
376
|
+
*/
|
|
377
|
+
export function refreshDirsFromEnv(): void {
|
|
378
|
+
dirs = new DirResolver({
|
|
379
|
+
agentDirOverride: resolveActiveAgentDirOverride(),
|
|
380
|
+
profile: activeProfile,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
201
384
|
// =============================================================================
|
|
202
385
|
// Root directories
|
|
203
386
|
// =============================================================================
|
|
@@ -209,10 +392,74 @@ export function getConfigRootDir(): string {
|
|
|
209
392
|
|
|
210
393
|
/** Set the coding agent directory. Creates a fresh resolver, invalidating all cached paths. */
|
|
211
394
|
export function setAgentDir(dir: string): void {
|
|
212
|
-
|
|
395
|
+
activeProfile = undefined;
|
|
396
|
+
dirs = new DirResolver({ agentDirOverride: dir });
|
|
213
397
|
process.env.PI_CODING_AGENT_DIR = dir;
|
|
398
|
+
preProfileAgentDirEnv = dir;
|
|
399
|
+
for (const key of PROFILE_ENV_KEYS) {
|
|
400
|
+
delete process.env[key];
|
|
401
|
+
}
|
|
214
402
|
}
|
|
215
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Test-only: reset the pre-profile `PI_CODING_AGENT_DIR` snapshot to whatever
|
|
406
|
+
* the current environment looks like. Cross-suite test pollution can otherwise
|
|
407
|
+
* leak a stale snapshot through `setAgentDir` and corrupt `setProfile(undefined)`
|
|
408
|
+
* restore semantics. Production code MUST NOT call this — the snapshot's
|
|
409
|
+
* lifecycle is owned by `setAgentDir` / `setProfile` and a runtime caller has
|
|
410
|
+
* no business clearing it.
|
|
411
|
+
*/
|
|
412
|
+
export function __resetProfileSnapshotForTests(): void {
|
|
413
|
+
preProfileAgentDirEnv = resolvePreProfileAgentDir(
|
|
414
|
+
activeProfile,
|
|
415
|
+
process.env.PI_CODING_AGENT_DIR,
|
|
416
|
+
activeProfile ?? readPiProfileFromEnvSafe(),
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** Activate a named profile. Passing undefined or "default" returns to the default profile. */
|
|
421
|
+
export function setProfile(profile: string | undefined): void {
|
|
422
|
+
const next = normalizeProfileName(profile);
|
|
423
|
+
if (next && !activeProfile) {
|
|
424
|
+
// First activation of a named profile in this process: snapshot the
|
|
425
|
+
// current PI_CODING_AGENT_DIR so a later reset can restore the user's
|
|
426
|
+
// explicit override. Subsequent profile switches keep the original
|
|
427
|
+
// snapshot — the "pre-profile" baseline is the state before profiles
|
|
428
|
+
// entered the picture, not the state between two activations.
|
|
429
|
+
preProfileAgentDirEnv = resolvePreProfileAgentDir(
|
|
430
|
+
undefined,
|
|
431
|
+
process.env.PI_CODING_AGENT_DIR,
|
|
432
|
+
readPiProfileFromEnvSafe(),
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
activeProfile = next;
|
|
436
|
+
if (activeProfile) {
|
|
437
|
+
dirs = new DirResolver({ profile: activeProfile });
|
|
438
|
+
process.env.OMP_PROFILE = activeProfile;
|
|
439
|
+
process.env.PI_PROFILE = activeProfile;
|
|
440
|
+
process.env.PI_CODING_AGENT_DIR = dirs.agentDir;
|
|
441
|
+
} else {
|
|
442
|
+
for (const key of PROFILE_ENV_KEYS) {
|
|
443
|
+
delete process.env[key];
|
|
444
|
+
}
|
|
445
|
+
if (preProfileAgentDirEnv === undefined) {
|
|
446
|
+
delete process.env.PI_CODING_AGENT_DIR;
|
|
447
|
+
} else {
|
|
448
|
+
process.env.PI_CODING_AGENT_DIR = preProfileAgentDirEnv;
|
|
449
|
+
}
|
|
450
|
+
dirs = new DirResolver({ agentDirOverride: preProfileAgentDirEnv });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Get the active named profile. Undefined means the default profile. */
|
|
455
|
+
export function getActiveProfile(): string | undefined {
|
|
456
|
+
return activeProfile;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Resolve the config root that backs a profile without activating it. */
|
|
460
|
+
export function getProfileRootDir(profile: string | undefined): string {
|
|
461
|
+
return getProfileConfigRoot(normalizeProfileName(profile));
|
|
462
|
+
}
|
|
216
463
|
/** Get the agent config directory (~/.omp/agent). */
|
|
217
464
|
export function getAgentDir(): string {
|
|
218
465
|
return dirs.agentDir;
|
|
@@ -534,10 +781,15 @@ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
|
|
534
781
|
* winner's id). Survives independently of agent state: deleting
|
|
535
782
|
* `~/.omp/agent/` does not regenerate it. Server-side dedup for grievance
|
|
536
783
|
* pushes (and similar telemetry) keys on this id.
|
|
784
|
+
*
|
|
785
|
+
* Anchored to the base config root (`~/.omp/install-id`) regardless of the
|
|
786
|
+
* active profile: install identity is per-install, not per-profile, so every
|
|
787
|
+
* profile shares one id and the global cache stays correct no matter the
|
|
788
|
+
* profile / `getInstallId` call order.
|
|
537
789
|
*/
|
|
538
790
|
export function getInstallId(): string {
|
|
539
791
|
if (cachedInstallId) return cachedInstallId;
|
|
540
|
-
const filePath = path.join(
|
|
792
|
+
const filePath = path.join(getBaseConfigRoot(), INSTALL_ID_FILE);
|
|
541
793
|
|
|
542
794
|
let observedInvalid = false;
|
|
543
795
|
try {
|
package/src/env.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { getAgentDir, getConfigRootDir } from "./dirs";
|
|
4
|
+
import { getAgentDir, getConfigRootDir, refreshDirsFromEnv } from "./dirs";
|
|
5
|
+
|
|
6
|
+
export * from "./worker-host";
|
|
5
7
|
|
|
6
8
|
const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
7
9
|
|
|
@@ -117,6 +119,13 @@ for (const file of [projectEnv, agentEnv, piEnv, homeEnv]) {
|
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
|
|
122
|
+
// Directory-affecting keys (XDG_*_HOME, and in default mode PI_CODING_AGENT_DIR)
|
|
123
|
+
// may have just arrived from the profile/agent `.env` applied above. The dirs
|
|
124
|
+
// resolver cached its paths at module load — before this file ran — so rebuild
|
|
125
|
+
// it now from the updated env. `getAgentDir()` already located the `.env` from
|
|
126
|
+
// the profile name + home, so this re-reads only the directory vars.
|
|
127
|
+
refreshDirsFromEnv();
|
|
128
|
+
|
|
120
129
|
/**
|
|
121
130
|
* Intentional re-export of Bun.env.
|
|
122
131
|
*
|
|
@@ -172,26 +181,6 @@ export function isCompiledBinary(): boolean {
|
|
|
172
181
|
return url.includes("$bunfs") || url.includes("~BUN") || url.includes("%7EBUN");
|
|
173
182
|
}
|
|
174
183
|
|
|
175
|
-
/**
|
|
176
|
-
* Main-module path declared by self-dispatching CLI entrypoints — entries
|
|
177
|
-
* whose top-level argv handling routes hidden `__omp_*` worker selectors.
|
|
178
|
-
* Worker spawn sites re-enter this module via `new Worker(entry, { argv })`,
|
|
179
|
-
* so every distribution (source, npm bundle, compiled binary) needs exactly
|
|
180
|
-
* one JavaScript entrypoint. Never set under `bun test`, SDK embedding, or
|
|
181
|
-
* standalone package bins — those hosts load worker modules directly.
|
|
182
|
-
*/
|
|
183
|
-
let workerHostMain: string | null = null;
|
|
184
|
-
|
|
185
|
-
/** Called by CLI entrypoints whose main module dispatches worker argv selectors. */
|
|
186
|
-
export function declareWorkerHostEntry(): void {
|
|
187
|
-
workerHostMain = Bun.main;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** Main-module path of the self-dispatching CLI host, or null outside it. */
|
|
191
|
-
export function workerHostEntry(): string | null {
|
|
192
|
-
return workerHostMain;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
184
|
const TRUTHY: Dict<boolean> = {
|
|
196
185
|
"1": true,
|
|
197
186
|
Y: true,
|
package/src/runtime-install.ts
CHANGED
|
@@ -170,6 +170,16 @@ function resolverRegistry(): ResolverRegistration[] {
|
|
|
170
170
|
holder[REGISTRY] ??= [];
|
|
171
171
|
return holder[REGISTRY];
|
|
172
172
|
}
|
|
173
|
+
function pathContains(root: string, candidate: string): boolean {
|
|
174
|
+
const relative = path.relative(root, candidate);
|
|
175
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function parentFilename(parent: unknown): string | null {
|
|
179
|
+
if (!isRecord(parent)) return null;
|
|
180
|
+
const filename = parent.filename;
|
|
181
|
+
return typeof filename === "string" ? filename : null;
|
|
182
|
+
}
|
|
173
183
|
|
|
174
184
|
export interface RuntimeResolverOptions {
|
|
175
185
|
/** Absolute path to the runtime cache's `node_modules`. */
|
|
@@ -212,7 +222,17 @@ export function installRuntimeModuleResolver({ runtimeNodeModules, stubs = {} }:
|
|
|
212
222
|
}
|
|
213
223
|
const bare = !request.startsWith(".") && !request.startsWith("node:") && !path.isAbsolute(request);
|
|
214
224
|
if (bare) {
|
|
225
|
+
const parentFile = parentFilename(parent);
|
|
215
226
|
for (const registration of resolverRegistry()) {
|
|
227
|
+
const parentInRuntime = parentFile !== null && pathContains(registration.runtimeNodeModules, parentFile);
|
|
228
|
+
if (parentInRuntime) {
|
|
229
|
+
const stub = registration.stubs[request];
|
|
230
|
+
if (stub) return stub;
|
|
231
|
+
if (!stockResolved || !pathContains(registration.runtimeNodeModules, stockResolved)) {
|
|
232
|
+
const fallback = resolveRuntimeModule(registration.runtimeNodeModules, request);
|
|
233
|
+
if (fallback) return fallback;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
216
236
|
if (stockResolved) {
|
|
217
237
|
// Correct a stock hit only inside the top-level package the
|
|
218
238
|
// request names. A hit in a nested node_modules (e.g. tar's
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main-module path declared by self-dispatching CLI entrypoints — entries
|
|
3
|
+
* whose top-level argv handling routes hidden `__omp_*` worker selectors.
|
|
4
|
+
* Worker spawn sites re-enter this module via `new Worker(entry, { argv })`,
|
|
5
|
+
* so every distribution (source, npm bundle, compiled binary) needs exactly
|
|
6
|
+
* one JavaScript entrypoint. Never set under `bun test`, SDK embedding, or
|
|
7
|
+
* standalone package bins — those hosts load worker modules directly.
|
|
8
|
+
*/
|
|
9
|
+
let workerHostMain: string | null = null;
|
|
10
|
+
|
|
11
|
+
/** Called by CLI entrypoints whose main module dispatches worker argv selectors. */
|
|
12
|
+
export function declareWorkerHostEntry(): void {
|
|
13
|
+
workerHostMain = Bun.main;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Main-module path of the self-dispatching CLI host, or null outside it. */
|
|
17
|
+
export function workerHostEntry(): string | null {
|
|
18
|
+
return workerHostMain;
|
|
19
|
+
}
|