chore: reformat
This commit is contained in:
@@ -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",
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<boolean> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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();
|
||||
|
||||
@@ -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<string>): void {
|
||||
#rememberSessionDefaultSelectedMCPToolNames(
|
||||
sessionFile: string | null | undefined,
|
||||
toolNames: Iterable<string>,
|
||||
): void {
|
||||
if (!sessionFile) return;
|
||||
this.#sessionDefaultSelectedMCPToolNames.set(
|
||||
path.resolve(sessionFile),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<MCPServer[]> {
|
||||
|
||||
@@ -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();
|
||||
|
||||
+3
-5
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user