feat: add exec approvals tooling and service status

This commit is contained in:
Peter Steinberger
2026-01-18 15:23:36 +00:00
parent 9c06689569
commit 3686bde783
39 changed files with 1472 additions and 35 deletions

View File

@@ -8,10 +8,16 @@ import type { BridgeInvokeRequestFrame } from "../infra/bridge/server/types.js";
import {
addAllowlistEntry,
matchAllowlist,
normalizeExecApprovals,
recordAllowlistUse,
requestExecApprovalViaSocket,
resolveCommandResolution,
resolveExecApprovals,
ensureExecApprovals,
readExecApprovalsSnapshot,
resolveExecApprovalsSocketPath,
saveExecApprovals,
type ExecApprovalsFile,
} from "../infra/exec-approvals.js";
import { getMachineDisplayName } from "../infra/machine-name.js";
import { VERSION } from "../version.js";
@@ -43,6 +49,18 @@ type SystemWhichParams = {
bins: string[];
};
type SystemExecApprovalsSetParams = {
file: ExecApprovalsFile;
baseHash?: string | null;
};
type ExecApprovalsSnapshot = {
path: string;
exists: boolean;
hash: string;
file: ExecApprovalsFile;
};
type RunResult = {
exitCode?: number;
timedOut: boolean;
@@ -143,6 +161,31 @@ function truncateOutput(raw: string, maxChars: number): { text: string; truncate
return { text: `... (truncated) ${raw.slice(raw.length - maxChars)}`, truncated: true };
}
function redactExecApprovals(file: ExecApprovalsFile): ExecApprovalsFile {
const socketPath = file.socket?.path?.trim();
return {
...file,
socket: socketPath ? { path: socketPath } : undefined,
};
}
function requireExecApprovalsBaseHash(
params: SystemExecApprovalsSetParams,
snapshot: ExecApprovalsSnapshot,
) {
if (!snapshot.exists) return;
if (!snapshot.hash) {
throw new Error("INVALID_REQUEST: exec approvals base hash unavailable; reload and retry");
}
const baseHash = typeof params.baseHash === "string" ? params.baseHash.trim() : "";
if (!baseHash) {
throw new Error("INVALID_REQUEST: exec approvals base hash required; reload and retry");
}
if (baseHash !== snapshot.hash) {
throw new Error("INVALID_REQUEST: exec approvals changed; reload and retry");
}
}
async function runCommand(
argv: string[],
cwd: string | undefined,
@@ -306,7 +349,12 @@ export async function runNodeHost(opts: NodeHostRunOptions): Promise<void> {
deviceFamily: os.platform(),
modelIdentifier: os.hostname(),
caps: ["system"],
commands: ["system.run", "system.which"],
commands: [
"system.run",
"system.which",
"system.execApprovals.get",
"system.execApprovals.set",
],
onPairToken: async (token) => {
config.token = token;
await saveNodeHostConfig(config);
@@ -355,6 +403,80 @@ async function handleInvoke(
skillBins: SkillBinsCache,
) {
const command = String(frame.command ?? "");
if (command === "system.execApprovals.get") {
try {
ensureExecApprovals();
const snapshot = readExecApprovalsSnapshot();
const payload: ExecApprovalsSnapshot = {
path: snapshot.path,
exists: snapshot.exists,
hash: snapshot.hash,
file: redactExecApprovals(snapshot.file),
};
client.sendInvokeResponse({
type: "invoke-res",
id: frame.id,
ok: true,
payloadJSON: JSON.stringify(payload),
});
} catch (err) {
client.sendInvokeResponse({
type: "invoke-res",
id: frame.id,
ok: false,
error: { code: "INVALID_REQUEST", message: String(err) },
});
}
return;
}
if (command === "system.execApprovals.set") {
try {
const params = decodeParams<SystemExecApprovalsSetParams>(frame.paramsJSON);
if (!params.file || typeof params.file !== "object") {
throw new Error("INVALID_REQUEST: exec approvals file required");
}
ensureExecApprovals();
const snapshot = readExecApprovalsSnapshot();
requireExecApprovalsBaseHash(params, snapshot);
const normalized = normalizeExecApprovals(params.file);
const currentSocketPath = snapshot.file.socket?.path?.trim();
const currentToken = snapshot.file.socket?.token?.trim();
const socketPath =
normalized.socket?.path?.trim() ?? currentSocketPath ?? resolveExecApprovalsSocketPath();
const token = normalized.socket?.token?.trim() ?? currentToken ?? "";
const next: ExecApprovalsFile = {
...normalized,
socket: {
path: socketPath,
token,
},
};
saveExecApprovals(next);
const nextSnapshot = readExecApprovalsSnapshot();
const payload: ExecApprovalsSnapshot = {
path: nextSnapshot.path,
exists: nextSnapshot.exists,
hash: nextSnapshot.hash,
file: redactExecApprovals(nextSnapshot.file),
};
client.sendInvokeResponse({
type: "invoke-res",
id: frame.id,
ok: true,
payloadJSON: JSON.stringify(payload),
});
} catch (err) {
client.sendInvokeResponse({
type: "invoke-res",
id: frame.id,
ok: false,
error: { code: "INVALID_REQUEST", message: String(err) },
});
}
return;
}
if (command === "system.which") {
try {
const params = decodeParams<SystemWhichParams>(frame.paramsJSON);