#!/usr/bin/env node /** * Passive recorder hook: POST one lifecycle event into the daemon and get out of the way. * * Wired to every observed event. This used to be a `type: "http"` hook — no process spawn at * all — until it turned out that Grok Build's http runner validates the URL against its SSRF * rules and rejects any scheme that is not https (crates/codegen/xai-grok-hooks/src/runner/ * http.rs, `validate_hook_url`). The daemon speaks plain http on loopback, so an http hook * could never reach it: those hooks were failing validation, silently, on every event. * * A command hook costs a Node start (~40ms) per event, and buys back the ability to send an * authentication header, which the http runner has no config surface for. * * It also boots the daemon if nothing else has. That is not this hook being greedy: a plugin's * `SessionStart` entry provably never runs (see `ensureDaemon` in glance-lib.mjs), so there is * no single boot event to delegate to and whichever recorder fires first has to do it. * * Always exits 0. A dashboard must never be the reason a tool call fails. */ import { baseUrl, ensureDaemon, envEnvelope, hookHeaders, postJson, readConfig, readStdinJson, } from "./glance-lib.mjs"; /** * hooks/hooks.json gives this hook 5s. Everything below has to finish inside that with room to * spare, because a killed script is a lost event either way — and losing one is fine, the next * event is 40ms behind it. */ const START_BUDGET_MS = 1200; const POST_BUDGET_MS = 1500; try { const payload = envEnvelope(await readStdinJson()); const cfg = readConfig(); // Cheap when the daemon is already up, which is every call but the first of the session. if (await ensureDaemon(cfg, { waitMs: START_BUDGET_MS, startedBy: "record-hook" })) { // hookHeaders() is read after the daemon is up: on a first-ever run it is the daemon we // just started that created hook.secret. await postJson(`${baseUrl(cfg)}/hook/record`, payload, POST_BUDGET_MS, hookHeaders()); } } catch { // Daemon down, no secret yet, malformed payload — all the same answer: carry on. } process.exit(0);