From f16b0cf80d78d9a718f98e58288055500fa34122 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 18 Jan 2026 17:05:21 +0000 Subject: [PATCH] fix: stabilize ci protocol + openai batch retry --- .../ClawdbotProtocol/GatewayModels.swift | 34 ++++++++++++ src/infra/bonjour.test.ts | 4 +- src/infra/skills-remote.ts | 15 +++++- src/memory/batch-openai.ts | 52 +++++++++++++------ src/providers/github-copilot-token.test.ts | 10 +++- 5 files changed, 96 insertions(+), 19 deletions(-) diff --git a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift index b135261d2..a44f6739a 100644 --- a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift +++ b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift @@ -1660,6 +1660,40 @@ public struct ExecApprovalsSetParams: Codable, Sendable { } } +public struct ExecApprovalsNodeGetParams: Codable, Sendable { + public let nodeid: String + + public init( + nodeid: String + ) { + self.nodeid = nodeid + } + private enum CodingKeys: String, CodingKey { + case nodeid = "nodeId" + } +} + +public struct ExecApprovalsNodeSetParams: Codable, Sendable { + public let nodeid: String + public let file: [String: AnyCodable] + public let basehash: String? + + public init( + nodeid: String, + file: [String: AnyCodable], + basehash: String? + ) { + self.nodeid = nodeid + self.file = file + self.basehash = basehash + } + private enum CodingKeys: String, CodingKey { + case nodeid = "nodeId" + case file + case basehash = "baseHash" + } +} + public struct ExecApprovalsSnapshot: Codable, Sendable { public let path: String public let exists: Bool diff --git a/src/infra/bonjour.test.ts b/src/infra/bonjour.test.ts index d91f431c5..0993218c7 100644 --- a/src/infra/bonjour.test.ts +++ b/src/infra/bonjour.test.ts @@ -23,8 +23,10 @@ vi.mock("../logger.js", () => { }; }); -vi.mock("../logging.js", () => { +vi.mock("../logging.js", async () => { + const actual = await vi.importActual("../logging.js"); return { + ...actual, getLogger: () => ({ info: (...args: unknown[]) => getLoggerInfo(...args) }), }; }); diff --git a/src/infra/skills-remote.ts b/src/infra/skills-remote.ts index cf81b7657..10b7b7d32 100644 --- a/src/infra/skills-remote.ts +++ b/src/infra/skills-remote.ts @@ -36,7 +36,20 @@ function extractErrorMessage(err: unknown): string | undefined { if (typeof err === "object" && "message" in err && typeof err.message === "string") { return err.message; } - return String(err); + if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") { + return String(err); + } + if (typeof err === "symbol") { + return err.toString(); + } + if (typeof err === "object") { + try { + return JSON.stringify(err); + } catch { + return undefined; + } + } + return undefined; } function logRemoteBinProbeFailure(nodeId: string, err: unknown) { diff --git a/src/memory/batch-openai.ts b/src/memory/batch-openai.ts index 7d729832a..908a95b2b 100644 --- a/src/memory/batch-openai.ts +++ b/src/memory/batch-openai.ts @@ -1,3 +1,4 @@ +import { retryAsync } from "../infra/retry.js"; import type { OpenAiEmbeddingClient } from "./embeddings-openai.js"; import { hashText } from "./internal.js"; @@ -92,23 +93,42 @@ async function submitOpenAiBatch(params: { throw new Error("openai batch file upload failed: missing file id"); } - const batchRes = await fetch(`${baseUrl}/batches`, { - method: "POST", - headers: getOpenAiHeaders(params.openAi, { json: true }), - body: JSON.stringify({ - input_file_id: filePayload.id, - endpoint: OPENAI_BATCH_ENDPOINT, - completion_window: OPENAI_BATCH_COMPLETION_WINDOW, - metadata: { - source: "clawdbot-memory", - agent: params.agentId, + const batchRes = await retryAsync( + async () => { + const res = await fetch(`${baseUrl}/batches`, { + method: "POST", + headers: getOpenAiHeaders(params.openAi, { json: true }), + body: JSON.stringify({ + input_file_id: filePayload.id, + endpoint: OPENAI_BATCH_ENDPOINT, + completion_window: OPENAI_BATCH_COMPLETION_WINDOW, + metadata: { + source: "clawdbot-memory", + agent: params.agentId, + }, + }), + }); + if (!res.ok) { + const text = await res.text(); + const err = new Error(`openai batch create failed: ${res.status} ${text}`) as Error & { + status?: number; + }; + err.status = res.status; + throw err; + } + return res; + }, + { + attempts: 3, + minDelayMs: 300, + maxDelayMs: 2000, + jitter: 0.2, + shouldRetry: (err) => { + const status = (err as { status?: number }).status; + return status === 429 || (typeof status === "number" && status >= 500); }, - }), - }); - if (!batchRes.ok) { - const text = await batchRes.text(); - throw new Error(`openai batch create failed: ${batchRes.status} ${text}`); - } + }, + ); return (await batchRes.json()) as OpenAiBatchStatus; } diff --git a/src/providers/github-copilot-token.test.ts b/src/providers/github-copilot-token.test.ts index 7516f5df7..ea42ad733 100644 --- a/src/providers/github-copilot-token.test.ts +++ b/src/providers/github-copilot-token.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; const loadJsonFile = vi.fn(); const saveJsonFile = vi.fn(); @@ -14,6 +14,14 @@ vi.mock("../config/paths.js", () => ({ })); describe("github-copilot token", () => { + beforeEach(() => { + vi.resetModules(); + loadJsonFile.mockReset(); + saveJsonFile.mockReset(); + resolveStateDir.mockReset(); + resolveStateDir.mockReturnValue("/tmp/clawdbot-state"); + }); + it("derives baseUrl from token", async () => { const { deriveCopilotApiBaseUrlFromToken } = await import("./github-copilot-token.js");