diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-09 15:59:34 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-09 15:59:34 +0300 |
| commit | df7880c54b8b5d337421cb4b1dd8daea27008801 (patch) | |
| tree | 4d0cc6290ad71c413bac71cfaab4ef41ea017ecb /src/integration/assets/copilot | |
| parent | eeacb45b4b6ab4446c5bea1331f743fee5e5b9f8 (diff) | |
feat: update agent lifecycle integrations
Diffstat (limited to 'src/integration/assets/copilot')
| -rw-r--r-- | src/integration/assets/copilot/herdr-agent-state.ps1 | 84 | ||||
| -rw-r--r-- | src/integration/assets/copilot/herdr-agent-state.sh | 150 |
2 files changed, 46 insertions, 188 deletions
diff --git a/src/integration/assets/copilot/herdr-agent-state.ps1 b/src/integration/assets/copilot/herdr-agent-state.ps1 index 61cec0c9..8951eacb 100644 --- a/src/integration/assets/copilot/herdr-agent-state.ps1 +++ b/src/integration/assets/copilot/herdr-agent-state.ps1 @@ -2,10 +2,11 @@ # managed by herdr; reinstalling or updating the integration overwrites this file. # add custom hooks beside this file instead of editing it. # HERDR_INTEGRATION_ID=copilot -# HERDR_INTEGRATION_VERSION=1 +# HERDR_INTEGRATION_VERSION=2 if ($env:HERDR_ENV -ne "1") { exit 0 } if ([string]::IsNullOrWhiteSpace($env:HERDR_PANE_ID)) { exit 0 } +if ([string]::IsNullOrWhiteSpace($env:HERDR_SOCKET_PATH)) { exit 0 } $inputText = [Console]::In.ReadToEnd() try { @@ -31,74 +32,21 @@ function Normalize-Event { return $Event.Replace("_", "").Replace("-", "").ToLowerInvariant() } -function Infer-Event { - $explicit = First-Text @("hook_event_name", "hookEventName") - if ($explicit) { return $explicit } - if (First-Text @("notification_type", "notificationType")) { return "notification" } - if ($payload.PSObject.Properties.Name -contains "toolResult" -or $payload.PSObject.Properties.Name -contains "tool_result") { return "postToolUse" } - if (($payload.PSObject.Properties.Name -contains "error") -and (First-Text @("tool_name", "toolName"))) { return "postToolUseFailure" } - if (First-Text @("tool_name", "toolName")) { return "preToolUse" } - if (First-Text @("stop_reason", "stopReason")) { return "agentStop" } - if (First-Text @("reason")) { return "sessionEnd" } - if ($payload.PSObject.Properties.Name -contains "prompt") { return "userPromptSubmitted" } - if ( - $payload.PSObject.Properties.Name -contains "initial_prompt" -or - $payload.PSObject.Properties.Name -contains "initialPrompt" -or - $payload.PSObject.Properties.Name -contains "source" -or - (First-Text @("session_id", "sessionId")) - ) { return "sessionStart" } - return "" +$eventName = First-Text @("hook_event_name", "hookEventName") +if ($eventName) { + if ((Normalize-Event $eventName) -ne "sessionstart") { exit 0 } +} elseif ( + ($payload.PSObject.Properties.Name -contains "prompt") -or + (First-Text @("tool_name", "toolName", "notification_type", "notificationType", "stop_reason", "stopReason", "reason")) +) { + exit 0 } -function Has-Initial-Prompt { - $value = $payload.initial_prompt - if ($null -eq $value) { $value = $payload.initialPrompt } - return ($value -is [string] -and -not [string]::IsNullOrWhiteSpace($value)) +$sessionId = $payload.session_id +if ([string]::IsNullOrWhiteSpace($sessionId)) { + $sessionId = $payload.sessionId } +if ([string]::IsNullOrWhiteSpace($sessionId)) { exit 0 } -function Report-State { - param([string]$State, [string]$SessionId) - $seq = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() - $args = @("pane", "report-agent", $env:HERDR_PANE_ID, "--source", "herdr:copilot", "--agent", "copilot", "--state", $State, "--seq", "$seq") - if (-not [string]::IsNullOrWhiteSpace($SessionId)) { - $args += @("--agent-session-id", $SessionId) - } - & herdr @args 2>$null | Out-Null -} - -function Release-Agent { - $seq = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() - & herdr pane release-agent $env:HERDR_PANE_ID --source herdr:copilot --agent copilot --seq $seq 2>$null | Out-Null -} - -try { - $eventKey = Normalize-Event (Infer-Event) - $sessionId = First-Text @("session_id", "sessionId") - $toolName = First-Text @("tool_name", "toolName") - $notificationType = First-Text @("notification_type", "notificationType") - $stopReason = First-Text @("stop_reason", "stopReason") - $reason = First-Text @("reason") - - if ($eventKey -eq "sessionstart") { - Report-State ($(if (Has-Initial-Prompt) { "working" } else { "idle" })) $sessionId - } elseif ($eventKey -in @("userpromptsubmit", "userpromptsubmitted")) { - Report-State "working" $sessionId - } elseif ($eventKey -eq "pretooluse") { - Report-State ($(if ($toolName -in @("ask_user", "exit_plan_mode")) { "blocked" } else { "working" })) $sessionId - } elseif ($eventKey -in @("posttooluse", "posttoolusefailure")) { - if ($toolName -ne "report_intent") { Report-State "working" $sessionId } - } elseif ($eventKey -eq "notification") { - if ($notificationType -in @("permission_prompt", "elicitation_dialog")) { - Report-State "blocked" $sessionId - } elseif ($notificationType -eq "agent_idle") { - Report-State "idle" $sessionId - } - } elseif ($eventKey -in @("stop", "agentstop", "sessionstop")) { - if ([string]::IsNullOrWhiteSpace($stopReason) -or $stopReason -eq "end_turn") { - Report-State "idle" $sessionId - } - } elseif ($eventKey -eq "sessionend") { - if ($reason -in @("user_exit", "abort")) { Release-Agent } - } -} catch { -} +$seq = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() +& herdr pane report-agent-session $env:HERDR_PANE_ID --source herdr:copilot --agent copilot --agent-session-id $sessionId --seq $seq 2>$null | Out-Null diff --git a/src/integration/assets/copilot/herdr-agent-state.sh b/src/integration/assets/copilot/herdr-agent-state.sh index 766b562a..1ae0362b 100644 --- a/src/integration/assets/copilot/herdr-agent-state.sh +++ b/src/integration/assets/copilot/herdr-agent-state.sh @@ -2,18 +2,8 @@ # installed by herdr # managed by herdr; reinstalling or updating the integration overwrites this file. # add custom hooks beside this file instead of editing it. -# High-level state machine: -# - SessionStart reports working when Copilot includes an initial prompt, else idle. -# - UserPromptSubmit reports working for the active turn. -# - PreToolUse reports blocked for ask_user and exit_plan_mode, else working. -# - notification permission_prompt / elicitation_dialog reports blocked. -# - PostToolUse / PostToolUseFailure clears handled blockers back to working. -# - agentStop / Stop with end_turn reports idle. -# - SessionEnd only releases ownership for real exits such as user_exit / abort. -# Official Copilot hooks reference: -# https://docs.github.com/en/copilot/reference/hooks-reference # HERDR_INTEGRATION_ID=copilot -# HERDR_INTEGRATION_VERSION=1 +# HERDR_INTEGRATION_VERSION=2 set -eu @@ -34,7 +24,6 @@ import socket import time source = "herdr:copilot" -agent = "copilot" 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") @@ -64,120 +53,41 @@ def first_text(*keys): def normalize_event(event): return event.replace("_", "").replace("-", "").lower() -def infer_event(): - explicit = first_text("hook_event_name", "hookEventName") - if explicit: - return explicit - if first_text("notification_type", "notificationType"): - return "notification" - if "toolResult" in hook_input or "tool_result" in hook_input: - return "postToolUse" - if "error" in hook_input and first_text("tool_name", "toolName"): - return "postToolUseFailure" - if first_text("tool_name", "toolName"): - return "preToolUse" - if first_text("stop_reason", "stopReason"): - return "agentStop" - if first_text("reason"): - return "sessionEnd" - if "prompt" in hook_input: - return "userPromptSubmitted" - if ( - "initial_prompt" in hook_input - or "initialPrompt" in hook_input - or "source" in hook_input - or first_text("session_id", "sessionId") - ): - return "sessionStart" - return "" - -def has_initial_prompt(): - value = hook_input.get("initial_prompt", hook_input.get("initialPrompt")) - return isinstance(value, str) and bool(value.strip()) +event = first_text("hook_event_name", "hookEventName") +if event: + if normalize_event(event) != "sessionstart": + raise SystemExit(0) +elif "prompt" in hook_input or first_text("tool_name", "toolName", "notification_type", "notificationType", "stop_reason", "stopReason", "reason"): + raise SystemExit(0) -def is_user_blocking_tool(name): - return name in ("ask_user", "exit_plan_mode") +session_id = hook_input.get("session_id") +if not isinstance(session_id, str) or not session_id: + session_id = hook_input.get("sessionId") +if not isinstance(session_id, str) or not session_id: + raise SystemExit(0) -def is_ignored_post_tool(name): - # report_intent can arrive after ask_user has already blocked the turn but - # before the user answers. Suppress it so it cannot clear that blocker. - return name in ("report_intent",) +request = { + "id": f"{source}:{int(time.time() * 1000)}:{random.randrange(1_000_000):06d}", + "method": "pane.report_agent_session", + "params": { + "pane_id": pane_id, + "source": source, + "agent": "copilot", + "agent_session_id": session_id, + "seq": time.time_ns(), + }, +} -def send(method, params): - request = { - "id": f"{source}:{int(time.time() * 1000)}:{random.randrange(1_000_000):06d}", - "method": method, - "params": { - "pane_id": pane_id, - "source": source, - "agent": agent, - "seq": time.time_ns(), - **params, - }, - } +try: + client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + client.settimeout(0.5) + client.connect(socket_path) + client.sendall((json.dumps(request) + "\n").encode("utf-8")) try: - client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - client.settimeout(0.5) - client.connect(socket_path) - client.sendall((json.dumps(request) + "\n").encode("utf-8")) - try: - client.recv(4096) - except Exception: - pass - client.close() + client.recv(4096) except Exception: pass - -def report(state, session_id): - params = {"state": state} - if session_id: - params["agent_session_id"] = session_id - send("pane.report_agent", params) - -def release(): - send("pane.release_agent", {}) - -try: - event = infer_event() - event_key = normalize_event(event) - session_id = first_text("session_id", "sessionId") - tool_name = first_text("tool_name", "toolName") - notification_type = first_text("notification_type", "notificationType") - stop_reason = first_text("stop_reason", "stopReason") - reason = first_text("reason") - - if event_key == "sessionstart": - report("working" if has_initial_prompt() else "idle", session_id) - elif event_key in ("userpromptsubmit", "userpromptsubmitted"): - report("working", session_id) - elif event_key == "pretooluse": - report("blocked" if is_user_blocking_tool(tool_name) else "working", session_id) - elif event_key in ("posttooluse", "posttoolusefailure"): - if is_ignored_post_tool(tool_name): - pass - else: - report("working", session_id) - elif event_key == "notification": - if notification_type in ("permission_prompt", "elicitation_dialog"): - report("blocked", session_id) - elif notification_type == "agent_idle": - report("idle", session_id) - else: - pass - elif event_key in ("stop", "agentstop", "sessionstop"): - if not stop_reason or stop_reason == "end_turn": - report("idle", session_id) - else: - pass - elif event_key == "sessionend": - # Copilot can emit reason=complete at normal turn boundaries. Keep - # ownership until a real session exit so Herdr can still track later turns. - if reason in ("user_exit", "abort"): - release() - else: - pass - else: - pass + client.close() except Exception: pass PY |
