diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-23 21:27:26 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-23 21:37:05 +0300 |
| commit | db67e9d74ffafc9c63bdf78fcf712419359f634b (patch) | |
| tree | 9d181b55e65aaf6a2fad2d6cf785bb09a8d9748f /src/integration/assets | |
| parent | 1bab1d2ce21507747ea1f506fb891fe7cd5a13c9 (diff) | |
feat: restore agent sessions
refs #233
Diffstat (limited to 'src/integration/assets')
| -rw-r--r-- | src/integration/assets/claude/herdr-agent-state.sh | 6 | ||||
| -rw-r--r-- | src/integration/assets/codex/herdr-agent-state.sh | 23 | ||||
| -rw-r--r-- | src/integration/assets/hermes/__init__.py | 26 | ||||
| -rw-r--r-- | src/integration/assets/opencode/herdr-agent-state.js | 65 | ||||
| -rw-r--r-- | src/integration/assets/pi/herdr-agent-state.ts | 44 |
5 files changed, 121 insertions, 43 deletions
diff --git a/src/integration/assets/claude/herdr-agent-state.sh b/src/integration/assets/claude/herdr-agent-state.sh index 5042cad8..b76d98e7 100644 --- a/src/integration/assets/claude/herdr-agent-state.sh +++ b/src/integration/assets/claude/herdr-agent-state.sh @@ -2,7 +2,7 @@ # installed by herdr # safe to edit. this hook only activates inside herdr-managed panes. # HERDR_INTEGRATION_ID=claude -# HERDR_INTEGRATION_VERSION=3 +# HERDR_INTEGRATION_VERSION=4 set -eu @@ -60,6 +60,8 @@ if is_subagent and action in ("idle", "release"): request_id = f"{source}:{int(time.time() * 1000)}:{random.randrange(1_000_000):06d}" report_seq = time.time_ns() +session_id = hook_input.get("session_id") +agent_session_id = session_id if isinstance(session_id, str) and session_id else None if action == "release": request = { "id": request_id, @@ -83,6 +85,8 @@ else: "seq": report_seq, }, } + if agent_session_id: + request["params"]["agent_session_id"] = agent_session_id try: client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) diff --git a/src/integration/assets/codex/herdr-agent-state.sh b/src/integration/assets/codex/herdr-agent-state.sh index fbfa24cb..0f0979e3 100644 --- a/src/integration/assets/codex/herdr-agent-state.sh +++ b/src/integration/assets/codex/herdr-agent-state.sh @@ -2,12 +2,14 @@ # installed by herdr # safe to edit. this hook only activates inside herdr-managed panes. # HERDR_INTEGRATION_ID=codex -# HERDR_INTEGRATION_VERSION=3 +# HERDR_INTEGRATION_VERSION=4 set -eu action="${1:-}" -cat >/dev/null 2>/dev/null || true +hook_input_file="$(mktemp "${TMPDIR:-/tmp}/herdr-codex-hook.XXXXXX")" || exit 0 +trap 'rm -f "$hook_input_file"' EXIT HUP INT TERM +cat >"$hook_input_file" 2>/dev/null || true case "$action" in working|idle|blocked|release) ;; @@ -19,7 +21,7 @@ esac [ -n "${HERDR_PANE_ID:-}" ] || exit 0 command -v python3 >/dev/null 2>&1 || exit 0 -HERDR_ACTION="$action" python3 - <<'PY' +HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY' import json import os import random @@ -30,12 +32,25 @@ source = "herdr:codex" action = os.environ.get("HERDR_ACTION", "") pane_id = os.environ.get("HERDR_PANE_ID") socket_path = os.environ.get("HERDR_SOCKET_PATH") +hook_input_file = os.environ.get("HERDR_HOOK_INPUT_FILE") if not pane_id or not socket_path: raise SystemExit(0) +hook_input = {} +if hook_input_file: + try: + with open(hook_input_file, encoding="utf-8") as handle: + content = handle.read() + if content.strip(): + hook_input = json.loads(content) + except Exception: + hook_input = {} + request_id = f"{source}:{int(time.time() * 1000)}:{random.randrange(1_000_000):06d}" report_seq = time.time_ns() +session_id = hook_input.get("session_id") +agent_session_id = session_id if isinstance(session_id, str) and session_id else None if action == "release": request = { "id": request_id, @@ -59,6 +74,8 @@ else: "seq": report_seq, }, } + if agent_session_id: + request["params"]["agent_session_id"] = agent_session_id try: client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) diff --git a/src/integration/assets/hermes/__init__.py b/src/integration/assets/hermes/__init__.py index 03d15a55..6bb0fe8a 100644 --- a/src/integration/assets/hermes/__init__.py +++ b/src/integration/assets/hermes/__init__.py @@ -1,7 +1,7 @@ """Hermes plugin installed by Herdr to report agent lifecycle state.""" # HERDR_INTEGRATION_ID=hermes -# HERDR_INTEGRATION_VERSION=1 +# HERDR_INTEGRATION_VERSION=2 from __future__ import annotations @@ -56,8 +56,19 @@ def _send(method: str, params: dict) -> None: pass -def _report(state: str) -> None: - _send("pane.report_agent", {"state": state}) +def _session_id(kwargs: dict) -> str | None: + value = kwargs.get("session_id") + if isinstance(value, str) and value: + return value + return None + + +def _report(state: str, **kwargs) -> None: + params = {"state": state} + session_id = _session_id(kwargs) + if session_id: + params["agent_session_id"] = session_id + _send("pane.report_agent", params) def _release() -> None: @@ -65,18 +76,15 @@ def _release() -> None: def _working(**kwargs) -> None: - del kwargs - _report("working") + _report("working", **kwargs) def _blocked(**kwargs) -> None: - del kwargs - _report("blocked") + _report("blocked", **kwargs) def _idle(**kwargs) -> None: - del kwargs - _report("idle") + _report("idle", **kwargs) def _finalize(**kwargs) -> None: diff --git a/src/integration/assets/opencode/herdr-agent-state.js b/src/integration/assets/opencode/herdr-agent-state.js index 0fb96217..778fe564 100644 --- a/src/integration/assets/opencode/herdr-agent-state.js +++ b/src/integration/assets/opencode/herdr-agent-state.js @@ -1,7 +1,7 @@ // installed by herdr // safe to edit. this plugin only activates inside herdr-managed panes. // HERDR_INTEGRATION_ID=opencode -// HERDR_INTEGRATION_VERSION=1 +// HERDR_INTEGRATION_VERSION=2 import net from "node:net"; @@ -13,7 +13,13 @@ function nextReportSeq() { return reportSeq; } -function reportState(action) { +function sessionIDFromProperties(properties) { + return typeof properties?.sessionID === "string" && properties.sessionID + ? properties.sessionID + : undefined; +} + +function reportState(action, sessionID) { const paneId = process.env.HERDR_PANE_ID; const socketPath = process.env.HERDR_SOCKET_PATH; @@ -24,24 +30,26 @@ function reportState(action) { const requestId = `${SOURCE}:${Date.now()}:${Math.floor(Math.random() * 1_000_000) .toString() .padStart(6, "0")}`; + const params = + action === "release" + ? { + pane_id: paneId, + source: SOURCE, + agent: "opencode", + seq: nextReportSeq(), + } + : { + pane_id: paneId, + source: SOURCE, + agent: "opencode", + state: action, + seq: nextReportSeq(), + ...(sessionID ? { agent_session_id: sessionID } : {}), + }; const request = { id: requestId, method: action === "release" ? "pane.release_agent" : "pane.report_agent", - params: - action === "release" - ? { - pane_id: paneId, - source: SOURCE, - agent: "opencode", - seq: nextReportSeq(), - } - : { - pane_id: paneId, - source: SOURCE, - agent: "opencode", - state: action, - seq: nextReportSeq(), - }, + params, }; return new Promise((resolve) => { @@ -75,26 +83,33 @@ export const HerdrAgentStatePlugin = async () => { event: async ({ event }) => { const type = event?.type; const properties = event?.properties ?? {}; + const sessionID = sessionIDFromProperties(properties); switch (type) { case "permission.asked": case "question.asked": - await reportState("blocked"); + await reportState("blocked", sessionID); break; case "permission.replied": { const reply = properties.reply ?? properties.response; if (reply === "reject") { - await reportState("idle"); + await reportState("idle", sessionID); } else if (reply === "once" || reply === "always") { - await reportState("working"); + await reportState("working", sessionID); } break; } case "question.replied": - await reportState("working"); + await reportState("working", sessionID); break; case "question.rejected": - await reportState("idle"); + await reportState("idle", sessionID); + break; + case "session.created": + case "session.updated": + if (sessionID) { + await reportState("idle", sessionID); + } break; case "session.status": { const status = @@ -102,14 +117,14 @@ export const HerdrAgentStatePlugin = async () => { ? properties.status : properties.status?.type; if (status === "busy" || status === "retry") { - await reportState("working"); + await reportState("working", sessionID); } else if (status === "idle") { - await reportState("idle"); + await reportState("idle", sessionID); } break; } case "session.idle": - await reportState("idle"); + await reportState("idle", sessionID); break; default: break; diff --git a/src/integration/assets/pi/herdr-agent-state.ts b/src/integration/assets/pi/herdr-agent-state.ts index 26869f1c..450fd48c 100644 --- a/src/integration/assets/pi/herdr-agent-state.ts +++ b/src/integration/assets/pi/herdr-agent-state.ts @@ -1,7 +1,7 @@ // installed by herdr // safe to edit. this integration only activates inside herdr-managed panes. // HERDR_INTEGRATION_ID=pi -// HERDR_INTEGRATION_VERSION=1 +// HERDR_INTEGRATION_VERSION=2 // @ts-nocheck import { createConnection } from "node:net"; @@ -52,6 +52,8 @@ 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; @@ -70,18 +72,45 @@ function parseDurationEnv(name: string, fallback: number): number { 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 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: { + params: withSessionRef({ pane_id: paneId, source, agent: "pi", state, message, seq, - }, + }), }); } @@ -200,9 +229,9 @@ export default function (pi) { return { state: "idle" as const, message: undefined }; } - function publishState() { + function publishState(force = false) { const next = desiredState(); - if (next.state === lastState && next.message === lastMessage) { + if (!force && next.state === lastState && next.message === lastMessage) { return; } lastState = next.state; @@ -220,6 +249,11 @@ export default function (pi) { idleTimer.unref?.(); } + pi.on("session_start", (_event, ctx) => { + updateSessionRef(ctx); + publishState(true); + }); + function holdForRetry(message: string) { clearPendingTimers(); retryHoldActive = true; |
