diff --git a/pi/files.linux/agent/settings.json b/pi/files.linux/agent/settings.json index 5ab3fc0..d6ffa00 100644 --- a/pi/files.linux/agent/settings.json +++ b/pi/files.linux/agent/settings.json @@ -7,5 +7,9 @@ "lsp": { "hookMode": "edit_write" }, - "hideThinkingBlock": false -} \ No newline at end of file + "hideThinkingBlock": false, + "slowtool": { + "timeoutSeconds": 120, + "enabled": true + } +} diff --git a/pi/files/agent/extensions/slowtool.ts b/pi/files/agent/extensions/slowtool.ts index 5aa9a5a..d8fc12b 100644 --- a/pi/files/agent/extensions/slowtool.ts +++ b/pi/files/agent/extensions/slowtool.ts @@ -14,6 +14,9 @@ */ import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as os from "node:os"; interface ToolTimeout { toolCallId: string; @@ -28,6 +31,8 @@ interface ToolTimeout { // Configuration let timeoutSeconds = 30; let enabled = true; +const SETTINGS_NAMESPACE = "slowtool"; +const globalSettingsPath = path.join(os.homedir(), ".pi", "agent", "settings.json"); // Track running tools const runningTools: Map = new Map(); @@ -43,6 +48,55 @@ function formatDuration(ms: number): string { return `${minutes}m ${remainingSeconds}s`; } +function asRecord(value: unknown): Record | undefined { + if (!value || typeof value !== "object") return undefined; + return value as Record; +} + +function readSettingsFile(filePath: string): Record { + try { + if (!fs.existsSync(filePath)) return {}; + const raw = fs.readFileSync(filePath, "utf-8"); + const parsed = JSON.parse(raw) as unknown; + return asRecord(parsed) ?? {}; + } catch { + return {}; + } +} + +function loadGlobalConfig(): { timeoutSeconds: number; enabled: boolean } { + const settings = readSettingsFile(globalSettingsPath); + const slowtoolSettings = asRecord(settings[SETTINGS_NAMESPACE]); + + const configuredTimeout = slowtoolSettings?.timeoutSeconds; + const nextTimeout = + typeof configuredTimeout === "number" && Number.isFinite(configuredTimeout) && configuredTimeout >= 1 + ? Math.floor(configuredTimeout) + : 30; + + const configuredEnabled = slowtoolSettings?.enabled; + const nextEnabled = typeof configuredEnabled === "boolean" ? configuredEnabled : true; + + return { timeoutSeconds: nextTimeout, enabled: nextEnabled }; +} + +function saveGlobalConfig(next: { timeoutSeconds: number; enabled: boolean }): boolean { + try { + const settings = readSettingsFile(globalSettingsPath); + const existing = asRecord(settings[SETTINGS_NAMESPACE]) ?? {}; + settings[SETTINGS_NAMESPACE] = { + ...existing, + timeoutSeconds: next.timeoutSeconds, + enabled: next.enabled, + }; + fs.mkdirSync(path.dirname(globalSettingsPath), { recursive: true }); + fs.writeFileSync(globalSettingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf-8"); + return true; + } catch { + return false; + } +} + function getCommandPreview(args: unknown): string | undefined { if (!args) return undefined; const anyArgs = args as Record; @@ -77,6 +131,29 @@ function notifyTimeout(pi: ExtensionAPI, tool: ToolTimeout): void { // ============ EVENT HANDLERS ============ export default function(pi: ExtensionAPI) { + const applyPersistedConfig = () => { + const persisted = loadGlobalConfig(); + timeoutSeconds = persisted.timeoutSeconds; + enabled = persisted.enabled; + }; + + const persistCurrentConfig = (ctx: ExtensionCommandContext): void => { + const ok = saveGlobalConfig({ timeoutSeconds, enabled }); + if (!ok) { + ctx.ui.notify("Failed to persist slowtool settings", "warning"); + } + }; + + applyPersistedConfig(); + + pi.on("session_start", async (_event, _ctx) => { + applyPersistedConfig(); + }); + + pi.on("session_switch", async (_event, _ctx) => { + applyPersistedConfig(); + }); + // Register commands pi.registerCommand("slowtool:timeout", { description: "Set timeout threshold in seconds (default: 30)", @@ -91,6 +168,7 @@ export default function(pi: ExtensionAPI) { return; } timeoutSeconds = newTimeout; + persistCurrentConfig(ctx); ctx.ui.notify(`Timeout set to ${timeoutSeconds}s`, "info"); }, }); @@ -99,6 +177,7 @@ export default function(pi: ExtensionAPI) { description: "Enable slow tool notifications", handler: async (_args: string, ctx: ExtensionCommandContext) => { enabled = true; + persistCurrentConfig(ctx); ctx.ui.notify("Slow tool notifications enabled", "info"); }, }); @@ -107,6 +186,7 @@ export default function(pi: ExtensionAPI) { description: "Disable slow tool notifications", handler: async (_args: string, ctx: ExtensionCommandContext) => { enabled = false; + persistCurrentConfig(ctx); ctx.ui.notify("Slow tool notifications disabled", "info"); }, });