@oh-my-pi/pi-utils 17.2.2 → 17.2.4

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 CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.2.4] - 2026-08-01
6
+
7
+ ### Added
8
+
9
+ - Added `getSecretPlaceholderKeyPath()`, `getDaemonRuntimeDir()`, `getProviderInFlightRoot()`, and `getMarketplacesRegistryPath()` to resolve secret key, daemon runtime, provider in-flight, and marketplace registry paths under their respective XDG categories (state, data) instead of the config root.
10
+ - Existing installs enabling XDG keep their data: a legacy `~/.omp/agent/secret-placeholder.key` or `~/.omp/marketplaces.json` is copied to its XDG location on first resolution, so persisted transcripts still deobfuscate and added marketplaces survive the move.
11
+
12
+ ### Changed
13
+
14
+ - Headless hosts (print/RPC/ACP/eval/SDK) now use a 1s SQLite `busy_timeout` for the session-critical databases (agent.db, history.db, stats.db) via `getDbBusyTimeoutMs()`, so lock contention no longer freezes the protocol loop for the full interactive 5s timeout; interactive hosts keep the 5s timeout.
15
+
16
+ ### Fixed
17
+
18
+ - Fixed Bun test-runtime detection treating application-owned `NODE_ENV=test` and `BUN_ENV=test` values as test-runner signals ([#7261](https://github.com/can1357/oh-my-pi/issues/7261)).
19
+
5
20
  ## [17.2.1] - 2026-07-30
6
21
 
7
22
  ### Added
@@ -237,6 +237,14 @@ export declare function getTerminalSessionsDir(agentDir?: string): string;
237
237
  export declare function getCrashLogPath(agentDir?: string): string;
238
238
  /** Get the debug log path (~/.omp/agent/omp-debug.log). */
239
239
  export declare function getDebugLogPath(agentDir?: string): string;
240
+ /** Get the secret placeholder key path (~/.omp/agent/secret-placeholder.key; XDG default: $XDG_STATE_HOME/omp/secret-placeholder.key). Adopts a legacy key on first XDG resolution. */
241
+ export declare function getSecretPlaceholderKeyPath(): string;
242
+ /** Get the daemon runtime directory for a project (~/.omp/run/daemons/<hash>; XDG default: $XDG_STATE_HOME/omp/run/daemons/<hash>). */
243
+ export declare function getDaemonRuntimeDir(projectDir: string): string;
244
+ /** Get the provider in-flight root directory (~/.omp/run/provider-inflight; XDG default: $XDG_STATE_HOME/omp/run/provider-inflight). */
245
+ export declare function getProviderInFlightRoot(): string;
246
+ /** Get the marketplaces registry path (~/.omp/marketplaces.json; XDG default: $XDG_DATA_HOME/omp/marketplaces.json). Adopts a legacy registry on first XDG resolution. */
247
+ export declare function getMarketplacesRegistryPath(): string;
240
248
  /** Get the project-level Python modules directory (.omp/modules). */
241
249
  export declare function getProjectModulesDir(cwd?: string): string;
242
250
  /** Get the project-level prompts directory (.omp/prompts). */
@@ -44,7 +44,7 @@ export declare function $pickenv(...keys: string[]): string | undefined;
44
44
  * Empty, invalid, NaN, zero, or negative values return `defaultValue`.
45
45
  */
46
46
  export declare function $envpos(name: string, defaultValue: number): number;
47
- /** True when `BUN_ENV` or `NODE_ENV` is the string `test`. */
47
+ /** True when the process is an explicitly marked test child or Bun is running a test entrypoint. */
48
48
  export declare function isBunTestRuntime(): boolean;
49
49
  /**
50
50
  * True when real-terminal side effects must be suppressed: stdout escape/frame
@@ -77,6 +77,20 @@ export declare function isInteractiveHost(): boolean;
77
77
  * restore exact prior state. See {@link isInteractiveHost}.
78
78
  */
79
79
  export declare function setInteractiveHost(interactive: boolean): boolean;
80
+ /**
81
+ * SQLite `busy_timeout` for the session-critical databases (agent.db,
82
+ * history.db, stats.db).
83
+ *
84
+ * Interactive hosts tolerate a longer synchronous wait on lock contention
85
+ * (SQLITE_BUSY during WAL recovery/checkpoint — see oh-my-pi#2421): the
86
+ * operator sees a brief freeze and the statement eventually completes.
87
+ * Headless hosts (print/RPC/ACP/eval/SDK) run a protocol on the same thread —
88
+ * a multi-second synchronous busy-wait freezes their event loop and stalls
89
+ * every in-flight frame with no liveness signal, so they use a short timeout
90
+ * and rely on the existing asynchronous open/retry paths to recover from
91
+ * contention instead of blocking.
92
+ */
93
+ export declare function getDbBusyTimeoutMs(): number;
80
94
  /**
81
95
  * True when this code is running inside a `bun build --compile` standalone
82
96
  * binary. Detects via the embedded virtual-filesystem path markers
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-utils",
4
- "version": "17.2.2",
4
+ "version": "17.2.4",
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": "17.2.2",
34
+ "@oh-my-pi/pi-natives": "17.2.4",
35
35
  "handlebars": "^4.7.9",
36
36
  "winston": "^3.19.0",
37
37
  "winston-daily-rotate-file": "5.0.0"
package/src/dirs.ts CHANGED
@@ -820,6 +820,50 @@ export function getDebugLogPath(agentDir?: string): string {
820
820
  return dirs.agentSubdir(agentDir, `${APP_NAME}-debug.log`, "state");
821
821
  }
822
822
 
823
+ /**
824
+ * Best-effort one-time copy of a legacy config-root file to its redirected XDG
825
+ * location. Existing installs that enable XDG after the file was created keep
826
+ * their data (e.g. a placeholder key whose loss would break deobfuscation of
827
+ * persisted transcripts). The legacy file is left in place for older omp
828
+ * versions sharing the profile.
829
+ */
830
+ function adoptLegacyFile(legacyPath: string, targetPath: string): void {
831
+ if (targetPath === legacyPath) return;
832
+ try {
833
+ if (fs.existsSync(targetPath) || !fs.existsSync(legacyPath)) return;
834
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
835
+ fs.copyFileSync(legacyPath, targetPath, fs.constants.COPYFILE_EXCL);
836
+ } catch {
837
+ // Opportunistic: a copy race or unwritable XDG dir falls back to a fresh
838
+ // file at the new path — the pre-adoption behavior.
839
+ }
840
+ }
841
+
842
+ /** Get the secret placeholder key path (~/.omp/agent/secret-placeholder.key; XDG default: $XDG_STATE_HOME/omp/secret-placeholder.key). Adopts a legacy key on first XDG resolution. */
843
+ export function getSecretPlaceholderKeyPath(): string {
844
+ const keyPath = dirs.agentSubdir(undefined, "secret-placeholder.key", "state");
845
+ adoptLegacyFile(path.join(dirs.agentDir, "secret-placeholder.key"), keyPath);
846
+ return keyPath;
847
+ }
848
+
849
+ /** Get the daemon runtime directory for a project (~/.omp/run/daemons/<hash>; XDG default: $XDG_STATE_HOME/omp/run/daemons/<hash>). */
850
+ export function getDaemonRuntimeDir(projectDir: string): string {
851
+ const key = Bun.hash.wyhash(path.resolve(projectDir)).toString(16).padStart(16, "0");
852
+ return dirs.rootSubdir(path.join("run", "daemons", key), "state");
853
+ }
854
+
855
+ /** Get the provider in-flight root directory (~/.omp/run/provider-inflight; XDG default: $XDG_STATE_HOME/omp/run/provider-inflight). */
856
+ export function getProviderInFlightRoot(): string {
857
+ return dirs.rootSubdir(path.join("run", "provider-inflight"), "state");
858
+ }
859
+
860
+ /** Get the marketplaces registry path (~/.omp/marketplaces.json; XDG default: $XDG_DATA_HOME/omp/marketplaces.json). Adopts a legacy registry on first XDG resolution. */
861
+ export function getMarketplacesRegistryPath(): string {
862
+ const registryPath = dirs.rootSubdir("marketplaces.json", "data");
863
+ adoptLegacyFile(path.join(dirs.configRoot, "marketplaces.json"), registryPath);
864
+ return registryPath;
865
+ }
866
+
823
867
  // =============================================================================
824
868
  // Project subdirectories (.omp/*)
825
869
  // =============================================================================
package/src/env.ts CHANGED
@@ -258,9 +258,13 @@ export function $envpos(name: string, defaultValue: number): number {
258
258
  return parsed;
259
259
  }
260
260
 
261
- /** True when `BUN_ENV` or `NODE_ENV` is the string `test`. */
261
+ const BUN_TEST_ENTRY_PATTERN = /[._](?:test|spec)\.[cm]?[jt]sx?$/;
262
+
263
+ /** True when the process is an explicitly marked test child or Bun is running a test entrypoint. */
262
264
  export function isBunTestRuntime(): boolean {
263
- return Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
265
+ if (Bun.env.PI_TEST_RUNTIME === "1") return true;
266
+ const hasTestEnvironment = Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
267
+ return hasTestEnvironment && BUN_TEST_ENTRY_PATTERN.test(Bun.main);
264
268
  }
265
269
 
266
270
  let terminalHeadless = isBunTestRuntime();
@@ -314,6 +318,23 @@ export function setInteractiveHost(interactive: boolean): boolean {
314
318
  return previous;
315
319
  }
316
320
 
321
+ /**
322
+ * SQLite `busy_timeout` for the session-critical databases (agent.db,
323
+ * history.db, stats.db).
324
+ *
325
+ * Interactive hosts tolerate a longer synchronous wait on lock contention
326
+ * (SQLITE_BUSY during WAL recovery/checkpoint — see oh-my-pi#2421): the
327
+ * operator sees a brief freeze and the statement eventually completes.
328
+ * Headless hosts (print/RPC/ACP/eval/SDK) run a protocol on the same thread —
329
+ * a multi-second synchronous busy-wait freezes their event loop and stalls
330
+ * every in-flight frame with no liveness signal, so they use a short timeout
331
+ * and rely on the existing asynchronous open/retry paths to recover from
332
+ * contention instead of blocking.
333
+ */
334
+ export function getDbBusyTimeoutMs(): number {
335
+ return isInteractiveHost() ? 5000 : 1000;
336
+ }
337
+
317
338
  /**
318
339
  * True when this code is running inside a `bun build --compile` standalone
319
340
  * binary. Detects via the embedded virtual-filesystem path markers