chore: fix lint formatting

This commit is contained in:
Peter Steinberger
2026-01-03 14:57:49 +00:00
parent 77c76ca52f
commit 1a00175eb7
9 changed files with 120 additions and 108 deletions

View File

@@ -378,16 +378,16 @@ export async function runEmbeddedPiAgent(params: {
const apiKey = await getApiKeyForModel(model, authStorage); const apiKey = await getApiKeyForModel(model, authStorage);
authStorage.setRuntimeApiKey(model.provider, apiKey); authStorage.setRuntimeApiKey(model.provider, apiKey);
const thinkingLevel = mapThinkingLevel(params.thinkLevel); const thinkingLevel = mapThinkingLevel(params.thinkLevel);
logVerbose( logVerbose(
`embedded run start: runId=${params.runId} sessionId=${params.sessionId} provider=${provider} model=${modelId} surface=${params.surface ?? "unknown"}`, `embedded run start: runId=${params.runId} sessionId=${params.sessionId} provider=${provider} model=${modelId} surface=${params.surface ?? "unknown"}`,
); );
await fs.mkdir(resolvedWorkspace, { recursive: true }); await fs.mkdir(resolvedWorkspace, { recursive: true });
await ensureSessionHeader({ await ensureSessionHeader({
sessionFile: params.sessionFile, sessionFile: params.sessionFile,
sessionId: params.sessionId, sessionId: params.sessionId,
cwd: resolvedWorkspace, cwd: resolvedWorkspace,
}); });
@@ -510,20 +510,23 @@ export async function runEmbeddedPiAgent(params: {
}); });
let abortWarnTimer: NodeJS.Timeout | undefined; let abortWarnTimer: NodeJS.Timeout | undefined;
const abortTimer = setTimeout(() => { const abortTimer = setTimeout(
defaultRuntime.warn?.( () => {
`embedded run timeout: runId=${params.runId} sessionId=${params.sessionId} timeoutMs=${params.timeoutMs}`, defaultRuntime.warn?.(
); `embedded run timeout: runId=${params.runId} sessionId=${params.sessionId} timeoutMs=${params.timeoutMs}`,
abortRun(); );
if (!abortWarnTimer) { abortRun();
abortWarnTimer = setTimeout(() => { if (!abortWarnTimer) {
if (!session.isStreaming) return; abortWarnTimer = setTimeout(() => {
defaultRuntime.warn?.( if (!session.isStreaming) return;
`embedded run abort still streaming: runId=${params.runId} sessionId=${params.sessionId}`, defaultRuntime.warn?.(
); `embedded run abort still streaming: runId=${params.runId} sessionId=${params.sessionId}`,
}, 10_000); );
} }, 10_000);
}, Math.max(1, params.timeoutMs)); }
},
Math.max(1, params.timeoutMs),
);
let messagesSnapshot: AgentMessage[] = []; let messagesSnapshot: AgentMessage[] = [];
let sessionIdUsed = session.sessionId; let sessionIdUsed = session.sessionId;

View File

@@ -6,13 +6,13 @@ import {
createToolDebouncer, createToolDebouncer,
formatToolAggregate, formatToolAggregate,
} from "../auto-reply/tool-meta.js"; } from "../auto-reply/tool-meta.js";
import { logVerbose } from "../globals.js";
import { emitAgentEvent } from "../infra/agent-events.js"; import { emitAgentEvent } from "../infra/agent-events.js";
import { splitMediaFromOutput } from "../media/parse.js"; import { splitMediaFromOutput } from "../media/parse.js";
import { import {
extractAssistantText, extractAssistantText,
inferToolMetaFromArgs, inferToolMetaFromArgs,
} from "./pi-embedded-utils.js"; } from "./pi-embedded-utils.js";
import { logVerbose } from "../globals.js";
const THINKING_TAG_RE = /<\s*\/?\s*think(?:ing)?\s*>/gi; const THINKING_TAG_RE = /<\s*\/?\s*think(?:ing)?\s*>/gi;
const THINKING_OPEN_RE = /<\s*think(?:ing)?\s*>/i; const THINKING_OPEN_RE = /<\s*think(?:ing)?\s*>/i;

