chore: run format and fix sandbox browser timeouts
This commit is contained in:
@@ -191,8 +191,8 @@ extension CronJobEditor {
|
||||
func applyDeleteAfterRun(
|
||||
to root: inout [String: Any],
|
||||
scheduleKind: ScheduleKind? = nil,
|
||||
deleteAfterRun: Bool? = nil
|
||||
) {
|
||||
deleteAfterRun: Bool? = nil)
|
||||
{
|
||||
let resolvedSchedule = scheduleKind ?? self.scheduleKind
|
||||
let resolvedDelete = deleteAfterRun ?? self.deleteAfterRun
|
||||
if resolvedSchedule == .at {
|
||||
|
||||
@@ -58,9 +58,7 @@ export function stripThoughtSignatures<T>(
|
||||
if (!block || typeof block !== "object") return block;
|
||||
const rec = block as ContentBlockWithSignature;
|
||||
const stripSnake = shouldStripSignature(rec.thought_signature);
|
||||
const stripCamel = includeCamelCase
|
||||
? shouldStripSignature(rec.thoughtSignature)
|
||||
: false;
|
||||
const stripCamel = includeCamelCase ? shouldStripSignature(rec.thoughtSignature) : false;
|
||||
if (!stripSnake && !stripCamel) {
|
||||
return block;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ describe("sanitizeSessionHistory (google thinking)", () => {
|
||||
id: "call_1",
|
||||
name: "read",
|
||||
arguments: { path: "/tmp/foo" },
|
||||
thoughtSignature: "{\"id\":1}",
|
||||
thoughtSignature: '{"id":1}',
|
||||
},
|
||||
{
|
||||
type: "toolCall",
|
||||
@@ -192,7 +192,7 @@ describe("sanitizeSessionHistory (google thinking)", () => {
|
||||
{ type: "text", text: "ok" },
|
||||
{
|
||||
type: "text",
|
||||
text: "[Tool Call: read (ID: call_1)]\nArguments: {\n \"path\": \"/tmp/foo\"\n}",
|
||||
text: '[Tool Call: read (ID: call_1)]\nArguments: {\n "path": "/tmp/foo"\n}',
|
||||
},
|
||||
{
|
||||
type: "toolCall",
|
||||
|
||||
@@ -11,9 +11,7 @@ vi.mock("./pi-embedded-helpers.js", async () => {
|
||||
...actual,
|
||||
isGoogleModelApi: vi.fn(),
|
||||
downgradeGeminiHistory: vi.fn(),
|
||||
sanitizeSessionMessagesImages: vi
|
||||
.fn()
|
||||
.mockImplementation(async (msgs) => msgs),
|
||||
sanitizeSessionMessagesImages: vi.fn().mockImplementation(async (msgs) => msgs),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -30,9 +28,7 @@ describe("sanitizeSessionHistory", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(helpers.sanitizeSessionMessagesImages).mockImplementation(
|
||||
async (msgs) => msgs,
|
||||
);
|
||||
vi.mocked(helpers.sanitizeSessionMessagesImages).mockImplementation(async (msgs) => msgs);
|
||||
// Default mock implementation
|
||||
vi.mocked(helpers.downgradeGeminiHistory).mockImplementation((msgs) => {
|
||||
if (!msgs) return [];
|
||||
|
||||
@@ -82,12 +82,13 @@ function findUnsupportedSchemaKeywords(schema: unknown, path: string): string[]
|
||||
return violations;
|
||||
}
|
||||
|
||||
export function sanitizeToolsForGoogle<TSchemaType extends TSchema = TSchema, TResult = unknown>(
|
||||
params: {
|
||||
tools: AgentTool<TSchemaType, TResult>[];
|
||||
provider: string;
|
||||
},
|
||||
): AgentTool<TSchemaType, TResult>[] {
|
||||
export function sanitizeToolsForGoogle<
|
||||
TSchemaType extends TSchema = TSchema,
|
||||
TResult = unknown,
|
||||
>(params: {
|
||||
tools: AgentTool<TSchemaType, TResult>[];
|
||||
provider: string;
|
||||
}): AgentTool<TSchemaType, TResult>[] {
|
||||
if (params.provider !== "google-antigravity" && params.provider !== "google-gemini-cli") {
|
||||
return params.tools;
|
||||
}
|
||||
@@ -95,7 +96,9 @@ export function sanitizeToolsForGoogle<TSchemaType extends TSchema = TSchema, TR
|
||||
if (!tool.parameters || typeof tool.parameters !== "object") return tool;
|
||||
return {
|
||||
...tool,
|
||||
parameters: cleanToolSchemaForGemini(tool.parameters as Record<string, unknown>) as TSchemaType,
|
||||
parameters: cleanToolSchemaForGemini(
|
||||
tool.parameters as Record<string, unknown>,
|
||||
) as TSchemaType,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,10 +22,7 @@ export type SubscribeEmbeddedPiSessionParams = {
|
||||
blockReplyChunking?: BlockReplyChunking;
|
||||
onPartialReply?: (payload: { text?: string; mediaUrls?: string[] }) => void | Promise<void>;
|
||||
onAssistantMessageStart?: () => void | Promise<void>;
|
||||
onAgentEvent?: (evt: {
|
||||
stream: string;
|
||||
data: Record<string, unknown>;
|
||||
}) => void | Promise<void>;
|
||||
onAgentEvent?: (evt: { stream: string; data: Record<string, unknown> }) => void | Promise<void>;
|
||||
enforceFinalTag?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ function buildSandboxBrowserResolvedConfig(params: {
|
||||
cdpProtocol: "http",
|
||||
cdpHost,
|
||||
cdpIsLoopback: true,
|
||||
remoteCdpTimeoutMs: 1500,
|
||||
remoteCdpHandshakeTimeoutMs: 3000,
|
||||
color: DEFAULT_CLAWD_BROWSER_COLOR,
|
||||
executablePath: undefined,
|
||||
headless: params.headless,
|
||||
|
||||
@@ -15,9 +15,7 @@ describe("cdp.helpers", () => {
|
||||
|
||||
it("adds basic auth headers when credentials are present", () => {
|
||||
const headers = getHeadersWithAuth("https://user:pass@example.com");
|
||||
expect(headers.Authorization).toBe(
|
||||
`Basic ${Buffer.from("user:pass").toString("base64")}`,
|
||||
);
|
||||
expect(headers.Authorization).toBe(`Basic ${Buffer.from("user:pass").toString("base64")}`);
|
||||
});
|
||||
|
||||
it("keeps preexisting authorization headers", () => {
|
||||
|
||||
@@ -28,15 +28,10 @@ export function isLoopbackHost(host: string) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getHeadersWithAuth(
|
||||
url: string,
|
||||
headers: Record<string, string> = {},
|
||||
) {
|
||||
export function getHeadersWithAuth(url: string, headers: Record<string, string> = {}) {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
const hasAuthHeader = Object.keys(headers).some(
|
||||
(key) => key.toLowerCase() === "authorization",
|
||||
);
|
||||
const hasAuthHeader = Object.keys(headers).some((key) => key.toLowerCase() === "authorization");
|
||||
if (hasAuthHeader) return headers;
|
||||
if (parsed.username || parsed.password) {
|
||||
const auth = Buffer.from(`${parsed.username}:${parsed.password}`).toString("base64");
|
||||
@@ -103,18 +98,11 @@ function createCdpSender(ws: WebSocket) {
|
||||
return { send, closeWithError };
|
||||
}
|
||||
|
||||
export async function fetchJson<T>(
|
||||
url: string,
|
||||
timeoutMs = 1500,
|
||||
init?: RequestInit,
|
||||
): Promise<T> {
|
||||
export async function fetchJson<T>(url: string, timeoutMs = 1500, init?: RequestInit): Promise<T> {
|
||||
const ctrl = new AbortController();
|
||||
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
try {
|
||||
const headers = getHeadersWithAuth(
|
||||
url,
|
||||
(init?.headers as Record<string, string>) || {},
|
||||
);
|
||||
const headers = getHeadersWithAuth(url, (init?.headers as Record<string, string>) || {});
|
||||
const res = await fetch(url, { ...init, headers, signal: ctrl.signal });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return (await res.json()) as T;
|
||||
@@ -123,18 +111,11 @@ export async function fetchJson<T>(
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchOk(
|
||||
url: string,
|
||||
timeoutMs = 1500,
|
||||
init?: RequestInit,
|
||||
): Promise<void> {
|
||||
export async function fetchOk(url: string, timeoutMs = 1500, init?: RequestInit): Promise<void> {
|
||||
const ctrl = new AbortController();
|
||||
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
try {
|
||||
const headers = getHeadersWithAuth(
|
||||
url,
|
||||
(init?.headers as Record<string, string>) || {},
|
||||
);
|
||||
const headers = getHeadersWithAuth(url, (init?.headers as Record<string, string>) || {});
|
||||
const res = await fetch(url, { ...init, headers, signal: ctrl.signal });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
} finally {
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
appendCdpPath,
|
||||
fetchJson,
|
||||
isLoopbackHost,
|
||||
withCdpSocket,
|
||||
} from "./cdp.helpers.js";
|
||||
import { appendCdpPath, fetchJson, isLoopbackHost, withCdpSocket } from "./cdp.helpers.js";
|
||||
|
||||
export { appendCdpPath, fetchJson, fetchOk, getHeadersWithAuth } from "./cdp.helpers.js";
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@ describe("browser default executable detection", () => {
|
||||
vi.mocked(execFileSync).mockImplementation((cmd, args) => {
|
||||
const argsStr = Array.isArray(args) ? args.join(" ") : "";
|
||||
if (cmd === "/usr/bin/plutil" && argsStr.includes("LSHandlers")) {
|
||||
return JSON.stringify([{ LSHandlerURLScheme: "http", LSHandlerRoleAll: "com.google.Chrome" }]);
|
||||
return JSON.stringify([
|
||||
{ LSHandlerURLScheme: "http", LSHandlerRoleAll: "com.google.Chrome" },
|
||||
]);
|
||||
}
|
||||
if (cmd === "/usr/bin/osascript" && argsStr.includes("path to application id")) {
|
||||
return "/Applications/Google Chrome.app";
|
||||
@@ -55,7 +57,9 @@ describe("browser default executable detection", () => {
|
||||
vi.mocked(execFileSync).mockImplementation((cmd, args) => {
|
||||
const argsStr = Array.isArray(args) ? args.join(" ") : "";
|
||||
if (cmd === "/usr/bin/plutil" && argsStr.includes("LSHandlers")) {
|
||||
return JSON.stringify([{ LSHandlerURLScheme: "http", LSHandlerRoleAll: "com.apple.Safari" }]);
|
||||
return JSON.stringify([
|
||||
{ LSHandlerURLScheme: "http", LSHandlerRoleAll: "com.apple.Safari" },
|
||||
]);
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
@@ -119,7 +119,12 @@ function inferKindFromIdentifier(identifier: string): BrowserExecutable["kind"]
|
||||
if (id.includes("edge")) return "edge";
|
||||
if (id.includes("chromium")) return "chromium";
|
||||
if (id.includes("canary")) return "canary";
|
||||
if (id.includes("opera") || id.includes("vivaldi") || id.includes("yandex") || id.includes("thebrowser")) {
|
||||
if (
|
||||
id.includes("opera") ||
|
||||
id.includes("vivaldi") ||
|
||||
id.includes("yandex") ||
|
||||
id.includes("thebrowser")
|
||||
) {
|
||||
return "chromium";
|
||||
}
|
||||
return "chrome";
|
||||
@@ -131,13 +136,12 @@ function inferKindFromExecutableName(name: string): BrowserExecutable["kind"] {
|
||||
if (lower.includes("edge") || lower.includes("msedge")) return "edge";
|
||||
if (lower.includes("chromium")) return "chromium";
|
||||
if (lower.includes("canary") || lower.includes("sxs")) return "canary";
|
||||
if (lower.includes("opera") || lower.includes("vivaldi") || lower.includes("yandex")) return "chromium";
|
||||
if (lower.includes("opera") || lower.includes("vivaldi") || lower.includes("yandex"))
|
||||
return "chromium";
|
||||
return "chrome";
|
||||
}
|
||||
|
||||
function detectDefaultChromiumExecutable(
|
||||
platform: NodeJS.Platform,
|
||||
): BrowserExecutable | null {
|
||||
function detectDefaultChromiumExecutable(platform: NodeJS.Platform): BrowserExecutable | null {
|
||||
if (platform === "darwin") return detectDefaultChromiumExecutableMac();
|
||||
if (platform === "linux") return detectDefaultChromiumExecutableLinux();
|
||||
if (platform === "win32") return detectDefaultChromiumExecutableWindows();
|
||||
@@ -227,8 +231,7 @@ function detectDefaultChromiumExecutableLinux(): BrowserExecutable | null {
|
||||
function detectDefaultChromiumExecutableWindows(): BrowserExecutable | null {
|
||||
const progId = readWindowsProgId();
|
||||
const command =
|
||||
(progId ? readWindowsCommandForProgId(progId) : null) ||
|
||||
readWindowsCommandForProgId("http");
|
||||
(progId ? readWindowsCommandForProgId(progId) : null) || readWindowsCommandForProgId("http");
|
||||
if (!command) return null;
|
||||
const expanded = expandWindowsEnvVars(command);
|
||||
const exePath = extractWindowsExecutablePath(expanded);
|
||||
@@ -285,7 +288,7 @@ function splitExecLine(line: string): string[] {
|
||||
let quoteChar = "";
|
||||
for (let i = 0; i < line.length; i += 1) {
|
||||
const ch = line[i];
|
||||
if ((ch === "\"" || ch === "'") && (!inQuotes || ch === quoteChar)) {
|
||||
if ((ch === '"' || ch === "'") && (!inQuotes || ch === quoteChar)) {
|
||||
if (inQuotes) {
|
||||
inQuotes = false;
|
||||
quoteChar = "";
|
||||
@@ -342,7 +345,7 @@ function readWindowsCommandForProgId(progId: string): string | null {
|
||||
function expandWindowsEnvVars(value: string): string {
|
||||
return value.replace(/%([^%]+)%/g, (_match, name) => {
|
||||
const key = String(name ?? "").trim();
|
||||
return key ? process.env[key] ?? `%${key}%` : _match;
|
||||
return key ? (process.env[key] ?? `%${key}%`) : _match;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -267,9 +267,7 @@ async function connectBrowser(cdpUrl: string): Promise<ConnectedBrowser> {
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
try {
|
||||
const timeout = 5000 + attempt * 2000;
|
||||
const wsUrl = await getChromeWebSocketUrl(normalized, timeout).catch(
|
||||
() => null,
|
||||
);
|
||||
const wsUrl = await getChromeWebSocketUrl(normalized, timeout).catch(() => null);
|
||||
const endpoint = wsUrl ?? normalized;
|
||||
const headers = getHeadersWithAuth(endpoint);
|
||||
const browser = await chromium.connectOverCDP(endpoint, { timeout, headers });
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
|
||||
import {
|
||||
appendCdpPath,
|
||||
createTargetViaCdp,
|
||||
getHeadersWithAuth,
|
||||
normalizeCdpWsUrl,
|
||||
} from "./cdp.js";
|
||||
import { appendCdpPath, createTargetViaCdp, getHeadersWithAuth, normalizeCdpWsUrl } from "./cdp.js";
|
||||
import {
|
||||
isChromeCdpReady,
|
||||
isChromeReachable,
|
||||
@@ -55,10 +50,7 @@ async function fetchJson<T>(url: string, timeoutMs = 1500, init?: RequestInit):
|
||||
const ctrl = new AbortController();
|
||||
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
try {
|
||||
const headers = getHeadersWithAuth(
|
||||
url,
|
||||
(init?.headers as Record<string, string>) || {},
|
||||
);
|
||||
const headers = getHeadersWithAuth(url, (init?.headers as Record<string, string>) || {});
|
||||
const res = await fetch(url, { ...init, headers, signal: ctrl.signal });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return (await res.json()) as T;
|
||||
@@ -71,10 +63,7 @@ async function fetchOk(url: string, timeoutMs = 1500, init?: RequestInit): Promi
|
||||
const ctrl = new AbortController();
|
||||
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
try {
|
||||
const headers = getHeadersWithAuth(
|
||||
url,
|
||||
(init?.headers as Record<string, string>) || {},
|
||||
);
|
||||
const headers = getHeadersWithAuth(url, (init?.headers as Record<string, string>) || {});
|
||||
const res = await fetch(url, { ...init, headers, signal: ctrl.signal });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
} finally {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -238,7 +238,11 @@ export function registerConfigCli(program: Command) {
|
||||
defaultRuntime.log(JSON.stringify(res.value ?? null, null, 2));
|
||||
return;
|
||||
}
|
||||
if (typeof res.value === "string" || typeof res.value === "number" || typeof res.value === "boolean") {
|
||||
if (
|
||||
typeof res.value === "string" ||
|
||||
typeof res.value === "number" ||
|
||||
typeof res.value === "boolean"
|
||||
) {
|
||||
defaultRuntime.log(String(res.value));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -225,7 +225,11 @@ describe("daemon-cli coverage", () => {
|
||||
});
|
||||
|
||||
const jsonLine = runtimeLogs.find((line) => line.trim().startsWith("{"));
|
||||
const parsed = JSON.parse(jsonLine ?? "{}") as { ok?: boolean; action?: string; result?: string };
|
||||
const parsed = JSON.parse(jsonLine ?? "{}") as {
|
||||
ok?: boolean;
|
||||
action?: string;
|
||||
result?: string;
|
||||
};
|
||||
expect(parsed.ok).toBe(true);
|
||||
expect(parsed.action).toBe("install");
|
||||
expect(parsed.result).toBe("installed");
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { Command } from "commander";
|
||||
import {
|
||||
listPairingChannels,
|
||||
notifyPairingApproved,
|
||||
} from "../channels/plugins/pairing.js";
|
||||
import { listPairingChannels, notifyPairingApproved } from "../channels/plugins/pairing.js";
|
||||
import { normalizeChannelId } from "../channels/plugins/index.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { resolvePairingIdLabel } from "../pairing/pairing-labels.js";
|
||||
|
||||
@@ -378,9 +378,7 @@ export function registerPluginsCli(program: Command) {
|
||||
continue;
|
||||
}
|
||||
if (record.source !== "npm") {
|
||||
defaultRuntime.log(
|
||||
chalk.yellow(`Skipping "${pluginId}" (source: ${record.source}).`),
|
||||
);
|
||||
defaultRuntime.log(chalk.yellow(`Skipping "${pluginId}" (source: ${record.source}).`));
|
||||
continue;
|
||||
}
|
||||
if (!record.spec) {
|
||||
@@ -412,9 +410,7 @@ export function registerPluginsCli(program: Command) {
|
||||
if (currentVersion && probe.version && currentVersion === probe.version) {
|
||||
defaultRuntime.log(`${pluginId} is up to date (${currentLabel}).`);
|
||||
} else {
|
||||
defaultRuntime.log(
|
||||
`Would update ${pluginId}: ${currentLabel} → ${nextVersion}.`,
|
||||
);
|
||||
defaultRuntime.log(`Would update ${pluginId}: ${currentLabel} → ${nextVersion}.`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -372,8 +372,6 @@ async function resolveSshTarget(
|
||||
});
|
||||
if (!target) return { target: rawTarget, identity: identity ?? undefined };
|
||||
const identityFile =
|
||||
identity ??
|
||||
config.identityFiles.find((entry) => entry.trim().length > 0)?.trim() ??
|
||||
undefined;
|
||||
identity ?? config.identityFiles.find((entry) => entry.trim().length > 0)?.trim() ?? undefined;
|
||||
return { target, identity: identityFile };
|
||||
}
|
||||
|
||||
@@ -291,9 +291,7 @@ export async function statusAllCommand(
|
||||
: gatewayProbe?.error
|
||||
? `unreachable (${gatewayProbe.error})`
|
||||
: "unreachable";
|
||||
const gatewayAuth = gatewayReachable
|
||||
? ` · auth ${formatGatewayAuthUsed(probeAuth)}`
|
||||
: "";
|
||||
const gatewayAuth = gatewayReachable ? ` · auth ${formatGatewayAuthUsed(probeAuth)}` : "";
|
||||
const gatewaySelfLine =
|
||||
gatewaySelf?.host || gatewaySelf?.ip || gatewaySelf?.version || gatewaySelf?.platform
|
||||
? [
|
||||
|
||||
@@ -28,18 +28,18 @@ describe("config discord", () => {
|
||||
dm: {
|
||||
enabled: true,
|
||||
allowFrom: ["steipete"],
|
||||
groupEnabled: true,
|
||||
groupChannels: ["clawd-dm"],
|
||||
},
|
||||
actions: {
|
||||
emojiUploads: true,
|
||||
stickerUploads: false,
|
||||
channels: true,
|
||||
},
|
||||
guilds: {
|
||||
"123": {
|
||||
slug: "friends-of-clawd",
|
||||
requireMention: false,
|
||||
groupEnabled: true,
|
||||
groupChannels: ["clawd-dm"],
|
||||
},
|
||||
actions: {
|
||||
emojiUploads: true,
|
||||
stickerUploads: false,
|
||||
channels: true,
|
||||
},
|
||||
guilds: {
|
||||
"123": {
|
||||
slug: "friends-of-clawd",
|
||||
requireMention: false,
|
||||
users: ["steipete"],
|
||||
channels: {
|
||||
general: { allow: true },
|
||||
|
||||
@@ -306,7 +306,8 @@ const FIELD_HELP: Record<string, string> = {
|
||||
"plugins.installs.*.source": 'Install source ("npm", "archive", or "path").',
|
||||
"plugins.installs.*.spec": "Original npm spec used for install (if source is npm).",
|
||||
"plugins.installs.*.sourcePath": "Original archive/path used for install (if any).",
|
||||
"plugins.installs.*.installPath": "Resolved install directory (usually ~/.clawdbot/extensions/<id>).",
|
||||
"plugins.installs.*.installPath":
|
||||
"Resolved install directory (usually ~/.clawdbot/extensions/<id>).",
|
||||
"plugins.installs.*.version": "Version recorded at install time (if available).",
|
||||
"plugins.installs.*.installedAt": "ISO timestamp of last install/update.",
|
||||
"agents.defaults.model.primary": "Primary model (provider/model).",
|
||||
|
||||
@@ -10,11 +10,9 @@ import {
|
||||
export const SessionSchema = z
|
||||
.object({
|
||||
scope: z.union([z.literal("per-sender"), z.literal("global")]).optional(),
|
||||
dmScope: z.union([
|
||||
z.literal("main"),
|
||||
z.literal("per-peer"),
|
||||
z.literal("per-channel-peer"),
|
||||
]).optional(),
|
||||
dmScope: z
|
||||
.union([z.literal("main"), z.literal("per-peer"), z.literal("per-channel-peer")])
|
||||
.optional(),
|
||||
resetTriggers: z.array(z.string()).optional(),
|
||||
idleMinutes: z.number().int().positive().optional(),
|
||||
heartbeatIdleMinutes: z.number().int().positive().optional(),
|
||||
|
||||
@@ -15,7 +15,7 @@ describe("parseSystemdExecStart", () => {
|
||||
});
|
||||
|
||||
it("preserves quoted arguments", () => {
|
||||
const execStart = "/usr/bin/clawdbot gateway start --name \"My Bot\"";
|
||||
const execStart = '/usr/bin/clawdbot gateway start --name "My Bot"';
|
||||
expect(parseSystemdExecStart(execStart)).toEqual([
|
||||
"/usr/bin/clawdbot",
|
||||
"gateway",
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { timingSafeEqual } from "node:crypto";
|
||||
import type { IncomingMessage } from "node:http";
|
||||
import type {
|
||||
GatewayAuthConfig,
|
||||
GatewayTailscaleMode,
|
||||
} from "../config/config.js";
|
||||
import type { GatewayAuthConfig, GatewayTailscaleMode } from "../config/config.js";
|
||||
export type ResolvedGatewayAuthMode = "none" | "token" | "password";
|
||||
|
||||
export type ResolvedGatewayAuth = {
|
||||
@@ -62,14 +59,13 @@ function isLocalDirectRequest(req?: IncomingMessage): boolean {
|
||||
if (!isLoopbackAddress(clientIp)) return false;
|
||||
|
||||
const host = getHostName(req.headers?.host);
|
||||
const hostIsLocal =
|
||||
host === "localhost" || host === "127.0.0.1" || host === "::1";
|
||||
const hostIsLocal = host === "localhost" || host === "127.0.0.1" || host === "::1";
|
||||
const hostIsTailscaleServe = host.endsWith(".ts.net");
|
||||
|
||||
const hasForwarded = Boolean(
|
||||
req.headers?.["x-forwarded-for"] ||
|
||||
req.headers?.["x-real-ip"] ||
|
||||
req.headers?.["x-forwarded-host"],
|
||||
req.headers?.["x-real-ip"] ||
|
||||
req.headers?.["x-forwarded-host"],
|
||||
);
|
||||
|
||||
return (hostIsLocal || hostIsTailscaleServe) && !hasForwarded;
|
||||
@@ -81,17 +77,11 @@ function getTailscaleUser(req?: IncomingMessage): TailscaleUser | null {
|
||||
if (typeof login !== "string" || !login.trim()) return null;
|
||||
const nameRaw = req.headers["tailscale-user-name"];
|
||||
const profilePic = req.headers["tailscale-user-profile-pic"];
|
||||
const name =
|
||||
typeof nameRaw === "string" && nameRaw.trim()
|
||||
? nameRaw.trim()
|
||||
: login.trim();
|
||||
const name = typeof nameRaw === "string" && nameRaw.trim() ? nameRaw.trim() : login.trim();
|
||||
return {
|
||||
login: login.trim(),
|
||||
name,
|
||||
profilePic:
|
||||
typeof profilePic === "string" && profilePic.trim()
|
||||
? profilePic.trim()
|
||||
: undefined,
|
||||
profilePic: typeof profilePic === "string" && profilePic.trim() ? profilePic.trim() : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,17 +89,14 @@ function hasTailscaleProxyHeaders(req?: IncomingMessage): boolean {
|
||||
if (!req) return false;
|
||||
return Boolean(
|
||||
req.headers["x-forwarded-for"] &&
|
||||
req.headers["x-forwarded-proto"] &&
|
||||
req.headers["x-forwarded-host"],
|
||||
req.headers["x-forwarded-proto"] &&
|
||||
req.headers["x-forwarded-host"],
|
||||
);
|
||||
}
|
||||
|
||||
function isTailscaleProxyRequest(req?: IncomingMessage): boolean {
|
||||
if (!req) return false;
|
||||
return (
|
||||
isLoopbackAddress(req.socket?.remoteAddress) &&
|
||||
hasTailscaleProxyHeaders(req)
|
||||
);
|
||||
return isLoopbackAddress(req.socket?.remoteAddress) && hasTailscaleProxyHeaders(req);
|
||||
}
|
||||
|
||||
export function resolveGatewayAuth(params: {
|
||||
@@ -120,13 +107,11 @@ export function resolveGatewayAuth(params: {
|
||||
const authConfig = params.authConfig ?? {};
|
||||
const env = params.env ?? process.env;
|
||||
const token = authConfig.token ?? env.CLAWDBOT_GATEWAY_TOKEN ?? undefined;
|
||||
const password =
|
||||
authConfig.password ?? env.CLAWDBOT_GATEWAY_PASSWORD ?? undefined;
|
||||
const password = authConfig.password ?? env.CLAWDBOT_GATEWAY_PASSWORD ?? undefined;
|
||||
const mode: ResolvedGatewayAuth["mode"] =
|
||||
authConfig.mode ?? (password ? "password" : token ? "token" : "none");
|
||||
const allowTailscale =
|
||||
authConfig.allowTailscale ??
|
||||
(params.tailscaleMode === "serve" && mode !== "password");
|
||||
authConfig.allowTailscale ?? (params.tailscaleMode === "serve" && mode !== "password");
|
||||
return {
|
||||
mode,
|
||||
token,
|
||||
@@ -142,9 +127,7 @@ export function assertGatewayAuthConfigured(auth: ResolvedGatewayAuth): void {
|
||||
);
|
||||
}
|
||||
if (auth.mode === "password" && !auth.password) {
|
||||
throw new Error(
|
||||
"gateway auth mode is password, but no password was configured",
|
||||
);
|
||||
throw new Error("gateway auth mode is password, but no password was configured");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,9 @@ export async function startGatewayNodeBridge(params: {
|
||||
});
|
||||
if (started.port > 0) {
|
||||
const scheme = params.bridgeTls?.enabled ? "tls" : "tcp";
|
||||
params.logBridge.info(`listening on ${scheme}://${params.bridgeHost}:${started.port} (node)`);
|
||||
params.logBridge.info(
|
||||
`listening on ${scheme}://${params.bridgeHost}:${started.port} (node)`,
|
||||
);
|
||||
return { bridge: started, nodePresenceTimers };
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -48,7 +48,8 @@ export async function startNodeBridgeServer(opts: NodeBridgeServerOpts): Promise
|
||||
const loopbackHost = "127.0.0.1";
|
||||
|
||||
const listeners: Array<{ host: string; server: net.Server }> = [];
|
||||
const createServer = () => (opts.tls ? tls.createServer(opts.tls, onConnection) : net.createServer(onConnection));
|
||||
const createServer = () =>
|
||||
opts.tls ? tls.createServer(opts.tls, onConnection) : net.createServer(onConnection);
|
||||
const primary = createServer();
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const onError = (err: Error) => reject(err);
|
||||
|
||||
@@ -145,9 +145,7 @@ function parseBinProbePayload(payloadJSON: string | null | undefined): string[]
|
||||
try {
|
||||
const parsed = JSON.parse(payloadJSON) as { stdout?: unknown; bins?: unknown };
|
||||
if (Array.isArray(parsed.bins)) {
|
||||
return parsed.bins
|
||||
.map((bin) => String(bin).trim())
|
||||
.filter(Boolean);
|
||||
return parsed.bins.map((bin) => String(bin).trim()).filter(Boolean);
|
||||
}
|
||||
if (typeof parsed.stdout === "string") {
|
||||
return parsed.stdout
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
|
||||
export type PluginInstallUpdate = PluginInstallRecord & { pluginId: string };
|
||||
|
||||
export function recordPluginInstall(cfg: ClawdbotConfig, update: PluginInstallUpdate): ClawdbotConfig {
|
||||
export function recordPluginInstall(
|
||||
cfg: ClawdbotConfig,
|
||||
update: PluginInstallUpdate,
|
||||
): ClawdbotConfig {
|
||||
const { pluginId, ...record } = update;
|
||||
const installs = {
|
||||
...cfg.plugins?.installs,
|
||||
|
||||
@@ -70,11 +70,9 @@ export const registerTelegramNativeCommands = ({
|
||||
) => Promise<unknown>;
|
||||
};
|
||||
if (typeof api.setMyCommands === "function") {
|
||||
api
|
||||
.setMyCommands(allCommands)
|
||||
.catch((err) => {
|
||||
runtime.error?.(danger(`telegram setMyCommands failed: ${String(err)}`));
|
||||
});
|
||||
api.setMyCommands(allCommands).catch((err) => {
|
||||
runtime.error?.(danger(`telegram setMyCommands failed: ${String(err)}`));
|
||||
});
|
||||
} else {
|
||||
logVerbose("telegram: setMyCommands unavailable; skipping registration");
|
||||
}
|
||||
|
||||
@@ -235,9 +235,7 @@ describe("createTelegramBot", () => {
|
||||
expect(nativeStatus).toBeDefined();
|
||||
expect(registered).toContainEqual({ command: "custom_backup", description: "Git backup" });
|
||||
expect(registered).not.toContainEqual({ command: "status", description: "Custom status" });
|
||||
expect(registered.filter((command) => command.command === "status")).toEqual([
|
||||
nativeStatus,
|
||||
]);
|
||||
expect(registered.filter((command) => command.command === "status")).toEqual([nativeStatus]);
|
||||
expect(errorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user