diff --git a/dot_omp/private_agent/mcp.json b/dot_omp/private_agent/mcp.json index c92581f..5525ba0 100644 --- a/dot_omp/private_agent/mcp.json +++ b/dot_omp/private_agent/mcp.json @@ -3,20 +3,11 @@ "mcpServers": { "firecrawl": { "command": "npx", - "args": [ - "-y", - "firecrawl-mcp" - ], + "args": ["-y", "firecrawl-mcp"], "env": { "FIRECRAWL_API_KEY": "123", "FIRECRAWL_API_URL": "http://bazzite.local:3002" } - }, - "godot-mcp-pro": { - "command": "node", - "args": [ - "/home/melon/Desktop/godot-mcp-pro-v1.16.0/server/build/index.js" - ] } } -} \ No newline at end of file +} diff --git a/dot_omp/private_agent/private_config.yml b/dot_omp/private_agent/private_config.yml index 98c3579..e12d5fe 100644 --- a/dot_omp/private_agent/private_config.yml +++ b/dot_omp/private_agent/private_config.yml @@ -29,18 +29,18 @@ symbolPreset: nerd theme: dark: dark-gruvbox light: light -setupVersion: 2 +setupVersion: 1 modelRoles: - task: llama-swap.miche/godoter-27b + task: llama-swap.byte/qwen36-27b-711 plan: zai-coding/glm-5.2:xhigh slow: deepseek/deepseek-v4-pro:high vision: llama-swap.byte/qwen36-35b-heretic-apex tiny: llama-swap.byte/gemma4-26b-hauhau + default: deepseek/deepseek-v4-flash:high smol: llama-swap.byte/kat-coder-v2.5-dev commit: llama-swap.byte/gemma4-26b-hauhau designer: zai-coding/glm-5.2 advisor: zai-coding/glm-5.2 - default: openrouter/stealth/ox-alpha:max retry: fallbackChains: default: @@ -73,5 +73,5 @@ tui: textSizing: false defaultThinkingLevel: auto personality: pragmatic -hideThinkingBlock: true +hideThinkingBlock: false readLineNumbers: true \ No newline at end of file diff --git a/dot_pi/agent/extensions/caveman-native.js.tmpl b/dot_pi/agent/extensions/caveman-native.js.tmpl deleted file mode 100644 index dbf7d1a..0000000 --- a/dot_pi/agent/extensions/caveman-native.js.tmpl +++ /dev/null @@ -1,789 +0,0 @@ -// caveman:native-pi — GENERATED by `caveman enable pi`. -process.env.CAVEMAN_PI_HOOK_CMD ??= "[\"{{ .chezmoi.homeDir }}/.npm-global/bin/caveman\"]"; -// ../pi-extension/src/index.ts -import { readFileSync as readFileSync2 } from "node:fs"; -import { homedir as homedir3 } from "node:os"; -import { join as join4 } from "node:path"; -import { Type } from "typebox"; - -// ../pi-extension/src/lifecycle.ts -import { execFile } from "node:child_process"; -import { createHash } from "node:crypto"; -import { homedir } from "node:os"; -import { join as join2 } from "node:path"; - -// ../pi-extension/src/portable-command.ts -import { existsSync, readFileSync, statSync } from "node:fs"; -import { dirname, extname, isAbsolute, join, resolve } from "node:path"; -function parseWindowsNodeShim(source) { - for (const line of source.split(/\r?\n/)) { - if (!/(?:\bnode(?:\.exe)?\b|_prog)/i.test(line) || !/%\*/.test(line)) continue; - const match = line.match(/"%(?:dp0%|~dp0)\\([^"\r\n]+\.(?:cjs|mjs|js))"\s+%\*/i) ?? line.match(/"([A-Za-z]:[\\/][^"\r\n]+\.(?:cjs|mjs|js))"\s+%\*/i); - if (match) return match[1]; - } - return null; -} -function envValue(env, name) { - const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase()); - return key === void 0 ? void 0 : env[key]; -} -function resolveWindowsCommand(command, env) { - const pathExt = envValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD"; - const names = extname(command) ? [command] : pathExt.split(";").map((extension) => `${command}${extension.startsWith(".") ? extension : `.${extension}`}`); - if (isAbsolute(command) || /[\\/]/.test(command)) { - for (const name of names) if (existsSync(name)) return name; - return existsSync(command) ? command : void 0; - } - for (const directory of (envValue(env, "PATH") ?? "").split(";")) { - if (!directory) continue; - for (const name of names) { - const candidate = join(directory, name); - if (existsSync(candidate)) return candidate; - } - } - return void 0; -} -function portableInvocation(command, args, platform = process.platform, env = process.env) { - if (platform !== "win32") return { command, args: [...args] }; - const executable = resolveWindowsCommand(command, env) ?? command; - if (!/\.(?:cmd|bat)$/i.test(executable)) return { command: executable, args: [...args] }; - const stat = statSync(executable); - if (!stat.isFile() || stat.size > 256 * 1024) { - throw new Error(`cannot safely launch Windows command shim: ${executable}`); - } - const shimScript = parseWindowsNodeShim(readFileSync(executable, "utf8")); - if (!shimScript) { - throw new Error(`cannot safely launch non-Node Windows command shim: ${executable}; install a native .exe`); - } - const script = /^[A-Za-z]:[\\/]/.test(shimScript) ? shimScript : resolve(dirname(executable), ...shimScript.split(/[\\/]+/)); - if (!statSync(script).isFile()) { - throw new Error(`Windows command shim target is missing: ${script}`); - } - return { command: process.execPath, args: [script, ...args] }; -} - -// ../pi-extension/src/protocol.ts -var MAX_CONTEXT_BYTES = 64 * 1024; -var MAX_MESSAGE_BYTES = 4 * 1024; -var MAX_OUTPUT_REPLACEMENT_BYTES = 2 * 1024 * 1024; -var MAX_RECOVERY_REF_BYTES = 1024; -var MAX_DECISION_ID_BYTES = 256; -var MAX_TOOL_OUTPUT_BYTES = 2 * 1024 * 1024; -function fits(value, max) { - return typeof value === "string" && Buffer.byteLength(value, "utf8") <= max; -} -function sanitizeHookResponse(raw) { - if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {}; - const value = raw; - const out = {}; - if (fits(value.context, MAX_CONTEXT_BYTES)) out.context = value.context; - if (fits(value.message, MAX_MESSAGE_BYTES)) out.message = value.message; - if (fits(value.output_replacement, MAX_OUTPUT_REPLACEMENT_BYTES)) out.output_replacement = value.output_replacement; - if (fits(value.recovery_ref, MAX_RECOVERY_REF_BYTES)) out.recovery_ref = value.recovery_ref; - if (fits(value.decision_id, MAX_DECISION_ID_BYTES)) out.decision_id = value.decision_id; - if (typeof value.action === "string") out.action = value.action; - const hso = value.hookSpecificOutput; - if (hso && typeof hso === "object" && !Array.isArray(hso)) { - const nested = hso; - const cleaned = {}; - if (typeof nested.hookEventName === "string") cleaned.hookEventName = nested.hookEventName; - if (fits(nested.additionalContext, MAX_CONTEXT_BYTES)) cleaned.additionalContext = nested.additionalContext; - if (fits(nested.updatedToolOutput, MAX_OUTPUT_REPLACEMENT_BYTES)) cleaned.updatedToolOutput = nested.updatedToolOutput; - out.hookSpecificOutput = cleaned; - } - return out; -} -function additionalContextOf(response) { - const context = response?.hookSpecificOutput?.additionalContext ?? response?.context; - return context ? context : void 0; -} -function outputReplacementOf(response) { - const replacement = response?.hookSpecificOutput?.updatedToolOutput ?? response?.output_replacement; - return replacement ? replacement : void 0; -} -var ROUTES_BY_API = { - "anthropic-messages": "/w/pi", - "openai-completions": "/w/pi/openai/v1", - "openai-responses": "/w/pi/openai/v1", - "google-generative-ai": "/w/pi/v1beta" -}; -function routeForApi(gateway, api) { - const path = api ? ROUTES_BY_API[api] : void 0; - return path ? joinUrl(gateway, path) : void 0; -} -function joinUrl(base, path) { - return `${base.replace(/\/+$/, "")}${path}`; -} -function isLoopbackUrl(url) { - try { - const host = new URL(url).hostname; - return host === "127.0.0.1" || host === "::1" || host === "[::1]" || host === "localhost"; - } catch { - return false; - } -} - -// ../pi-extension/src/lifecycle.ts -var HOOK_TIMEOUT_MS = 2e3; -var SESSION_START_TIMEOUT_MS = 6e3; -var HOOK_MAX_BUFFER = 2 * 1024 * 1024; -function resolveHookInvocations(env = process.env) { - const localBin = join2(env.CAVEMAN_HOME || join2(homedir(), ".caveman"), "bin", "caveman"); - const candidates = [ - { command: "caveman", args: [] }, - { command: "cave", args: [] }, - { command: localBin, args: [] } - ]; - const stamped = env.CAVEMAN_PI_HOOK_CMD; - if (stamped) { - try { - const parsed = JSON.parse(stamped); - if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((part) => typeof part === "string" && part.length > 0)) { - candidates.unshift({ command: parsed[0], args: parsed.slice(1) }); - } - } catch { - } - } - return candidates; -} -var TRY_NEXT = "__caveman_try_next__"; -var HookBridge = class { - invocations; - constructor(invocations = resolveHookInvocations()) { - this.invocations = invocations; - } - // call runs one native-hook event. Fail-open by contract: any spawn error, - // timeout, or unparseable output resolves to undefined. - async call(event, payload) { - const input = JSON.stringify({ event_name: event, surface: "cli", ...payload }); - for (const invocation of this.invocations) { - const raw = await this.invoke(invocation, event, input); - if (raw === TRY_NEXT) continue; - if (raw === void 0 || !raw.trim()) return void 0; - try { - return sanitizeHookResponse(JSON.parse(raw)); - } catch { - return void 0; - } - } - return void 0; - } - invoke(invocation, event, input) { - return new Promise((resolve2) => { - let portable; - try { - portable = portableInvocation(invocation.command, [...invocation.args, "native-hook", "pi", event]); - } catch { - resolve2(TRY_NEXT); - return; - } - const child = execFile( - portable.command, - portable.args, - { timeout: event === "SessionStart" ? SESSION_START_TIMEOUT_MS : HOOK_TIMEOUT_MS, maxBuffer: HOOK_MAX_BUFFER, encoding: "utf8" }, - (error, stdout) => { - const timedOut = !!error && error.killed === true; - if (error && stdout === "") return resolve2(timedOut ? void 0 : TRY_NEXT); - resolve2(stdout); - } - ); - child.stdin?.on("error", () => { - }); - child.stdin?.end(input); - }); - } -}; -function promptDigest(prompt) { - return { - bytes: Buffer.byteLength(prompt, "utf8"), - sha256: `sha256:${createHash("sha256").update(prompt, "utf8").digest("hex")}` - }; -} -function taskType(text) { - const lower = text.toLowerCase(); - const has = (...terms) => terms.some((term) => lower.includes(term)); - if (has("migration", "migrate", "schema change", "backfill", "rollback")) return "migration"; - if (has("bug", "fix", "broken", "regression", "crash", "error", "incorrect")) return "bugfix"; - if (has("investigate", "diagnose", "root cause", "why does", "trace")) return "investigation"; - if (has("refactor", "restructure", "reorganize", "cleanup")) return "refactor"; - if (has("review", "audit", "critique", "assess")) return "review"; - if (has("verify", "verification", "prove", "validate", "check that")) return "verification"; - if (has("build", "implement", "add", "create", "ship", "feature")) return "feature"; - return "general"; -} -var TASK_STOPWORDS = /* @__PURE__ */ new Set(["about", "after", "agent", "before", "build", "change", "code", "create", "from", "have", "help", "implement", "into", "make", "please", "project", "repository", "should", "spec", "task", "that", "then", "there", "these", "they", "this", "through", "user", "want", "what", "when", "where", "which", "with", "would", "your"]); -function taskTerms(text) { - const out = []; - const seen = /* @__PURE__ */ new Set(); - for (const raw of text.match(/[A-Za-z][A-Za-z0-9_./-]{2,63}/g) ?? []) { - const term = raw.toLowerCase().replace(/^[-./]+|[-./]+$/g, ""); - if (!term || term.includes("..") || TASK_STOPWORDS.has(term) || seen.has(term)) continue; - if (/^(?:sk|pk|rk|ghp|github_pat|xox[baprs]|akia)[-_]/i.test(term) || /^[a-z0-9_-]{40,}$/i.test(term)) continue; - seen.add(term); - out.push(term); - if (out.length === 12) break; - } - return out; -} -function taskContinuation(text) { - const prompt = text.trim().toLowerCase(); - if (!prompt || prompt.length > 160 || prompt.split(/\s+/).length > 14) return false; - return /^(?:please\s+)?(?:continue|go ahead|keep going|proceed|do (?:it|that)|fix (?:it|that)|retry|try again|explain (?:it|that)|what do you mean|yes|yep|yeah|why\??|how\??)[.!?\s]*$/.test(prompt); -} - -// ../pi-extension/src/provider.ts -var ProviderRouter = class { - pi; - notify; - gateway; - gateOpen = false; - overridden = /* @__PURE__ */ new Set(); - applying = false; - warnedModels = /* @__PURE__ */ new Set(); - constructor(pi, notify) { - this.pi = pi; - this.notify = notify; - } - // openGate is called once per session after the recovery gate held. Refuses - // non-loopback gateways: managed routing needs auth proof v1 does not carry. - async openGate(gateway, ctx) { - if (!isLoopbackUrl(gateway)) { - this.notify("Caveman: direct mode, no compression this session (gateway is not loopback)", "warning"); - return; - } - this.gateway = gateway; - this.gateOpen = true; - await this.apply(ctx.model, ctx); - } - closeGate() { - this.gateOpen = false; - this.clearOverrides(); - } - routing() { - return this.gateOpen && this.overridden.size > 0; - } - // apply routes one model's provider, or restores direct mode when the model - // has no verified route. Called from the gate and from model_select; the - // applying flag swallows the model_select echo of our own setModel call. - async apply(model, ctx) { - if (!this.gateOpen || this.applying || !this.gateway) return; - if (!model) return; - const route = routeForApi(this.gateway, model.api); - let oauth = true; - try { - oauth = ctx.modelRegistry.isUsingOAuth(model); - } catch { - } - if (!route || oauth) { - this.clearOverrides(); - const key = `${model.provider}/${model.id}`; - if (!this.warnedModels.has(key)) { - this.warnedModels.add(key); - const reason = oauth ? "OAuth/subscription credentials are not routed" : `unsupported API "${model.api}"`; - this.notify(`Caveman: pass-through for ${key} (${reason}); no compression`, "warning"); - } - return; - } - this.applying = true; - try { - for (const provider of this.overridden) { - if (provider !== model.provider) { - this.pi.unregisterProvider(provider); - this.overridden.delete(provider); - } - } - this.pi.registerProvider(model.provider, { baseUrl: route }); - this.overridden.add(model.provider); - const refreshed = ctx.modelRegistry.find(model.provider, model.id); - if (!refreshed || !await this.pi.setModel(refreshed)) { - this.clearOverrides(); - this.notify("Caveman: direct mode, no compression this session (model re-resolution failed)", "warning"); - return; - } - } catch { - this.clearOverrides(); - this.notify("Caveman: direct mode, no compression this session (provider override failed)", "warning"); - } finally { - this.applying = false; - } - } - clearOverrides() { - for (const provider of this.overridden) { - try { - this.pi.unregisterProvider(provider); - } catch { - } - } - this.overridden.clear(); - } -}; - -// ../pi-extension/src/recovery.ts -import { execFile as execFile2, spawn } from "node:child_process"; -import { existsSync as existsSync2 } from "node:fs"; -import { homedir as homedir2 } from "node:os"; -import { delimiter, join as join3 } from "node:path"; -var PROBE_TIMEOUT_MS = 2e3; -var CALL_TIMEOUT_MS = 3e4; -var INIT_TIMEOUT_MS = 2e3; -function resolveMcpBinary(env = process.env) { - const explicit = env.CAVEMAN_MCP_BIN?.trim(); - if (explicit) return explicit; - const name = process.platform === "win32" ? "caveman-mcp.exe" : "caveman-mcp"; - for (const dir of (env.PATH ?? "").split(delimiter)) { - if (dir && existsSync2(join3(dir, name))) return join3(dir, name); - } - const local = join3(env.CAVEMAN_HOME || join3(homedir2(), ".caveman"), "bin", name); - return existsSync2(local) ? local : void 0; -} -var RecoveryClient = class { - binary; - child; - initialized = false; - probed; - ensuring; - nextId = 1; - pending = /* @__PURE__ */ new Map(); - buffer = ""; - disposed = false; - constructor(binary = resolveMcpBinary()) { - this.binary = binary; - } - // ensure probes the binary once and brings up an initialized child. Returns - // false (never throws) when recovery cannot hold — the caller passes through. - async ensure() { - if (this.disposed || !this.binary) return false; - if (this.child && this.initialized) return true; - this.ensuring ??= this.bringUp().finally(() => { - this.ensuring = void 0; - }); - return this.ensuring; - } - async bringUp() { - if (this.probed === void 0) this.probed = await this.probe(); - if (!this.probed || this.disposed) return false; - if (this.child && this.initialized) return true; - return this.start(); - } - ready() { - return Boolean(this.child && this.initialized && !this.disposed); - } - async retrieve(handle, query, signal) { - if (!await this.ensure()) { - return { text: JSON.stringify({ error: "cave_recovery_unavailable", message: "caveman-mcp is not available this session" }), isError: true }; - } - try { - const result = await this.call("tools/call", { - name: "caveman_retrieve", - arguments: { recovery_handle: handle, ...query ? { query } : {} } - }, signal); - const value = result; - const text = (value.content ?? []).map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join(""); - return { text, isError: Boolean(value.isError) }; - } catch (error) { - return { text: JSON.stringify({ error: "cave_recovery_transport", message: error.message }), isError: true }; - } - } - dispose() { - this.disposed = true; - const child = this.child; - this.child = void 0; - this.initialized = false; - for (const [, entry] of this.pending) { - clearTimeout(entry.timer); - entry.reject(new Error("recovery client disposed")); - } - this.pending.clear(); - this.stop(child); - void this.ensuring?.then(() => { - const late = this.child; - this.child = void 0; - this.stop(late); - }); - } - // resolveMcpBinary appends .exe on win32, but CAVEMAN_MCP_BIN is an operator - // override that can name anything — including a .cmd shim, which execFile - // and spawn both refuse with EFTYPE. Route through the same resolver the hook - // bridge uses so the override is as spawnable as the default. Fail-closed: - // callers treat a throw as "recovery cannot hold" and pass through. - invocation(args) { - try { - return portableInvocation(this.binary, args); - } catch { - return void 0; - } - } - probe() { - return new Promise((resolve2) => { - const portable = this.invocation(["version", "--json"]); - if (!portable) return resolve2(false); - execFile2(portable.command, portable.args, { timeout: PROBE_TIMEOUT_MS, encoding: "utf8" }, (error, stdout) => { - if (error) return resolve2(false); - try { - const parsed = JSON.parse(stdout); - resolve2(Array.isArray(parsed?.capabilities) && parsed.capabilities.includes("mcp_recovery")); - } catch { - resolve2(false); - } - }); - }); - } - async start() { - let child; - try { - const portable = this.invocation([]); - if (!portable) return false; - child = spawn(portable.command, portable.args, { stdio: ["pipe", "pipe", "ignore"] }); - } catch { - return false; - } - this.child = child; - this.initialized = false; - this.buffer = ""; - child.on("error", () => this.onChildGone(child)); - child.on("exit", () => this.onChildGone(child)); - child.stdin?.on("error", () => this.onChildGone(child)); - child.stdout?.setEncoding("utf8"); - child.stdout?.on("data", (chunk) => this.onData(chunk)); - try { - await this.call("initialize", { - protocolVersion: "2024-11-05", - capabilities: {}, - clientInfo: { name: "caveman-pi-extension", version: "0.1.0" } - }, void 0, INIT_TIMEOUT_MS); - this.notify("notifications/initialized"); - if (this.disposed) { - this.onChildGone(child); - this.stop(child); - return false; - } - this.initialized = true; - return true; - } catch { - this.onChildGone(child); - this.stop(child); - return false; - } - } - stop(child) { - if (!child) return; - try { - child.stdin?.end(); - } catch { - } - const term = setTimeout(() => { - try { - child.kill("SIGTERM"); - } catch { - } - }, 500); - const kill = setTimeout(() => { - try { - child.kill("SIGKILL"); - } catch { - } - }, 2e3); - child.once("exit", () => { - clearTimeout(term); - clearTimeout(kill); - }); - } - onChildGone(child) { - if (this.child !== child) return; - this.child = void 0; - this.initialized = false; - for (const [, entry] of this.pending) { - clearTimeout(entry.timer); - entry.reject(new Error("caveman-mcp exited")); - } - this.pending.clear(); - } - onData(chunk) { - this.buffer += chunk; - let newline; - while ((newline = this.buffer.indexOf("\n")) !== -1) { - const line = this.buffer.slice(0, newline).trim(); - this.buffer = this.buffer.slice(newline + 1); - if (!line) continue; - let message; - try { - message = JSON.parse(line); - } catch { - continue; - } - if (typeof message.id !== "number") continue; - const entry = this.pending.get(message.id); - if (!entry) continue; - this.pending.delete(message.id); - clearTimeout(entry.timer); - if (message.error) entry.reject(new Error(message.error.message || "caveman-mcp error")); - else entry.resolve(message.result); - } - } - call(method, params, signal, timeoutMs = CALL_TIMEOUT_MS) { - const child = this.child; - if (!child?.stdin?.writable) return Promise.reject(new Error("caveman-mcp not running")); - if (signal?.aborted) return Promise.reject(new Error("retrieve cancelled")); - const id = this.nextId++; - return new Promise((resolve2, reject) => { - const timer = setTimeout(() => { - this.pending.delete(id); - reject(new Error(`caveman-mcp ${method} timed out`)); - }, timeoutMs); - timer.unref?.(); - const onAbort = () => { - this.pending.delete(id); - clearTimeout(timer); - reject(new Error("retrieve cancelled")); - }; - signal?.addEventListener("abort", onAbort, { once: true }); - this.pending.set(id, { - resolve: (value) => { - signal?.removeEventListener("abort", onAbort); - resolve2(value); - }, - reject: (error) => { - signal?.removeEventListener("abort", onAbort); - reject(error); - }, - timer - }); - child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n", (error) => { - if (error) { - this.pending.delete(id); - clearTimeout(timer); - signal?.removeEventListener("abort", onAbort); - reject(error); - } - }); - }); - } - notify(method) { - try { - this.child?.stdin?.write(JSON.stringify({ jsonrpc: "2.0", method }) + "\n"); - } catch { - } - } -}; - -// ../pi-extension/src/tool-output.ts -async function shrinkToolResult(bridge, sessionId, event) { - const text = event.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join(""); - if (!text) return void 0; - if (Buffer.byteLength(text, "utf8") > MAX_TOOL_OUTPUT_BYTES) return void 0; - const response = await bridge.call(event.isError ? "PostToolUseFailure" : "PostToolUse", { - session_id: sessionId, - tool_name: event.toolName, - tool_input: event.input, - tool_output: text - }); - const replacement = outputReplacementOf(response); - if (!replacement) return void 0; - return { content: [{ type: "text", text: replacement }, ...event.content.filter((block) => block.type !== "text")] }; -} - -// ../pi-extension/src/index.ts -var HEALTH_TIMEOUT_MS = 750; -var RECOVERY_TOOL = "caveman_retrieve"; -function cavemanHome() { - return process.env.CAVEMAN_HOME || join4(homedir3(), ".caveman"); -} -function gatewayUrl() { - const env = process.env.CAVE_GATEWAY_URL?.trim(); - if (env) return env; - try { - const config = JSON.parse(readFileSync2(join4(homedir3(), ".caveman-cloud", "config.json"), "utf8")); - if (typeof config.gatewayUrl === "string" && config.gatewayUrl) return config.gatewayUrl; - } catch { - } - return "http://127.0.0.1:8787"; -} -function runStateRecoveryViaMcp(gateway) { - let port; - try { - const url = new URL(gateway); - port = url.port || (url.protocol === "https:" ? "443" : "80"); - } catch { - return void 0; - } - try { - const state = JSON.parse(readFileSync2(join4(cavemanHome(), "run", `${port}.json`), "utf8")); - if (state?.schema !== "caveman.proxy.run.v1") return void 0; - if (typeof state.pid !== "number" || typeof state.instance_token !== "string") return void 0; - if (state.owner !== "wrap" && state.owner !== "start") return void 0; - try { - process.kill(state.pid, 0); - } catch { - return void 0; - } - return Boolean(state.recovery_via_mcp); - } catch { - return void 0; - } -} -async function proxyAliveOnce(gateway) { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS); - try { - const response = await fetch(`${gateway.replace(/\/+$/, "")}/health/live`, { signal: controller.signal }); - await response.body?.cancel(); - return response.ok; - } catch { - return false; - } finally { - clearTimeout(timer); - } -} -async function proxyAlive(gateway) { - for (let attempt = 0; attempt < 3; attempt++) { - if (await proxyAliveOnce(gateway)) return true; - await new Promise((resolve2) => setTimeout(resolve2, 300)); - } - return false; -} -function index_default(pi) { - const bridge = new HookBridge(); - const recovery = new RecoveryClient(); - let router; - let sessionId = "default"; - let coreContext; - let pendingContext = []; - let pendingBytes = 0; - let gateDone = false; - const notify = (ctx, message, kind) => { - if (ctx?.hasUI) ctx.ui.notify(message, kind); - else process.stderr.write(`${message} -`); - }; - const debug = (message) => { - if (process.env.CAVEMAN_PI_DEBUG === "1") process.stderr.write(`[caveman-pi] ${message} -`); - }; - const guardedBase = (fn, label = fn.name || "handler") => (async (...args) => { - try { - debug(`enter ${label}`); - const result = await fn(...args); - debug(`exit ${label}`); - return result; - } catch (error) { - process.stderr.write(`caveman pi extension: ${error.message} -`); - return void 0; - } - }); - const GUARD = (label) => (fn) => guardedBase(fn, label); - const GUARD_session_start = GUARD("session_start"); - const GUARD_model_select = GUARD("model_select"); - const GUARD_before_agent_start = GUARD("before_agent_start"); - const GUARD_turn_start = GUARD("turn_start"); - const GUARD_turn_end = GUARD("turn_end"); - const GUARD_tool_call = GUARD("tool_call"); - const GUARD_tool_result = GUARD("tool_result"); - const GUARD_session_before_compact = GUARD("session_before_compact"); - const GUARD_session_compact = GUARD("session_compact"); - const GUARD_session_shutdown = GUARD("session_shutdown"); - pi.registerTool({ - name: RECOVERY_TOOL, - label: "Retrieve compressed context", - description: "Recover exact original content from a Caveman recovery handle.", - parameters: Type.Object({ - recovery_handle: Type.String({ description: "Exact ccr_ handle returned by Caveman or copied from a <> marker." }), - query: Type.Optional(Type.String({ description: "One broad description covering every detail needed from this handle." })) - }), - async execute(_toolCallId, params, signal) { - const result = await recovery.retrieve(params.recovery_handle, params.query, signal); - if (result.isError) throw new Error(result.text || "caveman_retrieve failed"); - return { content: [{ type: "text", text: result.text }], details: { recovery_handle: params.recovery_handle } }; - } - }); - pi.on("session_start", GUARD_session_start(async (_event, ctx) => { - router ??= new ProviderRouter(pi, (message, kind) => notify(ctx, message, kind)); - gateDone = false; - coreContext = void 0; - pendingContext = []; - pendingBytes = 0; - try { - sessionId = ctx.sessionManager.getSessionId() || "default"; - } catch { - sessionId = "default"; - } - const start = await bridge.call("SessionStart", { session_id: sessionId }); - coreContext = additionalContextOf(start); - if (!start) { - gateDone = true; - notify(ctx, "Caveman: direct mode, no compression this session (caveman native runtime unreachable)", "warning"); - return; - } - const gateway = gatewayUrl(); - const alive = await proxyAlive(gateway); - const published = runStateRecoveryViaMcp(gateway); - const recoveryReady = alive ? await recovery.ensure() : false; - gateDone = true; - if (!alive || published === void 0) { - notify(ctx, "Caveman: direct mode, no compression this session (local proxy not running)", "warning"); - return; - } - if (!published || !recoveryReady) { - notify(ctx, "Caveman: direct mode, no compression this session (recovery not available \u2014 run `caveman doctor pi`)", "warning"); - return; - } - await router.openGate(gateway, ctx); - })); - pi.on("model_select", GUARD_model_select(async (event, ctx) => { - if (gateDone) await router?.apply(event.model, ctx); - })); - pi.on("before_agent_start", GUARD_before_agent_start(async (event, ctx) => { - const response = await bridge.call("UserPromptSubmit", { - session_id: sessionId, - model: ctx.model?.id, - provider: ctx.model?.provider, - prompt: promptDigest(event.prompt), - task_type: taskType(event.prompt), - task_terms: taskTerms(event.prompt), - task_continuation: taskContinuation(event.prompt) - }); - const dynamic = [additionalContextOf(response), ...pendingContext].filter(Boolean); - pendingContext = []; - pendingBytes = 0; - if (!coreContext && dynamic.length === 0) return void 0; - const parts = [event.systemPrompt, coreContext, ...dynamic].filter(Boolean); - return { systemPrompt: parts.join("\n\n") }; - })); - pi.on("turn_start", GUARD_turn_start(() => { - void bridge.call("ModelBefore", { session_id: sessionId }); - })); - pi.on("turn_end", GUARD_turn_end(async () => { - await bridge.call("ModelAfter", { session_id: sessionId }); - void bridge.call("Stop", { session_id: sessionId }); - })); - pi.on("tool_call", GUARD_tool_call(async (event) => { - const response = await bridge.call("PreToolUse", { - session_id: sessionId, - tool_name: event.toolName, - tool_input: event.input - }); - const context = additionalContextOf(response); - const bytes = context ? Buffer.byteLength(context, "utf8") : 0; - if (context && pendingBytes + bytes <= MAX_CONTEXT_BYTES) { - pendingContext.push(context); - pendingBytes += bytes; - } - })); - pi.on("tool_result", GUARD_tool_result((event) => event.toolName === RECOVERY_TOOL ? void 0 : shrinkToolResult(bridge, sessionId, event))); - pi.on("session_before_compact", GUARD_session_before_compact(() => { - void bridge.call("PreCompact", { session_id: sessionId }); - })); - pi.on("session_compact", GUARD_session_compact(async () => { - const response = await bridge.call("PostCompact", { session_id: sessionId }); - const context = additionalContextOf(response); - if (context) coreContext = context; - })); - pi.on("session_shutdown", GUARD_session_shutdown(async () => { - await bridge.call("SessionEnd", { session_id: sessionId }); - router?.closeGate(); - recovery.dispose(); - })); -} -export { - index_default as default -}; diff --git a/dot_pi/agent/extensions/herdr-agent-state.ts b/dot_pi/agent/extensions/herdr-agent-state.ts deleted file mode 100644 index 9048911..0000000 --- a/dot_pi/agent/extensions/herdr-agent-state.ts +++ /dev/null @@ -1,394 +0,0 @@ -// installed by herdr -// managed by herdr; reinstalling or updating the integration overwrites this file. -// add custom hooks/plugins beside this file instead of editing it. -// HERDR_INTEGRATION_ID=pi -// HERDR_INTEGRATION_VERSION=4 -// @ts-nocheck - -import { createConnection } from "node:net"; - -const HERDR_ENV = process.env.HERDR_ENV; -const socketPath = process.env.HERDR_SOCKET_PATH; -const paneId = process.env.HERDR_PANE_ID; -const source = "herdr:pi"; - -function enabled() { - return HERDR_ENV === "1" && !!socketPath && !!paneId; -} - -function sendRequestAttempt(request: unknown, timeoutMs: number): Promise { - if (!enabled()) { - return Promise.resolve(true); - } - - return new Promise((resolve) => { - let done = false; - let timeout: ReturnType | undefined; - const finish = (delivered: boolean) => { - if (done) return; - done = true; - if (timeout) { - clearTimeout(timeout); - } - socket.destroy(); - resolve(delivered); - }; - - const socket = createConnection(socketPath!); - socket.on("error", () => finish(false)); - socket.on("connect", () => socket.write(`${JSON.stringify(request)}\n`)); - socket.on("data", () => finish(true)); - socket.on("end", () => finish(false)); - timeout = setTimeout(() => finish(false), timeoutMs); - timeout.unref?.(); - }); -} - -async function sendRequest(request: unknown): Promise { - if (await sendRequestAttempt(request, 500)) { - return; - } - await sendRequestAttempt(request, 1500); -} - -type AgentState = "working" | "blocked" | "idle"; - -type QueuedState = { - state: AgentState; - message?: string; - seq: number; -}; - -const idleDebounceMs = parseDurationEnv("HERDR_PI_IDLE_DEBOUNCE_MS", 250); -const retryGraceMs = parseDurationEnv("HERDR_PI_RETRY_GRACE_MS", 2500); -const retryableErrorPattern = - /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|http2 request did not get a response|timed? out|timeout|terminated|retry delay/i; -let reportSeq = Date.now() * 1000; -let currentAgentSessionId: string | undefined; -let currentAgentSessionPath: string | undefined; - -function nextReportSeq(): number { - reportSeq += 1; - return reportSeq; -} - -function parseDurationEnv(name: string, fallback: number): number { - const raw = process.env[name]; - if (!raw) { - return fallback; - } - const parsed = Number.parseInt(raw, 10); - if (!Number.isFinite(parsed) || parsed < 0) { - return fallback; - } - return parsed; -} - -function updateSessionRef(ctx: any): void { - try { - const file = ctx?.sessionManager?.getSessionFile?.(); - currentAgentSessionPath = - typeof file === "string" && file.startsWith("/") ? file : undefined; - } catch { - currentAgentSessionPath = undefined; - } - - try { - const id = ctx?.sessionManager?.getSessionId?.(); - currentAgentSessionId = typeof id === "string" && id.length > 0 ? id : undefined; - } catch { - currentAgentSessionId = undefined; - } -} - -function withSessionRef(params: Record): Record { - if (currentAgentSessionPath) { - return { ...params, agent_session_path: currentAgentSessionPath }; - } - if (currentAgentSessionId) { - return { ...params, agent_session_id: currentAgentSessionId }; - } - return params; -} - -function currentSessionRef(): Record | undefined { - if (currentAgentSessionPath) { - return { agent_session_path: currentAgentSessionPath }; - } - if (currentAgentSessionId) { - return { agent_session_id: currentAgentSessionId }; - } - return undefined; -} - -function reportSession(): Promise { - const sessionRef = currentSessionRef(); - if (!sessionRef) { - return Promise.resolve(); - } - - return sendRequest({ - id: `${source}:session:${Date.now()}:${Math.random().toString(36).slice(2)}`, - method: "pane.report_agent_session", - params: { - pane_id: paneId, - source, - agent: "pi", - seq: nextReportSeq(), - ...sessionRef, - }, - }); -} - -function sendState(state: AgentState, message?: string, seq = nextReportSeq()): Promise { - return sendRequest({ - id: `${source}:${Date.now()}:${Math.random().toString(36).slice(2)}`, - method: "pane.report_agent", - params: withSessionRef({ - pane_id: paneId, - source, - agent: "pi", - state, - message, - seq, - }), - }); -} - -function releaseAgent(): Promise { - return sendRequest({ - id: `${source}:release:${Date.now()}:${Math.random().toString(36).slice(2)}`, - method: "pane.release_agent", - params: { - pane_id: paneId, - source, - agent: "pi", - seq: nextReportSeq(), - }, - }); -} - -function shouldReleaseOnSessionShutdown(event: any): boolean { - // Pi tears down and rebinds extension runtimes for internal lifecycle actions - // such as /reload, /new, /resume, and /fork. Those do not mean the pane's - // agent process has exited, and releasing hook authority there can suppress - // legitimate reports from the replacement runtime. Only a user/process quit - // should release Herdr's full-lifecycle authority. - const reason = event?.reason; - return reason === "quit"; -} - -let sendInFlight = false; -let queuedState: QueuedState | undefined; - -function queueState(state: AgentState, message?: string): void { - queuedState = { state, message, seq: nextReportSeq() }; - if (!sendInFlight) { - void drainStateQueue(); - } -} - -async function drainStateQueue(): Promise { - if (sendInFlight) { - return; - } - - sendInFlight = true; - try { - while (queuedState) { - const next = queuedState; - queuedState = undefined; - await sendState(next.state, next.message, next.seq); - } - } finally { - sendInFlight = false; - if (queuedState) { - void drainStateQueue(); - } - } -} - -function lastAssistantMessage(messages: unknown[]): any | undefined { - for (let i = messages.length - 1; i >= 0; i -= 1) { - const message = messages[i] as any; - if (message?.role === "assistant") { - return message; - } - } - return undefined; -} - -function retryableErrorMessage(event: any): string | undefined { - const messages = Array.isArray(event?.messages) ? event.messages : []; - const assistant = lastAssistantMessage(messages); - if (assistant?.stopReason !== "error") { - return undefined; - } - - const errorMessage = String(assistant.errorMessage ?? ""); - if (!retryableErrorPattern.test(errorMessage)) { - return undefined; - } - return errorMessage || "retryable provider error"; -} - -export default function (pi) { - if (!enabled()) { - return; - } - - let agentActive = false; - let retryHoldActive = false; - let failureBlocked = false; - let failureMessage: string | undefined; - let blockedCount = 0; - let blockedMessage: string | undefined; - let lastState: AgentState | undefined; - let lastMessage: string | undefined; - let idleTimer: ReturnType | undefined; - let retryTimer: ReturnType | undefined; - let rootSession = false; - - function clearTimer(timer: ReturnType | undefined) { - if (timer) { - clearTimeout(timer); - } - } - - function clearPendingTimers() { - clearTimer(idleTimer); - clearTimer(retryTimer); - idleTimer = undefined; - retryTimer = undefined; - } - - function clearFailureState() { - retryHoldActive = false; - failureBlocked = false; - failureMessage = undefined; - } - - function desiredState() { - if (blockedCount > 0) { - return { state: "blocked" as const, message: blockedMessage }; - } - if (failureBlocked) { - return { state: "blocked" as const, message: failureMessage }; - } - if (agentActive || retryHoldActive) { - return { state: "working" as const, message: undefined }; - } - return { state: "idle" as const, message: undefined }; - } - - function publishState(force = false) { - const next = desiredState(); - if (!force && next.state === lastState && next.message === lastMessage) { - return; - } - lastState = next.state; - lastMessage = next.message; - queueState(next.state, next.message); - } - - function scheduleIdle() { - clearPendingTimers(); - clearFailureState(); - idleTimer = setTimeout(() => { - idleTimer = undefined; - publishState(); - }, idleDebounceMs); - idleTimer.unref?.(); - } - - function holdForRetry(message: string) { - clearPendingTimers(); - retryHoldActive = true; - failureBlocked = false; - failureMessage = message; - publishState(); - - retryTimer = setTimeout(() => { - retryTimer = undefined; - retryHoldActive = false; - failureBlocked = true; - publishState(); - }, retryGraceMs); - retryTimer.unref?.(); - } - - pi.events.on("herdr:blocked", (data) => { - if (!rootSession) { - return; - } - if (!data?.active) { - blockedCount = Math.max(0, blockedCount - 1); - if (blockedCount === 0) { - blockedMessage = undefined; - } - publishState(); - return; - } - - clearPendingTimers(); - blockedCount += 1; - blockedMessage = data.label; - publishState(); - }); - - pi.on("session_start", (_event, ctx) => { - if (ctx?.hasUI !== true) { - return; - } - rootSession = true; - updateSessionRef(ctx); - void reportSession(); - // A reload can replace this extension mid-run without emitting another agent_start. - agentActive = ctx?.isIdle?.() === false; - publishState(true); - }); - - pi.on("agent_start", (_event, ctx) => { - if (!rootSession) { - return; - } - updateSessionRef(ctx); - void reportSession(); - clearPendingTimers(); - clearFailureState(); - agentActive = true; - publishState(); - }); - - pi.on("agent_end", (event) => { - if (!rootSession) { - return; - } - if (!agentActive) { - // Pi can emit duplicate/late end events while auto-retry is already - // holding the pane in Working. Do not let an unqualified duplicate end - // cancel the retry hold and publish a false Idle. - return; - } - - agentActive = false; - - const retryableMessage = retryableErrorMessage(event); - if (retryableMessage) { - holdForRetry(retryableMessage); - return; - } - - scheduleIdle(); - }); - - pi.on("session_shutdown", async (event) => { - if (!rootSession) { - return; - } - clearPendingTimers(); - if (shouldReleaseOnSessionShutdown(event)) { - await releaseAgent(); - } - }); -} diff --git a/dot_pi/agent/extensions/llama-swap.ts b/dot_pi/agent/extensions/llama-swap.ts index 924aa9c..f2a620a 100644 --- a/dot_pi/agent/extensions/llama-swap.ts +++ b/dot_pi/agent/extensions/llama-swap.ts @@ -357,7 +357,7 @@ export default async function (pi: ExtensionAPI) { "byte.llama", "byte.llama (byte.local GPU)", HOST.startsWith("byte") ? "http://localhost:9292/v1" : "http://byte.local:9292/v1", - "LLAMA_SWAP_API_KEY", + "none", ); // miche.llama — miche.local GPU (localhost when running on miche itself) @@ -366,6 +366,6 @@ export default async function (pi: ExtensionAPI) { "miche.llama", "miche.llama (miche.local GPU)", HOST.startsWith("miche") ? "http://localhost:9292/v1" : "http://miche.local:9292/v1", - "LLAMA_SWAP_API_KEY", + "none", ); } diff --git a/dot_pi/agent/extensions/private_notify.ts b/dot_pi/agent/extensions/private_notify.ts deleted file mode 100644 index e21a67a..0000000 --- a/dot_pi/agent/extensions/private_notify.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Pi Notify Extension (with audible bell) - * - * Fires a terminal bell + native desktop notification when Pi finishes. - * - Bell (\x07): tmux flags the window in the status bar + audible ding - * - OSC 777: Ghostty, iTerm2, WezTerm desktop notification popup - * - OSC 99: Kitty desktop notification popup - * - * Based on the official example from earendil-works/pi, with bell added. - */ -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; - -function notifyOSC777(title: string, body: string): void { - process.stdout.write(`\x1b]777;notify;${title};${body}\x07`); -} - -function notifyOSC99(title: string, body: string): void { - process.stdout.write(`\x1b]99;i=1:d=0;${title}\x1b\\`); - process.stdout.write(`\x1b]99;i=1:p=body;${body}\x1b\\`); -} - -function notify(title: string, body: string): void { - // Audible bell — tmux catches this, flags the window, terminal dings - process.stdout.write("\x07"); - // Desktop notification popup (OSC 777 for Ghostty, OSC 99 for Kitty) - if (process.env.KITTY_WINDOW_ID) { - notifyOSC99(title, body); - } else { - notifyOSC777(title, body); - } -} - -export default function (pi: ExtensionAPI) { - pi.on("agent_end", async () => { - notify("Pi", "Task complete — ready for input"); - }); -} diff --git a/dot_pi/agent/settings.json.tmpl b/dot_pi/agent/settings.json.tmpl index e655e30..8435ef3 100644 --- a/dot_pi/agent/settings.json.tmpl +++ b/dot_pi/agent/settings.json.tmpl @@ -19,9 +19,7 @@ "npm:@danielmeneses/pi-llama-swap", "npm:pi-llama-cpp-stats", "https://github.com/firecrawl/pi-firecrawl", - "git:github.com/davebcn87/pi-autoresearch", - "npm:pi-powerline-footer", - "npm:@firstpick/pi-themes-bundle" + "git:github.com/davebcn87/pi-autoresearch" ], "enableInstallTelemetry": false, "subagents": { @@ -55,4 +53,4 @@ } }, "hideThinkingBlock": true -} +} \ No newline at end of file diff --git a/dot_zshrc.tmpl b/dot_zshrc.tmpl index b53821c..9758eaa 100644 --- a/dot_zshrc.tmpl +++ b/dot_zshrc.tmpl @@ -115,9 +115,6 @@ alias ll='eza -l --color=always --group-directories-first --icons' alias la='eza -a --color=always --group-directories-first --icons' alias lt='eza -aT --color=always --group-directories-first --icons' -# Oh My Pi — self-update before launch -alias omp='omp update && omp' - {{ if eq .os_family "arch" -}} # Pacman / system (Arch-base only) alias update='sudo pacman -Syu'