diff --git a/packages/ai/src/auth-storage.ts b/packages/ai/src/auth-storage.ts index fb5e79232..473f34a49 100644 --- a/packages/ai/src/auth-storage.ts +++ b/packages/ai/src/auth-storage.ts @@ -2165,7 +2165,9 @@ export class AuthCredentialStore { `UPDATE auth_credentials SET disabled_cause = ?, updated_at = ${SQLITE_NOW_EPOCH} WHERE provider = ? AND disabled_cause IS NULL`, ); this.#hardDeleteStmt = this.#db.prepare("DELETE FROM auth_credentials WHERE id = ?"); - this.#getCacheStmt = this.#db.prepare(`SELECT value FROM cache WHERE key = ? AND expires_at > ${SQLITE_NOW_EPOCH}`); + this.#getCacheStmt = this.#db.prepare( + `SELECT value FROM cache WHERE key = ? AND expires_at > ${SQLITE_NOW_EPOCH}`, + ); this.#upsertCacheStmt = this.#db.prepare( "INSERT INTO cache (key, value, expires_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, expires_at = excluded.expires_at", ); diff --git a/packages/ai/test/auth-storage-email-dedupe.test.ts b/packages/ai/test/auth-storage-email-dedupe.test.ts index de7a0a6a0..266aa6dc1 100644 --- a/packages/ai/test/auth-storage-email-dedupe.test.ts +++ b/packages/ai/test/auth-storage-email-dedupe.test.ts @@ -424,7 +424,13 @@ describe("AuthStorage openai-codex email dedupe", () => { .run( "openai-codex", "oauth", - JSON.stringify(createCredential({ suffix: "legacy-v3", accountId: "legacy-v3-account", email: "legacy-v3@example.com" })), + JSON.stringify( + createCredential({ + suffix: "legacy-v3", + accountId: "legacy-v3-account", + email: "legacy-v3@example.com", + }), + ), null, "email:legacy-v3@example.com", LEGACY_TIMESTAMP, @@ -519,7 +525,9 @@ describe("AuthStorage openai-codex email dedupe", () => { .run( "openai-codex", "oauth", - JSON.stringify(createCredential({ suffix: "legacy", accountId: "legacy-account", email: "legacy@example.com" })), + JSON.stringify( + createCredential({ suffix: "legacy", accountId: "legacy-account", email: "legacy@example.com" }), + ), 1, LEGACY_TIMESTAMP, LEGACY_TIMESTAMP, diff --git a/packages/coding-agent/src/modes/controllers/selector-controller.ts b/packages/coding-agent/src/modes/controllers/selector-controller.ts index e7c0d29de..d7a5cea75 100644 --- a/packages/coding-agent/src/modes/controllers/selector-controller.ts +++ b/packages/coding-agent/src/modes/controllers/selector-controller.ts @@ -17,7 +17,7 @@ import { theme, } from "../../modes/theme/theme"; import type { InteractiveModeContext } from "../../modes/types"; -import { SessionManager, type SessionInfo } from "../../session/session-manager"; +import { type SessionInfo, SessionManager } from "../../session/session-manager"; import { FileSessionStorage } from "../../session/session-storage"; import { isCodeSearchProviderId, @@ -624,7 +624,6 @@ export class SelectorController { }); } - #clearTransientSessionUi(): void { if (this.ctx.loadingAnimation) { this.ctx.loadingAnimation.stop(); @@ -638,20 +637,17 @@ export class SelectorController { this.ctx.pendingTools.clear(); } - async #detachActiveSessionBeforeDeletion(sessionPath: string): Promise { const currentSessionFile = this.ctx.sessionManager.getSessionFile(); if (currentSessionFile !== sessionPath) { return true; } - const detached = await this.ctx.session.newSession(); if (!detached) { return false; } - this.#clearTransientSessionUi(); this.ctx.statusLine.invalidate(); this.ctx.statusLine.setSessionStartTime(Date.now()); @@ -662,15 +658,12 @@ export class SelectorController { return true; } - async handleResumeSession(sessionPath: string): Promise { this.#clearTransientSessionUi(); - // Switch session via AgentSession (emits hook and tool session events) await this.ctx.session.switchSession(sessionPath); - // Clear and re-render the chat this.ctx.chatContainer.clear(); this.ctx.renderInitialMessages(); @@ -678,7 +671,6 @@ export class SelectorController { this.ctx.showStatus("Resumed session"); } - async handleSessionDeleteCommand(): Promise { const sessionFile = this.ctx.sessionManager.getSessionFile(); if (!sessionFile) { @@ -686,7 +678,6 @@ export class SelectorController { return; } - // Check if session file exists (may not exist for brand new sessions) const storage = new FileSessionStorage(); const fileExists = await storage.exists(sessionFile); @@ -695,29 +686,24 @@ export class SelectorController { return; } - const confirmed = await this.ctx.showHookConfirm( "Delete Session", "This will permanently delete the current session.\nYou will be returned to the session selector.", ); - if (!confirmed) { this.ctx.showStatus("Delete cancelled"); return; } - if (!(await this.#detachActiveSessionBeforeDeletion(sessionFile))) { this.ctx.showStatus("Delete cancelled"); return; } - // Delete the session file and artifacts directory await storage.deleteSessionWithArtifacts(sessionFile); - // Show session selector this.ctx.showStatus("Session deleted"); await this.showSessionSelector(); diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index 9d09facfe..5b372bd84 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -484,7 +484,10 @@ export class AgentSession { ) { this.sessionManager.appendMCPToolSelection(currentSelectedMCPToolNames); } - this.#rememberSessionDefaultSelectedMCPToolNames(this.sessionManager.getSessionFile(), this.#defaultSelectedMCPToolNames); + this.#rememberSessionDefaultSelectedMCPToolNames( + this.sessionManager.getSessionFile(), + this.#defaultSelectedMCPToolNames, + ); this.#ttsrManager = config.ttsrManager; this.#obfuscator = config.obfuscator; this.agent.providerSessionState = this.#providerSessionState; @@ -1664,7 +1667,10 @@ export class AgentSession { return left.length === right.length && left.every((name, index) => name === right[index]); } - #rememberSessionDefaultSelectedMCPToolNames(sessionFile: string | null | undefined, toolNames: Iterable): void { + #rememberSessionDefaultSelectedMCPToolNames( + sessionFile: string | null | undefined, + toolNames: Iterable, + ): void { if (!sessionFile) return; this.#sessionDefaultSelectedMCPToolNames.set( path.resolve(sessionFile), diff --git a/packages/coding-agent/src/session/agent-storage.ts b/packages/coding-agent/src/session/agent-storage.ts index 07755d8d7..c6b37c8c6 100644 --- a/packages/coding-agent/src/session/agent-storage.ts +++ b/packages/coding-agent/src/session/agent-storage.ts @@ -126,7 +126,9 @@ CREATE TABLE settings ( ); `); if (settings) { - const insert = this.#db.prepare(`INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ${SQLITE_NOW_EPOCH})`); + const insert = this.#db.prepare( + `INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ${SQLITE_NOW_EPOCH})`, + ); for (const [key, value] of Object.entries(settings)) { if (value === undefined) continue; const serialized = JSON.stringify(value); diff --git a/packages/coding-agent/src/session/history-storage.ts b/packages/coding-agent/src/session/history-storage.ts index b720b3b73..8907890ce 100644 --- a/packages/coding-agent/src/session/history-storage.ts +++ b/packages/coding-agent/src/session/history-storage.ts @@ -142,9 +142,9 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN } #historySchemaUsesUnixEpoch(): boolean { - const row = this.#db - .prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'history'") - .get() as { sql?: string | null } | undefined; + const row = this.#db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'history'").get() as + | { sql?: string | null } + | undefined; return row?.sql?.includes("unixepoch(") ?? false; } diff --git a/packages/coding-agent/src/session/session-storage.ts b/packages/coding-agent/src/session/session-storage.ts index d504fed1e..d33984088 100644 --- a/packages/coding-agent/src/session/session-storage.ts +++ b/packages/coding-agent/src/session/session-storage.ts @@ -203,9 +203,12 @@ export class FileSessionStorage implements SessionStorage { await fsp.rm(artifactsDir, { recursive: true, force: true }); } catch (err) { const error = toError(err); - throw new Error(`Session file deleted but failed to remove artifacts directory ${artifactsDir}: ${error.message}`, { - cause: error, - }); + throw new Error( + `Session file deleted but failed to remove artifacts directory ${artifactsDir}: ${error.message}`, + { + cause: error, + }, + ); } } } diff --git a/packages/coding-agent/test/agent-storage-sqlite-compat.test.ts b/packages/coding-agent/test/agent-storage-sqlite-compat.test.ts index 65862a25e..e6b1146a2 100644 --- a/packages/coding-agent/test/agent-storage-sqlite-compat.test.ts +++ b/packages/coding-agent/test/agent-storage-sqlite-compat.test.ts @@ -34,9 +34,11 @@ function readSchemaVersion(dbPath: string): number | null { function readSettingsRows(dbPath: string): Array<{ key: string; value: string; updated_at: number }> { const db = new Database(dbPath, { readonly: true }); try { - return db - .prepare("SELECT key, value, updated_at FROM settings ORDER BY key ASC") - .all() as Array<{ key: string; value: string; updated_at: number }>; + return db.prepare("SELECT key, value, updated_at FROM settings ORDER BY key ASC").all() as Array<{ + key: string; + value: string; + updated_at: number; + }>; } finally { db.close(); } @@ -84,11 +86,12 @@ describe("AgentStorage SQLite compatibility", () => { last_used_at INTEGER NOT NULL DEFAULT (unixepoch()) ); `); - legacyDb.prepare("INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?)").run("theme", "\"dark\"", LEGACY_TIMESTAMP); - legacyDb.prepare("INSERT INTO model_usage (model_key, last_used_at) VALUES (?, ?)").run( - "anthropic/claude-sonnet-4-5", - LEGACY_TIMESTAMP, - ); + legacyDb + .prepare("INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?)") + .run("theme", '"dark"', LEGACY_TIMESTAMP); + legacyDb + .prepare("INSERT INTO model_usage (model_key, last_used_at) VALUES (?, ?)") + .run("anthropic/claude-sonnet-4-5", LEGACY_TIMESTAMP); legacyDb.close(); const storage = await AgentStorage.open(dbPath); @@ -100,6 +103,6 @@ describe("AgentStorage SQLite compatibility", () => { expect(readTableSql(dbPath, "model_usage")).toContain("strftime('%s','now')"); expect(storage.getSettings()).toEqual({ theme: "dark" }); expect(storage.getModelUsageOrder()).toEqual(["anthropic/claude-sonnet-4-5"]); - expect(readSettingsRows(dbPath)).toEqual([{ key: "theme", value: "\"dark\"", updated_at: LEGACY_TIMESTAMP }]); + expect(readSettingsRows(dbPath)).toEqual([{ key: "theme", value: '"dark"', updated_at: LEGACY_TIMESTAMP }]); }); }); diff --git a/packages/coding-agent/test/discovery/mcp-json.test.ts b/packages/coding-agent/test/discovery/mcp-json.test.ts index 48b59ee9c..448d6966b 100644 --- a/packages/coding-agent/test/discovery/mcp-json.test.ts +++ b/packages/coding-agent/test/discovery/mcp-json.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; -import { mcpCapability, type MCPServer } from "@oh-my-pi/pi-coding-agent/capability/mcp"; +import { type MCPServer, mcpCapability } from "@oh-my-pi/pi-coding-agent/capability/mcp"; import { loadCapability } from "@oh-my-pi/pi-coding-agent/discovery"; async function loadStandaloneMcpConfig(cwd: string): Promise { diff --git a/packages/coding-agent/test/history-storage-sqlite-compat.test.ts b/packages/coding-agent/test/history-storage-sqlite-compat.test.ts index 3256af62b..5d64f8a3c 100644 --- a/packages/coding-agent/test/history-storage-sqlite-compat.test.ts +++ b/packages/coding-agent/test/history-storage-sqlite-compat.test.ts @@ -1,5 +1,5 @@ import { Database } from "bun:sqlite"; -import { afterEach, it, expect } from "bun:test"; +import { afterEach, expect, it } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; @@ -40,11 +40,9 @@ it("migrates legacy history schema away from unixepoch defaults", async () => { cwd TEXT ); `); - legacyDb.prepare("INSERT INTO history (prompt, created_at, cwd) VALUES (?, ?, ?)").run( - "legacy prompt", - LEGACY_TIMESTAMP, - "/tmp/legacy", - ); + legacyDb + .prepare("INSERT INTO history (prompt, created_at, cwd) VALUES (?, ?, ?)") + .run("legacy prompt", LEGACY_TIMESTAMP, "/tmp/legacy"); legacyDb.close(); const storage = HistoryStorage.open(dbPath); @@ -53,21 +51,17 @@ it("migrates legacy history schema away from unixepoch defaults", async () => { const db = new Database(dbPath, { readonly: true }); try { - const prompts = db - .prepare("SELECT prompt FROM history ORDER BY id ASC") - .all() as Array<{ prompt: string }>; + const prompts = db.prepare("SELECT prompt FROM history ORDER BY id ASC").all() as Array<{ prompt: string }>; expect(prompts).toEqual([{ prompt: "legacy prompt" }, { prompt: "new prompt" }]); expect(readTableSql(dbPath, "history")).not.toContain("unixepoch("); expect(readTableSql(dbPath, "history")).toContain("strftime('%s','now')"); const indexRow = db .prepare("SELECT 1 AS present FROM sqlite_master WHERE type = 'index' AND name = 'idx_history_created_at'") - .get() as - | { present?: number } - | undefined; + .get() as { present?: number } | undefined; expect(indexRow?.present).toBe(1); - const ftsRow = db.prepare("SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = 'history_fts'").get() as - | { present?: number } - | undefined; + const ftsRow = db + .prepare("SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = 'history_fts'") + .get() as { present?: number } | undefined; expect(ftsRow?.present).toBe(1); } finally { db.close(); diff --git a/packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts b/packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts index dc078585d..666ce222a 100644 --- a/packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts +++ b/packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts @@ -133,10 +133,9 @@ function createContext(currentSessionFile: string): { }; } - beforeAll(() => { - initTheme(); - }); - +beforeAll(() => { + initTheme(); +}); describe("SelectorController session deletion", () => { beforeEach(() => { @@ -171,7 +170,6 @@ describe("SelectorController session deletion", () => { selector.handleInput("\n"); await Bun.sleep(0); - expect(deleteSessionWithArtifacts).toHaveBeenCalledWith(activeSession.path); expect(calls).toEqual([ "editorContainer.clear", diff --git a/packages/coding-agent/test/oauth-flow.test.ts b/packages/coding-agent/test/oauth-flow.test.ts index e5f9dd98b..33b3260c2 100644 --- a/packages/coding-agent/test/oauth-flow.test.ts +++ b/packages/coding-agent/test/oauth-flow.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from "bun:test"; -import { MCPOAuthFlow } from "../src/mcp/oauth-flow"; import { hookFetch } from "../../utils/src/hook-fetch"; +import { MCPOAuthFlow } from "../src/mcp/oauth-flow"; const originalFetch = global.fetch; @@ -271,7 +271,6 @@ describe("mcp oauth flow", () => { expect(serveSpy).toHaveBeenCalledTimes(1); }); - it("fails instead of falling back to a random port when redirectUri is exact", async () => { vi.spyOn(Bun, "serve").mockImplementation(() => { throw new Error("EADDRINUSE");