diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 698d155819..eca30ffdd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,20 @@ jobs: run: bun run build:sdk - name: Build Freebuff binary + # The compiled binary inlines NEXT_PUBLIC_* at build time, so the values + # must be present HERE, not only when the binary is run: with an empty + # build environment they are baked in as undefined and every later + # invocation dies in the env schema (smoke-binary.ts documents the same + # contract from the probe side). + env: + NEXT_PUBLIC_CB_ENVIRONMENT: prod + NEXT_PUBLIC_CODEBUFF_APP_URL: https://codebuff.com + NEXT_PUBLIC_SUPPORT_EMAIL: support@freebuff.com + NEXT_PUBLIC_POSTHOG_API_KEY: phc_public_placeholder + NEXT_PUBLIC_POSTHOG_HOST_URL: https://us.i.posthog.com + NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: pk_test_placeholder + NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL: https://billing.stripe.com/p/login/test + NEXT_PUBLIC_WEB_PORT: '3000' run: bun freebuff/cli/build.ts 0.0.0-ci - name: Smoke test Freebuff binary diff --git a/bun.lock b/bun.lock index 7930375f8d..cbb9f27906 100644 --- a/bun.lock +++ b/bun.lock @@ -52,6 +52,7 @@ "jimp": "^1.6.0", "node-machine-id": "^1.1.12", "open": "^10.1.0", + "picocolors": "^1.1.1", "pino": "9.4.0", "react": "^19.0.0", "remark-breaks": "^4.0.0", @@ -1111,6 +1112,8 @@ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "pino": ["pino@9.4.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^1.2.0", "pino-std-serializers": "^7.0.0", "process-warning": "^4.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-nbkQb5+9YPhQRz/BeQmrWpEknAaqjpAqRK8NwJpmrX/JHu7JuZC5G1CeAwJDJfGes4h+YihC6in3Q2nGb+Y09w=="], diff --git a/cli/package.json b/cli/package.json index f873cd35aa..d7ab26d05e 100644 --- a/cli/package.json +++ b/cli/package.json @@ -41,6 +41,7 @@ "jimp": "^1.6.0", "node-machine-id": "^1.1.12", "open": "^10.1.0", + "picocolors": "^1.1.1", "pino": "9.4.0", "react": "^19.0.0", "remark-breaks": "^4.0.0", diff --git a/common/src/types/json.ts b/common/src/types/json.ts index 244e9a31d8..f42f246af4 100644 --- a/common/src/types/json.ts +++ b/common/src/types/json.ts @@ -21,9 +21,16 @@ export const jsonValueSchema: z.ZodType = z.lazy(() => export const jsonObjectSchema: z.ZodType = z.lazy(() => z.record(z.string(), jsonValueSchema.optional()), ) -export type JSONObject = { [key in string]: JSONValue | undefined } +/** + * JSON as it actually crosses boundaries: read-only. The model SDKs type their + * payloads with `Readonly<...>` objects and arrays, and a mutable index + * signature does not accept them, so every `providerOptions` assignment was a + * type error. Reading is all the CLI does with this data anyway; the one place + * that needs a mutable copy builds `Record` for itself. + */ +export type JSONObject = { readonly [key in string]: JSONValue | undefined } export const jsonArraySchema: z.ZodType = z.lazy(() => z.array(jsonValueSchema), ) -export type JSONArray = JSONValue[] +export type JSONArray = readonly JSONValue[] diff --git a/packages/agent-runtime/src/tools/handlers/tool/gravity-index.ts b/packages/agent-runtime/src/tools/handlers/tool/gravity-index.ts index 2aa4374d22..a7770441b5 100644 --- a/packages/agent-runtime/src/tools/handlers/tool/gravity-index.ts +++ b/packages/agent-runtime/src/tools/handlers/tool/gravity-index.ts @@ -16,7 +16,9 @@ import type { Message } from '@codebuff/common/types/messages/codebuff-message' import type { AgentState } from '@codebuff/common/types/session-state' const omitUndefined = (value: Record) => { - const result: JSONObject = {} + // Built mutably and returned as `JSONObject`: JSON is read-only on the way + // back in, but assembling a new payload needs a writable index here. + const result: Record = {} for (const [key, field] of Object.entries(value)) { if (field !== undefined) { result[key] = field