fold omp runtime config and remaining pi extensions into source
This commit is contained in:
parent
6c3a47f350
commit
bacafdf715
4 changed files with 446 additions and 6 deletions
|
|
@ -3,11 +3,20 @@
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"firecrawl": {
|
"firecrawl": {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "firecrawl-mcp"],
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"firecrawl-mcp"
|
||||||
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"FIRECRAWL_API_KEY": "123",
|
"FIRECRAWL_API_KEY": "123",
|
||||||
"FIRECRAWL_API_URL": "http://bazzite.local:3002"
|
"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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,18 +29,18 @@ symbolPreset: nerd
|
||||||
theme:
|
theme:
|
||||||
dark: dark-gruvbox
|
dark: dark-gruvbox
|
||||||
light: light
|
light: light
|
||||||
setupVersion: 1
|
setupVersion: 2
|
||||||
modelRoles:
|
modelRoles:
|
||||||
task: llama-swap.byte/qwen36-27b-711
|
task: llama-swap.miche/godoter-27b
|
||||||
plan: zai-coding/glm-5.2:xhigh
|
plan: zai-coding/glm-5.2:xhigh
|
||||||
slow: deepseek/deepseek-v4-pro:high
|
slow: deepseek/deepseek-v4-pro:high
|
||||||
vision: llama-swap.byte/qwen36-35b-heretic-apex
|
vision: llama-swap.byte/qwen36-35b-heretic-apex
|
||||||
tiny: llama-swap.byte/gemma4-26b-hauhau
|
tiny: llama-swap.byte/gemma4-26b-hauhau
|
||||||
default: deepseek/deepseek-v4-flash:high
|
|
||||||
smol: llama-swap.byte/kat-coder-v2.5-dev
|
smol: llama-swap.byte/kat-coder-v2.5-dev
|
||||||
commit: llama-swap.byte/gemma4-26b-hauhau
|
commit: llama-swap.byte/gemma4-26b-hauhau
|
||||||
designer: zai-coding/glm-5.2
|
designer: zai-coding/glm-5.2
|
||||||
advisor: zai-coding/glm-5.2
|
advisor: zai-coding/glm-5.2
|
||||||
|
default: openrouter/stealth/ox-alpha:max
|
||||||
retry:
|
retry:
|
||||||
fallbackChains:
|
fallbackChains:
|
||||||
default:
|
default:
|
||||||
|
|
@ -73,5 +73,5 @@ tui:
|
||||||
textSizing: false
|
textSizing: false
|
||||||
defaultThinkingLevel: auto
|
defaultThinkingLevel: auto
|
||||||
personality: pragmatic
|
personality: pragmatic
|
||||||
hideThinkingBlock: false
|
hideThinkingBlock: true
|
||||||
readLineNumbers: true
|
readLineNumbers: true
|
||||||
394
dot_pi/agent/extensions/herdr-agent-state.ts
Normal file
394
dot_pi/agent/extensions/herdr-agent-state.ts
Normal file
|
|
@ -0,0 +1,394 @@
|
||||||
|
// 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<boolean> {
|
||||||
|
if (!enabled()) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let done = false;
|
||||||
|
let timeout: ReturnType<typeof setTimeout> | 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<void> {
|
||||||
|
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<string, unknown>): Record<string, unknown> {
|
||||||
|
if (currentAgentSessionPath) {
|
||||||
|
return { ...params, agent_session_path: currentAgentSessionPath };
|
||||||
|
}
|
||||||
|
if (currentAgentSessionId) {
|
||||||
|
return { ...params, agent_session_id: currentAgentSessionId };
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentSessionRef(): Record<string, unknown> | undefined {
|
||||||
|
if (currentAgentSessionPath) {
|
||||||
|
return { agent_session_path: currentAgentSessionPath };
|
||||||
|
}
|
||||||
|
if (currentAgentSessionId) {
|
||||||
|
return { agent_session_id: currentAgentSessionId };
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportSession(): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
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<typeof setTimeout> | undefined;
|
||||||
|
let retryTimer: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
let rootSession = false;
|
||||||
|
|
||||||
|
function clearTimer(timer: ReturnType<typeof setTimeout> | 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
37
dot_pi/agent/extensions/private_notify.ts
Normal file
37
dot_pi/agent/extensions/private_notify.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* 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");
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue