Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions apps/desktop/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<userData>/agent-monitor/`.
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "desktop",
"version": "0.15.113",
"version": "0.15.114",
"description": "ClosedLoop Desktop",
"author": "ClosedLoop AI <support@closedloop.ai>",
"private": true,
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/resources/hooks/codex-hook-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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).
*/
Expand Down
9 changes: 7 additions & 2 deletions apps/desktop/resources/hooks/hook-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
25 changes: 18 additions & 7 deletions apps/desktop/src/main/agent-monitor-listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading