From fe5e58af91fec3d3af968ddfab3fa748aa1487aa Mon Sep 17 00:00:00 2001 From: Petter Blomberg Date: Thu, 1 Jan 2026 15:29:01 +0100 Subject: [PATCH 1/4] scripts: fix ad-hoc signing crashes and bash unbound variable error --- scripts/codesign-mac-app.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/codesign-mac-app.sh b/scripts/codesign-mac-app.sh index 8bb1d7eed..3d558fb65 100755 --- a/scripts/codesign-mac-app.sh +++ b/scripts/codesign-mac-app.sh @@ -83,7 +83,10 @@ case "$TIMESTAMP_MODE" in ;; esac -options_args=("--options" "runtime") +options_args=() +if [[ "$IDENTITY" != "-" ]]; then + options_args=("--options" "runtime") +fi timestamp_args=("$timestamp_arg") cat > "$ENT_TMP_BASE" <<'PLIST' @@ -157,12 +160,12 @@ xattr -cr "$APP_BUNDLE" 2>/dev/null || true sign_item() { local target="$1" local entitlements="$2" - codesign --force "${options_args[@]}" "${timestamp_args[@]}" --entitlements "$entitlements" --sign "$IDENTITY" "$target" + codesign --force ${options_args+"${options_args[@]}"} "${timestamp_args[@]}" --entitlements "$entitlements" --sign "$IDENTITY" "$target" } sign_plain_item() { local target="$1" - codesign --force "${options_args[@]}" "${timestamp_args[@]}" --sign "$IDENTITY" "$target" + codesign --force ${options_args+"${options_args[@]}"} "${timestamp_args[@]}" --sign "$IDENTITY" "$target" } # Sign main binary From 04691ed598eb9211eb1d548a422682be5348c3d0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 1 Jan 2026 17:30:15 +0100 Subject: [PATCH 2/4] chore: apply biome formatting --- src/agents/minimax.live.test.ts | 68 ++++++++++++++++----------------- src/agents/skills-status.ts | 2 +- src/agents/skills.ts | 4 +- src/agents/zai.live.test.ts | 8 +--- src/gateway/server.test.ts | 7 +++- src/gateway/server.ts | 2 +- 6 files changed, 42 insertions(+), 49 deletions(-) diff --git a/src/agents/minimax.live.test.ts b/src/agents/minimax.live.test.ts index 59167ef1b..666943876 100644 --- a/src/agents/minimax.live.test.ts +++ b/src/agents/minimax.live.test.ts @@ -10,40 +10,36 @@ const LIVE = process.env.MINIMAX_LIVE_TEST === "1" || process.env.LIVE === "1"; const describeLive = LIVE && MINIMAX_KEY ? describe : describe.skip; describeLive("minimax live", () => { - it( - "returns assistant text", - async () => { - const model: Model<"openai-completions"> = { - id: MINIMAX_MODEL, - name: `MiniMax ${MINIMAX_MODEL}`, - api: "openai-completions", - provider: "minimax", - baseUrl: MINIMAX_BASE_URL, - reasoning: false, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 200000, - maxTokens: 8192, - }; - const res = await completeSimple( - model, - { - messages: [ - { - role: "user", - content: "Reply with the word ok.", - timestamp: Date.now(), - }, - ], - }, - { apiKey: MINIMAX_KEY, maxTokens: 64 }, - ); - const text = res.content - .filter((block) => block.type === "text") - .map((block) => block.text.trim()) - .join(" "); - expect(text.length).toBeGreaterThan(0); - }, - 20000, - ); + it("returns assistant text", async () => { + const model: Model<"openai-completions"> = { + id: MINIMAX_MODEL, + name: `MiniMax ${MINIMAX_MODEL}`, + api: "openai-completions", + provider: "minimax", + baseUrl: MINIMAX_BASE_URL, + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 200000, + maxTokens: 8192, + }; + const res = await completeSimple( + model, + { + messages: [ + { + role: "user", + content: "Reply with the word ok.", + timestamp: Date.now(), + }, + ], + }, + { apiKey: MINIMAX_KEY, maxTokens: 64 }, + ); + const text = res.content + .filter((block) => block.type === "text") + .map((block) => block.text.trim()) + .join(" "); + expect(text.length).toBeGreaterThan(0); + }, 20000); }); diff --git a/src/agents/skills-status.ts b/src/agents/skills-status.ts index 367aadad4..bec69f5a6 100644 --- a/src/agents/skills-status.ts +++ b/src/agents/skills-status.ts @@ -4,13 +4,13 @@ import type { ClawdisConfig } from "../config/config.js"; import { CONFIG_DIR } from "../utils.js"; import { hasBinary, + isBundledSkillAllowed, isConfigPathTruthy, loadWorkspaceSkillEntries, resolveBundledAllowlist, resolveConfigPath, resolveSkillConfig, resolveSkillsInstallPreferences, - isBundledSkillAllowed, type SkillEntry, type SkillInstallSpec, type SkillsInstallPreferences, diff --git a/src/agents/skills.ts b/src/agents/skills.ts index 0e83066a6..8ad19586b 100644 --- a/src/agents/skills.ts +++ b/src/agents/skills.ts @@ -223,9 +223,7 @@ export function resolveSkillConfig( function normalizeAllowlist(input: unknown): string[] | undefined { if (!input) return undefined; if (!Array.isArray(input)) return undefined; - const normalized = input - .map((entry) => String(entry).trim()) - .filter(Boolean); + const normalized = input.map((entry) => String(entry).trim()).filter(Boolean); return normalized.length > 0 ? normalized : undefined; } diff --git a/src/agents/zai.live.test.ts b/src/agents/zai.live.test.ts index df5cb1dec..04c5ca80b 100644 --- a/src/agents/zai.live.test.ts +++ b/src/agents/zai.live.test.ts @@ -7,9 +7,7 @@ const LIVE = process.env.ZAI_LIVE_TEST === "1" || process.env.LIVE === "1"; const describeLive = LIVE && ZAI_KEY ? describe : describe.skip; describeLive("zai live", () => { - it( - "returns assistant text", - async () => { + it("returns assistant text", async () => { const model = getModel("zai", "glm-4.7"); const res = await completeSimple( model, @@ -29,7 +27,5 @@ describeLive("zai live", () => { .map((block) => block.text.trim()) .join(" "); expect(text.length).toBeGreaterThan(0); - }, - 20000, - ); + }, 20000); }); diff --git a/src/gateway/server.test.ts b/src/gateway/server.test.ts index bffd10021..355ed54bd 100644 --- a/src/gateway/server.test.ts +++ b/src/gateway/server.test.ts @@ -8,8 +8,8 @@ import { WebSocket } from "ws"; import { agentCommand } from "../commands/agent.js"; import { CONFIG_PATH_CLAWDIS, - STATE_DIR_CLAWDIS, readConfigFileSnapshot, + STATE_DIR_CLAWDIS, writeConfigFile, } from "../config/config.js"; import { emitAgentEvent } from "../infra/agent-events.js"; @@ -2053,7 +2053,10 @@ describe("gateway server", () => { ); expect(res.ok).toBe(true); const payload = res.payload as - | { type?: unknown; snapshot?: { configPath?: string; stateDir?: string } } + | { + type?: unknown; + snapshot?: { configPath?: string; stateDir?: string }; + } | undefined; expect(payload?.type).toBe("hello-ok"); expect(payload?.snapshot?.configPath).toBe(CONFIG_PATH_CLAWDIS); diff --git a/src/gateway/server.ts b/src/gateway/server.ts index c0cc55de2..9ab9fa85e 100644 --- a/src/gateway/server.ts +++ b/src/gateway/server.ts @@ -46,11 +46,11 @@ import { getStatusSummary } from "../commands/status.js"; import { type ClawdisConfig, CONFIG_PATH_CLAWDIS, - STATE_DIR_CLAWDIS, isNixMode, loadConfig, parseConfigJson5, readConfigFileSnapshot, + STATE_DIR_CLAWDIS, validateConfigObject, writeConfigFile, } from "../config/config.js"; From 56ea6b6e43b4147ee48e231d918f5b224d90211c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 1 Jan 2026 17:30:19 +0100 Subject: [PATCH 3/4] fix: align tool schemas and health snapshot --- package.json | 5 +++++ pnpm-lock.yaml | 5 ++++- src/agents/bash-tools.ts | 23 ++++++++++++++++++----- src/agents/clawdis-tools.ts | 5 +++-- src/agents/pi-tools.ts | 7 ++++--- src/commands/health.snapshot.test.ts | 1 + src/config/config.ts | 23 ++++++++++++----------- 7 files changed, 47 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 30a30a37d..13a57461d 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,11 @@ "vitest": "^4.0.16", "wireit": "^0.14.12" }, + "pnpm": { + "overrides": { + "@sinclair/typebox": "0.34.45" + } + }, "vitest": { "coverage": { "provider": "v8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d524e4c4a..5894da5e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@sinclair/typebox': 0.34.45 + patchedDependencies: '@mariozechner/pi-ai': hash: bf3e904ebaad236b8c3bb48c7d1150a1463735e783acaab6d15d6cd381b43832 @@ -29,7 +32,7 @@ importers: specifier: ^0.30.2 version: 0.30.2(ws@8.18.3)(zod@4.2.1) '@sinclair/typebox': - specifier: ^0.34.45 + specifier: 0.34.45 version: 0.34.45 '@whiskeysockets/baileys': specifier: 7.0.0-rc.9 diff --git a/src/agents/bash-tools.ts b/src/agents/bash-tools.ts index d0ac65f47..30e43a875 100644 --- a/src/agents/bash-tools.ts +++ b/src/agents/bash-tools.ts @@ -1,7 +1,6 @@ import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process"; import { randomUUID } from "node:crypto"; import type { AgentTool, AgentToolResult } from "@mariozechner/pi-ai"; -import { StringEnum } from "@mariozechner/pi-ai"; import { Type } from "@sinclair/typebox"; import { @@ -31,6 +30,18 @@ const DEFAULT_MAX_OUTPUT = clampNumber( 150_000, ); +const stringEnum = ( + values: readonly string[], + options?: Parameters[1], +) => + Type.Union( + values.map((value) => Type.Literal(value)) as [ + ReturnType, + ...ReturnType[], + ], + options, + ); + export type BashToolDefaults = { backgroundMs?: number; timeoutSec?: number; @@ -60,7 +71,7 @@ const bashSchema = Type.Object({ }), ), stdinMode: Type.Optional( - StringEnum(["pipe", "pty"] as const, { + stringEnum(["pipe", "pty"] as const, { description: "Only pipe is supported", }), ), @@ -83,7 +94,8 @@ export type BashToolDetails = export function createBashTool( defaults?: BashToolDefaults, -): AgentTool { + // biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-ai uses a different module instance. +): AgentTool { const defaultBackgroundMs = clampNumber( defaults?.backgroundMs ?? readEnvInt("PI_BASH_YIELD_MS"), 20_000, @@ -329,7 +341,7 @@ export function createBashTool( export const bashTool = createBashTool(); const processSchema = Type.Object({ - action: StringEnum( + action: stringEnum( ["list", "poll", "log", "write", "kill", "clear", "remove"] as const, { description: "Process action", @@ -346,7 +358,8 @@ const processSchema = Type.Object({ export function createProcessTool( defaults?: ProcessToolDefaults, -): AgentTool { + // biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-ai uses a different module instance. +): AgentTool { if (defaults?.cleanupMs !== undefined) { setJobTtlMs(defaults.cleanupMs); } diff --git a/src/agents/clawdis-tools.ts b/src/agents/clawdis-tools.ts index 0a273008e..e77a66472 100644 --- a/src/agents/clawdis-tools.ts +++ b/src/agents/clawdis-tools.ts @@ -2,7 +2,7 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; import type { AgentTool, AgentToolResult } from "@mariozechner/pi-ai"; -import { type TSchema, Type } from "@sinclair/typebox"; +import { Type } from "@sinclair/typebox"; import { browserCloseTab, browserFocusTab, @@ -45,7 +45,8 @@ import { callGateway } from "../gateway/call.js"; import { detectMime } from "../media/mime.js"; import { sanitizeToolResultImages } from "./tool-images.js"; -type AnyAgentTool = AgentTool; +// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-ai uses a different module instance. +type AnyAgentTool = AgentTool; const DEFAULT_GATEWAY_URL = "ws://127.0.0.1:18789"; diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index ffd5bb339..b7c9c9cc4 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -1,6 +1,6 @@ import type { AgentTool, AgentToolResult } from "@mariozechner/pi-ai"; import { codingTools, readTool } from "@mariozechner/pi-coding-agent"; -import { type TSchema, Type } from "@sinclair/typebox"; +import { Type } from "@sinclair/typebox"; import { detectMime } from "../media/mime.js"; import { startWebLoginWithQr, waitForWebLogin } from "../web/login-qr.js"; @@ -103,7 +103,8 @@ async function normalizeReadImageResult( return { ...result, content: nextContent }; } -type AnyAgentTool = AgentTool; +// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-ai uses a different module instance. +type AnyAgentTool = AgentTool; function extractEnumValues(schema: unknown): unknown[] | undefined { if (!schema || typeof schema !== "object") return undefined; @@ -204,7 +205,7 @@ function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool { : {}), additionalProperties: "additionalProperties" in schema ? schema.additionalProperties : true, - } as unknown as TSchema, + }, }; } diff --git a/src/commands/health.snapshot.test.ts b/src/commands/health.snapshot.test.ts index cdeb5a505..4de1fa2a5 100644 --- a/src/commands/health.snapshot.test.ts +++ b/src/commands/health.snapshot.test.ts @@ -109,6 +109,7 @@ describe("getHealthSnapshot", () => { fs.writeFileSync(tokenFile, "t-file\n", "utf-8"); testConfig = { telegram: { tokenFile } }; testStore = {}; + vi.stubEnv("TELEGRAM_BOT_TOKEN", ""); const calls: string[] = []; vi.stubGlobal( diff --git a/src/config/config.ts b/src/config/config.ts index fe898c89d..c9cbf4b5b 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -431,7 +431,6 @@ export type ClawdisConfig = { canvasHost?: CanvasHostConfig; talk?: TalkConfig; gateway?: GatewayConfig; - skills?: SkillsConfig; }; /** @@ -903,16 +902,18 @@ const ClawdisSchema = z.object({ .optional(), }) .optional(), - entries: z.record( - z.string(), - z - .object({ - enabled: z.boolean().optional(), - apiKey: z.string().optional(), - env: z.record(z.string(), z.string()).optional(), - }) - .passthrough(), - ).optional(), + entries: z + .record( + z.string(), + z + .object({ + enabled: z.boolean().optional(), + apiKey: z.string().optional(), + env: z.record(z.string(), z.string()).optional(), + }) + .passthrough(), + ) + .optional(), }) .optional(), }); From 2401abe17e7f704dc10707af088227c5cd614c7d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 1 Jan 2026 17:30:22 +0100 Subject: [PATCH 4/4] docs: update changelog for codesign fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62052b741..40f5be095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - macOS Debug: add app log verbosity and rolling file log toggle for swift-log-backed app logs. ### Fixes +- macOS codesign: skip hardened runtime for ad-hoc signing and avoid empty options args (#70) — thanks @petter-b - Docs/agent tools: clarify that browser `wait` should be avoided by default and used only in exceptional cases. - Browser tools: `upload` supports auto-click refs, direct `inputRef`/`element` file inputs, and emits input/change after `setFiles` so JS-heavy sites pick up attachments. - macOS: Voice Wake now fully tears down the Speech pipeline when disabled (cancel pending restarts, drop stale callbacks) to avoid high CPU in the background.