Merge PR #7065: fix(xdg): fix files and folder for xdg-maintained (@Parsifa1)
This commit is contained in:
@@ -15,7 +15,7 @@ import {
|
||||
} from "@oh-my-pi/pi-catalog/model-thinking";
|
||||
import { CATALOG_PROVIDERS, type ProviderCatalogEntry } from "@oh-my-pi/pi-catalog/provider-models";
|
||||
import { CODEX_BASE_URL } from "@oh-my-pi/pi-catalog/wire/codex";
|
||||
import { $env, $pickenv, getConfigRootDir, isEnoent, logger, withExtraCaFetch } from "@oh-my-pi/pi-utils";
|
||||
import { $env, $pickenv, getProviderInFlightRoot, isEnoent, logger, withExtraCaFetch } from "@oh-my-pi/pi-utils";
|
||||
import { getCustomApi } from "./api-registry";
|
||||
import { createAuthRetryKeyState, isApiKeyResolver, resolveNextAuthRetryKey } from "./auth-retry";
|
||||
import * as AIError from "./error";
|
||||
@@ -189,7 +189,7 @@ function resolveProviderInFlightLimit(
|
||||
|
||||
function providerInFlightRoot(): string {
|
||||
if (providerInFlightRootOverride) return providerInFlightRootOverride;
|
||||
return path.join(getConfigRootDir(), "run", "provider-inflight");
|
||||
return getProviderInFlightRoot();
|
||||
}
|
||||
|
||||
function providerInFlightSegment(provider: string): string {
|
||||
|
||||
@@ -31,6 +31,11 @@
|
||||
- Preserved explicit `-e`/`--extension` and `--hook` packages under
|
||||
`--no-extensions` while excluding ambient extension factories and sibling
|
||||
capabilities from settings or installed OMP packages.
|
||||
### Changed
|
||||
|
||||
- `secret-placeholder.key` now resolves under XDG state (`$XDG_STATE_HOME/omp/secret-placeholder.key`) instead of the agent config directory, so it follows the same XDG layout as other state files.
|
||||
- Daemon runtime directories (`run/daemons/<hash>`) and provider in-flight tracking (`run/provider-inflight`) now resolve under XDG state (`$XDG_STATE_HOME/omp/run/`) instead of the config root, keeping ephemeral runtime state out of `~/.config`.
|
||||
- `marketplaces.json` now resolves under XDG data (`$XDG_DATA_HOME/omp/marketplaces.json`) instead of the config root, aligning with the XDG data category for user-scoped registry files.
|
||||
|
||||
## [17.2.3] - 2026-08-01
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
|
||||
import { getConfigRootDir, getPluginsDir, isEnoent, logger, tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { getPluginsDir, isEnoent, logger, tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
|
||||
export { getMarketplacesRegistryPath } from "@oh-my-pi/pi-utils";
|
||||
|
||||
import type {
|
||||
InstalledPluginEntry,
|
||||
@@ -24,12 +26,6 @@ import type {
|
||||
MarketplacesRegistry,
|
||||
} from "./types";
|
||||
|
||||
// ── Path helpers ─────────────────────────────────────────────────────
|
||||
|
||||
export function getMarketplacesRegistryPath(): string {
|
||||
return path.join(getConfigRootDir(), "marketplaces.json");
|
||||
}
|
||||
|
||||
export function getInstalledPluginsRegistryPath(): string {
|
||||
return path.join(getPluginsDir(), "installed_plugins.json");
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import * as path from "node:path";
|
||||
import { getConfigRootDir } from "@oh-my-pi/pi-utils";
|
||||
import { getDaemonRuntimeDir } from "@oh-my-pi/pi-utils";
|
||||
|
||||
/** Resolve the private runtime directory shared by omp processes in one project directory. */
|
||||
export function daemonRuntimeDir(projectDir: string, configRoot: string = getConfigRootDir()): string {
|
||||
const key = Bun.hash.wyhash(path.resolve(projectDir)).toString(16).padStart(16, "0");
|
||||
return path.join(configRoot, "run", "daemons", key);
|
||||
}
|
||||
export { getDaemonRuntimeDir as daemonRuntimeDir };
|
||||
|
||||
/** Resolve the Unix socket or Windows named pipe used by one project broker. */
|
||||
export function daemonBrokerEndpoint(projectDir: string, runtimeDir: string): string {
|
||||
|
||||
@@ -1391,11 +1391,15 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
|
||||
// headless run on an unwritable default config root pays for a feature it
|
||||
// does not use.
|
||||
const needsPlaceholderKey = secretEntriesNeedPlaceholderKey([...envEntries, ...fileEntries]);
|
||||
const explicitAgentDir = options.agentDir;
|
||||
const placeholderKey = needsPlaceholderKey
|
||||
? await getSecretPlaceholderKey(agentDir)
|
||||
: await getExistingSecretPlaceholderKey(agentDir);
|
||||
? await getSecretPlaceholderKey(explicitAgentDir)
|
||||
: await getExistingSecretPlaceholderKey(explicitAgentDir);
|
||||
if (allEntries.length > 0) {
|
||||
obfuscator = new SecretObfuscator(allEntries, placeholderKey ?? (() => getSecretPlaceholderKeySync(agentDir)));
|
||||
obfuscator = new SecretObfuscator(
|
||||
allEntries,
|
||||
placeholderKey ?? (() => getSecretPlaceholderKeySync(explicitAgentDir)),
|
||||
);
|
||||
}
|
||||
if (obfuscator?.hasSecrets() !== true && placeholderKey !== undefined) {
|
||||
// No configured entry produced an active secret (e.g. only ignored short
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as crypto from "node:crypto";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { SENSITIVE_TOKEN_RE } from "@oh-my-pi/pi-ai/providers/transform-messages";
|
||||
import { getAgentDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
||||
import { getSecretPlaceholderKeyPath, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
||||
import { YAML } from "bun";
|
||||
import { regexHasUnresolvableShortMatchFallback, type SecretEntry, sanitizeSecretFriendlyName } from "./obfuscator";
|
||||
import { compileSecretRegex } from "./regex";
|
||||
@@ -11,17 +11,15 @@ const PLACEHOLDER_KEY_RE = /^[A-Za-z0-9_-]{43}$/;
|
||||
const cachedPlaceholderKeys = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* Per-install secret key for the placeholder digest. Persisted under the agent
|
||||
* config directory and never sent to a provider, so model-visible placeholders
|
||||
* cannot be reversed by dictionary-hashing candidate secrets. Stable across
|
||||
* sessions so persisted transcripts deobfuscate consistently. Defaults to
|
||||
* `getAgentDir()` — the same directory `createAgentSession()` passes as
|
||||
* `agentDir` — so a caller relying on the default reads/writes the identical
|
||||
* key file live sessions use, per `~/.omp/agent/secret-placeholder.key` in
|
||||
* docs/secrets.md.
|
||||
* Per-install secret key for the placeholder digest. Persisted under XDG state
|
||||
* and never sent to a provider, so model-visible placeholders cannot be reversed
|
||||
* by dictionary-hashing candidate secrets. Stable across sessions so persisted
|
||||
* transcripts deobfuscate consistently. Defaults to `getSecretPlaceholderKeyPath()`
|
||||
* — `$XDG_STATE_HOME/omp/secret-placeholder.key` (or `~/.omp/agent/secret-placeholder.key`
|
||||
* without XDG), per docs/secrets.md.
|
||||
*/
|
||||
export async function getSecretPlaceholderKey(keyDir: string = getAgentDir()): Promise<string> {
|
||||
const keyPath = path.join(keyDir, "secret-placeholder.key");
|
||||
export async function getSecretPlaceholderKey(keyDir?: string): Promise<string> {
|
||||
const keyPath = keyDir ? path.join(keyDir, "secret-placeholder.key") : getSecretPlaceholderKeyPath();
|
||||
const cached = cachedPlaceholderKeys.get(keyPath);
|
||||
if (cached !== undefined) return cached;
|
||||
|
||||
@@ -32,7 +30,7 @@ export async function getSecretPlaceholderKey(keyDir: string = getAgentDir()): P
|
||||
}
|
||||
|
||||
const generated = crypto.randomBytes(32).toString("base64url");
|
||||
await fs.promises.mkdir(keyDir, { recursive: true });
|
||||
await fs.promises.mkdir(path.dirname(keyPath), { recursive: true });
|
||||
try {
|
||||
await fs.promises.writeFile(keyPath, generated, { flag: "wx", mode: 0o600 });
|
||||
cachedPlaceholderKeys.set(keyPath, generated);
|
||||
@@ -53,8 +51,8 @@ export async function getSecretPlaceholderKey(keyDir: string = getAgentDir()): P
|
||||
}
|
||||
|
||||
/** Return an existing placeholder key for redaction without creating a new key file. */
|
||||
export async function getExistingSecretPlaceholderKey(keyDir: string = getAgentDir()): Promise<string | undefined> {
|
||||
const keyPath = path.join(keyDir, "secret-placeholder.key");
|
||||
export async function getExistingSecretPlaceholderKey(keyDir?: string): Promise<string | undefined> {
|
||||
const keyPath = keyDir ? path.join(keyDir, "secret-placeholder.key") : getSecretPlaceholderKeyPath();
|
||||
const cached = cachedPlaceholderKeys.get(keyPath);
|
||||
if (cached !== undefined) return cached;
|
||||
// Redaction-only: this key is loaded solely to redact an existing key file from
|
||||
@@ -85,8 +83,8 @@ let ephemeralSyncPlaceholderKey: string | undefined;
|
||||
* throws: an unreadable or unwritable key file degrades to a process-ephemeral
|
||||
* key (with a warning) instead of breaking the session.
|
||||
*/
|
||||
export function getSecretPlaceholderKeySync(keyDir: string = getAgentDir()): string {
|
||||
const keyPath = path.join(keyDir, "secret-placeholder.key");
|
||||
export function getSecretPlaceholderKeySync(keyDir?: string): string {
|
||||
const keyPath = keyDir ? path.join(keyDir, "secret-placeholder.key") : getSecretPlaceholderKeyPath();
|
||||
const cached = cachedPlaceholderKeys.get(keyPath);
|
||||
if (cached !== undefined) return cached;
|
||||
try {
|
||||
@@ -100,7 +98,7 @@ export function getSecretPlaceholderKeySync(keyDir: string = getAgentDir()): str
|
||||
}
|
||||
const generated = crypto.randomBytes(32).toString("base64url");
|
||||
try {
|
||||
fs.mkdirSync(keyDir, { recursive: true });
|
||||
fs.mkdirSync(path.dirname(keyPath), { recursive: true });
|
||||
fs.writeFileSync(keyPath, generated, { flag: "wx", mode: 0o600 });
|
||||
cachedPlaceholderKeys.set(keyPath, generated);
|
||||
return generated;
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
### Fixed
|
||||
|
||||
- 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)).
|
||||
### Added
|
||||
|
||||
- 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.
|
||||
|
||||
## [17.2.1] - 2026-07-30
|
||||
|
||||
|
||||
@@ -820,6 +820,27 @@ export function getDebugLogPath(agentDir?: string): string {
|
||||
return dirs.agentSubdir(agentDir, `${APP_NAME}-debug.log`, "state");
|
||||
}
|
||||
|
||||
/** Get the secret placeholder key path (~/.omp/agent/secret-placeholder.key; XDG default: $XDG_STATE_HOME/omp/secret-placeholder.key). */
|
||||
export function getSecretPlaceholderKeyPath(): string {
|
||||
return dirs.agentSubdir(undefined, "secret-placeholder.key", "state");
|
||||
}
|
||||
|
||||
/** Get the daemon runtime directory for a project (~/.omp/run/daemons/<hash>; XDG default: $XDG_STATE_HOME/omp/run/daemons/<hash>). */
|
||||
export function getDaemonRuntimeDir(projectDir: string): string {
|
||||
const key = Bun.hash.wyhash(path.resolve(projectDir)).toString(16).padStart(16, "0");
|
||||
return dirs.rootSubdir(path.join("run", "daemons", key), "state");
|
||||
}
|
||||
|
||||
/** Get the provider in-flight root directory (~/.omp/run/provider-inflight; XDG default: $XDG_STATE_HOME/omp/run/provider-inflight). */
|
||||
export function getProviderInFlightRoot(): string {
|
||||
return dirs.rootSubdir(path.join("run", "provider-inflight"), "state");
|
||||
}
|
||||
|
||||
/** Get the marketplaces registry path (~/.omp/marketplaces.json; XDG default: $XDG_DATA_HOME/omp/marketplaces.json). */
|
||||
export function getMarketplacesRegistryPath(): string {
|
||||
return dirs.rootSubdir("marketplaces.json", "data");
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Project subdirectories (.omp/*)
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user