View File

@@ -144,12 +144,11 @@ function mergePropertySchemas(existing: unknown, incoming: unknown): unknown {
function cleanSchemaForGemini(schema: unknown): unknown { function cleanSchemaForGemini(schema: unknown): unknown {
if (!schema || typeof schema !== "object") return schema; if (!schema || typeof schema !== "object") return schema;
if (Array.isArray(schema)) return schema.map(cleanSchemaForGemini); if (Array.isArray(schema)) return schema.map(cleanSchemaForGemini);
const obj = schema as Record<string, unknown>; const obj = schema as Record<string, unknown>;
const hasAnyOf = "anyOf" in obj && Array.isArray(obj.anyOf); const hasAnyOf = "anyOf" in obj && Array.isArray(obj.anyOf);
const hasConst = "const" in obj;
const cleaned: Record<string, unknown> = {}; const cleaned: Record<string, unknown> = {};
for (const [key, value] of Object.entries(obj)) { for (const [key, value] of Object.entries(obj)) {
// Skip unsupported schema features for Gemini: // Skip unsupported schema features for Gemini:
// - patternProperties: not in OpenAPI 3.0 subset // - patternProperties: not in OpenAPI 3.0 subset
@@ -158,44 +157,48 @@ function cleanSchemaForGemini(schema: unknown): unknown {
// Gemini doesn't support patternProperties - skip it // Gemini doesn't support patternProperties - skip it
continue; continue;
} }
// Convert const to enum (Gemini doesn't support const) // Convert const to enum (Gemini doesn't support const)
if (key === "const") { if (key === "const") {
cleaned.enum = [value]; cleaned.enum = [value];
continue; continue;
} }
// Skip 'type' if we have 'anyOf' — Gemini doesn't allow both // Skip 'type' if we have 'anyOf' — Gemini doesn't allow both
if (key === "type" && hasAnyOf) { if (key === "type" && hasAnyOf) {
continue; continue;
} }
if (key === "properties" && value && typeof value === "object") { if (key === "properties" && value && typeof value === "object") {
// Recursively clean nested properties // Recursively clean nested properties
const props = value as Record<string, unknown>; const props = value as Record<string, unknown>;
cleaned[key] = Object.fromEntries( cleaned[key] = Object.fromEntries(
Object.entries(props).map(([k, v]) => [k, cleanSchemaForGemini(v)]) Object.entries(props).map(([k, v]) => [k, cleanSchemaForGemini(v)]),
); );
} else if (key === "items" && value && typeof value === "object") { } else if (key === "items" && value && typeof value === "object") {
// Recursively clean array items schema // Recursively clean array items schema
cleaned[key] = cleanSchemaForGemini(value); cleaned[key] = cleanSchemaForGemini(value);
} else if (key === "anyOf" && Array.isArray(value)) { } else if (key === "anyOf" && Array.isArray(value)) {
// Clean each anyOf variant // Clean each anyOf variant
cleaned[key] = value.map(v => cleanSchemaForGemini(v)); cleaned[key] = value.map((v) => cleanSchemaForGemini(v));
} else if (key === "oneOf" && Array.isArray(value)) { } else if (key === "oneOf" && Array.isArray(value)) {
// Clean each oneOf variant // Clean each oneOf variant
cleaned[key] = value.map(v => cleanSchemaForGemini(v)); cleaned[key] = value.map((v) => cleanSchemaForGemini(v));
} else if (key === "allOf" && Array.isArray(value)) { } else if (key === "allOf" && Array.isArray(value)) {
// Clean each allOf variant // Clean each allOf variant
cleaned[key] = value.map(v => cleanSchemaForGemini(v)); cleaned[key] = value.map((v) => cleanSchemaForGemini(v));
} else if (key === "additionalProperties" && value && typeof value === "object") { } else if (
key === "additionalProperties" &&
value &&
typeof value === "object"
) {
// Recursively clean additionalProperties schema // Recursively clean additionalProperties schema
cleaned[key] = cleanSchemaForGemini(value); cleaned[key] = cleanSchemaForGemini(value);
} else { } else {
cleaned[key] = value; cleaned[key] = value;
} }
} }
return cleaned; return cleaned;
} }
@@ -205,16 +208,20 @@ function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool {
? (tool.parameters as Record<string, unknown>) ? (tool.parameters as Record<string, unknown>)
: undefined; : undefined;
if (!schema) return tool; if (!schema) return tool;
// If schema already has type + properties (no top-level anyOf to merge), // If schema already has type + properties (no top-level anyOf to merge),
// still clean it for Gemini compatibility // still clean it for Gemini compatibility
if ("type" in schema && "properties" in schema && !Array.isArray(schema.anyOf)) { if (
"type" in schema &&
"properties" in schema &&
!Array.isArray(schema.anyOf)
) {
return { return {
...tool, ...tool,
parameters: cleanSchemaForGemini(schema), parameters: cleanSchemaForGemini(schema),
}; };
} }
if (!Array.isArray(schema.anyOf)) return tool; if (!Array.isArray(schema.anyOf)) return tool;
const mergedProperties: Record<string, unknown> = {}; const mergedProperties: Record<string, unknown> = {};
const requiredCounts = new Map<string, number>(); const requiredCounts = new Map<string, number>();

View File

@@ -782,10 +782,7 @@ export async function getReplyFromConfig(
const typingIntervalSeconds = const typingIntervalSeconds =
typeof configuredTypingSeconds === "number" ? configuredTypingSeconds : 6; typeof configuredTypingSeconds === "number" ? configuredTypingSeconds : 6;
const typingIntervalMs = typingIntervalSeconds * 1000; const typingIntervalMs = typingIntervalSeconds * 1000;
const typingTtlMs = Math.min( const typingTtlMs = Math.min(Math.max(15_000, typingIntervalMs * 5), 60_000);
Math.max(15_000, typingIntervalMs * 5),
60_000,
);
const cleanupTyping = () => { const cleanupTyping = () => {
if (typingTtlTimer) { if (typingTtlTimer) {
clearTimeout(typingTtlTimer); clearTimeout(typingTtlTimer);

View File

@@ -22,7 +22,7 @@ describe("resolvePythonExecutablePath", () => {
const shim = path.join(shimDir, "python3"); const shim = path.join(shimDir, "python3");
await fs.writeFile( await fs.writeFile(
shim, shim,
`#!/bin/sh\nif [ \"$1\" = \"-c\" ]; then\n echo \"${realPython}\"\n exit 0\nfi\nexit 1\n`, `#!/bin/sh\nif [ "$1" = "-c" ]; then\n echo "${realPython}"\n exit 0\nfi\nexit 1\n`,
"utf-8", "utf-8",
); );
await fs.chmod(shim, 0o755); await fs.chmod(shim, 0o755);

View File

@@ -58,7 +58,9 @@ function ensureGcloudOnPath(): boolean {
return false; return false;
} }
export async function resolvePythonExecutablePath(): Promise<string | undefined> { export async function resolvePythonExecutablePath(): Promise<
string | undefined
> {
if (cachedPythonPath !== undefined) { if (cachedPythonPath !== undefined) {
return cachedPythonPath ?? undefined; return cachedPythonPath ?? undefined;
} }
@@ -171,7 +173,14 @@ export async function ensureSubscription(
pushEndpoint: string, pushEndpoint: string,
) { ) {
const describe = await runGcloudCommand( const describe = await runGcloudCommand(
["pubsub", "subscriptions", "describe", subscription, "--project", projectId], [
"pubsub",
"subscriptions",
"describe",
subscription,
"--project",
projectId,
],
30_000, 30_000,
); );
if (describe.code === 0) { if (describe.code === 0) {

View File

@@ -396,10 +396,12 @@ const SUBSYSTEM_COLORS = [
"magenta", "magenta",
"red", "red",
] as const; ] as const;
const SUBSYSTEM_COLOR_OVERRIDES: Record<string, (typeof SUBSYSTEM_COLORS)[number]> = const SUBSYSTEM_COLOR_OVERRIDES: Record<
{ string,
"gmail-watcher": "blue", (typeof SUBSYSTEM_COLORS)[number]
}; > = {
"gmail-watcher": "blue",
};
const SUBSYSTEM_PREFIXES_TO_DROP = ["gateway", "providers"] as const; const SUBSYSTEM_PREFIXES_TO_DROP = ["gateway", "providers"] as const;
const SUBSYSTEM_MAX_SEGMENTS = 2; const SUBSYSTEM_MAX_SEGMENTS = 2;

View File

@@ -190,7 +190,7 @@ export async function monitorWebInbox(options: {
await sock.readMessages([ await sock.readMessages([
{ remoteJid, id, participant, fromMe: false }, { remoteJid, id, participant, fromMe: false },
]); ]);
if (shouldLogVerbose()) { if (shouldLogVerbose()) {
const suffix = participant ? ` (participant ${participant})` : ""; const suffix = participant ? ` (participant ${participant})` : "";
logVerbose( logVerbose(
`Marked message ${id} as read for ${remoteJid}${suffix}`, `Marked message ${id} as read for ${remoteJid}${suffix}`,

View File

@@ -1,12 +1,15 @@
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
import { randomUUID } from "node:crypto"; import { randomUUID } from "node:crypto";
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import { request as httpRequest } from "node:http"; import { request as httpRequest } from "node:http";
import net from "node:net"; import net from "node:net";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
import { afterAll, describe, expect, it } from "vitest"; import { afterAll, describe, expect, it } from "vitest";
import { approveNodePairing, listNodePairing } from "../src/infra/node-pairing.js"; import {
approveNodePairing,
listNodePairing,
} from "../src/infra/node-pairing.js";
type GatewayInstance = { type GatewayInstance = {
name: string; name: string;
@@ -96,7 +99,9 @@ const spawnGatewayInstance = async (name: string): Promise<GatewayInstance> => {
const port = await getFreePort(); const port = await getFreePort();
const bridgePort = await getFreePort(); const bridgePort = await getFreePort();
const hookToken = `token-${name}-${randomUUID()}`; const hookToken = `token-${name}-${randomUUID()}`;
const homeDir = await fs.mkdtemp(path.join(os.tmpdir(), `clawdis-e2e-${name}-`)); const homeDir = await fs.mkdtemp(
path.join(os.tmpdir(), `clawdis-e2e-${name}-`),
);
const configDir = path.join(homeDir, ".clawdis"); const configDir = path.join(homeDir, ".clawdis");
await fs.mkdir(configDir, { recursive: true }); await fs.mkdir(configDir, { recursive: true });
const configPath = path.join(configDir, "clawdis.json"); const configPath = path.join(configDir, "clawdis.json");
@@ -226,8 +231,11 @@ const runCliJson = async (
child.stderr?.setEncoding("utf8"); child.stderr?.setEncoding("utf8");
child.stdout?.on("data", (d) => stdout.push(String(d))); child.stdout?.on("data", (d) => stdout.push(String(d)));
child.stderr?.on("data", (d) => stderr.push(String(d))); child.stderr?.on("data", (d) => stderr.push(String(d)));
const result = await new Promise<{ code: number | null; signal: string | null }>( const result = await new Promise<{
(resolve) => child.once("exit", (code, signal) => resolve({ code, signal })), code: number | null;
signal: string | null;
}>((resolve) =>
child.once("exit", (code, signal) => resolve({ code, signal })),
); );
const out = stdout.join("").trim(); const out = stdout.join("").trim();
if (result.code !== 0) { if (result.code !== 0) {
@@ -249,41 +257,43 @@ const runCliJson = async (
const postJson = async (url: string, body: unknown) => { const postJson = async (url: string, body: unknown) => {
const payload = JSON.stringify(body); const payload = JSON.stringify(body);
const parsed = new URL(url); const parsed = new URL(url);
return await new Promise<{ status: number; json: unknown }>((resolve, reject) => { return await new Promise<{ status: number; json: unknown }>(
const req = httpRequest( (resolve, reject) => {
{ const req = httpRequest(
method: "POST", {
hostname: parsed.hostname, method: "POST",
port: Number(parsed.port), hostname: parsed.hostname,
path: `${parsed.pathname}${parsed.search}`, port: Number(parsed.port),
headers: { path: `${parsed.pathname}${parsed.search}`,
"Content-Type": "application/json", headers: {
"Content-Length": Buffer.byteLength(payload), "Content-Type": "application/json",
"Content-Length": Buffer.byteLength(payload),
},
}, },
}, (res) => {
(res) => { let data = "";
let data = ""; res.setEncoding("utf8");
res.setEncoding("utf8"); res.on("data", (chunk) => {
res.on("data", (chunk) => { data += chunk;
data += chunk; });
}); res.on("end", () => {
res.on("end", () => { let json: unknown = null;
let json: unknown = null; if (data.trim()) {
if (data.trim()) { try {
try { json = JSON.parse(data);
json = JSON.parse(data); } catch {
} catch { json = data;
json = data; }
} }
} resolve({ status: res.statusCode ?? 0, json });
resolve({ status: res.statusCode ?? 0, json }); });
}); },
}, );
); req.on("error", reject);
req.on("error", reject); req.write(payload);
req.write(payload); req.end();
req.end(); },
}); );
}; };
const createLineReader = (socket: net.Socket) => { const createLineReader = (socket: net.Socket) => {
@@ -437,26 +447,14 @@ describe("gateway multi-instance e2e", () => {
const [nodeListA, nodeListB] = (await Promise.all([ const [nodeListA, nodeListB] = (await Promise.all([
runCliJson( runCliJson(
[ ["nodes", "status", "--json", "--url", `ws://127.0.0.1:${gwA.port}`],
"nodes",
"status",
"--json",
"--url",
`ws://127.0.0.1:${gwA.port}`,
],
{ {
CLAWDIS_GATEWAY_TOKEN: "", CLAWDIS_GATEWAY_TOKEN: "",
CLAWDIS_GATEWAY_PASSWORD: "", CLAWDIS_GATEWAY_PASSWORD: "",
}, },
), ),
runCliJson( runCliJson(
[ ["nodes", "status", "--json", "--url", `ws://127.0.0.1:${gwB.port}`],
"nodes",
"status",
"--json",
"--url",
`ws://127.0.0.1:${gwB.port}`,
],
{ {
CLAWDIS_GATEWAY_TOKEN: "", CLAWDIS_GATEWAY_TOKEN: "",
CLAWDIS_GATEWAY_PASSWORD: "", CLAWDIS_GATEWAY_PASSWORD: "",
@@ -466,17 +464,13 @@ describe("gateway multi-instance e2e", () => {
expect( expect(
nodeListA.nodes?.some( nodeListA.nodes?.some(
(n) => (n) =>
n.nodeId === "node-a" && n.nodeId === "node-a" && n.connected === true && n.paired === true,
n.connected === true &&
n.paired === true,
), ),
).toBe(true); ).toBe(true);
expect( expect(
nodeListB.nodes?.some( nodeListB.nodes?.some(
(n) => (n) =>
n.nodeId === "node-b" && n.nodeId === "node-b" && n.connected === true && n.paired === true,
n.connected === true &&
n.paired === true,
), ),
).toBe(true); ).toBe(true);