rivetplane 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/dist/acp-attach.js +26 -4
- package/dist/acp-attach.js.map +1 -1
- package/dist/claude-code-discovery.js +123 -7
- package/dist/claude-code-discovery.js.map +1 -1
- package/dist/cli.js +48 -1
- package/dist/cli.js.map +1 -1
- package/dist/client.js +92 -13
- package/dist/client.js.map +1 -1
- package/dist/codex-app-server.js +153 -8
- package/dist/codex-app-server.js.map +1 -1
- package/dist/codex-rollout-discovery.js +121 -7
- package/dist/codex-rollout-discovery.js.map +1 -1
- package/dist/hook-bridge-install.js +133 -0
- package/dist/hook-bridge-install.js.map +1 -0
- package/dist/hook-bridge.js +79 -0
- package/dist/hook-bridge.js.map +1 -0
- package/dist/hook-discovery.js +115 -0
- package/dist/hook-discovery.js.map +1 -0
- package/dist/hook-ingestion.js +507 -0
- package/dist/hook-ingestion.js.map +1 -0
- package/dist/hook-install.js +348 -0
- package/dist/hook-install.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/launch-command.js +51 -0
- package/dist/launch-command.js.map +1 -0
- package/dist/local-api.js +44 -3
- package/dist/local-api.js.map +1 -1
- package/dist/opencode-export-discovery.js +16 -4
- package/dist/opencode-export-discovery.js.map +1 -1
- package/dist/opencode-manager.js +126 -7
- package/dist/opencode-manager.js.map +1 -1
- package/dist/pending-normalization.js +29 -0
- package/dist/pending-normalization.js.map +1 -0
- package/dist/registry.js +6 -2
- package/dist/registry.js.map +1 -1
- package/dist/relay.js +184 -14
- package/dist/relay.js.map +1 -1
- package/dist/session-manager.js +3 -1
- package/dist/session-manager.js.map +1 -1
- package/dist/usage.js +131 -0
- package/dist/usage.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { chmod, lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { HOOK_DISCOVERY_VERSION, HOOK_OWNER } from "./hook-discovery.js";
|
|
6
|
+
export const HOOK_BRIDGE_VERSION = 1;
|
|
7
|
+
const BRIDGE_MARKER = `${HOOK_OWNER}:bridge-v${HOOK_BRIDGE_VERSION}`;
|
|
8
|
+
export function defaultHookBridgePath(env = process.env, home = homedir()) {
|
|
9
|
+
const root = env.XDG_CONFIG_HOME || join(home, ".config");
|
|
10
|
+
return join(root, "harness-cp", "hooks", `v${HOOK_BRIDGE_VERSION}`, "bridge.cjs");
|
|
11
|
+
}
|
|
12
|
+
export function hookBridgeInstallation(options = {}) {
|
|
13
|
+
const env = options.env ?? process.env;
|
|
14
|
+
const bridge = defaultHookBridgePath(env, options.home ?? homedir());
|
|
15
|
+
const statusline = join(dirname(bridge), "claude-statusline.cjs");
|
|
16
|
+
const node = options.node ?? process.execPath;
|
|
17
|
+
const platform = options.platform ?? process.platform;
|
|
18
|
+
return { node, bridge, statusline, command: (harness, event) => [node, bridge, "--owner", HOOK_OWNER, "--harness", harness, "--event", event].map((value) => shellQuote(value, platform)).join(" "), statusLineCommand: (original) => [node, statusline, Buffer.from(JSON.stringify(original ?? null)).toString("base64url")].map((value) => shellQuote(value, platform)).join(" ") };
|
|
19
|
+
}
|
|
20
|
+
export async function installHookBridge(options = {}) {
|
|
21
|
+
const installation = hookBridgeInstallation(options);
|
|
22
|
+
const parent = dirname(installation.bridge);
|
|
23
|
+
await mkdir(parent, { recursive: true, mode: 0o700 });
|
|
24
|
+
const parentInfo = await lstat(parent);
|
|
25
|
+
if (!parentInfo.isDirectory() || parentInfo.isSymbolicLink())
|
|
26
|
+
throw new Error("Hook bridge directory must be a regular directory");
|
|
27
|
+
assertCurrentUser(parentInfo, "Hook bridge directory");
|
|
28
|
+
if (process.platform !== "win32")
|
|
29
|
+
await chmod(parent, 0o700);
|
|
30
|
+
let prior;
|
|
31
|
+
try {
|
|
32
|
+
const info = await lstat(installation.bridge);
|
|
33
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
34
|
+
throw new Error("Refused to replace a non-regular hook bridge");
|
|
35
|
+
assertCurrentUser(info, "Hook bridge");
|
|
36
|
+
if (process.platform !== "win32" && (info.mode & 0o077) !== 0)
|
|
37
|
+
throw new Error("Hook bridge permissions are not private");
|
|
38
|
+
prior = await readFile(installation.bridge, "utf8");
|
|
39
|
+
if (!prior.includes(BRIDGE_MARKER))
|
|
40
|
+
throw new Error("Refused to overwrite an unmarked hook bridge");
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code !== "ENOENT")
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
const source = durableHookBridgeSource().replace("configuredTimeout : actionable ? 125000 : 3000", "configuredTimeout : harness === \"opencode\" && actionable ? 1805000 : actionable ? 125000 : 3000");
|
|
47
|
+
if (prior !== source) {
|
|
48
|
+
const temporary = `${installation.bridge}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
49
|
+
await writeFile(temporary, source, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
50
|
+
if (process.platform !== "win32")
|
|
51
|
+
await chmod(temporary, 0o600);
|
|
52
|
+
await rename(temporary, installation.bridge);
|
|
53
|
+
}
|
|
54
|
+
if (process.platform !== "win32")
|
|
55
|
+
await chmod(installation.bridge, 0o600);
|
|
56
|
+
await writeOwnedStatusLine(installation.statusline, durableClaudeStatusLineSource(installation));
|
|
57
|
+
return installation;
|
|
58
|
+
}
|
|
59
|
+
export async function uninstallHookBridge(options = {}) {
|
|
60
|
+
const path = defaultHookBridgePath(options.env ?? process.env, options.home ?? homedir());
|
|
61
|
+
try {
|
|
62
|
+
const info = await lstat(path);
|
|
63
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
64
|
+
throw new Error("Refused to remove a non-regular hook bridge");
|
|
65
|
+
assertCurrentUser(info, "Hook bridge");
|
|
66
|
+
const source = await readFile(path, "utf8");
|
|
67
|
+
if (!source.includes(BRIDGE_MARKER))
|
|
68
|
+
throw new Error("Refused to remove an unmarked hook bridge");
|
|
69
|
+
await rm(path);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error.code !== "ENOENT")
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
const statusline = join(dirname(path), "claude-statusline.cjs");
|
|
76
|
+
try {
|
|
77
|
+
const source = await readFile(statusline, "utf8");
|
|
78
|
+
if (!source.includes(`${HOOK_OWNER}:claude-statusline-v1`))
|
|
79
|
+
throw new Error("Refused to remove an unmarked status-line wrapper");
|
|
80
|
+
await rm(statusline);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error.code !== "ENOENT")
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function writeOwnedStatusLine(path, source) {
|
|
88
|
+
let prior;
|
|
89
|
+
try {
|
|
90
|
+
const info = await lstat(path);
|
|
91
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
92
|
+
throw new Error("Refused to replace a non-regular status-line wrapper");
|
|
93
|
+
assertCurrentUser(info, "Status-line wrapper");
|
|
94
|
+
prior = await readFile(path, "utf8");
|
|
95
|
+
if (!prior.includes(`${HOOK_OWNER}:claude-statusline-v1`))
|
|
96
|
+
throw new Error("Refused to overwrite an unmarked status-line wrapper");
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error.code !== "ENOENT")
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
if (prior === source)
|
|
103
|
+
return;
|
|
104
|
+
const temporary = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
105
|
+
await writeFile(temporary, source, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
106
|
+
if (process.platform !== "win32")
|
|
107
|
+
await chmod(temporary, 0o600);
|
|
108
|
+
await rename(temporary, path);
|
|
109
|
+
}
|
|
110
|
+
function durableClaudeStatusLineSource(installation) {
|
|
111
|
+
return `// ${HOOK_OWNER}:claude-statusline-v1\n"use strict";\nconst { spawn } = require("node:child_process");\nconst original = JSON.parse(Buffer.from(process.argv[2] || "bnVsbA", "base64url").toString("utf8"));\nasync function read() { let value = ""; for await (const chunk of process.stdin) value += String(chunk); return value; }\nasync function main() { const input = await read(); const telemetry = spawn(${JSON.stringify(installation.node)}, [${JSON.stringify(installation.bridge)}, "--owner", ${JSON.stringify(HOOK_OWNER)}, "--harness", "claude-code", "--event", "StatusLine"], { stdio: ["pipe", "ignore", "ignore"], windowsHide: true }); telemetry.stdin.end(input); telemetry.unref(); if (!original || original.type !== "command" || typeof original.command !== "string") return; const child = spawn(original.command, { shell: true, stdio: ["pipe", "pipe", "inherit"], windowsHide: true }); child.stdin.end(input); for await (const chunk of child.stdout) process.stdout.write(chunk); await new Promise((resolve) => child.once("close", resolve)); }\nmain().catch(() => {});\n`;
|
|
112
|
+
}
|
|
113
|
+
function shellQuote(value, platform) {
|
|
114
|
+
if (/[\0\r\n]/.test(value))
|
|
115
|
+
throw new Error("Hook command path contains an invalid character");
|
|
116
|
+
if (platform === "win32")
|
|
117
|
+
return `"${value.replace(/%/g, "%%").replace(/"/g, '""')}"`;
|
|
118
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
119
|
+
}
|
|
120
|
+
function assertCurrentUser(info, label) {
|
|
121
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
122
|
+
if (uid !== undefined && info.uid !== uid)
|
|
123
|
+
throw new Error(`${label} is not owned by the current user`);
|
|
124
|
+
}
|
|
125
|
+
function durableHookBridgeSourceBase() {
|
|
126
|
+
return `// ${BRIDGE_MARKER}\n"use strict";\nconst fs = require("node:fs/promises");\nconst os = require("node:os");\nconst path = require("node:path");\nconst crypto = require("node:crypto");\nconst OWNER = ${JSON.stringify(HOOK_OWNER)};\nconst VERSION = ${HOOK_DISCOVERY_VERSION};\nconst MAX_STDIN = 1000000;\nfunction flag(name) { const offset = process.argv.indexOf(name); return offset >= 0 ? process.argv[offset + 1] : undefined; }\nfunction object(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : {}; }\nfunction first(input, names) { for (const name of names) if (typeof input[name] === "string" && input[name]) return input[name]; }\nfunction endpoint(value) { const url = new URL(value); if (url.protocol !== "http:" || !["127.0.0.1", "[::1]", "::1"].includes(url.hostname) || url.pathname !== "/v1/hooks/events" || url.username || url.password || url.search || url.hash) throw new Error("invalid endpoint"); return url.toString(); }\nasync function input() { let raw = ""; for await (const chunk of process.stdin) { raw += String(chunk); if (raw.length > MAX_STDIN) throw new Error("input too large"); } return object(raw ? JSON.parse(raw) : {}); }\nasync function discovery() { const root = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"); const file = process.env.RIVETPLANE_HOOK_DISCOVERY || path.join(root, "harness-cp", "hook-endpoint.json"); const info = await fs.lstat(file); if (!info.isFile() || info.isSymbolicLink()) throw new Error("unsafe discovery"); if (typeof process.getuid === "function" && info.uid !== process.getuid()) throw new Error("wrong owner"); if (process.platform !== "win32" && (info.mode & 0o077) !== 0) throw new Error("broad permissions"); const value = JSON.parse(await fs.readFile(file, "utf8")); if (value.version !== VERSION || value.owner !== OWNER || typeof value.token !== "string" || value.token.length < 32 || !Number.isSafeInteger(value.pid) || typeof value.started_at !== "string") throw new Error("invalid discovery"); try { process.kill(value.pid, 0); } catch (error) { if (error.code !== "EPERM") throw new Error("stale discovery"); } return { endpoint: endpoint(value.endpoint), token: value.token }; }\nfunction envelope(harness, configuredEvent, payload) { const session = first(payload, ["session_id", "sessionId", "sessionID", "thread-id", "thread_id", "threadId"]); if (!session) throw new Error("no session"); const event = first(payload, ["hook_event_name", "event", "type"]) || configuredEvent; const request = first(payload, ["request_id", "requestId", "requestID", "permission_request_id", "tool_use_id", "toolUseId", "tool_call_id", "toolCallId"].concat(harness === "opencode" ? ["id"] : [])) || (harness === "claude-code" && event === "PermissionRequest" ? "rivetplane-" + crypto.randomUUID() : undefined); return { version: 1, harness, event, session_id: session, cwd: first(payload, ["cwd", "directory", "working_directory"]) || process.cwd(), transport: harness === "opencode" ? "opencode-plugin" : harness + "-hook-command", payload, ...(request ? { request_id: request } : {}) }; }\nfunction native(harness, event, payload, result) { if (!result || result.decision === "neutral") return {}; if (harness === "claude-code" && event === "PermissionRequest") return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: result.decision === "deny" ? { behavior: "deny", message: "Denied through Rivetplane" } : { behavior: "allow", updatedInput: { ...object(payload.tool_input), ...object(result.updated_input) } } } }; return result; }\nasync function main() { const owner = flag("--owner"); const harness = flag("--harness"); const event = flag("--event"); if (owner !== OWNER) throw new Error("Hook ownership marker is invalid"); if (!harness || !event) throw new Error("--harness and --event are required"); try { if (process.env.RIVETPLANE_HOOKS_DISABLED === "1") return {}; const payload = await input(); const record = await discovery(); const actionable = (harness === "claude-code" && event === "PermissionRequest") || (harness === "opencode" && (event === "permission.asked" || event === "question.asked")); const configuredTimeout = Number(process.env.RIVETPLANE_HOOK_TIMEOUT_MS); const timeout = Number.isSafeInteger(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : actionable ? 125000 : 3000; const response = await fetch(record.endpoint, { method: "POST", headers: { "content-type": "application/json", "x-rivetplane-hook-owner": OWNER, "x-rivetplane-hook-token": record.token }, body: JSON.stringify(envelope(harness, event, payload)), signal: AbortSignal.timeout(timeout) }); if (!response.ok) return {}; return native(harness, event, payload, await response.json()); } catch { return {}; } }\nmain().then((value) => process.stdout.write(JSON.stringify(value) + "\\n")).catch((error) => { process.stderr.write("rivetplane hook bridge: " + error.message + "\\n"); process.exitCode = 1; });\n`;
|
|
127
|
+
}
|
|
128
|
+
export function durableHookBridgeSource() {
|
|
129
|
+
return durableHookBridgeSourceBase()
|
|
130
|
+
.replace("function native(harness, event, payload, result)", 'function usagePayload(payload) { return { session_id: first(payload, ["session_id", "sessionId"]), cwd: first(payload, ["cwd"]), hook_event_name: "StatusLine", model: payload.model, context_window: object(payload.context_window || payload.contextWindow), cost: object(payload.cost), rate_limits: object(payload.rate_limits || payload.rateLimits) }; }\nfunction native(harness, event, payload, result)')
|
|
131
|
+
.replace("const payload = await input(); const record = await discovery();", 'let payload = await input(); if (harness === "claude-code" && event === "StatusLine") payload = usagePayload(payload); const record = await discovery();');
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=hook-bridge-install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-bridge-install.js","sourceRoot":"","sources":["../src/hook-bridge-install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { defaultHookDiscoveryPath, HOOK_OWNER, readHookDiscovery, validateHookEndpoint } from "./hook-discovery.js";
|
|
3
|
+
const MAX_STDIN = 1_000_000;
|
|
4
|
+
function object(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : {}; }
|
|
5
|
+
function firstString(input, names) { for (const name of names)
|
|
6
|
+
if (typeof input[name] === "string" && input[name])
|
|
7
|
+
return input[name]; return undefined; }
|
|
8
|
+
export async function readHookInput(stream = process.stdin) {
|
|
9
|
+
const argument = process.argv.at(-1);
|
|
10
|
+
if (argument?.trimStart().startsWith("{"))
|
|
11
|
+
return object(JSON.parse(argument));
|
|
12
|
+
let raw = "";
|
|
13
|
+
for await (const chunk of stream) {
|
|
14
|
+
raw += String(chunk);
|
|
15
|
+
if (raw.length > MAX_STDIN)
|
|
16
|
+
throw new Error("Hook input is too large");
|
|
17
|
+
}
|
|
18
|
+
return object(raw ? JSON.parse(raw) : {});
|
|
19
|
+
}
|
|
20
|
+
export function toHookEnvelope(harness, configuredEvent, payload) {
|
|
21
|
+
const session_id = firstString(payload, ["session_id", "sessionId", "sessionID", "thread-id", "thread_id", "threadId"]);
|
|
22
|
+
if (!session_id)
|
|
23
|
+
throw new Error("Native hook input has no session ID");
|
|
24
|
+
const event = firstString(payload, ["hook_event_name", "event", "type"]) ?? configuredEvent;
|
|
25
|
+
const cwd = firstString(payload, ["cwd", "directory", "working_directory"]) ?? process.cwd();
|
|
26
|
+
const nativeRequestId = firstString(payload, ["request_id", "requestId", "requestID", "permission_request_id", "tool_use_id", "toolUseId", "tool_call_id", "toolCallId", ...(harness === "opencode" ? ["id"] : [])]);
|
|
27
|
+
const telemetryIdentity = !nativeRequestId && harness === "codex" && event === "PermissionRequest"
|
|
28
|
+
? `codex-telemetry-${createHash("sha256").update(session_id).update("\0").update(String(payload.turn_id ?? "")).update("\0").update(String(payload.tool_name ?? "")).update("\0").update(JSON.stringify(payload.tool_input ?? null)).digest("hex").slice(0, 32)}`
|
|
29
|
+
: undefined;
|
|
30
|
+
const request_id = nativeRequestId ?? (harness === "claude-code" && event === "PermissionRequest" ? `rivetplane-${randomUUID()}` : telemetryIdentity);
|
|
31
|
+
return { version: 1, harness, event, session_id, cwd, transport: harness === "opencode" ? "opencode-plugin" : `${harness}-hook-command`, payload,
|
|
32
|
+
...(request_id ? { request_id } : {}), ...(telemetryIdentity ? { request_id_kind: "telemetry" } : nativeRequestId ? { request_id_kind: "native" } : {}), ...(firstString(payload, ["model", "model_id"]) ? { model: firstString(payload, ["model", "model_id"]) } : {}),
|
|
33
|
+
...(firstString(payload, ["agent", "agent_name"]) ? { agent: firstString(payload, ["agent", "agent_name"]) } : {}),
|
|
34
|
+
...(firstString(payload, ["timestamp"]) ? { timestamp: firstString(payload, ["timestamp"]) } : {}) };
|
|
35
|
+
}
|
|
36
|
+
export async function emitHook(harness, event, options = {}) {
|
|
37
|
+
if (options.owner !== HOOK_OWNER)
|
|
38
|
+
throw new Error("Hook ownership marker is invalid");
|
|
39
|
+
try {
|
|
40
|
+
if (process.env.RIVETPLANE_HOOKS_DISABLED === "1")
|
|
41
|
+
return {};
|
|
42
|
+
const payload = options.payload ?? await readHookInput();
|
|
43
|
+
const envelope = toHookEnvelope(harness, event, payload);
|
|
44
|
+
const configuredEndpoint = options.endpoint ?? process.env.RIVETPLANE_HOOK_ENDPOINT;
|
|
45
|
+
const configuredToken = options.token ?? process.env.RIVETPLANE_HOOK_TOKEN;
|
|
46
|
+
const discovery = configuredEndpoint && configuredToken ? undefined : await readHookDiscovery(options.discovery_path ?? process.env.RIVETPLANE_HOOK_DISCOVERY ?? defaultHookDiscoveryPath());
|
|
47
|
+
const endpoint = validateHookEndpoint(configuredEndpoint ?? discovery?.endpoint);
|
|
48
|
+
const token = configuredToken ?? discovery?.token;
|
|
49
|
+
if (!token)
|
|
50
|
+
return {};
|
|
51
|
+
const actionable = (harness === "claude-code" && event === "PermissionRequest") || (harness === "opencode" && (event === "permission.asked" || event === "question.asked"));
|
|
52
|
+
const environmentTimeout = Number(process.env.RIVETPLANE_HOOK_TIMEOUT_MS);
|
|
53
|
+
const timeout = options.timeout_ms ?? (Number.isSafeInteger(environmentTimeout) && environmentTimeout > 0
|
|
54
|
+
? environmentTimeout
|
|
55
|
+
: harness === "opencode" && actionable ? 30 * 60_000 + 5_000 : actionable ? 125_000 : 3_000);
|
|
56
|
+
const response = await fetch(endpoint, { method: "POST", headers: { "content-type": "application/json", "x-rivetplane-hook-owner": HOOK_OWNER, "x-rivetplane-hook-token": token }, body: JSON.stringify(envelope), signal: AbortSignal.timeout(timeout) });
|
|
57
|
+
if (!response.ok)
|
|
58
|
+
return {};
|
|
59
|
+
return formatNativeResult(harness, event, payload, await response.json());
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function formatNativeResult(harness, event, input, result) {
|
|
66
|
+
if (result.decision === "neutral")
|
|
67
|
+
return {};
|
|
68
|
+
if (harness === "opencode")
|
|
69
|
+
return result;
|
|
70
|
+
if (harness === "claude-code" && event === "PermissionRequest") {
|
|
71
|
+
const decision = result.decision === "deny" ? { behavior: "deny", message: "Denied through Rivetplane" } : { behavior: "allow", updatedInput: { ...object(input.tool_input), ...object(result.updated_input) } };
|
|
72
|
+
return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision } };
|
|
73
|
+
}
|
|
74
|
+
if (harness === "claude-code" && (event === "AskUserQuestion" || (event === "PreToolUse" && input.tool_name === "AskUserQuestion")) && result.decision === "answer") {
|
|
75
|
+
return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow", updatedInput: { ...object(input.tool_input), ...result.updated_input } } };
|
|
76
|
+
}
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=hook-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-bridge.js","sourceRoot":"","sources":["../src/hook-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { chmod, lstat, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
export const HOOK_OWNER = "rivetplane-hook-v1";
|
|
6
|
+
export const HOOK_DISCOVERY_VERSION = 1;
|
|
7
|
+
export function defaultHookDiscoveryPath(env = process.env, home = homedir()) {
|
|
8
|
+
const root = env.XDG_CONFIG_HOME || join(home, ".config");
|
|
9
|
+
return join(root, "harness-cp", "hook-endpoint.json");
|
|
10
|
+
}
|
|
11
|
+
export function createHookToken() { return randomBytes(32).toString("base64url"); }
|
|
12
|
+
export async function writeHookDiscovery(path, record) {
|
|
13
|
+
await assertSafeParent(path);
|
|
14
|
+
try {
|
|
15
|
+
const info = await lstat(path);
|
|
16
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
17
|
+
throw new Error("Hook discovery file must be a regular file");
|
|
18
|
+
assertOwnedAndPrivate(info, "Hook discovery file");
|
|
19
|
+
const prior = JSON.parse(await readFile(path, "utf8"));
|
|
20
|
+
if (prior.owner !== HOOK_OWNER || typeof prior.token !== "string")
|
|
21
|
+
throw new Error("Refused to replace a discovery file not owned by Rivetplane");
|
|
22
|
+
if (await authenticatedHookEndpointAlive(prior))
|
|
23
|
+
throw new Error(`Another Rivetplane hook endpoint is active (process ${prior.pid ?? "unknown"})`);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code !== "ENOENT")
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
const temporary = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
30
|
+
await writeFile(temporary, `${JSON.stringify(record, null, 2)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
31
|
+
await chmod(temporary, 0o600);
|
|
32
|
+
await rename(temporary, path);
|
|
33
|
+
await chmod(path, 0o600);
|
|
34
|
+
}
|
|
35
|
+
export async function readHookDiscovery(path = defaultHookDiscoveryPath()) {
|
|
36
|
+
const info = await lstat(path);
|
|
37
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
38
|
+
throw new Error("Hook discovery path is not a regular file");
|
|
39
|
+
assertOwnedAndPrivate(info, "Hook discovery file");
|
|
40
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
41
|
+
if (value.version !== HOOK_DISCOVERY_VERSION || value.owner !== HOOK_OWNER || typeof value.token !== "string" || value.token.length < 32 || !Number.isSafeInteger(value.pid) || typeof value.started_at !== "string")
|
|
42
|
+
throw new Error("Hook discovery file is invalid");
|
|
43
|
+
const endpoint = validateHookEndpoint(value.endpoint);
|
|
44
|
+
if (!processExists(value.pid)) {
|
|
45
|
+
await removeHookDiscovery(path, value.token);
|
|
46
|
+
throw new Error("Hook discovery process is not running");
|
|
47
|
+
}
|
|
48
|
+
return { version: HOOK_DISCOVERY_VERSION, owner: HOOK_OWNER, endpoint, token: value.token, pid: value.pid, started_at: value.started_at };
|
|
49
|
+
}
|
|
50
|
+
export async function removeHookDiscovery(path, token) {
|
|
51
|
+
try {
|
|
52
|
+
const current = JSON.parse(await readFile(path, "utf8"));
|
|
53
|
+
if (current.owner === HOOK_OWNER && secretEquals(current.token, token))
|
|
54
|
+
await rm(path);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error.code !== "ENOENT")
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function validateHookEndpoint(value) {
|
|
62
|
+
if (typeof value !== "string")
|
|
63
|
+
throw new Error("Hook endpoint is invalid");
|
|
64
|
+
const url = new URL(value);
|
|
65
|
+
if (url.protocol !== "http:" || (url.hostname !== "127.0.0.1" && url.hostname !== "[::1]" && url.hostname !== "::1") || url.pathname !== "/v1/hooks/events" || url.username || url.password || url.search || url.hash)
|
|
66
|
+
throw new Error("Hook endpoint must be the loopback ingestion endpoint");
|
|
67
|
+
return url.toString();
|
|
68
|
+
}
|
|
69
|
+
export function secretEquals(actual, expected) {
|
|
70
|
+
if (typeof actual !== "string")
|
|
71
|
+
return false;
|
|
72
|
+
const left = Buffer.from(actual);
|
|
73
|
+
const right = Buffer.from(expected);
|
|
74
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
75
|
+
}
|
|
76
|
+
export async function authenticatedHookEndpointAlive(record, timeout_ms = 750) {
|
|
77
|
+
try {
|
|
78
|
+
const events = new URL(validateHookEndpoint(record.endpoint));
|
|
79
|
+
events.pathname = "/v1/hooks/health";
|
|
80
|
+
const response = await fetch(events, { headers: { "x-rivetplane-hook-owner": HOOK_OWNER, "x-rivetplane-hook-token": record.token ?? "" }, signal: AbortSignal.timeout(timeout_ms) });
|
|
81
|
+
if (!response.ok)
|
|
82
|
+
return false;
|
|
83
|
+
const value = await response.json();
|
|
84
|
+
return value.owner === HOOK_OWNER && value.version === HOOK_DISCOVERY_VERSION && value.pid === record.pid;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function assertSafeParent(path) {
|
|
91
|
+
const parent = dirname(path);
|
|
92
|
+
await mkdir(parent, { recursive: true, mode: 0o700 });
|
|
93
|
+
await chmod(parent, 0o700);
|
|
94
|
+
const info = await stat(parent);
|
|
95
|
+
if (!info.isDirectory())
|
|
96
|
+
throw new Error("Hook discovery parent is not a directory");
|
|
97
|
+
assertOwnedAndPrivate(info, "Hook discovery directory");
|
|
98
|
+
}
|
|
99
|
+
function assertOwnedAndPrivate(info, label) {
|
|
100
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
101
|
+
if (uid !== undefined && info.uid !== uid)
|
|
102
|
+
throw new Error(`${label} is not owned by the current user`);
|
|
103
|
+
if (process.platform !== "win32" && (info.mode & 0o077) !== 0)
|
|
104
|
+
throw new Error(`${label} permissions are not private`);
|
|
105
|
+
}
|
|
106
|
+
function processExists(pid) {
|
|
107
|
+
try {
|
|
108
|
+
process.kill(pid, 0);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
return error.code === "EPERM";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=hook-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-discovery.js","sourceRoot":"","sources":["../src/hook-discovery.ts"],"names":[],"mappings":"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"}
|