diff options
| author | Eric Yue <yue1995pro@gmail.com> | 2026-06-15 21:50:24 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-06-15 16:50:24 +0300 |
| commit | 07261e09be60ac0c145685fd11f94351cc0e5ba6 (patch) | |
| tree | 771b0180c7a1560189547e6d23ef004ec1cf6d5c /src/integration/assets | |
| parent | 08bb0768ae224c8db04bbe016b050353ce13eee7 (diff) | |
feat: add devin cli detection and restore support (#622)
* feat: add devin cli detection and restore support
refs #606
* fix: keep devin integration session-only
---------
Co-authored-by: Ogulcan Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'src/integration/assets')
| -rw-r--r-- | src/integration/assets/devin/herdr-agent-state.sh | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/src/integration/assets/devin/herdr-agent-state.sh b/src/integration/assets/devin/herdr-agent-state.sh new file mode 100644 index 00000000..83f8c190 --- /dev/null +++ b/src/integration/assets/devin/herdr-agent-state.sh @@ -0,0 +1,173 @@ +#!/bin/sh +# installed by herdr +# managed by herdr; reinstalling or updating the integration overwrites this file. +# add custom hooks beside this file instead of editing it. +# HERDR_INTEGRATION_ID=devin +# HERDR_INTEGRATION_VERSION=1 + +set -eu + +action="${1:-}" +hook_input_file="$(mktemp "${TMPDIR:-/tmp}/herdr-devin-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 + session) ;; + *) exit 0 ;; +esac + +[ "${HERDR_ENV:-}" = "1" ] || exit 0 +[ -n "${HERDR_SOCKET_PATH:-}" ] || exit 0 +[ -n "${HERDR_PANE_ID:-}" ] || exit 0 +command -v python3 >/dev/null 2>&1 || exit 0 + +HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY' +import json +import os +import random +import socket +import subprocess +import time + +SOURCE = "herdr:devin" +AGENT = "devin" + + +def load_hook_input(path: str | None) -> dict: + if not path: + return {} + try: + with open(path, encoding="utf-8") as handle: + content = handle.read() + if not content.strip(): + return {} + parsed = json.loads(content) + return parsed if isinstance(parsed, dict) else {} + except Exception: + return {} + + +def load_session_list(project_dir: str | None): + injected = os.environ.get("HERDR_DEVIN_LIST_JSON") + if injected is not None: + try: + parsed = json.loads(injected) + return parsed if isinstance(parsed, list) else [] + except Exception: + return [] + + cmd = ["devin", "list", "--format", "json"] + try: + result = subprocess.run( + cmd, + cwd=project_dir or None, + capture_output=True, + text=True, + timeout=2, + check=False, + ) + except Exception: + return [] + + if result.returncode != 0: + return [] + + try: + parsed = json.loads(result.stdout) + return parsed if isinstance(parsed, list) else [] + except Exception: + return [] + + +def normalize_path(path: str | None) -> str | None: + if not isinstance(path, str) or not path: + return None + try: + return os.path.realpath(path) + except Exception: + return path + + +def hook_session_id(hook_input: dict) -> str | None: + for key in ("session_id", "sessionId"): + value = hook_input.get(key) + if isinstance(value, str) and value: + return value + return None + + +def hook_event_name(hook_input: dict) -> str: + value = hook_input.get("hook_event_name") + return value if isinstance(value, str) else "" + + +def allow_session_list_fallback(hook_input: dict) -> bool: + event = hook_event_name(hook_input) + if event == "UserPromptSubmit": + return False + if event == "SessionStart" and hook_input.get("source") == "startup": + return False + return True + + +def resolve_session_id(project_dir: str, hook_input: dict) -> str | None: + direct = hook_session_id(hook_input) + if direct: + return direct + if not allow_session_list_fallback(hook_input): + return None + + entries = load_session_list(project_dir) + project_dir = normalize_path(project_dir) + for entry in entries: + if not isinstance(entry, dict): + continue + session_id = entry.get("id") + if not isinstance(session_id, str) or not session_id: + continue + working_directory = normalize_path(entry.get("working_directory")) + if working_directory == project_dir: + return session_id + return None + + +pane_id = os.environ.get("HERDR_PANE_ID") +socket_path = os.environ.get("HERDR_SOCKET_PATH") +project_dir = os.environ.get("DEVIN_PROJECT_DIR") or os.getcwd() +hook_input = load_hook_input(os.environ.get("HERDR_HOOK_INPUT_FILE")) + +if not pane_id or not socket_path: + raise SystemExit(0) + +request_id = f"{SOURCE}:{int(time.time() * 1000)}:{random.randrange(1_000_000):06d}" +report_seq = time.time_ns() + +session_id = resolve_session_id(project_dir, hook_input) +if not session_id: + raise SystemExit(0) +request = { + "id": request_id, + "method": "pane.report_agent_session", + "params": { + "pane_id": pane_id, + "source": SOURCE, + "agent": AGENT, + "agent_session_id": session_id, + "seq": report_seq, + }, +} + +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()) + try: + client.recv(4096) + except Exception: + pass + client.close() +except Exception: + pass +PY |
