aboutsummaryrefslogtreecommitdiff
path: root/src/integration/assets/omp
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-23 15:04:14 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-23 15:19:36 +0300
commited11779fcaebea7cc3223c4e221ea43674057891 (patch)
tree13f2962445f60252ef9bb77a44c2033f7f67b5bb /src/integration/assets/omp
parent5954ece9f7e13346a9ae6e7bcabda307280c26ea (diff)
feat: add omp integration
Diffstat (limited to 'src/integration/assets/omp')
-rw-r--r--src/integration/assets/omp/herdr-agent-state.ts289
1 files changed, 289 insertions, 0 deletions
diff --git a/src/integration/assets/omp/herdr-agent-state.ts b/src/integration/assets/omp/herdr-agent-state.ts
new file mode 100644
index 00000000..64c4b02a
--- /dev/null
+++ b/src/integration/assets/omp/herdr-agent-state.ts
@@ -0,0 +1,289 @@
+// installed by herdr
+// safe to edit. this integration only activates inside herdr-managed panes.
+// HERDR_INTEGRATION_ID=omp
+// HERDR_INTEGRATION_VERSION=1
+// @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:omp";
+
+function enabled() {
+ return HERDR_ENV === "1" && !!socketPath && !!paneId;
+}
+
+function sendRequest(request: unknown): Promise<void> {
+ if (!enabled()) {
+ return Promise.resolve();
+ }
+
+ return new Promise((resolve) => {
+ let done = false;
+ const finish = () => {
+ if (done) return;
+ done = true;
+ socket.destroy();
+ resolve();
+ };
+
+ const socket = createConnection(socketPath!);
+ socket.on("error", finish);
+ socket.on("connect", () => socket.write(`${JSON.stringify(request)}\n`));
+ socket.on("data", finish);
+ socket.on("end", finish);
+ const timeout = setTimeout(finish, 500);
+ timeout.unref?.();
+ });
+}
+
+type AgentState = "working" | "blocked" | "idle";
+
+type QueuedState = {
+ state: AgentState;
+ message?: string;
+ seq: number;
+};
+
+const idleDebounceMs = parseDurationEnv("HERDR_OMP_IDLE_DEBOUNCE_MS", 250);
+const retryGraceMs = parseDurationEnv("HERDR_OMP_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;
+
+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 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: {
+ pane_id: paneId,
+ source,
+ agent: "omp",
+ state,
+ message,
+ seq,
+ },
+ });
+}
+
+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";
+}
+
+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: "omp",
+ seq: nextReportSeq(),
+ },
+ });
+}
+
+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;
+
+ 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() {
+ const next = desiredState();
+ if (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 (!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", () => {
+ publishState();
+ });
+
+ pi.on("agent_start", () => {
+ clearPendingTimers();
+ clearFailureState();
+ agentActive = true;
+ publishState();
+ });
+
+ pi.on("agent_end", (event) => {
+ if (!agentActive) {
+ // OMP 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 () => {
+ clearPendingTimers();
+ await releaseAgent();
+ });
+}