/** * @file Tests POST /api/hooks/event for a hook Grok Build fired through the * Claude hooks in `~/.claude/settings.json` (Grok's Claude compatibility): it * arrives with no `tool`, is told apart by the camelCase keys only Grok sends * (`hookEventName`, `workspaceRoot`), and lands as a `tool = 'grok'` session * on the grok workspace — never the claude one sharing the directory — with * its `transcript_path` dropped. A Claude hook is left Claude's. The DB lives * in a temp dir. * @author Nguyễn Ngọc Trí Vĩ */ const { describe, it, before, after } = require("node:test"); const assert = require("node:assert/strict"); const path = require("node:path"); const os = require("node:os"); const fs = require("node:fs"); const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-grok-hook-route-")); process.env.DASHBOARD_DB_PATH = path.join(ROOT, "dashboard.db"); process.env.CLAUDE_HOME = path.join(ROOT, "claude"); process.env.DASHBOARD_LIVENESS_PROBE = "0"; const { createApp, startServer } = require("../index"); const { db } = require("../db"); const lanes = require("../lib/lanes"); let server; let BASE; const CWD = path.join(fs.realpathSync(ROOT), "repo"); fs.mkdirSync(CWD, { recursive: true }); before(async () => { server = await startServer(createApp(), 0); BASE = `http://127.0.0.1:${server.address().port}`; }); after(() => { server.close(); fs.rmSync(ROOT, { recursive: true, force: true }); }); const post = (body) => fetch(`${BASE}/api/hooks/event`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }).then((r) => r.json()); const grokData = (id, extra = {}) => ({ hookEventName: "session_start", hook_event_name: "SessionStart", sessionId: id, session_id: id, cwd: CWD, workspaceRoot: CWD, transcript_path: path.join(ROOT, "grok", "sessions", id, "chat_history.jsonl"), permissionMode: "default", ...extra, }); describe("Grok hooks through the Claude settings", () => { it("records a grok session, on the grok workspace, with no transcript path", async () => { const claudeLane = lanes.createLane({ title: "c", cwd: CWD, tool: "claude" }); const grokLane = lanes.createLane({ title: "g", cwd: CWD, tool: "grok" }); const id = "0199f0aa-1111-7222-8333-444455556666"; await post({ hook_type: "SessionStart", data: grokData(id) }); await post({ hook_type: "UserPromptSubmit", data: grokData(id, { hookEventName: "user_prompt_submit", prompt: "fix the bug" }), }); const s = db.prepare("SELECT * FROM sessions WHERE id = ?").get(id); assert.equal(s.tool, "grok"); assert.equal(s.transcript_path, null); const events = db.prepare("SELECT data FROM events WHERE session_id = ?").all(id); assert.ok(events.length >= 1); for (const e of events) assert.equal(JSON.parse(e.data).transcript_path, undefined); assert.equal(lanes.getLane(claudeLane.id).session_id, null); assert.equal(lanes.getLane(grokLane.id).session_id, id); }); it("leaves a Claude hook Claude's", async () => { const id = "0199f0aa-1111-7222-8333-777788889999"; await post({ hook_type: "SessionStart", data: { hook_event_name: "SessionStart", session_id: id, cwd: CWD }, }); assert.equal(db.prepare("SELECT tool FROM sessions WHERE id = ?").get(id).tool, "claude"); }); });