fix: resolve format/build failures

This commit is contained in:
Peter Steinberger
2026-01-19 11:32:15 +00:00
parent b826bd668c
commit 588dc43787
22 changed files with 82 additions and 106 deletions

View File

@@ -495,7 +495,7 @@ export function createExecTool(
)) as { decision?: string } | null; )) as { decision?: string } | null;
const decision = const decision =
decisionResult && typeof decisionResult === "object" decisionResult && typeof decisionResult === "object"
? decisionResult.decision ?? null ? (decisionResult.decision ?? null)
: null; : null;
if (decision === "deny") { if (decision === "deny") {
@@ -506,9 +506,7 @@ export function createExecTool(
approvedByAsk = true; approvedByAsk = true;
} else if (askFallback === "allowlist") { } else if (askFallback === "allowlist") {
if (!allowlistMatch) { if (!allowlistMatch) {
throw new Error( throw new Error("exec denied: approval required (approval UI not available)");
"exec denied: approval required (approval UI not available)",
);
} }
approvedByAsk = true; approvedByAsk = true;
} else { } else {
@@ -624,7 +622,7 @@ export function createExecTool(
)) as { decision?: string } | null; )) as { decision?: string } | null;
const decision = const decision =
decisionResult && typeof decisionResult === "object" decisionResult && typeof decisionResult === "object"
? decisionResult.decision ?? null ? (decisionResult.decision ?? null)
: null; : null;
if (decision === "deny") { if (decision === "deny") {
@@ -635,9 +633,7 @@ export function createExecTool(
approvedByAsk = true; approvedByAsk = true;
} else if (askFallback === "allowlist") { } else if (askFallback === "allowlist") {
if (!allowlistMatch) { if (!allowlistMatch) {
throw new Error( throw new Error("exec denied: approval required (approval UI not available)");
"exec denied: approval required (approval UI not available)",
);
} }
approvedByAsk = true; approvedByAsk = true;
} else { } else {

View File

@@ -55,7 +55,9 @@ function resolveAgentIdByWorkspace(
): string[] { ): string[] {
const list = listAgentEntries(cfg); const list = listAgentEntries(cfg);
const ids = const ids =
list.length > 0 ? list.map((entry) => normalizeAgentId(entry.id)) : [resolveDefaultAgentId(cfg)]; list.length > 0
? list.map((entry) => normalizeAgentId(entry.id))
: [resolveDefaultAgentId(cfg)];
const normalizedTarget = normalizeWorkspacePath(workspaceDir); const normalizedTarget = normalizeWorkspacePath(workspaceDir);
return ids.filter( return ids.filter(
(id) => normalizeWorkspacePath(resolveAgentWorkspaceDir(cfg, id)) === normalizedTarget, (id) => normalizeWorkspacePath(resolveAgentWorkspaceDir(cfg, id)) === normalizedTarget,
@@ -134,10 +136,7 @@ export async function agentsSetIdentityCommand(
} }
const fileTheme = const fileTheme =
identityFromFile?.theme ?? identityFromFile?.theme ?? identityFromFile?.creature ?? identityFromFile?.vibe ?? undefined;
identityFromFile?.creature ??
identityFromFile?.vibe ??
undefined;
const incomingIdentity: IdentityConfig = { const incomingIdentity: IdentityConfig = {
...(nameRaw || identityFromFile?.name ? { name: nameRaw ?? identityFromFile?.name } : {}), ...(nameRaw || identityFromFile?.name ? { name: nameRaw ?? identityFromFile?.name } : {}),
...(emojiRaw || identityFromFile?.emoji ? { emoji: emojiRaw ?? identityFromFile?.emoji } : {}), ...(emojiRaw || identityFromFile?.emoji ? { emoji: emojiRaw ?? identityFromFile?.emoji } : {}),

View File

@@ -79,11 +79,7 @@ export function parseIdentityMarkdown(content: string): AgentIdentity {
const cleaned = line.trim().replace(/^\s*-\s*/, ""); const cleaned = line.trim().replace(/^\s*-\s*/, "");
const colonIndex = cleaned.indexOf(":"); const colonIndex = cleaned.indexOf(":");
if (colonIndex === -1) continue; if (colonIndex === -1) continue;
const label = cleaned const label = cleaned.slice(0, colonIndex).replace(/[*_]/g, "").trim().toLowerCase();
.slice(0, colonIndex)
.replace(/[*_]/g, "")
.trim()
.toLowerCase();
const value = cleaned const value = cleaned
.slice(colonIndex + 1) .slice(colonIndex + 1)
.replace(/^[*_]+|[*_]+$/g, "") .replace(/^[*_]+|[*_]+$/g, "")

View File

@@ -93,7 +93,12 @@ describe("agents set-identity command", () => {
configMocks.readConfigFileSnapshot.mockResolvedValue({ configMocks.readConfigFileSnapshot.mockResolvedValue({
...baseSnapshot, ...baseSnapshot,
config: { config: {
agents: { list: [{ id: "main", workspace }, { id: "ops", workspace }] }, agents: {
list: [
{ id: "main", workspace },
{ id: "ops", workspace },
],
},
}, },
}); });

View File

@@ -4,7 +4,12 @@ import type { LoggingConfig, SessionConfig, WebConfig } from "./types.base.js";
import type { BrowserConfig } from "./types.browser.js"; import type { BrowserConfig } from "./types.browser.js";
import type { ChannelsConfig } from "./types.channels.js"; import type { ChannelsConfig } from "./types.channels.js";
import type { CronConfig } from "./types.cron.js"; import type { CronConfig } from "./types.cron.js";
import type { CanvasHostConfig, DiscoveryConfig, GatewayConfig, TalkConfig } from "./types.gateway.js"; import type {
CanvasHostConfig,
DiscoveryConfig,
GatewayConfig,
TalkConfig,
} from "./types.gateway.js";
import type { HooksConfig } from "./types.hooks.js"; import type { HooksConfig } from "./types.hooks.js";
import type { import type {
AudioConfig, AudioConfig,

View File

@@ -113,7 +113,6 @@ export type GatewayHttpConfig = {
endpoints?: GatewayHttpEndpointsConfig; endpoints?: GatewayHttpEndpointsConfig;
}; };
export type GatewayConfig = { export type GatewayConfig = {
/** Single multiplexed port for Gateway WS + HTTP (default: 18789). */ /** Single multiplexed port for Gateway WS + HTTP (default: 18789). */
port?: number; port?: number;

View File

@@ -231,12 +231,7 @@ export const ClawdbotSchema = z
port: z.number().int().positive().optional(), port: z.number().int().positive().optional(),
mode: z.union([z.literal("local"), z.literal("remote")]).optional(), mode: z.union([z.literal("local"), z.literal("remote")]).optional(),
bind: z bind: z
.union([ .union([z.literal("auto"), z.literal("lan"), z.literal("loopback"), z.literal("custom")])
z.literal("auto"),
z.literal("lan"),
z.literal("loopback"),
z.literal("custom"),
])
.optional(), .optional(),
controlUi: z controlUi: z
.object({ .object({

View File

@@ -136,10 +136,7 @@ export async function callGateway<T = unknown>(opts: CallGatewayOptions): Promis
}); });
const url = connectionDetails.url; const url = connectionDetails.url;
const useLocalTls = const useLocalTls =
config.gateway?.tls?.enabled === true && config.gateway?.tls?.enabled === true && !urlOverride && !remoteUrl && url.startsWith("wss://");
!urlOverride &&
!remoteUrl &&
url.startsWith("wss://");
const tlsRuntime = useLocalTls ? await loadGatewayTlsRuntime(config.gateway?.tls) : undefined; const tlsRuntime = useLocalTls ? await loadGatewayTlsRuntime(config.gateway?.tls) : undefined;
const tlsFingerprint = tlsRuntime?.enabled ? tlsRuntime.fingerprintSha256 : undefined; const tlsFingerprint = tlsRuntime?.enabled ? tlsRuntime.fingerprintSha256 : undefined;
const token = const token =

View File

@@ -93,22 +93,14 @@ export class GatewayClient {
wsOptions.checkServerIdentity = (_host: string, cert: CertMeta) => { wsOptions.checkServerIdentity = (_host: string, cert: CertMeta) => {
const fingerprintValue = const fingerprintValue =
typeof cert === "object" && cert && "fingerprint256" in cert typeof cert === "object" && cert && "fingerprint256" in cert
? (cert as { fingerprint256?: string }).fingerprint256 ?? "" ? ((cert as { fingerprint256?: string }).fingerprint256 ?? "")
: ""; : "";
const fingerprint = normalizeFingerprint( const fingerprint = normalizeFingerprint(
typeof fingerprintValue === "string" ? fingerprintValue : "", typeof fingerprintValue === "string" ? fingerprintValue : "",
); );
const expected = normalizeFingerprint(this.opts.tlsFingerprint ?? ""); const expected = normalizeFingerprint(this.opts.tlsFingerprint ?? "");
if (!expected) { if (!expected || !fingerprint) return false;
return new Error("gateway tls fingerprint missing"); return fingerprint === expected;
}
if (!fingerprint) {
return new Error("gateway tls fingerprint unavailable");
}
if (fingerprint !== expected) {
return new Error("gateway tls fingerprint mismatch");
}
return undefined;
}; };
} }
this.ws = new WebSocket(url, wsOptions); this.ws = new WebSocket(url, wsOptions);

View File

@@ -45,7 +45,10 @@ export class ExecApprovalManager {
return record; return record;
} }
async waitForDecision(record: ExecApprovalRecord, timeoutMs: number): Promise<ExecApprovalDecision | null> { async waitForDecision(
record: ExecApprovalRecord,
timeoutMs: number,
): Promise<ExecApprovalDecision | null> {
return await new Promise<ExecApprovalDecision | null>((resolve, reject) => { return await new Promise<ExecApprovalDecision | null>((resolve, reject) => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
this.pending.delete(record.id); this.pending.delete(record.id);

View File

@@ -45,7 +45,7 @@ export class NodeRegistry {
const nodeId = connect.device?.id ?? connect.client.id; const nodeId = connect.device?.id ?? connect.client.id;
const caps = Array.isArray(connect.caps) ? connect.caps : []; const caps = Array.isArray(connect.caps) ? connect.caps : [];
const commands = Array.isArray((connect as { commands?: string[] }).commands) const commands = Array.isArray((connect as { commands?: string[] }).commands)
? (connect as { commands?: string[] }).commands ?? [] ? ((connect as { commands?: string[] }).commands ?? [])
: []; : [];
const permissions = const permissions =
typeof (connect as { permissions?: Record<string, boolean> }).permissions === "object" typeof (connect as { permissions?: Record<string, boolean> }).permissions === "object"

View File

@@ -9,9 +9,7 @@ import {
} from "../protocol/index.js"; } from "../protocol/index.js";
import type { GatewayRequestHandlers } from "./types.js"; import type { GatewayRequestHandlers } from "./types.js";
export function createExecApprovalHandlers( export function createExecApprovalHandlers(manager: ExecApprovalManager): GatewayRequestHandlers {
manager: ExecApprovalManager,
): GatewayRequestHandlers {
return { return {
"exec.approval.request": async ({ params, respond, context }) => { "exec.approval.request": async ({ params, respond, context }) => {
if (!validateExecApprovalRequestParams(params)) { if (!validateExecApprovalRequestParams(params)) {
@@ -61,12 +59,16 @@ export function createExecApprovalHandlers(
{ dropIfSlow: true }, { dropIfSlow: true },
); );
const decision = await manager.waitForDecision(record, timeoutMs); const decision = await manager.waitForDecision(record, timeoutMs);
respond(true, { respond(
true,
{
id: record.id, id: record.id,
decision, decision,
createdAtMs: record.createdAtMs, createdAtMs: record.createdAtMs,
expiresAtMs: record.expiresAtMs, expiresAtMs: record.expiresAtMs,
}, undefined); },
undefined,
);
}, },
"exec.approval.resolve": async ({ params, respond, client, context }) => { "exec.approval.resolve": async ({ params, respond, client, context }) => {
if (!validateExecApprovalResolveParams(params)) { if (!validateExecApprovalResolveParams(params)) {

View File

@@ -453,14 +453,10 @@ export const nodeHandlers: GatewayRequestHandlers = {
loadGatewayModelCatalog: context.loadGatewayModelCatalog, loadGatewayModelCatalog: context.loadGatewayModelCatalog,
logGateway: { warn: context.logGateway.warn }, logGateway: { warn: context.logGateway.warn },
}; };
await handleNodeEvent( await handleNodeEvent(nodeContext, "node", {
nodeContext,
"node",
{
event: p.event, event: p.event,
payloadJSON, payloadJSON,
}, });
);
respond(true, { ok: true }, undefined); respond(true, { ok: true }, undefined);
}); });
}, },

View File

@@ -11,11 +11,7 @@ import type { NodeEvent, NodeEventContext } from "./server-node-events-types.js"
import { loadSessionEntry } from "./session-utils.js"; import { loadSessionEntry } from "./session-utils.js";
import { formatForLog } from "./ws-log.js"; import { formatForLog } from "./ws-log.js";
export const handleNodeEvent = async ( export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt: NodeEvent) => {
ctx: NodeEventContext,
nodeId: string,
evt: NodeEvent,
) => {
switch (evt.event) { switch (evt.event) {
case "voice.transcript": { case "voice.transcript": {
if (!evt.payloadJSON) return; if (!evt.payloadJSON) return;

View File

@@ -1,4 +1,9 @@
import type { GatewayAuthConfig, GatewayBindMode, GatewayTailscaleConfig, loadConfig } from "../config/config.js"; import type {
GatewayAuthConfig,
GatewayBindMode,
GatewayTailscaleConfig,
loadConfig,
} from "../config/config.js";
import { import {
assertGatewayAuthConfigured, assertGatewayAuthConfigured,
type ResolvedGatewayAuth, type ResolvedGatewayAuth,

View File

@@ -22,7 +22,9 @@ export function logGatewayStartup(params: {
consoleMessage: `agent model: ${chalk.whiteBright(modelRef)}`, consoleMessage: `agent model: ${chalk.whiteBright(modelRef)}`,
}); });
const scheme = params.tlsEnabled ? "wss" : "ws"; const scheme = params.tlsEnabled ? "wss" : "ws";
params.log.info(`listening on ${scheme}://${params.bindHost}:${params.port} (PID ${process.pid})`); params.log.info(
`listening on ${scheme}://${params.bindHost}:${params.port} (PID ${process.pid})`,
);
params.log.info(`log file: ${getResolvedLoggerSettings().file}`); params.log.info(`log file: ${getResolvedLoggerSettings().file}`);
if (params.isNixMode) { if (params.isNixMode) {
params.log.info("gateway: running in Nix mode (config managed externally)"); params.log.info("gateway: running in Nix mode (config managed externally)");

View File

@@ -260,5 +260,4 @@ describe("gateway server models + voicewake", () => {
ws.close(); ws.close();
await server.close(); await server.close();
}); });
}); });

View File

@@ -499,7 +499,9 @@ export function attachGatewayWsMessageHandler(params: {
commands: nodeSession.commands, commands: nodeSession.commands,
cfg: loadConfig(), cfg: loadConfig(),
}).catch((err) => }).catch((err) =>
logGateway.warn(`remote bin probe failed for ${nodeSession.nodeId}: ${formatForLog(err)}`), logGateway.warn(
`remote bin probe failed for ${nodeSession.nodeId}: ${formatForLog(err)}`,
),
); );
void loadVoiceWakeConfig() void loadVoiceWakeConfig()
.then((cfg) => { .then((cfg) => {

View File

@@ -13,7 +13,6 @@ import type { PluginRegistry } from "../plugins/registry.js";
import { setActivePluginRegistry } from "../plugins/runtime.js"; import { setActivePluginRegistry } from "../plugins/runtime.js";
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js"; import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
type StubChannelOptions = { type StubChannelOptions = {
id: ChannelPlugin["id"]; id: ChannelPlugin["id"];
label: string; label: string;

View File

@@ -27,11 +27,7 @@ function ensureDir(filePath: string) {
const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex"); const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
function base64UrlEncode(buf: Buffer): string { function base64UrlEncode(buf: Buffer): string {
return buf return buf.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/g, "");
.toString("base64")
.replaceAll("+", "-")
.replaceAll("/", "_")
.replace(/=+$/g, "");
} }
function base64UrlDecode(input: string): Buffer { function base64UrlDecode(input: string): Buffer {

View File

@@ -26,10 +26,7 @@ import { getMachineDisplayName } from "../infra/machine-name.js";
import { loadOrCreateDeviceIdentity } from "../infra/device-identity.js"; import { loadOrCreateDeviceIdentity } from "../infra/device-identity.js";
import { loadConfig } from "../config/config.js"; import { loadConfig } from "../config/config.js";
import { VERSION } from "../version.js"; import { VERSION } from "../version.js";
import { import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
GATEWAY_CLIENT_MODES,
GATEWAY_CLIENT_NAMES,
} from "../utils/message-channel.js";
import { ensureNodeHostConfig, saveNodeHostConfig, type NodeHostGatewayConfig } from "./config.js"; import { ensureNodeHostConfig, saveNodeHostConfig, type NodeHostGatewayConfig } from "./config.js";
import { GatewayClient } from "../gateway/client.js"; import { GatewayClient } from "../gateway/client.js";
@@ -804,8 +801,7 @@ function coerceNodeInvokePayload(payload: unknown): NodeInvokeRequestPayload | n
? JSON.stringify(obj.params) ? JSON.stringify(obj.params)
: null; : null;
const timeoutMs = typeof obj.timeoutMs === "number" ? obj.timeoutMs : null; const timeoutMs = typeof obj.timeoutMs === "number" ? obj.timeoutMs : null;
const idempotencyKey = const idempotencyKey = typeof obj.idempotencyKey === "string" ? obj.idempotencyKey : null;
typeof obj.idempotencyKey === "string" ? obj.idempotencyKey : null;
return { return {
id, id,
nodeId, nodeId,
@@ -840,11 +836,7 @@ async function sendInvokeResult(
} }
} }
async function sendNodeEvent( async function sendNodeEvent(client: GatewayClient, event: string, payload: unknown) {
client: GatewayClient,
event: string,
payload: unknown,
) {
try { try {
await client.request("node.event", { await client.request("node.event", {
event, event,