diff --git a/apps/desktop/CLAUDE.md b/apps/desktop/CLAUDE.md index b08711c1..8f9d11a6 100644 --- a/apps/desktop/CLAUDE.md +++ b/apps/desktop/CLAUDE.md @@ -177,11 +177,17 @@ Gateway section remains. all five agent CLIs, writing through the first-party `importSession` into the same in-process DB. - **Fixed port (differs from the gateway):** `127.0.0.1:4820` - (`AGENT_MONITOR_PORT` in `src/shared/contracts.ts`). It MUST be fixed — the - hook handler POSTs to `127.0.0.1:${CLAUDE_DASHBOARD_PORT||4820}`, baked into - `~/.claude/settings.json` at install time, so 4820 means hooks need zero - per-hook env. 4820 is outside `PORT_PROBE_ORDER`, so it never collides with - the gateway. (FEA-1500 tracks migrating this transport later.) + (`AGENT_MONITOR_PORT` in `src/shared/contracts.ts`). It MUST be fixed — + `~/.claude/settings.json` pins only the Electron-as-Node command pointing at the + userData `hook-handler.js` copy; that copied handler owns the + `CLAUDE_DASHBOARD_PORT || 4820` fallback, the route, and the payload envelope, so + hooks need zero per-hook env. 4820 is outside `PORT_PROBE_ORDER`, so it never + collides with the gateway. The route + envelope must stay backward-compatible + across handler/receiver changes — a persisted handler copy refreshes only + best-effort on boot, and `/api/hooks/event` is served by **both** the legacy + sidecar (default mode) and the in-process listener (design-system mode). This + loopback-HTTP transport is the accepted permanent design; a unix-socket + alternative was considered and declined (FEA-1500, obsoleted). - **Durable DB:** `app.getPath("userData")/agent-dashboard.sqlite` (schema in `src/main/database/schema.ts`), Node's built-in `node:sqlite`. Persisted collector caches live under `/agent-monitor/`. diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 78e45ff4..da64bdcc 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "desktop", - "version": "0.15.113", + "version": "0.15.114", "description": "ClosedLoop Desktop", "author": "ClosedLoop AI ", "private": true, diff --git a/apps/desktop/resources/hooks/codex-hook-handler.js b/apps/desktop/resources/hooks/codex-hook-handler.js index 56077c40..36d804be 100644 --- a/apps/desktop/resources/hooks/codex-hook-handler.js +++ b/apps/desktop/resources/hooks/codex-hook-handler.js @@ -10,7 +10,10 @@ * Zero-dependency, plain CommonJS, fail-silent — a hook must never block a Codex * turn. Codex calls this once per lifecycle event (SessionStart, UserPromptSubmit, * PreToolUse, PostToolUse, Stop) with the event name as the single argv arg. The - * port + path + payload envelope are a backward-compatible contract (FEA-1500). + * port + route + payload envelope must stay backward-compatible: this handler is + * a persisted userData copy that settings keep invoking and that refreshes only + * best-effort on boot, so do not make breaking changes to /api/hooks/codex/event + * without keeping the 4820 receiver (AgentHookListener) backward-compatible. * * Part of FEA-1444 (opt-in Codex hook ingestion). */ diff --git a/apps/desktop/resources/hooks/hook-handler.js b/apps/desktop/resources/hooks/hook-handler.js index 3f564bfa..05ee9b28 100644 --- a/apps/desktop/resources/hooks/hook-handler.js +++ b/apps/desktop/resources/hooks/hook-handler.js @@ -12,8 +12,13 @@ * * Zero-dependency, plain CommonJS, fail-silent: it runs via the Electron binary * as Node (ELECTRON_RUN_AS_NODE) from a userData copy, and must NEVER block or - * fail a Claude turn. The port + path + payload envelope are a backward-compatible - * contract baked into ~/.claude/settings.json (see FEA-1500); do not change them. + * fail a Claude turn. The port + route + payload envelope must stay + * backward-compatible with BOTH receivers of /api/hooks/event: the legacy + * AgentMonitorSidecar (default Agent Monitor mode) and the in-process + * AgentHookListener (design-system mode). This handler is a persisted userData + * copy that settings.json keeps invoking and that refreshes only best-effort on + * boot, so do not make breaking route/envelope changes without keeping the + * receivers backward-compatible. */ const http = require("http"); diff --git a/apps/desktop/src/main/agent-monitor-listener.ts b/apps/desktop/src/main/agent-monitor-listener.ts index f40f1b52..22f6b3ef 100644 --- a/apps/desktop/src/main/agent-monitor-listener.ts +++ b/apps/desktop/src/main/agent-monitor-listener.ts @@ -6,13 +6,24 @@ import { AGENT_MONITOR_PORT } from "../shared/contracts.js"; import { isSessionInSandbox } from "./agent-session-sync-service.js"; import type { createLifecycle, HookData } from "./database/lifecycle.js"; -// CLOSEDLOOP-TICKET FEA-1500: remove legacy HTTP hook listener on 4820 after -// transport migration (FEA-1497 breaking-change discipline contract #1). The hook -// commands baked into ~/.claude/settings.json and ~/.codex/hooks.json POST to -// 127.0.0.1:4820/api/hooks/event; this in-process listener replaces the vendor -// sidecar that previously owned that port. The contract (port, path, payload -// envelope) MUST stay backward-compatible until all installs self-heal to a -// lighter transport. +// Fixed-port hook transport (127.0.0.1:4820). The first-party hook handlers +// (resources/hooks/hook-handler.js, codex-hook-handler.js) POST the +// `{ hook_type, data }` envelope to /api/hooks/event (Codex: +// /api/hooks/codex/event). This in-process listener serves that route in +// design-system mode; in default mode the legacy AgentMonitorSidecar serves the +// same route, so /api/hooks/event has two receivers across modes. +// +// COMPATIBILITY REQUIREMENT — the route + payload envelope must stay +// backward-compatible; do NOT treat it as freely changeable. The handler is a +// persisted userData copy that Claude/Codex settings keep invoking; +// refreshHandlerCopy() only refreshes it best-effort on boot (failures are +// caught and logged), so a stale copy can outlive an app upgrade. Any route or +// envelope change therefore needs backward-compatible receiver support until all +// persisted handler copies are known refreshed. +// +// 4820 is fixed and outside PORT_PROBE_ORDER so hooks need zero per-hook env. +// This loopback-HTTP transport is the accepted permanent design; a unix-socket +// alternative was considered and declined (FEA-1500, obsoleted). const HOST = "127.0.0.1"; const MAX_BODY_BYTES = 8 * 1024 * 1024; // hook payloads (incl. large tool_input) cap