From 51d39e7eeaa9b4c3a1d7969d145e9647409a8d5b Mon Sep 17 00:00:00 2001 From: can1357 Date: Sat, 24 Jan 2026 03:33:12 +0100 Subject: [PATCH] refactor(imports): migrated node module imports to namespace imports - Converted named imports from node modules (fs, path, os) to namespace imports across all packages. - Extended extension loader error handling with isEacces and hasFsCode type guards. --- AGENTS.md | 4 +- docs/porting-from-pi-mono.md | 42 ++--- packages/ai/CHANGELOG.md | 3 + packages/ai/README.md | 18 +- .../src/providers/openai-codex-responses.ts | 2 +- packages/ai/src/storage.ts | 4 +- packages/ai/src/stream.ts | 4 +- packages/ai/test/oauth.ts | 8 +- packages/ai/test/stream.test.ts | 4 +- packages/coding-agent/CHANGELOG.md | 6 +- packages/coding-agent/docs/session.md | 155 +++++++++++++----- packages/coding-agent/src/capability/index.ts | 4 +- packages/coding-agent/src/commit/git/index.ts | 4 +- packages/coding-agent/src/config.ts | 6 +- .../coding-agent/src/discovery/builtin.ts | 76 ++++----- .../coding-agent/src/discovery/helpers.ts | 6 +- packages/coding-agent/src/exa/mcp-client.ts | 4 +- .../src/extensibility/extensions/loader.ts | 20 ++- .../src/extensibility/plugins/manager.ts | 10 +- .../src/extensibility/plugins/paths.ts | 4 +- .../coding-agent/src/extensibility/skills.ts | 1 - packages/coding-agent/src/ipy/modules.ts | 4 +- packages/coding-agent/src/lsp/config.ts | 4 +- packages/coding-agent/src/lsp/edits.ts | 1 - packages/coding-agent/src/lsp/index.ts | 8 +- packages/coding-agent/src/lsp/lspmux.ts | 6 +- packages/coding-agent/src/main.ts | 6 +- .../components/extensions/inspector-panel.ts | 4 +- .../src/modes/components/footer.ts | 6 +- .../src/modes/components/status-line.ts | 12 +- .../modes/components/status-line/segments.ts | 4 +- .../modes/controllers/command-controller.ts | 1 - .../src/ssh/connection-manager.ts | 16 +- packages/coding-agent/src/ssh/sshfs-mount.ts | 4 +- packages/coding-agent/src/system-prompt.ts | 4 +- packages/coding-agent/src/task/index.ts | 7 +- packages/coding-agent/src/task/worktree.ts | 8 +- packages/coding-agent/src/tools/fetch.ts | 1 - .../coding-agent/src/tools/gemini-image.ts | 4 +- packages/coding-agent/src/tools/index.ts | 2 +- packages/coding-agent/src/tools/read.ts | 4 +- .../coding-agent/src/tools/render-utils.ts | 4 +- packages/coding-agent/src/utils/clipboard.ts | 6 +- .../coding-agent/src/utils/shell-snapshot.ts | 6 +- .../coding-agent/src/utils/tools-manager.ts | 8 +- .../coding-agent/src/web/scrapers/utils.ts | 4 +- .../coding-agent/src/web/scrapers/youtube.ts | 4 +- .../src/web/search/providers/exa.ts | 4 +- .../test/agent-session-branching.test.ts | 4 +- .../test/agent-session-compaction.test.ts | 4 +- .../test/agent-session-concurrent.test.ts | 4 +- .../coding-agent/test/block-images.test.ts | 6 +- .../test/compaction-hooks.test.ts | 4 +- .../test/compaction-thinking-model.test.ts | 6 +- .../test/core/apply-patch-adverserial.test.ts | 4 +- .../test/core/apply-patch-regression.test.ts | 30 ++-- .../test/core/apply-patch.test.ts | 8 +- .../coding-agent/test/model-registry.test.ts | 16 +- .../test/python-tool-settings.test.ts | 4 +- packages/coding-agent/test/rpc.test.ts | 4 +- packages/coding-agent/test/sdk-skills.test.ts | 4 +- .../session-manager/file-operations.test.ts | 6 +- packages/coding-agent/test/skills.test.ts | 4 +- .../test/streaming-edit-abort.test.ts | 4 +- packages/coding-agent/test/tools.test.ts | 6 +- packages/coding-agent/test/utilities.ts | 4 +- packages/pi-utils/src/logger.ts | 4 +- packages/pi-utils/src/temp.ts | 4 +- packages/react-edit-benchmark/tarball.ts | 1 - packages/stats/src/db.ts | 6 +- packages/stats/src/parser.ts | 4 +- packages/tui/CHANGELOG.md | 1 + packages/tui/src/autocomplete.ts | 6 +- packages/tui/src/mermaid.ts | 5 +- 74 files changed, 371 insertions(+), 309 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1e749efba..c9b313d62 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -77,10 +77,12 @@ $`do-stuff ${tmpFile}`.quiet().nothrow(); // BAD: Named imports import { readdir, stat } from "node:fs/promises"; import { join, resolve } from "node:path"; +import { tmpdir } from "node:os"; // GOOD: Namespace imports import * as fs from "node:fs/promises"; import * as path from "node:path"; +import * as os from "node:os"; // Then use: fs.readdir(), path.join(), etc. ``` @@ -98,7 +100,7 @@ await fs.stat(path); // File mixing sync and async (e.g., sync in constructor, async in methods) import * as fs from "node:fs"; -fs.existsSync(path); // sync +fs.existsSync(path); // sync await fs.promises.readdir(dir); // async ``` diff --git a/docs/porting-from-pi-mono.md b/docs/porting-from-pi-mono.md index a484d7ed6..1ce0aef7c 100644 --- a/docs/porting-from-pi-mono.md +++ b/docs/porting-from-pi-mono.md @@ -33,7 +33,7 @@ We use a bundler and strip `.js` from TypeScript imports. - Remove `.js` extensions from all internal imports. - Keep real file extensions only when required by tooling (e.g., `.json`, `.css`). - Example: - - `import { x } from "./foo.js";` -> `import { x } from "./foo";` + - `import { x } from "./foo.js";` -> `import { x } from "./foo";` ## 3) Replace import scopes @@ -41,9 +41,9 @@ Upstream uses different package scopes. Replace them consistently. - Replace old scopes with the local scope used here. - Examples (adjust to match the actual packages you are porting): - - `@mariozechner/pi-coding-agent` -> `@oh-my-pi/pi-coding-agent` - - `@mariozechner/pi-agent-core` -> `@oh-my-pi/pi-agent-core` - - `@mariozechner/tui` -> `@oh-my-pi/pi-tui` + - `@mariozechner/pi-coding-agent` -> `@oh-my-pi/pi-coding-agent` + - `@mariozechner/pi-agent-core` -> `@oh-my-pi/pi-agent-core` + - `@mariozechner/tui` -> `@oh-my-pi/pi-tui` ## 4) Use Bun APIs where they improve on Node @@ -65,7 +65,7 @@ We run on Bun. Replace Node APIs only when Bun provides a better alternative. - `fs.mkdtempSync()` — do NOT replace with manual path construction - `path.join()`, `path.resolve()`, etc. — these are fine -**Import style:** Use `node:` prefix for Node builtins (`import { homedir } from "node:os"`). +**Import style:** Use `node:` prefix for Node builtins with namespace imports (`import * as os from "node:os"`). **Wrong:** @@ -78,12 +78,12 @@ const tmp = Bun.env.TMPDIR || "/tmp"; **Correct:** ```typescript -import { homedir, tmpdir } from "node:os"; -import { mkdtempSync } from "node:fs"; -import { join } from "node:path"; +import * as os from "node:os"; +import * as fs from "node:fs"; +import * as path from "node:path"; -const configDir = join(homedir(), ".config", "myapp"); -const tempDir = mkdtempSync(join(tmpdir(), "myapp-")); +const configDir = path.join(os.homedir(), ".config", "myapp"); +const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "myapp-")); ``` ## 5) Prefer Bun embeds (no copying) @@ -95,10 +95,10 @@ Do not copy runtime assets or vendor files at build time. - Keep assets in-repo and let the bundler include them. - Eliminate copy scripts unless the user explicitly requests them. - If upstream reads a bundled fallback file at runtime, replace filesystem reads with a Bun text embed import. - - Example (Codex instructions fallback): - - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, "codex-instructions.md");` -> removed - - `import FALLBACK_INSTRUCTIONS from "./codex-instructions.md" with { type: "text" };` - - Use `return FALLBACK_INSTRUCTIONS;` instead of `readFileSync(FALLBACK_PROMPT_PATH, "utf8")` + - Example (Codex instructions fallback): + - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, "codex-instructions.md");` -> removed + - `import FALLBACK_INSTRUCTIONS from "./codex-instructions.md" with { type: "text" };` + - Use `return FALLBACK_INSTRUCTIONS;` instead of `readFileSync(FALLBACK_PROMPT_PATH, "utf8")` ## 6) Port `package.json` carefully @@ -117,13 +117,13 @@ Treat `package.json` as a contract. Merge intentionally. - Avoid dynamic imports and inline type imports. - Prefer existing helpers and utilities over new ad-hoc code. - Preserve Bun-first infrastructure changes already made in this repo: - - Runtime is Bun (no Node entry points). - - Package manager is Bun (no npm lockfiles). - - Heavy Node APIs (`child_process`, `readline`) are replaced with Bun equivalents. - - Lightweight Node APIs (`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`) are kept. - - CLI shebangs use `bun` (not `node`, not `tsx`). - - Packages use source files directly (no TypeScript build step). - - CI workflows run Bun for install/check/test. + - Runtime is Bun (no Node entry points). + - Package manager is Bun (no npm lockfiles). + - Heavy Node APIs (`child_process`, `readline`) are replaced with Bun equivalents. + - Lightweight Node APIs (`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`) are kept. + - CLI shebangs use `bun` (not `node`, not `tsx`). + - Packages use source files directly (no TypeScript build step). + - CI workflows run Bun for install/check/test. ## 8) Remove old compatibility layers diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index d56d3f32a..5c88285e2 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## [Unreleased] + +### Changed +- Migrated node module imports from named to namespace imports across all packages for consistency with project guidelines ## [8.0.0] - 2026-01-23 ### Fixed diff --git a/packages/ai/README.md b/packages/ai/README.md index 2efb51750..310415a64 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -231,7 +231,7 @@ const bookMeetingTool: Tool = { Tool results use content blocks and can include both text and images: ```typescript -import { readFileSync } from "fs"; +import * as fs from "node:fs"; const context: Context = { messages: [{ role: "user", content: "What is the weather in London?" }], @@ -260,7 +260,7 @@ for (const block of response.content) { } // Tool results can also include images (for vision-capable models) -const imageBuffer = readFileSync("chart.png"); +const imageBuffer = fs.readFileSync("chart.png"); context.messages.push({ role: "toolResult", toolCallId: "tool_xyz", @@ -379,7 +379,7 @@ All streaming events emitted during assistant message generation: Models with vision capabilities can process images. You can check if a model supports images via the `input` property. If you pass images to a non-vision model, they are silently ignored. ```typescript -import { readFileSync } from "fs"; +import * as fs from "node:fs"; import { getModel, complete } from "@oh-my-pi/pi-ai"; const model = getModel("openai", "gpt-4o-mini"); @@ -389,7 +389,7 @@ if (model.input.includes("image")) { console.log("Model supports vision"); } -const imageBuffer = readFileSync("image.png"); +const imageBuffer = fs.readFileSync("image.png"); const base64Image = imageBuffer.toString("base64"); const response = await complete(model, { @@ -1021,7 +1021,7 @@ await loginOpenAICodex({ ```typescript import { loginGitHubCopilot } from "@oh-my-pi/pi-ai"; -import { writeFileSync } from "fs"; +import * as fs from "node:fs"; const credentials = await loginGitHubCopilot({ onAuth: (url, instructions) => { @@ -1036,7 +1036,7 @@ const credentials = await loginGitHubCopilot({ // Store credentials yourself const auth = { "github-copilot": { type: "oauth", ...credentials } }; -writeFileSync("auth.json", JSON.stringify(auth, null, 2)); +fs.writeFileSync("auth.json", JSON.stringify(auth, null, 2)); ``` ### Using OAuth Tokens @@ -1045,10 +1045,10 @@ Use `getOAuthApiKey()` to get an API key, automatically refreshing if expired: ```typescript import { getModel, complete, getOAuthApiKey } from "@oh-my-pi/pi-ai"; -import { readFileSync, writeFileSync } from "fs"; +import * as fs from "node:fs"; // Load your stored credentials -const auth = JSON.parse(readFileSync("auth.json", "utf-8")); +const auth = JSON.parse(fs.readFileSync("auth.json", "utf-8")); // Get API key (refreshes if expired) const result = await getOAuthApiKey("github-copilot", auth); @@ -1056,7 +1056,7 @@ if (!result) throw new Error("Not logged in"); // Save refreshed credentials auth["github-copilot"] = { type: "oauth", ...result.newCredentials }; -writeFileSync("auth.json", JSON.stringify(auth, null, 2)); +fs.writeFileSync("auth.json", JSON.stringify(auth, null, 2)); // Use the API key const model = getModel("github-copilot", "gpt-4o"); diff --git a/packages/ai/src/providers/openai-codex-responses.ts b/packages/ai/src/providers/openai-codex-responses.ts index 061ee0dfd..dac24e20c 100644 --- a/packages/ai/src/providers/openai-codex-responses.ts +++ b/packages/ai/src/providers/openai-codex-responses.ts @@ -1,4 +1,4 @@ -import os from "node:os"; +import * as os from "node:os"; import { abortableSleep } from "@oh-my-pi/pi-utils"; import type { ResponseFunctionToolCall, diff --git a/packages/ai/src/storage.ts b/packages/ai/src/storage.ts index b705f0538..cd5ecc983 100644 --- a/packages/ai/src/storage.ts +++ b/packages/ai/src/storage.ts @@ -5,7 +5,7 @@ import { Database } from "bun:sqlite"; import * as fs from "node:fs/promises"; -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import type { OAuthCredentials } from "./utils/oauth/types"; @@ -24,7 +24,7 @@ type AuthRow = { * Get the agent config directory (e.g., ~/.omp/agent/) */ function getAgentDir(): string { - const configDir = process.env.OMP_CODING_AGENT_DIR || path.join(homedir(), ".omp", "agent"); + const configDir = process.env.OMP_CODING_AGENT_DIR || path.join(os.homedir(), ".omp", "agent"); return configDir; } diff --git a/packages/ai/src/stream.ts b/packages/ai/src/stream.ts index 275ce7b53..fedc3af88 100644 --- a/packages/ai/src/stream.ts +++ b/packages/ai/src/stream.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs"; -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { supportsXhigh } from "./models"; import { type BedrockOptions, streamBedrock } from "./providers/amazon-bedrock"; @@ -37,7 +37,7 @@ function hasVertexAdcCredentials(): boolean { cachedVertexAdcCredentialsExists = fs.existsSync(gacPath); } else { cachedVertexAdcCredentialsExists = fs.existsSync( - path.join(homedir(), ".config", "gcloud", "application_default_credentials.json"), + path.join(os.homedir(), ".config", "gcloud", "application_default_credentials.json"), ); } } diff --git a/packages/ai/test/oauth.ts b/packages/ai/test/oauth.ts index 8a4e2dd54..5ccc415f6 100644 --- a/packages/ai/test/oauth.ts +++ b/packages/ai/test/oauth.ts @@ -25,12 +25,12 @@ export function e2eApiKey(envVar: string): string | undefined { return process.env[envVar]; } +import * as os from "node:os"; +import * as path from "node:path"; import { getOAuthApiKey } from "@oh-my-pi/pi-ai/utils/oauth"; import type { OAuthCredentials, OAuthProvider } from "@oh-my-pi/pi-ai/utils/oauth/types"; -import { homedir } from "os"; -import { dirname, join } from "path"; -const AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json"); +const AUTH_PATH = path.join(os.homedir(), ".pi", "agent", "auth.json"); type ApiKeyCredential = { type: "api_key"; @@ -56,8 +56,6 @@ async function loadAuthStorage(): Promise { } async function saveAuthStorage(storage: AuthStorage): Promise { - const configDir = dirname(AUTH_PATH); - await fs.mkdir(configDir, { recursive: true, mode: 0o700 }); await Bun.write(AUTH_PATH, JSON.stringify(storage, null, 2)); await fs.chmod(AUTH_PATH, 0o600); } diff --git a/packages/ai/test/stream.test.ts b/packages/ai/test/stream.test.ts index 4da9cf9c4..34ad3e731 100644 --- a/packages/ai/test/stream.test.ts +++ b/packages/ai/test/stream.test.ts @@ -1,12 +1,12 @@ import { afterAll, beforeAll, describe, expect, it } from "bun:test"; import { type ChildProcess, execSync, spawn } from "node:child_process"; import * as fs from "node:fs/promises"; +import * as path from "node:path"; import { getModel } from "@oh-my-pi/pi-ai/models"; import { complete, stream } from "@oh-my-pi/pi-ai/stream"; import type { Api, Context, ImageContent, Model, OptionsForApi, Tool, ToolResultMessage } from "@oh-my-pi/pi-ai/types"; import { StringEnum } from "@oh-my-pi/pi-ai/utils/typebox-helpers"; import { Type } from "@sinclair/typebox"; -import { join } from "path"; import { e2eApiKey, resolveApiKey } from "./oauth"; // Resolve OAuth tokens at module level (async, runs before tests) @@ -218,7 +218,7 @@ async function handleImage(model: Model, options?: Optio } // Read the test image - const imagePath = join(import.meta.dir, "data", "red-circle.png"); + const imagePath = path.join(import.meta.dir, "data", "red-circle.png"); const imageBuffer = await fs.readFile(imagePath); const base64Image = imageBuffer.toString("base64"); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 4bfa9c10c..5282edaed 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -105,12 +105,10 @@ - Updated TypeScript and Bun configuration for monorepo-wide build consistency and reduced boilerplate - Removed WASM base64 encoding build script; imports now use Bun loader with `wasm?raw` query parameter - Unified TypeScript checking pipeline with tsgo-based configuration instead of per-package tsconfig.publish.json boilerplate -- Migrated file system operations from synchronous to asynchronous APIs in skills loading, agent discovery, and file processing for improved performance - Refactored scanDirectoryForSkills to use async/await with concurrent directory scanning via Promise.all -- Migrated file system operations from synchronous to asynchronous APIs across plugins, settings, extensions, and config modules -- Updated import organization in model-registry.ts and other modules for consistency -- Changed FilesystemError handling to use new pi-utils type guards for safer error checking - Improved error logging in settings manager for config file access failures +- Migrated node module imports from named to namespace imports across all packages for consistency with project guidelines +- Improved filesystem error handling in extension loader with additional type guards (isEacces, hasFsCode) for permission and EPERM errors ### Fixed - Fixed database busy errors during concurrent access by adding retry logic with exponential backoff when opening storage diff --git a/packages/coding-agent/docs/session.md b/packages/coding-agent/docs/session.md index 0df2bd5f5..5c0687d9c 100644 --- a/packages/coding-agent/docs/session.md +++ b/packages/coding-agent/docs/session.md @@ -31,10 +31,10 @@ All entries (except `SessionHeader`) extend `SessionEntryBase`: ```typescript interface SessionEntryBase { - type: string; - id: string; // 8-char hex ID - parentId: string | null; // Parent entry ID (null for first entry) - timestamp: string; // ISO timestamp + type: string; + id: string; // 8-char hex ID + parentId: string | null; // Parent entry ID (null for first entry) + timestamp: string; // ISO timestamp } ``` @@ -45,13 +45,20 @@ interface SessionEntryBase { First line of the file. Metadata only, not part of the tree (no `id`/`parentId`). ```json -{"type":"session","version":2,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"} +{ "type": "session", "version": 2, "id": "uuid", "timestamp": "2024-12-03T14:00:00.000Z", "cwd": "/path/to/project" } ``` For sessions with a parent (created via `/branch` or `newSession({ parentSession })`): ```json -{"type":"session","version":2,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","parentSession":"/path/to/original/session.jsonl"} +{ + "type": "session", + "version": 2, + "id": "uuid", + "timestamp": "2024-12-03T14:00:00.000Z", + "cwd": "/path/to/project", + "parentSession": "/path/to/original/session.jsonl" +} ``` ### SessionMessageEntry @@ -69,7 +76,14 @@ A message in the conversation. The `message` field contains an `AgentMessage`. Emitted when the user switches models mid-session. ```json -{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"} +{ + "type": "model_change", + "id": "d4e5f6g7", + "parentId": "c3d4e5f6", + "timestamp": "2024-12-03T14:05:00.000Z", + "provider": "openai", + "modelId": "gpt-4o" +} ``` ### ThinkingLevelChangeEntry @@ -77,7 +91,13 @@ Emitted when the user switches models mid-session. Emitted when the user changes the thinking/reasoning level. ```json -{"type":"thinking_level_change","id":"e5f6g7h8","parentId":"d4e5f6g7","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"} +{ + "type": "thinking_level_change", + "id": "e5f6g7h8", + "parentId": "d4e5f6g7", + "timestamp": "2024-12-03T14:06:00.000Z", + "thinkingLevel": "high" +} ``` ### CompactionEntry @@ -85,10 +105,19 @@ Emitted when the user changes the thinking/reasoning level. Created when context is compacted. Stores a summary of earlier messages. ```json -{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","firstKeptEntryId":"c3d4e5f6","tokensBefore":50000} +{ + "type": "compaction", + "id": "f6g7h8i9", + "parentId": "e5f6g7h8", + "timestamp": "2024-12-03T14:10:00.000Z", + "summary": "User discussed X, Y, Z...", + "firstKeptEntryId": "c3d4e5f6", + "tokensBefore": 50000 +} ``` Optional fields: + - `details`: Compaction-implementation specific data (e.g., file operations for default implementation, or custom data for custom hook implementations) - `fromHook`: `true` if generated by a hook, `false`/`undefined` if omp-generated @@ -97,10 +126,18 @@ Optional fields: Created when switching branches via `/tree` with an LLM generated summary of the left branch up to the common ancestor. Captures context from the abandoned path. ```json -{"type":"branch_summary","id":"g7h8i9j0","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:15:00.000Z","fromId":"f6g7h8i9","summary":"Branch explored approach A..."} +{ + "type": "branch_summary", + "id": "g7h8i9j0", + "parentId": "a1b2c3d4", + "timestamp": "2024-12-03T14:15:00.000Z", + "fromId": "f6g7h8i9", + "summary": "Branch explored approach A..." +} ``` Optional fields: + - `details`: File tracking data (`{ readFiles: string[], modifiedFiles: string[] }`) for default implementation, arbitrary for custom implementation - `fromHook`: `true` if generated by a hook @@ -109,7 +146,14 @@ Optional fields: Hook state persistence. Does NOT participate in LLM context. ```json -{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-hook","data":{"count":42}} +{ + "type": "custom", + "id": "h8i9j0k1", + "parentId": "g7h8i9j0", + "timestamp": "2024-12-03T14:20:00.000Z", + "customType": "my-hook", + "data": { "count": 42 } +} ``` Use `customType` to identify your hook's entries on reload. @@ -119,10 +163,19 @@ Use `customType` to identify your hook's entries on reload. Hook-injected messages that DO participate in LLM context. ```json -{"type":"custom_message","id":"i9j0k1l2","parentId":"h8i9j0k1","timestamp":"2024-12-03T14:25:00.000Z","customType":"my-hook","content":"Injected context...","display":true} +{ + "type": "custom_message", + "id": "i9j0k1l2", + "parentId": "h8i9j0k1", + "timestamp": "2024-12-03T14:25:00.000Z", + "customType": "my-hook", + "content": "Injected context...", + "display": true +} ``` Fields: + - `content`: String or `(TextContent | ImageContent)[]` (same as UserMessage) - `display`: `true` = show in TUI with purple styling, `false` = hidden - `details`: Optional hook-specific metadata (not sent to LLM) @@ -132,7 +185,14 @@ Fields: User-defined bookmark/marker on an entry. ```json -{"type":"label","id":"j0k1l2m3","parentId":"i9j0k1l2","timestamp":"2024-12-03T14:30:00.000Z","targetId":"a1b2c3d4","label":"checkpoint-1"} +{ + "type": "label", + "id": "j0k1l2m3", + "parentId": "i9j0k1l2", + "timestamp": "2024-12-03T14:30:00.000Z", + "targetId": "a1b2c3d4", + "label": "checkpoint-1" +} ``` Set `label` to `undefined` to clear a label. @@ -140,6 +200,7 @@ Set `label` to `undefined` to clear a label. ## Tree Structure Entries form a tree: + - First entry has `parentId: null` - Each subsequent entry points to its parent via `parentId` - Branching creates new children from an earlier entry @@ -166,42 +227,42 @@ Entries form a tree: ## Parsing Example ```typescript -import { readFileSync } from "fs"; +import * as fs from "node:fs"; -const lines = readFileSync("session.jsonl", "utf8").trim().split("\n"); +const lines = fs.readFileSync("session.jsonl", "utf8").trim().split("\n"); for (const line of lines) { - const entry = JSON.parse(line); + const entry = JSON.parse(line); - switch (entry.type) { - case "session": - console.log(`Session v${entry.version ?? 1}: ${entry.id}`); - break; - case "message": - console.log(`[${entry.id}] ${entry.message.role}: ${JSON.stringify(entry.message.content)}`); - break; - case "compaction": - console.log(`[${entry.id}] Compaction: ${entry.tokensBefore} tokens summarized`); - break; - case "branch_summary": - console.log(`[${entry.id}] Branch from ${entry.fromId}`); - break; - case "custom": - console.log(`[${entry.id}] Custom (${entry.customType}): ${JSON.stringify(entry.data)}`); - break; - case "custom_message": - console.log(`[${entry.id}] Hook message (${entry.customType}): ${entry.content}`); - break; - case "label": - console.log(`[${entry.id}] Label "${entry.label}" on ${entry.targetId}`); - break; - case "model_change": - console.log(`[${entry.id}] Model: ${entry.provider}/${entry.modelId}`); - break; - case "thinking_level_change": - console.log(`[${entry.id}] Thinking: ${entry.thinkingLevel}`); - break; - } + switch (entry.type) { + case "session": + console.log(`Session v${entry.version ?? 1}: ${entry.id}`); + break; + case "message": + console.log(`[${entry.id}] ${entry.message.role}: ${JSON.stringify(entry.message.content)}`); + break; + case "compaction": + console.log(`[${entry.id}] Compaction: ${entry.tokensBefore} tokens summarized`); + break; + case "branch_summary": + console.log(`[${entry.id}] Branch from ${entry.fromId}`); + break; + case "custom": + console.log(`[${entry.id}] Custom (${entry.customType}): ${JSON.stringify(entry.data)}`); + break; + case "custom_message": + console.log(`[${entry.id}] Hook message (${entry.customType}): ${entry.content}`); + break; + case "label": + console.log(`[${entry.id}] Label "${entry.label}" on ${entry.targetId}`); + break; + case "model_change": + console.log(`[${entry.id}] Model: ${entry.provider}/${entry.modelId}`); + break; + case "thinking_level_change": + console.log(`[${entry.id}] Thinking: ${entry.thinkingLevel}`); + break; + } } ``` @@ -210,12 +271,14 @@ for (const line of lines) { Key methods for working with sessions programmatically: ### Creation + - `SessionManager.create(cwd, sessionDir?)` - New session - `SessionManager.open(path, sessionDir?)` - Open existing - `SessionManager.continueRecent(cwd, sessionDir?)` - Continue most recent or create new - `SessionManager.inMemory(cwd?)` - No file persistence ### Appending (all return entry ID) + - `appendMessage(message)` - Add message - `appendThinkingLevelChange(level)` - Record thinking change - `appendModelChange(provider, modelId)` - Record model change @@ -225,6 +288,7 @@ Key methods for working with sessions programmatically: - `appendLabelChange(targetId, label)` - Set/clear label ### Tree Navigation + - `getLeafId()` - Current position - `getEntry(id)` - Get entry by ID - `getPath(fromId?)` - Walk from entry to root @@ -235,6 +299,7 @@ Key methods for working with sessions programmatically: - `branchWithSummary(entryId, summary, details?, fromHook?)` - Branch with context summary ### Context + - `buildSessionContext()` - Get messages for LLM - `getEntries()` - All entries (excluding header) - `getHeader()` - Session metadata diff --git a/packages/coding-agent/src/capability/index.ts b/packages/coding-agent/src/capability/index.ts index 85e233317..792ffd428 100644 --- a/packages/coding-agent/src/capability/index.ts +++ b/packages/coding-agent/src/capability/index.ts @@ -7,7 +7,7 @@ * - Loading items for a capability across all providers */ -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { clearCache as clearFsCache, cacheStats as fsCacheStats, invalidate as invalidateFs } from "./fs"; import type { @@ -214,7 +214,7 @@ export async function loadCapability(capabilityId: string, options: LoadOptio } const cwd = options.cwd ?? process.cwd(); - const home = homedir(); + const home = os.homedir(); const ctx: LoadContext = { cwd, home }; const providers = filterProviders(capability, options); diff --git a/packages/coding-agent/src/commit/git/index.ts b/packages/coding-agent/src/commit/git/index.ts index 31bae9b51..5e28ec4df 100644 --- a/packages/coding-agent/src/commit/git/index.ts +++ b/packages/coding-agent/src/commit/git/index.ts @@ -1,6 +1,6 @@ import { randomUUID } from "node:crypto"; import * as fs from "node:fs/promises"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { logger } from "@oh-my-pi/pi-utils"; import type { FileDiff, FileHunks, NumstatEntry } from "../../commit/types"; @@ -134,7 +134,7 @@ export class ControlledGit { const patch = joinPatch(patchParts); if (!patch.trim()) return; - const tempPath = path.join(tmpdir(), `omp-hunks-${randomUUID()}.patch`); + const tempPath = path.join(os.tmpdir(), `omp-hunks-${randomUUID()}.patch`); try { await Bun.write(tempPath, patch); const result = await runGitCommand(this.cwd, ["apply", "--cached", "--binary", tempPath]); diff --git a/packages/coding-agent/src/config.ts b/packages/coding-agent/src/config.ts index 5ee89e028..b14d34722 100644 --- a/packages/coding-agent/src/config.ts +++ b/packages/coding-agent/src/config.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs"; -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { isEnoent, logger } from "@oh-my-pi/pi-utils"; // Embed package.json at build time for config @@ -56,7 +56,7 @@ export function getChangelogPath(): string { /** Get the agent config directory (e.g., ~/.omp/agent/) */ export function getAgentDir(): string { - return process.env[ENV_AGENT_DIR] || path.join(homedir(), CONFIG_DIR_NAME, "agent"); + return process.env[ENV_AGENT_DIR] || path.join(os.homedir(), CONFIG_DIR_NAME, "agent"); } /** Get path to user's custom themes directory */ @@ -128,7 +128,7 @@ export function getDebugLogPath(): string { * Project-level: .omp, .pi, .claude, .codex, .gemini */ const USER_CONFIG_BASES = priorityList.map(({ dir, globalAgentDir }) => ({ - base: () => path.join(homedir(), globalAgentDir ?? dir), + base: () => path.join(os.homedir(), globalAgentDir ?? dir), name: dir, })); diff --git a/packages/coding-agent/src/discovery/builtin.ts b/packages/coding-agent/src/discovery/builtin.ts index 825c1e432..2d054e8c7 100644 --- a/packages/coding-agent/src/discovery/builtin.ts +++ b/packages/coding-agent/src/discovery/builtin.ts @@ -5,7 +5,7 @@ * .pi is an alias for backwards compatibility. */ -import { dirname, isAbsolute, join, resolve } from "path"; +import * as path from "node:path"; import { registerProvider } from "../capability"; import { type ContextFile, contextFileCapability } from "../capability/context-file"; import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension"; @@ -47,7 +47,7 @@ async function getConfigDirs(ctx: LoadContext): Promise = []; for (const name of PROJECT_DIRS) { - const projectDir = join(ctx.cwd, name); + const projectDir = path.join(ctx.cwd, name); const entries = await readDirEntries(projectDir); if (entries.length > 0) { result.push({ dir: projectDir, level: "project" }); @@ -56,7 +56,7 @@ async function getConfigDirs(ctx: LoadContext): Promise 0) { result.push({ dir: userDir, level: "user" }); @@ -96,16 +96,16 @@ async function loadMCPServers(ctx: LoadContext): Promise> const projectDirs = await Promise.all( PROJECT_DIRS.map(async (name) => { - const dir = join(ctx.cwd, name); + const dir = path.join(ctx.cwd, name); const entries = await readDirEntries(dir); return entries.length > 0 ? dir : null; }), ); - const userPaths = USER_DIRS.map((name) => join(ctx.home, name, "mcp.json")); + const userPaths = USER_DIRS.map((name) => path.join(ctx.home, name, "mcp.json")); const projectDir = projectDirs.find((dir) => dir !== null); if (projectDir) { - const projectCandidates = ["mcp.json", ".mcp.json"].map((filename) => join(projectDir, filename)); + const projectCandidates = ["mcp.json", ".mcp.json"].map((filename) => path.join(projectDir, filename)); const projectContents = await Promise.all(projectCandidates.map((path) => readFile(path))); for (let i = 0; i < projectCandidates.length; i++) { const content = projectContents[i]; @@ -141,7 +141,7 @@ async function loadSystemPrompt(ctx: LoadContext): Promise - join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""), "SYSTEM.md"), + path.join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""), "SYSTEM.md"), ); const userContents = await Promise.all(userPaths.map((p) => readFile(p))); for (let i = 0; i < userPaths.length; i++) { @@ -161,18 +161,18 @@ async function loadSystemPrompt(ctx: LoadContext): Promise join(dir, name)); + const configDirs = PROJECT_DIRS.map((name) => path.join(dir, name)); const entriesResults = await Promise.all(configDirs.map((d) => readDirEntries(d))); const validConfigDir = configDirs.find((_, i) => entriesResults[i].length > 0); if (!validConfigDir) continue; - const projectPath = join(validConfigDir, "SYSTEM.md"); + const projectPath = path.join(validConfigDir, "SYSTEM.md"); const content = await readFile(projectPath); if (content) { items.push({ @@ -202,7 +202,7 @@ async function loadSkills(ctx: LoadContext): Promise> { const results = await Promise.all( configDirs.map(({ dir, level }) => loadSkillsFromDir(ctx, { - dir: join(dir, "skills"), + dir: path.join(dir, "skills"), providerId: PROVIDER_ID, level, requireDescription: true, @@ -230,7 +230,7 @@ async function loadSlashCommands(ctx: LoadContext): Promise(ctx, commandsDir, PROVIDER_ID, level, { extensions: ["md"], transform: (name, content, path, source) => ({ @@ -262,7 +262,7 @@ async function loadRules(ctx: LoadContext): Promise> { const warnings: string[] = []; for (const { dir, level } of await getConfigDirs(ctx)) { - const rulesDir = join(dir, "rules"); + const rulesDir = path.join(dir, "rules"); const result = await loadFilesFromDir(ctx, rulesDir, PROVIDER_ID, level, { extensions: ["md", "mdc"], transform: (name, content, path, source) => { @@ -300,7 +300,7 @@ async function loadPrompts(ctx: LoadContext): Promise> { const warnings: string[] = []; for (const { dir, level } of await getConfigDirs(ctx)) { - const promptsDir = join(dir, "prompts"); + const promptsDir = path.join(dir, "prompts"); const result = await loadFilesFromDir(ctx, promptsDir, PROVIDER_ID, level, { extensions: ["md"], transform: (name, content, path, source) => ({ @@ -332,15 +332,15 @@ async function loadExtensionModules(ctx: LoadContext): Promise { if (rawPath.startsWith("~/")) { - return join(ctx.home, rawPath.slice(2)); + return path.join(ctx.home, rawPath.slice(2)); } if (rawPath.startsWith("~")) { - return join(ctx.home, rawPath.slice(1)); + return path.join(ctx.home, rawPath.slice(1)); } - if (isAbsolute(rawPath)) { + if (path.isAbsolute(rawPath)) { return rawPath; } - return resolve(ctx.cwd, rawPath); + return path.resolve(ctx.cwd, rawPath); }; const createExtensionModule = (extPath: string, level: "user" | "project"): ExtensionModule => ({ @@ -353,8 +353,8 @@ async function loadExtensionModules(ctx: LoadContext): Promise discoverExtensionModulePaths(ctx, join(dir, "extensions")))), - Promise.all(configDirs.map(({ dir }) => readFile(join(dir, "settings.json")))), + Promise.all(configDirs.map(({ dir }) => discoverExtensionModulePaths(ctx, path.join(dir, "extensions")))), + Promise.all(configDirs.map(({ dir }) => readFile(path.join(dir, "settings.json")))), ]); for (let i = 0; i < configDirs.length; i++) { @@ -375,7 +375,7 @@ async function loadExtensionModules(ctx: LoadContext): Promise(settingsContent); const extensions = settingsData?.extensions; if (!Array.isArray(extensions)) continue; @@ -445,7 +445,7 @@ async function loadExtensions(ctx: LoadContext): Promise> const warnings: string[] = []; const configDirs = await getConfigDirs(ctx); - const entriesResults = await Promise.all(configDirs.map(({ dir }) => readDirEntries(join(dir, "extensions")))); + const entriesResults = await Promise.all(configDirs.map(({ dir }) => readDirEntries(path.join(dir, "extensions")))); const manifestCandidates: Array<{ extDir: string; @@ -457,16 +457,16 @@ async function loadExtensions(ctx: LoadContext): Promise> for (let i = 0; i < configDirs.length; i++) { const { dir, level } = configDirs[i]; const entries = entriesResults[i]; - const extensionsDir = join(dir, "extensions"); + const extensionsDir = path.join(dir, "extensions"); for (const entry of entries) { if (entry.name.startsWith(".")) continue; if (!entry.isDirectory()) continue; - const extDir = join(extensionsDir, entry.name); + const extDir = path.join(extensionsDir, entry.name); manifestCandidates.push({ extDir, - manifestPath: join(extDir, "gemini-extension.json"), + manifestPath: path.join(extDir, "gemini-extension.json"), entryName: entry.name, level, }); @@ -512,7 +512,7 @@ async function loadInstructions(ctx: LoadContext): Promise(ctx, instructionsDir, PROVIDER_ID, level, { extensions: ["md"], transform: (name, content, path, source) => { @@ -557,7 +557,7 @@ async function loadHooks(ctx: LoadContext): Promise> { for (const { dir, level } of configDirs) { for (const hookType of hookTypes) { typeDirRequests.push({ - typeDir: join(dir, "hooks", hookType), + typeDir: path.join(dir, "hooks", hookType), hookType, level, }); @@ -574,17 +574,17 @@ async function loadHooks(ctx: LoadContext): Promise> { if (entry.name.startsWith(".")) continue; if (!entry.isFile()) continue; - const path = join(typeDir, entry.name); + const hookPath = path.join(typeDir, entry.name); const baseName = entry.name.includes(".") ? entry.name.slice(0, entry.name.lastIndexOf(".")) : entry.name; const tool = baseName === "*" ? "*" : baseName; items.push({ name: entry.name, - path, + path: hookPath, type: hookType, tool, level, - _source: createSourceMeta(PROVIDER_ID, path, level), + _source: createSourceMeta(PROVIDER_ID, hookPath, level), }); } } @@ -606,7 +606,7 @@ async function loadTools(ctx: LoadContext): Promise> { const warnings: string[] = []; const configDirs = await getConfigDirs(ctx); - const entriesResults = await Promise.all(configDirs.map(({ dir }) => readDirEntries(join(dir, "tools")))); + const entriesResults = await Promise.all(configDirs.map(({ dir }) => readDirEntries(path.join(dir, "tools")))); const fileLoadPromises: Array> = []; const subDirCandidates: Array<{ @@ -620,7 +620,7 @@ async function loadTools(ctx: LoadContext): Promise> { const toolEntries = entriesResults[i]; if (toolEntries.length === 0) continue; - const toolsDir = join(dir, "tools"); + const toolsDir = path.join(dir, "tools"); fileLoadPromises.push( loadFilesFromDir(ctx, toolsDir, PROVIDER_ID, level, { @@ -653,7 +653,7 @@ async function loadTools(ctx: LoadContext): Promise> { if (!entry.isDirectory()) continue; subDirCandidates.push({ - indexPath: join(toolsDir, entry.name, "index.ts"), + indexPath: path.join(toolsDir, entry.name, "index.ts"), entryName: entry.name, level, }); @@ -701,7 +701,7 @@ async function loadSettings(ctx: LoadContext): Promise> { const warnings: string[] = []; for (const { dir, level } of await getConfigDirs(ctx)) { - const settingsPath = join(dir, "settings.json"); + const settingsPath = path.join(dir, "settings.json"); const content = await readFile(settingsPath); if (!content) continue; @@ -736,7 +736,7 @@ async function loadContextFiles(ctx: LoadContext): Promise - join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""), "AGENTS.md"), + path.join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""), "AGENTS.md"), ); const userContents = await Promise.all(userPaths.map((p) => readFile(p))); for (let i = 0; i < userPaths.length; i++) { @@ -757,19 +757,19 @@ async function loadContextFiles(ctx: LoadContext): Promise join(dir, name)); + const configDirs = PROJECT_DIRS.map((name) => path.join(dir, name)); const entriesResults = await Promise.all(configDirs.map((d) => readDirEntries(d))); const validConfigDir = configDirs.find((_, i) => entriesResults[i].length > 0); if (!validConfigDir) continue; - const projectPath = join(validConfigDir, "AGENTS.md"); + const projectPath = path.join(validConfigDir, "AGENTS.md"); const content = await readFile(projectPath); if (content) { items.push({ diff --git a/packages/coding-agent/src/discovery/helpers.ts b/packages/coding-agent/src/discovery/helpers.ts index ca1afdd34..0315e9f32 100644 --- a/packages/coding-agent/src/discovery/helpers.ts +++ b/packages/coding-agent/src/discovery/helpers.ts @@ -2,7 +2,7 @@ * Shared helpers for discovery providers. */ -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { readDirEntries, readFile } from "../capability/fs"; @@ -26,10 +26,10 @@ export function normalizeUnicodeSpaces(str: string): string { export function expandPath(p: string): string { const normalized = normalizeUnicodeSpaces(p); if (normalized.startsWith("~/")) { - return path.join(homedir(), normalized.slice(2)); + return path.join(os.homedir(), normalized.slice(2)); } if (normalized.startsWith("~")) { - return path.join(homedir(), normalized.slice(1)); + return path.join(os.homedir(), normalized.slice(1)); } return normalized; } diff --git a/packages/coding-agent/src/exa/mcp-client.ts b/packages/coding-agent/src/exa/mcp-client.ts index 671f033aa..6ddcfed09 100644 --- a/packages/coding-agent/src/exa/mcp-client.ts +++ b/packages/coding-agent/src/exa/mcp-client.ts @@ -4,7 +4,7 @@ * Client for interacting with Exa MCP servers. */ -import { homedir } from "node:os"; +import * as os from "node:os"; import { isEnoent, logger } from "@oh-my-pi/pi-utils"; import type { TSchema } from "@sinclair/typebox"; @@ -29,7 +29,7 @@ export async function findApiKey(): Promise { // Try loading from .env files in cwd and home const cwd = process.cwd(); - const home = homedir(); + const home = os.homedir(); for (const dir of [cwd, home]) { const envPath = `${dir}/.env`; diff --git a/packages/coding-agent/src/extensibility/extensions/loader.ts b/packages/coding-agent/src/extensibility/extensions/loader.ts index 658053772..e1164f578 100644 --- a/packages/coding-agent/src/extensibility/extensions/loader.ts +++ b/packages/coding-agent/src/extensibility/extensions/loader.ts @@ -9,7 +9,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import type { ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai"; import * as piCodingAgent from "@oh-my-pi/pi-coding-agent"; import type { KeyId } from "@oh-my-pi/pi-tui"; -import { isEnoent, logger } from "@oh-my-pi/pi-utils"; +import { hasFsCode, isEacces, isEnoent, logger } from "@oh-my-pi/pi-utils"; import type { TSchema } from "@sinclair/typebox"; import * as TypeBox from "@sinclair/typebox"; import { type ExtensionModule, extensionModuleCapability } from "../../capability/extension-module"; @@ -326,6 +326,9 @@ async function readExtensionManifest(packageJsonPath: string): Promise { await fs.stat(resolvedExtPath); entries.push(resolvedExtPath); } catch (err) { - if (!isEnoent(err)) throw err; + if (isEnoent(err) || isEacces(err) || hasFsCode(err, "EPERM")) continue; + throw err; } } if (entries.length > 0) { @@ -363,13 +367,21 @@ async function resolveExtensionEntries(dir: string): Promise { await fs.stat(indexTs); return [indexTs]; } catch (err) { - if (!isEnoent(err)) throw err; + if (isEnoent(err) || isEacces(err) || hasFsCode(err, "EPERM")) { + // Ignore + } else { + throw err; + } } try { await fs.stat(indexJs); return [indexJs]; } catch (err) { - if (!isEnoent(err)) throw err; + if (isEnoent(err) || isEacces(err) || hasFsCode(err, "EPERM")) { + // Ignore + } else { + throw err; + } } return null; diff --git a/packages/coding-agent/src/extensibility/plugins/manager.ts b/packages/coding-agent/src/extensibility/plugins/manager.ts index 994b41419..22d26c6ef 100644 --- a/packages/coding-agent/src/extensibility/plugins/manager.ts +++ b/packages/coding-agent/src/extensibility/plugins/manager.ts @@ -1,6 +1,6 @@ import * as fs from "node:fs"; import * as path from "node:path"; -import { isEnoent } from "@oh-my-pi/pi-utils"; +import { isEnoent, logger } from "@oh-my-pi/pi-utils"; import { extractPackageName, parsePluginSpec } from "./parser"; import { getPluginsDir, @@ -64,7 +64,8 @@ export class PluginManager { return await Bun.file(lockPath).json(); } catch (err) { if (isEnoent(err)) return { plugins: {}, settings: {} }; - throw err; + logger.warn("Failed to load plugin runtime config", { path: lockPath, error: String(err) }); + return { plugins: {}, settings: {} }; } } @@ -77,7 +78,6 @@ export class PluginManager { private async saveRuntimeConfig(): Promise { await this.ensureConfigLoaded(); - await this.ensurePluginsDir(); await Bun.write(getPluginsLockfile(), JSON.stringify(this.runtimeConfig, null, 2)); } @@ -87,7 +87,8 @@ export class PluginManager { return await Bun.file(overridesPath).json(); } catch (err) { if (isEnoent(err)) return {}; - throw err; + logger.warn("Failed to load project plugin overrides", { path: overridesPath, error: String(err) }); + return {}; } } @@ -101,7 +102,6 @@ export class PluginManager { } private async ensurePackageJson(): Promise { - await this.ensurePluginsDir(); const pkgJsonPath = getPluginsPackageJson(); try { await Bun.file(pkgJsonPath).json(); diff --git a/packages/coding-agent/src/extensibility/plugins/paths.ts b/packages/coding-agent/src/extensibility/plugins/paths.ts index 075418637..952ca7c07 100644 --- a/packages/coding-agent/src/extensibility/plugins/paths.ts +++ b/packages/coding-agent/src/extensibility/plugins/paths.ts @@ -1,4 +1,4 @@ -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { CONFIG_DIR_NAME, getConfigDirPaths } from "../../config"; @@ -8,7 +8,7 @@ import { CONFIG_DIR_NAME, getConfigDirPaths } from "../../config"; /** Root plugin directory: ~/.omp/plugins (not under agent/) */ export function getPluginsDir(): string { - return path.join(homedir(), CONFIG_DIR_NAME, "plugins"); + return path.join(os.homedir(), CONFIG_DIR_NAME, "plugins"); } /** Where npm installs packages: ~/.omp/plugins/node_modules */ diff --git a/packages/coding-agent/src/extensibility/skills.ts b/packages/coding-agent/src/extensibility/skills.ts index ce41b08ff..bb931c101 100644 --- a/packages/coding-agent/src/extensibility/skills.ts +++ b/packages/coding-agent/src/extensibility/skills.ts @@ -41,7 +41,6 @@ export interface LoadSkillsFromDirOptions { /** * Load skills from a directory recursively. * Skills are directories containing a SKILL.md file with frontmatter including a description. - * @deprecated Use loadSync("skills") from discovery API instead */ export async function loadSkillsFromDir(options: LoadSkillsFromDirOptions): Promise { const skills: Skill[] = []; diff --git a/packages/coding-agent/src/ipy/modules.ts b/packages/coding-agent/src/ipy/modules.ts index 9887ae88b..1da9d1186 100644 --- a/packages/coding-agent/src/ipy/modules.ts +++ b/packages/coding-agent/src/ipy/modules.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs/promises"; -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; export type PythonModuleSource = "user" | "project"; @@ -66,7 +66,7 @@ async function readModuleContent(candidate: ModuleCandidate): Promise { const cwd = options.cwd ?? process.cwd(); - const homeDir = options.homeDir ?? homedir(); + const homeDir = options.homeDir ?? os.homedir(); const userDirs = [path.join(homeDir, ".omp", "agent", "modules"), path.join(homeDir, ".pi", "agent", "modules")]; const projectDirs = [path.resolve(cwd, ".omp", "modules"), path.resolve(cwd, ".pi", "modules")]; diff --git a/packages/coding-agent/src/lsp/config.ts b/packages/coding-agent/src/lsp/config.ts index c31cef315..3aafdd94e 100644 --- a/packages/coding-agent/src/lsp/config.ts +++ b/packages/coding-agent/src/lsp/config.ts @@ -1,4 +1,4 @@ -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { logger } from "@oh-my-pi/pi-utils"; import { YAML } from "bun"; @@ -253,7 +253,7 @@ function getConfigPaths(cwd: string): string[] { // User home root files (lowest priority fallback) for (const filename of filenames) { - paths.push(path.join(homedir(), filename)); + paths.push(path.join(os.homedir(), filename)); } return paths; diff --git a/packages/coding-agent/src/lsp/edits.ts b/packages/coding-agent/src/lsp/edits.ts index b07c15649..c92cd24ab 100644 --- a/packages/coding-agent/src/lsp/edits.ts +++ b/packages/coding-agent/src/lsp/edits.ts @@ -86,7 +86,6 @@ export async function applyWorkspaceEdit(edit: WorkspaceEdit, cwd: string): Prom if (change.kind === "create") { const createOp = change as CreateFile; const filePath = uriToFile(createOp.uri); - await fs.mkdir(path.dirname(filePath), { recursive: true }); await Bun.write(filePath, ""); applied.push(`Created ${path.relative(cwd, filePath)}`); } else if (change.kind === "rename") { diff --git a/packages/coding-agent/src/lsp/index.ts b/packages/coding-agent/src/lsp/index.ts index cbb902348..08ce47147 100644 --- a/packages/coding-agent/src/lsp/index.ts +++ b/packages/coding-agent/src/lsp/index.ts @@ -1,8 +1,8 @@ +import * as fs from "node:fs"; +import path from "node:path"; import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; import { logger, once, untilAborted } from "@oh-my-pi/pi-utils"; import type { BunFile } from "bun"; -import * as fs from "node:fs"; -import path from "node:path"; import { renderPromptTemplate } from "../config/prompt-templates"; import { type Theme, theme } from "../modes/theme/theme"; import lspDescription from "../prompts/tools/lsp.md" with { type: "text" }; @@ -22,7 +22,7 @@ import { WARMUP_TIMEOUT_MS, } from "./client"; import { getLinterClient } from "./clients"; -import { getServersForFile, hasCapability, loadConfig, type LspConfig } from "./config"; +import { getServersForFile, hasCapability, type LspConfig, loadConfig } from "./config"; import { applyTextEditsToString, applyWorkspaceEdit } from "./edits"; import { detectLspmux } from "./lspmux"; import { renderCall, renderResult } from "./render"; @@ -40,8 +40,8 @@ import { type LocationLink, type LspClient, type LspParams, - lspSchema, type LspToolDetails, + lspSchema, type ServerConfig, type SymbolInformation, type TextEdit, diff --git a/packages/coding-agent/src/lsp/lspmux.ts b/packages/coding-agent/src/lsp/lspmux.ts index ca52fe5e7..336c1f342 100644 --- a/packages/coding-agent/src/lsp/lspmux.ts +++ b/packages/coding-agent/src/lsp/lspmux.ts @@ -1,4 +1,4 @@ -import { homedir, platform } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { logger } from "@oh-my-pi/pi-utils"; import { TOML } from "bun"; @@ -63,8 +63,8 @@ const STATE_CACHE_TTL_MS = 5 * 60 * 1000; * Matches Rust's `dirs::config_dir()` behavior. */ function getConfigPath(): string { - const home = homedir(); - switch (platform()) { + const home = os.homedir(); + switch (os.platform()) { case "win32": return path.join(process.env.APPDATA ?? path.join(home, "AppData", "Roaming"), "lspmux", "config.toml"); case "darwin": diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index f6cc9570e..94a88a10f 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -6,7 +6,7 @@ */ import * as fs from "node:fs/promises"; -import { homedir, tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { createInterface } from "node:readline/promises"; import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai"; @@ -274,7 +274,7 @@ async function maybeAutoChdir(parsed: Args): Promise { return; } - const home = homedir(); + const home = os.homedir(); if (!home) { return; } @@ -313,7 +313,7 @@ async function maybeAutoChdir(parsed: Args): Promise { } try { - const fallback = tmpdir(); + const fallback = os.tmpdir(); if (fallback && normalizePath(fallback) !== cwd && (await isDirectory(fallback))) { process.chdir(fallback); } diff --git a/packages/coding-agent/src/modes/components/extensions/inspector-panel.ts b/packages/coding-agent/src/modes/components/extensions/inspector-panel.ts index f320fb8bc..919b3c48e 100644 --- a/packages/coding-agent/src/modes/components/extensions/inspector-panel.ts +++ b/packages/coding-agent/src/modes/components/extensions/inspector-panel.ts @@ -4,7 +4,7 @@ * Shows name, description, origin, status, and kind-specific preview. */ -import { homedir } from "node:os"; +import * as os from "node:os"; import { type Component, truncateToWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui"; import { theme } from "../../../modes/theme/theme"; import type { Extension, ExtensionState } from "./types"; @@ -304,7 +304,7 @@ export class InspectorPanel implements Component { } private shortenPath(path: string): string { - const home = homedir(); + const home = os.homedir(); if (home && path.startsWith(home)) { return `~${path.slice(home.length)}`; } diff --git a/packages/coding-agent/src/modes/components/footer.ts b/packages/coding-agent/src/modes/components/footer.ts index d8ca60851..061a98da8 100644 --- a/packages/coding-agent/src/modes/components/footer.ts +++ b/packages/coding-agent/src/modes/components/footer.ts @@ -1,8 +1,8 @@ import * as fs from "node:fs"; +import * as path from "node:path"; import type { AssistantMessage } from "@oh-my-pi/pi-ai"; import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui"; import { isEnoent } from "@oh-my-pi/pi-utils"; -import { dirname, join } from "path"; import { theme } from "../../modes/theme/theme"; import type { AgentSession } from "../../session/agent-session"; @@ -24,14 +24,14 @@ function sanitizeStatusText(text: string): string { async function findGitHeadPath(): Promise<{ path: string; content: string } | null> { let dir = process.cwd(); while (true) { - const gitHeadPath = join(dir, ".git", "HEAD"); + const gitHeadPath = path.join(dir, ".git", "HEAD"); try { const content = await Bun.file(gitHeadPath).text(); return { path: gitHeadPath, content }; } catch (err) { if (!isEnoent(err)) throw err; } - const parent = dirname(dir); + const parent = path.dirname(dir); if (parent === dir) { return null; } diff --git a/packages/coding-agent/src/modes/components/status-line.ts b/packages/coding-agent/src/modes/components/status-line.ts index 0ea3204a0..401eafd63 100644 --- a/packages/coding-agent/src/modes/components/status-line.ts +++ b/packages/coding-agent/src/modes/components/status-line.ts @@ -1,8 +1,8 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; import type { AssistantMessage } from "@oh-my-pi/pi-ai"; import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui"; import { $ } from "bun"; -import { type FSWatcher, watch } from "fs"; -import { dirname, join } from "path"; import type { StatusLineSegmentOptions, StatusLineSettings } from "../../config/settings-manager"; import { theme } from "../../modes/theme/theme"; import type { AgentSession } from "../../session/agent-session"; @@ -26,11 +26,11 @@ function sanitizeStatusText(text: string): string { async function findGitHeadPath(): Promise { let dir = process.cwd(); while (true) { - const gitHeadPath = join(dir, ".git", "HEAD"); + const gitHeadPath = path.join(dir, ".git", "HEAD"); if (await Bun.file(gitHeadPath).exists()) { return gitHeadPath; } - const parent = dirname(dir); + const parent = path.dirname(dir); if (parent === dir) { return null; } @@ -46,7 +46,7 @@ export class StatusLineComponent implements Component { private session: AgentSession; private settings: StatusLineSettings = {}; private cachedBranch: string | null | undefined = undefined; - private gitWatcher: FSWatcher | null = null; + private gitWatcher: fs.FSWatcher | null = null; private onBranchChange: (() => void) | null = null; private autoCompactEnabled: boolean = true; private hookStatuses: Map = new Map(); @@ -102,7 +102,7 @@ export class StatusLineComponent implements Component { if (!gitHeadPath) return; try { - this.gitWatcher = watch(gitHeadPath, () => { + this.gitWatcher = fs.watch(gitHeadPath, () => { this.cachedBranch = undefined; if (this.onBranchChange) { this.onBranchChange(); diff --git a/packages/coding-agent/src/modes/components/status-line/segments.ts b/packages/coding-agent/src/modes/components/status-line/segments.ts index 58db8afce..36d165aa3 100644 --- a/packages/coding-agent/src/modes/components/status-line/segments.ts +++ b/packages/coding-agent/src/modes/components/status-line/segments.ts @@ -1,4 +1,4 @@ -import { hostname as osHostname } from "node:os"; +import * as os from "node:os"; import { theme } from "../../../modes/theme/theme"; import { shortenPath } from "../../../tools/render-utils"; import type { RenderedSegment, SegmentContext, StatusLineSegment, StatusLineSegmentId } from "./types"; @@ -286,7 +286,7 @@ const sessionSegment: StatusLineSegment = { const hostnameSegment: StatusLineSegment = { id: "hostname", render(_ctx) { - const name = osHostname().split(".")[0]; + const name = os.hostname().split(".")[0]; return { content: withIcon(theme.icon.host, name), visible: true }; }, }; diff --git a/packages/coding-agent/src/modes/controllers/command-controller.ts b/packages/coding-agent/src/modes/controllers/command-controller.ts index ad5e8ea43..5360c5215 100644 --- a/packages/coding-agent/src/modes/controllers/command-controller.ts +++ b/packages/coding-agent/src/modes/controllers/command-controller.ts @@ -439,7 +439,6 @@ export class CommandController { ].join("\n"); try { - await fs.mkdir(path.dirname(debugLogPath), { recursive: true }); await Bun.write(debugLogPath, debugData); } catch (error) { this.ctx.showError(`Failed to write debug log: ${error instanceof Error ? error.message : String(error)}`); diff --git a/packages/coding-agent/src/ssh/connection-manager.ts b/packages/coding-agent/src/ssh/connection-manager.ts index 52ff5ba24..5d34da04c 100644 --- a/packages/coding-agent/src/ssh/connection-manager.ts +++ b/packages/coding-agent/src/ssh/connection-manager.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs/promises"; -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { isEnoent, logger } from "@oh-my-pi/pi-utils"; import { $ } from "bun"; @@ -26,9 +26,9 @@ export interface SSHHostInfo { compatEnabled: boolean; } -const CONTROL_DIR = path.join(homedir(), CONFIG_DIR_NAME, "ssh-control"); +const CONTROL_DIR = path.join(os.homedir(), CONFIG_DIR_NAME, "ssh-control"); const CONTROL_PATH = path.join(CONTROL_DIR, "%h.sock"); -const HOST_INFO_DIR = path.join(homedir(), CONFIG_DIR_NAME, "remote-host"); +const HOST_INFO_DIR = path.join(os.homedir(), CONFIG_DIR_NAME, "remote-host"); const HOST_INFO_VERSION = 2; const activeHosts = new Map(); @@ -44,15 +44,6 @@ async function ensureControlDir(): Promise { } } -async function ensureHostInfoDir(): Promise { - await fs.mkdir(HOST_INFO_DIR, { recursive: true, mode: 0o700 }); - try { - await fs.chmod(HOST_INFO_DIR, 0o700); - } catch (err) { - logger.debug("SSH host info dir chmod failed", { path: HOST_INFO_DIR, error: String(err) }); - } -} - function sanitizeHostName(name: string): string { const sanitized = name.replace(/[^a-zA-Z0-9._-]+/g, "_"); return sanitized.length > 0 ? sanitized : "host"; @@ -242,7 +233,6 @@ async function loadHostInfoFromDiskByName(hostName: string): Promise { try { - await ensureHostInfoDir(); const path = getHostInfoPath(host.name); const payload = { ...info, version: HOST_INFO_VERSION }; hostInfoCache.set(host.name, payload); diff --git a/packages/coding-agent/src/ssh/sshfs-mount.ts b/packages/coding-agent/src/ssh/sshfs-mount.ts index ba1b5917e..d01899a57 100644 --- a/packages/coding-agent/src/ssh/sshfs-mount.ts +++ b/packages/coding-agent/src/ssh/sshfs-mount.ts @@ -1,11 +1,11 @@ import * as fs from "node:fs"; -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { $ } from "bun"; import { CONFIG_DIR_NAME } from "../config"; import { getControlDir, getControlPathTemplate, type SSHConnectionTarget } from "./connection-manager"; -const REMOTE_DIR = path.join(homedir(), CONFIG_DIR_NAME, "remote"); +const REMOTE_DIR = path.join(os.homedir(), CONFIG_DIR_NAME, "remote"); const CONTROL_DIR = getControlDir(); const CONTROL_PATH = getControlPathTemplate(); diff --git a/packages/coding-agent/src/system-prompt.ts b/packages/coding-agent/src/system-prompt.ts index 0fba43dfe..4288b5391 100644 --- a/packages/coding-agent/src/system-prompt.ts +++ b/packages/coding-agent/src/system-prompt.ts @@ -2,7 +2,7 @@ * System prompt construction and project context loading */ -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { $ } from "bun"; import chalk from "chalk"; @@ -429,7 +429,7 @@ interface SystemInfoCache { } function getSystemInfoCachePath(): string { - return path.join(homedir(), ".omp", "system_info.json"); + return path.join(os.homedir(), ".omp", "system_info.json"); } async function loadSystemInfoCache(): Promise { diff --git a/packages/coding-agent/src/task/index.ts b/packages/coding-agent/src/task/index.ts index 9512a3dc9..23bb7ac82 100644 --- a/packages/coding-agent/src/task/index.ts +++ b/packages/coding-agent/src/task/index.ts @@ -14,7 +14,7 @@ */ import * as fs from "node:fs/promises"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import path from "node:path"; import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; import type { Usage } from "@oh-my-pi/pi-ai"; @@ -308,9 +308,8 @@ export class TaskTool implements AgentTool(); @@ -587,7 +586,7 @@ export class TaskTool implements AgentTool { export async function ensureWorktree(baseCwd: string, id: string): Promise { const repoRoot = await getRepoRoot(baseCwd); const encodedProject = getEncodedProjectName(repoRoot); - const worktreeDir = path.join(homedir(), ".omp", "wt", encodedProject, id); + const worktreeDir = path.join(os.homedir(), ".omp", "wt", encodedProject, id); await fs.mkdir(path.dirname(worktreeDir), { recursive: true }); await $`git worktree remove -f ${worktreeDir}`.cwd(repoRoot).quiet().nothrow(); await fs.rm(worktreeDir, { recursive: true, force: true }); @@ -56,7 +56,7 @@ export async function captureBaseline(repoRoot: string): Promise { - const tempPath = path.join(tmpdir(), `omp-task-patch-${randomUUID()}.patch`); + const tempPath = path.join(os.tmpdir(), `omp-task-patch-${randomUUID()}.patch`); await Bun.write(tempPath, patch); return tempPath; } @@ -119,7 +119,7 @@ async function listUntracked(cwd: string): Promise { } export async function captureDeltaPatch(worktreeDir: string, baseline: WorktreeBaseline): Promise { - const tempIndex = path.join(tmpdir(), `omp-task-index-${randomUUID()}`); + const tempIndex = path.join(os.tmpdir(), `omp-task-index-${randomUUID()}`); try { await $`git read-tree HEAD`.cwd(worktreeDir).env({ GIT_INDEX_FILE: tempIndex, diff --git a/packages/coding-agent/src/tools/fetch.ts b/packages/coding-agent/src/tools/fetch.ts index a3decc502..c19b8a6c6 100644 --- a/packages/coding-agent/src/tools/fetch.ts +++ b/packages/coding-agent/src/tools/fetch.ts @@ -450,7 +450,6 @@ async function renderHtmlToText( timeout: number, scratchDir: string, ): Promise<{ content: string; ok: boolean; method: string }> { - await fs.mkdir(scratchDir, { recursive: true }); const tmpFile = path.join(scratchDir, `omp-${nanoid()}.html`); try { diff --git a/packages/coding-agent/src/tools/gemini-image.ts b/packages/coding-agent/src/tools/gemini-image.ts index e71ab837b..ed26c2fbf 100644 --- a/packages/coding-agent/src/tools/gemini-image.ts +++ b/packages/coding-agent/src/tools/gemini-image.ts @@ -1,4 +1,4 @@ -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { StringEnum } from "@oh-my-pi/pi-ai"; import { untilAborted } from "@oh-my-pi/pi-utils"; @@ -489,7 +489,7 @@ function getExtensionForMime(mimeType: string): string { async function saveImageToTemp(image: InlineImageData): Promise { const ext = getExtensionForMime(image.mimeType); const filename = `omp-image-${nanoid()}.${ext}`; - const filepath = path.join(tmpdir(), filename); + const filepath = path.join(os.tmpdir(), filename); await Bun.write(filepath, Buffer.from(image.data, "base64")); return filepath; } diff --git a/packages/coding-agent/src/tools/index.ts b/packages/coding-agent/src/tools/index.ts index 5ed47114f..77837003f 100644 --- a/packages/coding-agent/src/tools/index.ts +++ b/packages/coding-agent/src/tools/index.ts @@ -294,7 +294,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P ); time("createTools:afterFactories"); if (slowTools.length > 0 && process.env.OMP_TIMING === "1") { - console.error(` [slow tools: ${slowTools.map((t) => `${t.name}=${t.ms}ms`).join(", ")}]`); + logger.debug("Tool factory timings", { slowTools }); } const tools = results.filter((r) => r.tool !== null).map((r) => r.tool as Tool); const wrappedTools = wrapToolsWithMetaNotice(tools); diff --git a/packages/coding-agent/src/tools/read.ts b/packages/coding-agent/src/tools/read.ts index a84c428e9..4677f23d5 100644 --- a/packages/coding-agent/src/tools/read.ts +++ b/packages/coding-agent/src/tools/read.ts @@ -1,4 +1,4 @@ -import { homedir } from "node:os"; +import * as os from "node:os"; import path from "node:path"; import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai"; @@ -37,7 +37,7 @@ import { const CONVERTIBLE_EXTENSIONS = new Set([".pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".rtf", ".epub"]); // Remote mount path prefix (sshfs mounts) - skip fuzzy matching to avoid hangs -const REMOTE_MOUNT_PREFIX = path.join(homedir(), CONFIG_DIR_NAME, "remote") + path.sep; +const REMOTE_MOUNT_PREFIX = path.join(os.homedir(), CONFIG_DIR_NAME, "remote") + path.sep; function isRemoteMountPath(absolutePath: string): boolean { return absolutePath.startsWith(REMOTE_MOUNT_PREFIX); diff --git a/packages/coding-agent/src/tools/render-utils.ts b/packages/coding-agent/src/tools/render-utils.ts index 990d5cfe5..d7243f0a1 100644 --- a/packages/coding-agent/src/tools/render-utils.ts +++ b/packages/coding-agent/src/tools/render-utils.ts @@ -5,7 +5,7 @@ * tool renderers to ensure a unified TUI experience. */ -import { homedir } from "node:os"; +import * as os from "node:os"; import type { Theme } from "../modes/theme/theme"; import { getTreeBranch } from "../tui/utils"; @@ -643,7 +643,7 @@ export function truncateDiffByHunk( // ============================================================================= export function shortenPath(filePath: string, homeDir?: string): string { - const home = homeDir ?? homedir(); + const home = homeDir ?? os.homedir(); if (home && filePath.startsWith(home)) { return `~${filePath.slice(home.length)}`; } diff --git a/packages/coding-agent/src/utils/clipboard.ts b/packages/coding-agent/src/utils/clipboard.ts index 6e6e43225..6bd5ec654 100644 --- a/packages/coding-agent/src/utils/clipboard.ts +++ b/packages/coding-agent/src/utils/clipboard.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs/promises"; -import { platform } from "node:os"; +import * as os from "node:os"; import { $ } from "bun"; import { nanoid } from "nanoid"; @@ -32,7 +32,7 @@ function selectPreferredImageMimeType(mimeTypes: string[]): string | null { } export async function copyToClipboard(text: string): Promise { - const p = platform(); + const p = os.platform(); const timeout = 5000; try { @@ -83,7 +83,7 @@ export interface ClipboardImage { * - Windows: uses PowerShell */ export async function readImageFromClipboard(): Promise { - const p = platform(); + const p = os.platform(); const timeout = 3000; let promise: Promise; switch (p) { diff --git a/packages/coding-agent/src/utils/shell-snapshot.ts b/packages/coding-agent/src/utils/shell-snapshot.ts index c99ea29a2..691f9b50c 100644 --- a/packages/coding-agent/src/utils/shell-snapshot.ts +++ b/packages/coding-agent/src/utils/shell-snapshot.ts @@ -7,7 +7,7 @@ */ import * as fs from "node:fs"; -import { homedir, tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { postmortem } from "@oh-my-pi/pi-utils"; import { $ } from "bun"; @@ -18,7 +18,7 @@ let cachedSnapshotPath: string | null = null; * Get the user's shell config file path. */ function getShellConfigFile(shell: string): string { - const home = homedir(); + const home = os.homedir(); if (shell.includes("zsh")) return path.join(home, ".zshrc"); if (shell.includes("bash")) return path.join(home, ".bashrc"); return path.join(home, ".profile"); @@ -130,7 +130,7 @@ export async function getOrCreateSnapshot( const rcFile = getShellConfigFile(shell); // Create snapshot directory - const snapshotDir = path.join(tmpdir(), "omp-shell-snapshots"); + const snapshotDir = path.join(os.tmpdir(), "omp-shell-snapshots"); await fs.promises.mkdir(snapshotDir, { recursive: true }); // Generate unique snapshot path diff --git a/packages/coding-agent/src/utils/tools-manager.ts b/packages/coding-agent/src/utils/tools-manager.ts index c95fb9c84..b0e8023bb 100644 --- a/packages/coding-agent/src/utils/tools-manager.ts +++ b/packages/coding-agent/src/utils/tools-manager.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs/promises"; -import { arch, platform } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { logger, TempDir } from "@oh-my-pi/pi-utils"; import { $ } from "bun"; @@ -148,7 +148,7 @@ export async function getToolPath(tool: ToolName): Promise { if (!config) return null; // Check our tools directory first - const localPath = path.join(TOOLS_DIR, config.binaryName + (platform() === "win32" ? ".exe" : "")); + const localPath = path.join(TOOLS_DIR, config.binaryName + (os.platform() === "win32" ? ".exe" : "")); if (await Bun.file(localPath).exists()) { return localPath; } @@ -187,8 +187,8 @@ async function downloadTool(tool: ToolName): Promise { const config = TOOLS[tool]; if (!config) throw new Error(`Unknown tool: ${tool}`); - const plat = platform(); - const architecture = arch(); + const plat = os.platform(); + const architecture = os.arch(); // Get latest version const version = await getLatestVersion(config.repo); diff --git a/packages/coding-agent/src/web/scrapers/utils.ts b/packages/coding-agent/src/web/scrapers/utils.ts index c482d5b72..485469c91 100644 --- a/packages/coding-agent/src/web/scrapers/utils.ts +++ b/packages/coding-agent/src/web/scrapers/utils.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs/promises"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { ptree } from "@oh-my-pi/pi-utils"; import { nanoid } from "nanoid"; @@ -40,7 +40,7 @@ export async function convertWithMarkitdown( // Write to temp file with extension hint const ext = extensionHint || ".bin"; - const tmpDir = tmpdir(); + const tmpDir = os.tmpdir(); const tmpFile = path.join(tmpDir, `omp-convert-${nanoid()}${ext}`); if (content.length > MAX_BYTES) { diff --git a/packages/coding-agent/src/web/scrapers/youtube.ts b/packages/coding-agent/src/web/scrapers/youtube.ts index 65f11dc85..25fafc065 100644 --- a/packages/coding-agent/src/web/scrapers/youtube.ts +++ b/packages/coding-agent/src/web/scrapers/youtube.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs/promises"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import path from "node:path"; import { cspawn } from "@oh-my-pi/pi-utils"; import { nanoid } from "nanoid"; @@ -229,7 +229,7 @@ export const handleYouTube: SpecialHandler = async ( const hasAutoSubs = listResult.stdout.includes("[info] Available automatic captions"); // Create temp directory for subtitle download - const tmpDir = tmpdir(); + const tmpDir = os.tmpdir(); const tmpBase = path.join(tmpDir, `yt-${yt.videoId}-${nanoid()}`); try { diff --git a/packages/coding-agent/src/web/search/providers/exa.ts b/packages/coding-agent/src/web/search/providers/exa.ts index 2e378d27f..acce55c95 100644 --- a/packages/coding-agent/src/web/search/providers/exa.ts +++ b/packages/coding-agent/src/web/search/providers/exa.ts @@ -5,7 +5,7 @@ * Returns structured search results with optional content extraction. */ -import { homedir } from "node:os"; +import * as os from "node:os"; import type { WebSearchResponse, WebSearchSource } from "../../../web/search/types"; import { WebSearchProviderError } from "../../../web/search/types"; @@ -57,7 +57,7 @@ async function parseEnvFile(filePath: string): Promise> { } function getHomeDir(): string { - return homedir(); + return os.homedir(); } /** Find EXA_API_KEY from environment or .env files */ diff --git a/packages/coding-agent/test/agent-session-branching.test.ts b/packages/coding-agent/test/agent-session-branching.test.ts index 0240d6df1..029de8a2c 100644 --- a/packages/coding-agent/test/agent-session-branching.test.ts +++ b/packages/coding-agent/test/agent-session-branching.test.ts @@ -9,7 +9,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { Agent } from "@oh-my-pi/pi-agent-core"; import { getModel } from "@oh-my-pi/pi-ai"; @@ -29,7 +29,7 @@ describe.skipIf(!API_KEY)("AgentSession branching", () => { beforeEach(() => { // Create temp directory for session files - tempDir = path.join(tmpdir(), `pi-branching-test-${nanoid()}`); + tempDir = path.join(os.tmpdir(), `pi-branching-test-${nanoid()}`); fs.mkdirSync(tempDir, { recursive: true }); }); diff --git a/packages/coding-agent/test/agent-session-compaction.test.ts b/packages/coding-agent/test/agent-session-compaction.test.ts index 763a07261..bd492c767 100644 --- a/packages/coding-agent/test/agent-session-compaction.test.ts +++ b/packages/coding-agent/test/agent-session-compaction.test.ts @@ -9,7 +9,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { Agent } from "@oh-my-pi/pi-agent-core"; import { getModel } from "@oh-my-pi/pi-ai"; @@ -30,7 +30,7 @@ describe.skipIf(!API_KEY)("AgentSession compaction e2e", () => { beforeEach(() => { // Create temp directory for session files - tempDir = path.join(tmpdir(), `omp-compaction-test-${nanoid()}`); + tempDir = path.join(os.tmpdir(), `omp-compaction-test-${nanoid()}`); fs.mkdirSync(tempDir, { recursive: true }); // Track events diff --git a/packages/coding-agent/test/agent-session-concurrent.test.ts b/packages/coding-agent/test/agent-session-concurrent.test.ts index 0f7314d81..06489e5a6 100644 --- a/packages/coding-agent/test/agent-session-concurrent.test.ts +++ b/packages/coding-agent/test/agent-session-concurrent.test.ts @@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { Agent } from "@oh-my-pi/pi-agent-core"; import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from "@oh-my-pi/pi-ai"; @@ -54,7 +54,7 @@ describe("AgentSession concurrent prompt guard", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `pi-concurrent-test-${nanoid()}`); + tempDir = path.join(os.tmpdir(), `pi-concurrent-test-${nanoid()}`); fs.mkdirSync(tempDir, { recursive: true }); }); diff --git a/packages/coding-agent/test/block-images.test.ts b/packages/coding-agent/test/block-images.test.ts index 2d8176a1f..e7f9ea10c 100644 --- a/packages/coding-agent/test/block-images.test.ts +++ b/packages/coding-agent/test/block-images.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { processFileArguments } from "@oh-my-pi/pi-coding-agent/cli/file-processor"; import { SettingsManager } from "@oh-my-pi/pi-coding-agent/config/settings-manager"; @@ -56,7 +56,7 @@ describe("blockImages setting", () => { let testDir: string; beforeEach(() => { - testDir = path.join(tmpdir(), `block-images-test-${Date.now()}-${Math.random()}`); + testDir = path.join(os.tmpdir(), `block-images-test-${Date.now()}-${Math.random()}`); fs.mkdirSync(testDir, { recursive: true }); }); @@ -97,7 +97,7 @@ describe("blockImages setting", () => { let testDir: string; beforeEach(() => { - testDir = path.join(tmpdir(), `block-images-process-test-${Date.now()}-${Math.random()}`); + testDir = path.join(os.tmpdir(), `block-images-process-test-${Date.now()}-${Math.random()}`); fs.mkdirSync(testDir, { recursive: true }); }); diff --git a/packages/coding-agent/test/compaction-hooks.test.ts b/packages/coding-agent/test/compaction-hooks.test.ts index f0e56e757..7434b25b1 100644 --- a/packages/coding-agent/test/compaction-hooks.test.ts +++ b/packages/coding-agent/test/compaction-hooks.test.ts @@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { Agent } from "@oh-my-pi/pi-agent-core"; import { getModel } from "@oh-my-pi/pi-ai"; @@ -33,7 +33,7 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => { let capturedEvents: SessionEvent[]; beforeEach(() => { - tempDir = path.join(tmpdir(), `omp-compaction-hooks-test-${nanoid()}`); + tempDir = path.join(os.tmpdir(), `omp-compaction-hooks-test-${nanoid()}`); fs.mkdirSync(tempDir, { recursive: true }); capturedEvents = []; }); diff --git a/packages/coding-agent/test/compaction-thinking-model.test.ts b/packages/coding-agent/test/compaction-thinking-model.test.ts index 1178902b7..b662a98bd 100644 --- a/packages/coding-agent/test/compaction-thinking-model.test.ts +++ b/packages/coding-agent/test/compaction-thinking-model.test.ts @@ -10,7 +10,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { Agent, type ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { getModel, type Model } from "@oh-my-pi/pi-ai"; @@ -32,7 +32,7 @@ describe.skipIf(!HAS_ANTIGRAVITY_AUTH)("Compaction with thinking models (Antigra let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `pi-thinking-compaction-test-${nanoid()}`); + tempDir = path.join(os.tmpdir(), `pi-thinking-compaction-test-${nanoid()}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -142,7 +142,7 @@ describe.skipIf(!HAS_ANTHROPIC_AUTH)("Compaction with thinking models (Anthropic let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `pi-thinking-compaction-anthropic-test-${nanoid()}`); + tempDir = path.join(os.tmpdir(), `pi-thinking-compaction-anthropic-test-${nanoid()}`); fs.mkdirSync(tempDir, { recursive: true }); }); diff --git a/packages/coding-agent/test/core/apply-patch-adverserial.test.ts b/packages/coding-agent/test/core/apply-patch-adverserial.test.ts index 45f61736c..eedd13a80 100644 --- a/packages/coding-agent/test/core/apply-patch-adverserial.test.ts +++ b/packages/coding-agent/test/core/apply-patch-adverserial.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { ApplyPatchError, applyPatch } from "@oh-my-pi/pi-coding-agent/patch"; @@ -8,7 +8,7 @@ describe("applyPatch adversarial inputs", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `apply-patch-adversarial-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `apply-patch-adversarial-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); diff --git a/packages/coding-agent/test/core/apply-patch-regression.test.ts b/packages/coding-agent/test/core/apply-patch-regression.test.ts index 110f719de..b8cc6cdec 100644 --- a/packages/coding-agent/test/core/apply-patch-regression.test.ts +++ b/packages/coding-agent/test/core/apply-patch-regression.test.ts @@ -8,7 +8,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { applyPatch, findContextLine, seekSequence } from "@oh-my-pi/pi-coding-agent/patch"; @@ -16,7 +16,7 @@ describe("regression: indentation adjustment for line-based replacements (2B)", let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `regression-2b-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `regression-2b-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -97,7 +97,7 @@ describe("regression: ambiguity detection for context-less hunks (2C)", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `regression-2c-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `regression-2c-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -161,7 +161,7 @@ describe("regression: context search uses line hints (2D)", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `regression-2d-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `regression-2d-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -246,7 +246,7 @@ describe("regression: insertion uses newStartLine fallback (2E)", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `regression-2e-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `regression-2e-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -413,7 +413,7 @@ describe("plan: partial line matching for @@ context", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `plan-partial-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `plan-partial-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -477,7 +477,7 @@ describe("plan: unified diff format line numbers", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `plan-unified-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `plan-unified-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -551,7 +551,7 @@ describe("plan: Codex-style wrapped patches", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `plan-codex-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `plan-codex-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -692,7 +692,7 @@ describe("plan: strip + prefix from file creation", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `plan-create-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `plan-create-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -750,7 +750,7 @@ describe("regression: *** End of File marker handling (2A/2G)", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `regression-eof-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `regression-eof-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -809,7 +809,7 @@ describe("regression: model edit attempt - @@ line N syntax (session 2026-01-19) let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `model-line-n-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `model-line-n-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -861,7 +861,7 @@ describe("regression: model edit attempt - nested @@ anchors (session 2026-01-19 let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `model-nested-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `model-nested-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -954,7 +954,7 @@ describe("regression: model edit attempt - space-separated anchors (session 2026 let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `model-space-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `model-space-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -1042,7 +1042,7 @@ describe("regression: model edit attempt - unique substring on long line (sessio let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `model-long-line-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `model-long-line-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -1122,7 +1122,7 @@ describe("regression: bench edit failures (2026-01-19)", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `bench-regression-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `bench-regression-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); diff --git a/packages/coding-agent/test/core/apply-patch.test.ts b/packages/coding-agent/test/core/apply-patch.test.ts index 702b4625f..d32ddb639 100644 --- a/packages/coding-agent/test/core/apply-patch.test.ts +++ b/packages/coding-agent/test/core/apply-patch.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { ApplyPatchError, @@ -336,7 +336,7 @@ describe("apply-patch scenarios", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `apply-patch-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `apply-patch-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -424,7 +424,7 @@ describe("applyPatch", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `apply-patch-unit-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `apply-patch-unit-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -579,7 +579,7 @@ describe("simple replace mode", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `simple-replace-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `simple-replace-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); fs.mkdirSync(tempDir, { recursive: true }); }); diff --git a/packages/coding-agent/test/model-registry.test.ts b/packages/coding-agent/test/model-registry.test.ts index a4da87dde..186669f8e 100644 --- a/packages/coding-agent/test/model-registry.test.ts +++ b/packages/coding-agent/test/model-registry.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; @@ -12,7 +12,7 @@ describe("ModelRegistry", () => { let authStorage: AuthStorage; beforeEach(async () => { - tempDir = path.join(tmpdir(), `pi-test-model-registry-${nanoid()}`); + tempDir = path.join(os.tmpdir(), `pi-test-model-registry-${nanoid()}`); fs.mkdirSync(tempDir, { recursive: true }); modelsJsonPath = path.join(tempDir, "models.json"); authStorage = await AuthStorage.create(path.join(tempDir, "auth.json")); @@ -145,7 +145,7 @@ describe("ModelRegistry", () => { expect(googleModels[0].id).toBe("gemini-custom"); }); - test("refresh() picks up baseUrl override changes", () => { + test("refresh() picks up baseUrl override changes", async () => { writeRawModelsJson({ anthropic: overrideConfig("https://first-proxy.example.com/v1"), }); @@ -157,7 +157,7 @@ describe("ModelRegistry", () => { writeRawModelsJson({ anthropic: overrideConfig("https://second-proxy.example.com/v1"), }); - registry.refresh(); + await registry.refresh(); expect(getModelsForProvider(registry, "anthropic")[0].baseUrl).toBe("https://second-proxy.example.com/v1"); }); @@ -211,7 +211,7 @@ describe("ModelRegistry", () => { expect(googleModels[0].baseUrl).toBe("https://google-proxy.example.com/v1"); }); - test("refresh() reloads overrides from disk", () => { + test("refresh() reloads overrides from disk", async () => { writeModelsJson({ anthropic: providerConfig("https://first-proxy.example.com/v1", [{ id: "claude-first" }]), }); @@ -223,14 +223,14 @@ describe("ModelRegistry", () => { writeModelsJson({ anthropic: providerConfig("https://second-proxy.example.com/v1", [{ id: "claude-second" }]), }); - registry.refresh(); + await registry.refresh(); const anthropicModels = getModelsForProvider(registry, "anthropic"); expect(anthropicModels[0].id).toBe("claude-second"); expect(anthropicModels[0].baseUrl).toBe("https://second-proxy.example.com/v1"); }); - test("removing override from models.json restores built-in provider", () => { + test("removing override from models.json restores built-in provider", async () => { writeModelsJson({ anthropic: providerConfig("https://proxy.example.com/v1", [{ id: "claude-custom" }]), }); @@ -240,7 +240,7 @@ describe("ModelRegistry", () => { // Remove override and refresh writeModelsJson({}); - registry.refresh(); + await registry.refresh(); const anthropicModels = getModelsForProvider(registry, "anthropic"); expect(anthropicModels.length).toBeGreaterThan(1); diff --git a/packages/coding-agent/test/python-tool-settings.test.ts b/packages/coding-agent/test/python-tool-settings.test.ts index 7152b7817..d47da7684 100644 --- a/packages/coding-agent/test/python-tool-settings.test.ts +++ b/packages/coding-agent/test/python-tool-settings.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import * as pythonExecutor from "@oh-my-pi/pi-coding-agent/ipy/executor"; import * as pythonKernel from "@oh-my-pi/pi-coding-agent/ipy/kernel"; @@ -38,7 +38,7 @@ describe("python tool settings", () => { let testDir: string; beforeEach(() => { - testDir = path.join(tmpdir(), `python-tool-settings-${crypto.randomUUID()}`); + testDir = path.join(os.tmpdir(), `python-tool-settings-${crypto.randomUUID()}`); fs.mkdirSync(testDir, { recursive: true }); }); diff --git a/packages/coding-agent/test/rpc.test.ts b/packages/coding-agent/test/rpc.test.ts index c6b0b9054..a7a58dbf7 100644 --- a/packages/coding-agent/test/rpc.test.ts +++ b/packages/coding-agent/test/rpc.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import type { AgentEvent } from "@oh-my-pi/pi-agent-core"; import { RpcClient } from "@oh-my-pi/pi-coding-agent/modes/rpc/rpc-client"; @@ -14,7 +14,7 @@ describe.skipIf(!process.env.ANTHROPIC_API_KEY && !process.env.ANTHROPIC_OAUTH_T let sessionDir: string; beforeEach(() => { - sessionDir = path.join(tmpdir(), `omp-rpc-test-${nanoid()}`); + sessionDir = path.join(os.tmpdir(), `omp-rpc-test-${nanoid()}`); client = new RpcClient({ cliPath: path.join(import.meta.dir, "..", "dist", "cli.js"), cwd: path.join(import.meta.dir, ".."), diff --git a/packages/coding-agent/test/sdk-skills.test.ts b/packages/coding-agent/test/sdk-skills.test.ts index 9ea2ceafd..cada098b3 100644 --- a/packages/coding-agent/test/sdk-skills.test.ts +++ b/packages/coding-agent/test/sdk-skills.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import type { Skill } from "@oh-my-pi/pi-coding-agent/sdk"; import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; @@ -11,7 +11,7 @@ describe("createAgentSession skills option", () => { let skillsDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `pi-sdk-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = path.join(os.tmpdir(), `pi-sdk-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); // Create skill in .omp/skills/ for native project-level discovery skillsDir = path.join(tempDir, ".omp", "skills", "test-skill"); fs.mkdirSync(skillsDir, { recursive: true }); diff --git a/packages/coding-agent/test/session-manager/file-operations.test.ts b/packages/coding-agent/test/session-manager/file-operations.test.ts index ecf6a79ce..acb3d9615 100644 --- a/packages/coding-agent/test/session-manager/file-operations.test.ts +++ b/packages/coding-agent/test/session-manager/file-operations.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { findMostRecentSession, loadEntriesFromFile } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { nanoid } from "nanoid"; @@ -9,7 +9,7 @@ describe("loadEntriesFromFile", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `session-test-${nanoid()}`); + tempDir = path.join(os.tmpdir(), `session-test-${nanoid()}`); fs.mkdirSync(tempDir, { recursive: true }); }); @@ -70,7 +70,7 @@ describe("findMostRecentSession", () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(tmpdir(), `session-test-${nanoid()}`); + tempDir = path.join(os.tmpdir(), `session-test-${nanoid()}`); fs.mkdirSync(tempDir, { recursive: true }); }); diff --git a/packages/coding-agent/test/skills.test.ts b/packages/coding-agent/test/skills.test.ts index c643ff4fb..46b0a371a 100644 --- a/packages/coding-agent/test/skills.test.ts +++ b/packages/coding-agent/test/skills.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "bun:test"; -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { loadSkills, loadSkillsFromDir, type Skill } from "@oh-my-pi/pi-coding-agent/extensibility/skills"; @@ -188,7 +188,7 @@ describe("skills", () => { }); it("should expand ~ in customDirectories", async () => { - const homeSkillsDir = path.join(homedir(), ".omp/agent/skills"); + const homeSkillsDir = path.join(os.homedir(), ".omp/agent/skills"); const { skills: withTilde } = await loadSkills({ enableCodexUser: false, enableClaudeUser: false, diff --git a/packages/coding-agent/test/streaming-edit-abort.test.ts b/packages/coding-agent/test/streaming-edit-abort.test.ts index f5012788e..4371875a4 100644 --- a/packages/coding-agent/test/streaming-edit-abort.test.ts +++ b/packages/coding-agent/test/streaming-edit-abort.test.ts @@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { Agent, type AgentTool } from "@oh-my-pi/pi-agent-core"; import { @@ -214,7 +214,7 @@ describe("streaming edit abort", () => { const seeds = [7, 21, 42, 84, 128]; beforeEach(() => { - tempDir = path.join(tmpdir(), `pi-streaming-edit-${nanoid()}`); + tempDir = path.join(os.tmpdir(), `pi-streaming-edit-${nanoid()}`); fs.mkdirSync(tempDir, { recursive: true }); }); diff --git a/packages/coding-agent/test/tools.test.ts b/packages/coding-agent/test/tools.test.ts index fcc3b8515..1bef799aa 100644 --- a/packages/coding-agent/test/tools.test.ts +++ b/packages/coding-agent/test/tools.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { EditTool } from "@oh-my-pi/pi-coding-agent/patch"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; @@ -50,7 +50,7 @@ describe("Coding Agent Tools", () => { process.env.OMP_EDIT_VARIANT = "replace"; // Create a unique temporary directory for each test - testDir = path.join(tmpdir(), `coding-agent-test-${nanoid()}`); + testDir = path.join(os.tmpdir(), `coding-agent-test-${nanoid()}`); fs.mkdirSync(testDir, { recursive: true }); // Create tools for this test directory @@ -538,7 +538,7 @@ describe("edit tool CRLF handling", () => { originalEditVariant = process.env.OMP_EDIT_VARIANT; process.env.OMP_EDIT_VARIANT = "replace"; - testDir = path.join(tmpdir(), `coding-agent-crlf-test-${nanoid()}`); + testDir = path.join(os.tmpdir(), `coding-agent-crlf-test-${nanoid()}`); fs.mkdirSync(testDir, { recursive: true }); editTool = new EditTool(createTestToolSession(testDir)); }); diff --git a/packages/coding-agent/test/utilities.ts b/packages/coding-agent/test/utilities.ts index 27d537f72..7d8236c61 100644 --- a/packages/coding-agent/test/utilities.ts +++ b/packages/coding-agent/test/utilities.ts @@ -3,7 +3,7 @@ */ import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { Agent } from "@oh-my-pi/pi-agent-core"; import { getModel } from "@oh-my-pi/pi-ai"; @@ -78,7 +78,7 @@ export interface TestSessionContext { * Use this for e2e tests that need real LLM calls. */ export async function createTestSession(options: TestSessionOptions = {}): Promise { - const tempDir = path.join(tmpdir(), `omp-test-${nanoid()}`); + const tempDir = path.join(os.tmpdir(), `omp-test-${nanoid()}`); fs.mkdirSync(tempDir, { recursive: true }); const toolSession: ToolSession = { diff --git a/packages/pi-utils/src/logger.ts b/packages/pi-utils/src/logger.ts index efe795c1c..f5aaeff79 100644 --- a/packages/pi-utils/src/logger.ts +++ b/packages/pi-utils/src/logger.ts @@ -6,14 +6,14 @@ */ import * as fs from "node:fs"; -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import winston from "winston"; import DailyRotateFile from "winston-daily-rotate-file"; /** Get the logs directory (~/.omp/logs/) */ function getLogsDir(): string { - return path.join(homedir(), ".omp", "logs"); + return path.join(os.homedir(), ".omp", "logs"); } /** Ensure logs directory exists */ diff --git a/packages/pi-utils/src/temp.ts b/packages/pi-utils/src/temp.ts index 65c4c22a5..c890ad47c 100644 --- a/packages/pi-utils/src/temp.ts +++ b/packages/pi-utils/src/temp.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; export class TempDir { @@ -65,7 +65,7 @@ export class TempDir { } } -const kTempDir = tmpdir(); +const kTempDir = os.tmpdir(); function normalizePrefix(prefix?: string): string { if (!prefix) { diff --git a/packages/react-edit-benchmark/tarball.ts b/packages/react-edit-benchmark/tarball.ts index 826f9edcb..457609750 100644 --- a/packages/react-edit-benchmark/tarball.ts +++ b/packages/react-edit-benchmark/tarball.ts @@ -189,7 +189,6 @@ export async function extractTaskFiles( if (!relativePath) continue; const destPath = join(destDir, relativePath); - await fs.mkdir(dirname(destPath), { recursive: true }); await Bun.write(destPath, await file.arrayBuffer()); } } diff --git a/packages/stats/src/db.ts b/packages/stats/src/db.ts index adf3fe7b6..3a8516a12 100644 --- a/packages/stats/src/db.ts +++ b/packages/stats/src/db.ts @@ -1,10 +1,10 @@ import { Database } from "bun:sqlite"; import * as fs from "node:fs/promises"; -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import type { AggregatedStats, FolderStats, MessageStats, ModelStats, TimeSeriesPoint } from "./types"; -const DB_PATH = path.join(homedir(), ".omp", "stats.db"); +const DB_PATH = path.join(os.homedir(), ".omp", "stats.db"); let db: Database | null = null; @@ -15,7 +15,7 @@ export async function initDb(): Promise { if (db) return db; // Ensure directory exists - await fs.mkdir(path.join(homedir(), ".omp"), { recursive: true }); + await fs.mkdir(path.join(os.homedir(), ".omp"), { recursive: true }); db = new Database(DB_PATH); db.exec("PRAGMA journal_mode = WAL"); diff --git a/packages/stats/src/parser.ts b/packages/stats/src/parser.ts index 22a26da81..384ac7f29 100644 --- a/packages/stats/src/parser.ts +++ b/packages/stats/src/parser.ts @@ -1,10 +1,10 @@ import * as fs from "node:fs/promises"; -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import type { AssistantMessage } from "@oh-my-pi/pi-ai"; import type { MessageStats, SessionEntry, SessionMessageEntry } from "./types"; -const SESSIONS_DIR = path.join(homedir(), ".omp", "agent", "sessions"); +const SESSIONS_DIR = path.join(os.homedir(), ".omp", "agent", "sessions"); /** * Extract folder name from session filename. diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 1faf16577..a5859d3fc 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -10,6 +10,7 @@ ### Changed - Updated TypeScript configuration for better publish-time configuration handling with tsconfig.publish.json - Migrated file system operations from synchronous to asynchronous APIs in autocomplete provider for non-blocking I/O +- Migrated node module imports from named to namespace imports across all packages for consistency with project guidelines ### Fixed - Fixed crash when terminal becomes unavailable (EIO errors) by exiting gracefully instead of throwing diff --git a/packages/tui/src/autocomplete.ts b/packages/tui/src/autocomplete.ts index f0d1b33ac..4e1c1618d 100644 --- a/packages/tui/src/autocomplete.ts +++ b/packages/tui/src/autocomplete.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs"; -import { homedir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; async function walkDirectoryWithFd( @@ -314,11 +314,11 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider { // Expand home directory (~/) to actual home path private expandHomePath(filePath: string): string { if (filePath.startsWith("~/")) { - const expandedPath = path.join(homedir(), filePath.slice(2)); + const expandedPath = path.join(os.homedir(), filePath.slice(2)); // Preserve trailing slash if original path had one return filePath.endsWith("/") && !expandedPath.endsWith("/") ? `${expandedPath}/` : expandedPath; } else if (filePath === "~") { - return homedir(); + return os.homedir(); } return filePath; } diff --git a/packages/tui/src/mermaid.ts b/packages/tui/src/mermaid.ts index 9c0e82164..ad28e855c 100644 --- a/packages/tui/src/mermaid.ts +++ b/packages/tui/src/mermaid.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs/promises"; -import { tmpdir } from "node:os"; +import * as os from "node:os"; import * as path from "node:path"; import { $ } from "bun"; @@ -31,12 +31,11 @@ export async function renderMermaidToPng( return null; } - const tmpDir = path.join(tmpdir(), `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`); + const tmpDir = path.join(os.tmpdir(), `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`); const inputPath = path.join(tmpDir, "input.mmd"); const outputPath = path.join(tmpDir, "output.png"); try { - await fs.mkdir(tmpDir, { recursive: true }); await Bun.write(inputPath, source); const args: string[] = ["-i", inputPath, "-o", outputPath, "-q"];