Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
482 changes: 438 additions & 44 deletions bun.lock

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"@solid-primitives/storage": "4.3.3",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
"dompurify": "3.4.11",
"dompurify": "3.4.13",
"drizzle-kit": "1.0.0-rc.2",
"drizzle-orm": "1.0.0-rc.2",
"effect": "4.0.0-beta.83",
Expand Down Expand Up @@ -89,7 +89,7 @@
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
"@sentry/solid": "10.36.0",
"@sentry/vite-plugin": "4.6.0",
"solid-js": "1.9.10",
"solid-js": "1.9.15",

@cubic-dev-ai cubic-dev-ai Bot Sep 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The solid-js bump from 1.9.10 to 1.9.15 drops the previous solid-js@1.9.10.patch without carrying its fix forward. If that patch corrected a bug that 1.9.15 does not include, the fix is silently lost in whichever package relied on it (the TUI, console, and app all consume solid-js). Confirm the patch's fix landed upstream in 1.9.15, or port the patch to 1.9.15, before merging.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At package.json, line 92:

<comment>The solid-js bump from 1.9.10 to 1.9.15 drops the previous solid-js@1.9.10.patch without carrying its fix forward. If that patch corrected a bug that 1.9.15 does not include, the fix is silently lost in whichever package relied on it (the TUI, console, and app all consume solid-js). Confirm the patch's fix landed upstream in 1.9.15, or port the patch to 1.9.15, before merging.</comment>

<file context>
@@ -89,7 +89,7 @@
       "@sentry/solid": "10.36.0",
       "@sentry/vite-plugin": "4.6.0",
-      "solid-js": "1.9.10",
+      "solid-js": "1.9.15",
       "vite-plugin-solid": "2.11.10",
       "@lydell/node-pty": "1.2.0-beta.12"
</file context>
Fix with cubic

"vite-plugin-solid": "2.11.10",
"@lydell/node-pty": "1.2.0-beta.12"
}
Expand Down Expand Up @@ -139,6 +139,7 @@
"@opentui/core": "catalog:",
"@opentui/keymap": "catalog:",
"@opentui/solid": "catalog:",
"solid-js": "catalog:",
"@types/bun": "catalog:",
"@types/node": "catalog:",
"postcss": "8.5.16",
Expand All @@ -147,15 +148,14 @@
"@opentelemetry/sdk-trace-base": "2.9.0",
"@opentelemetry/resources": "2.9.0",
"@opentelemetry/sdk-trace-node": "2.9.0",
"astro": "6.4.8",
"@astrojs/starlight": "0.40.0"
"astro": "7.1.0",
"@astrojs/starlight": "0.41.7"
},
"patchedDependencies": {
"@ff-labs/fff-bun@0.9.3": "patches/@ff-labs%2Ffff-bun@0.9.3.patch",
"@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
"@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
Expand Down
6 changes: 3 additions & 3 deletions packages/bcode-browser/skills/browser-execute/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ Choose when the computer has no browser or to work in the background.

```js
// Provision and connect to a cloud browser
const r = await fetch("https://api.browser-use.com/api/v3/browsers", {
const r = await fetch("https://api.browser-use.com/api/v4/browsers", {
method: "POST",
headers: { "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY, "Content-Type": "application/json" },
body: "{}",
})
// Additional options: fetch https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session
// Additional options: fetch https://docs.browser-use.com/cloud/openapi/v4.json
const { id, cdpUrl, liveUrl } = await r.json()
const ver = await fetch(`${cdpUrl}/json/version`).then(r => r.json())
await session.connect({ wsUrl: ver.webSocketDebuggerUrl })
Expand All @@ -80,7 +80,7 @@ console.log("liveUrl for the user to watch:", liveUrl)

```js
// Browser Use cloud will eventually close idle browsers. An explicit stop frees the slot:
await fetch(`https://api.browser-use.com/api/v3/browsers/${id}`, {
await fetch(`https://api.browser-use.com/api/v4/browsers/${id}`, {
method: "PATCH",
headers: { "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ action: "stop" }),
Expand Down
43 changes: 41 additions & 2 deletions packages/bcode-browser/src/fetch-use.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { Context, Effect, Layer } from "effect"
import { HttpClient, HttpClientRequest } from "effect/unstable/http"

const ENDPOINT = "https://fetch.browser-use.com/fetch"
const DEFAULT_ENDPOINT = "https://fetch.browser-use.com/fetch"

export interface FetchResult {
readonly body: ArrayBuffer
Expand All @@ -32,11 +32,12 @@ export const layer = Layer.effect(
Effect.gen(function* () {
const http = yield* HttpClient.HttpClient
const apiKey = process.env.BROWSER_USE_API_KEY ?? ""
const endpoint = resolveEndpoint()

@cubic-dev-ai cubic-dev-ai Bot Sep 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: resolveEndpoint() throws when BCODE_FETCH_USE_ENDPOINT is invalid, and this runs at layer-build time unconditionally — even when the feature is disabled. The layer is composed into the tool registry unconditionally (packages/opencode/src/tool/registry.ts: layer: layer.pipe(Layer.provide(FetchUse.layer))), and the webfetch tool yields FetchUse.Service while defining itself (webfetch.ts:31), so any tool-registry build runs the validation. Before this PR a bad value was impossible; now a stale or unrelated BCODE_FETCH_USE_ENDPOINT hard-fails tool-layer construction for a user who may never use webfetch and holds no BROWSER_USE_API_KEY (in which case there is no key to leak, so fail-fast protects nothing here). Gate the validation on the feature actually being usable so the disabled case stays quiet, or confirm the wider blast radius is intended.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/src/fetch-use.ts, line 35:

<comment>`resolveEndpoint()` throws when `BCODE_FETCH_USE_ENDPOINT` is invalid, and this runs at layer-build time unconditionally — even when the feature is disabled. The layer is composed into the tool registry unconditionally (`packages/opencode/src/tool/registry.ts`: `layer: layer.pipe(Layer.provide(FetchUse.layer))`), and the webfetch tool yields `FetchUse.Service` while defining itself (`webfetch.ts:31`), so any tool-registry build runs the validation. Before this PR a bad value was impossible; now a stale or unrelated `BCODE_FETCH_USE_ENDPOINT` hard-fails tool-layer construction for a user who may never use webfetch and holds no `BROWSER_USE_API_KEY` (in which case there is no key to leak, so fail-fast protects nothing here). Gate the validation on the feature actually being usable so the disabled case stays quiet, or confirm the wider blast radius is intended.</comment>

<file context>
@@ -32,11 +32,12 @@ export const layer = Layer.effect(
   Effect.gen(function* () {
     const http = yield* HttpClient.HttpClient
     const apiKey = process.env.BROWSER_USE_API_KEY ?? ""
+    const endpoint = resolveEndpoint()
     return Service.of({
       enabled: apiKey.length > 0,
</file context>
Suggested change
const endpoint = resolveEndpoint()
const endpoint = apiKey.length > 0 ? resolveEndpoint() : DEFAULT_ENDPOINT
Fix with cubic

return Service.of({
enabled: apiKey.length > 0,
fetch: (url, { timeoutMs }) =>
Effect.gen(function* () {
const request = yield* HttpClientRequest.post(ENDPOINT).pipe(
const request = yield* HttpClientRequest.post(endpoint).pipe(

@cubic-dev-ai cubic-dev-ai Bot Sep 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When the configured endpoint returns a redirect, FetchHttpClient follows it and forwards X-Browser-Use-API-Key to the new origin. A local mediator or HTTPS proxy can therefore redirect the request to an external host and leak the key; reject redirects or follow them manually while removing the API-key header on cross-origin hops.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/src/fetch-use.ts, line 40:

<comment>When the configured endpoint returns a redirect, `FetchHttpClient` follows it and forwards `X-Browser-Use-API-Key` to the new origin. A local mediator or HTTPS proxy can therefore redirect the request to an external host and leak the key; reject redirects or follow them manually while removing the API-key header on cross-origin hops.</comment>

<file context>
@@ -32,11 +32,12 @@ export const layer = Layer.effect(
       fetch: (url, { timeoutMs }) =>
         Effect.gen(function* () {
-          const request = yield* HttpClientRequest.post(ENDPOINT).pipe(
+          const request = yield* HttpClientRequest.post(endpoint).pipe(
             HttpClientRequest.setHeaders({ "Content-Type": "application/json", "X-Browser-Use-API-Key": apiKey }),
             HttpClientRequest.bodyJson({ url, timeout_ms: timeoutMs }),
</file context>
Fix with cubic

HttpClientRequest.setHeaders({ "Content-Type": "application/json", "X-Browser-Use-API-Key": apiKey }),
HttpClientRequest.bodyJson({ url, timeout_ms: timeoutMs }),
)
Expand All @@ -56,4 +57,42 @@ export const layer = Layer.effect(
}),
)

// Overridable so a caller can mediate the request and keep the real key out of
// this process entirely. The default endpoint is a general-purpose URL fetcher,
// so anything holding the key can send it to an arbitrary host -- an untrusted
// or injectable agent should be given a mediating endpoint and a throwaway
// credential instead of the real one.
//
// Every rejection below is an operator mistake at startup, and each one would
// otherwise put X-Browser-Use-API-Key somewhere it should not go. Set-but-empty
// is a mistake rather than a default, because the default is the direct fetcher
// -- the exact path someone setting this variable is trying to leave.
function resolveEndpoint() {
const configured = process.env.BCODE_FETCH_USE_ENDPOINT

@cubic-dev-ai cubic-dev-ai Bot Sep 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: BCODE_FETCH_USE_ENDPOINT is new behavior that changes where the webfetch API key is sent (and that many values are rejected at startup), but it is documented nowhere in-tree. The README (packages/bcode-browser/README.md) and the opencode config docs (packages/core/src/v1/config/config.ts:187) describe only BROWSER_USE_API_KEY. Add a line to the README's contents/env section so operators know the variable exists and what values are accepted (https anywhere, http only to loopback).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/src/fetch-use.ts, line 71:

<comment>`BCODE_FETCH_USE_ENDPOINT` is new behavior that changes where the webfetch API key is sent (and that many values are rejected at startup), but it is documented nowhere in-tree. The README (`packages/bcode-browser/README.md`) and the opencode config docs (`packages/core/src/v1/config/config.ts:187`) describe only `BROWSER_USE_API_KEY`. Add a line to the README's contents/env section so operators know the variable exists and what values are accepted (https anywhere, http only to loopback).</comment>

<file context>
@@ -56,4 +57,42 @@ export const layer = Layer.effect(
+// is a mistake rather than a default, because the default is the direct fetcher
+// -- the exact path someone setting this variable is trying to leave.
+function resolveEndpoint() {
+  const configured = process.env.BCODE_FETCH_USE_ENDPOINT
+  if (configured === undefined) return DEFAULT_ENDPOINT
+  if (configured.trim() === "")
</file context>
Fix with cubic

if (configured === undefined) return DEFAULT_ENDPOINT
if (configured.trim() === "")
throw new Error("BCODE_FETCH_USE_ENDPOINT is set but empty; unset it to use the default fetcher")
// The messages below name the variable and at most the destination's origin,
// never the value: it can carry userinfo or a token in its query, and writing
// that to stderr is the same leak this override exists to close. The operator
// can read back their own environment variable.
if (!URL.canParse(configured)) throw new Error("BCODE_FETCH_USE_ENDPOINT is not a valid url")
const url = new URL(configured)
// Checked before the loopback exemption below, which would otherwise wave
// through ftp://localhost and defer the failure to the first webfetch.
if (url.protocol !== "https:" && url.protocol !== "http:")
throw new Error(`BCODE_FETCH_USE_ENDPOINT must be http or https, not ${url.protocol}`)
if (url.protocol !== "https:" && !LOOPBACK.test(url.hostname))
throw new Error(
`BCODE_FETCH_USE_ENDPOINT must use https outside loopback; refusing to send the api key in cleartext to ${url.origin}`,
)
return configured
}

// All of 127.0.0.0/8 is loopback rather than 127.0.0.1 alone, a trailing dot is
// the same name in its rooted form, and URL reports the IPv6 literal with its
// brackets, so "::1" would never match. Anchored and numeric so a DNS name like
// 127.example.com is not mistaken for the subnet.
const LOOPBACK = /^(localhost\.?|\[::1\]|127(\.\d{1,3}){3})$/

export * as FetchUse from "./fetch-use"
105 changes: 105 additions & 0 deletions packages/bcode-browser/test/fetch-use.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,111 @@ test("layer constructs and exposes `enabled` reflecting env", async () => {
expect(enabled).toBe(haveKey)
})

test("BCODE_FETCH_USE_ENDPOINT redirects the request and forwards the target url", async () => {
// A real server, so this pins the wire behaviour a mediating proxy depends on:
// the override must be used AND the target url must arrive in the body, or the
// proxy has nothing to forward.
const seen: { url?: string; key?: string } = {}
const server = Bun.serve({
port: 0,
fetch: async (req) => {
seen.url = ((await req.json()) as { url: string }).url
seen.key = req.headers.get("X-Browser-Use-API-Key") ?? undefined
return Response.json({ status_code: 200, body: "ok", headers: { "content-type": ["text/plain"] } })
},
})
const realKey = process.env.BROWSER_USE_API_KEY
process.env.BCODE_FETCH_USE_ENDPOINT = `http://localhost:${server.port}/fetch`
process.env.BROWSER_USE_API_KEY = "sentinel-not-a-real-key"
try {
const result = await Effect.gen(function* () {
return yield* (yield* FetchUse.Service).fetch("https://example.com/page", { timeoutMs: 30_000 })
}).pipe(Effect.provide(FetchUse.layer.pipe(Layer.provide(FetchHttpClient.layer))), Effect.runPromise)

expect(seen.url).toBe("https://example.com/page")
expect(seen.key).toBe("sentinel-not-a-real-key")
expect(new TextDecoder().decode(result.body)).toBe("ok")
} finally {
server.stop(true)
delete process.env.BCODE_FETCH_USE_ENDPOINT
if (realKey === undefined) delete process.env.BROWSER_USE_API_KEY
else process.env.BROWSER_USE_API_KEY = realKey
}
})

// The endpoint carries the api key, so a bad value leaks a credential rather
// than merely failing. Loopback http is allowed because a mediating proxy on the
// same host is the normal local arrangement.
test.each([
["", "set but empty"],
[" ", "set but empty"],
["not-a-url", "not a valid url"],
["http://evil.example/fetch", "https outside loopback"],
// Would otherwise pass startup and fail at the first webfetch instead.
["ftp://localhost/fetch", "must be http or https"],
])("rejects BCODE_FETCH_USE_ENDPOINT=%p", (value, reason) => {
process.env.BCODE_FETCH_USE_ENDPOINT = value
try {
expect(() =>
Effect.runSync(
Effect.gen(function* () {
return (yield* FetchUse.Service).enabled
}).pipe(Effect.provide(FetchUse.layer.pipe(Layer.provide(FetchHttpClient.layer)))),
),
).toThrow(new RegExp(reason.replace(/ /g, "\\s")))
} finally {
delete process.env.BCODE_FETCH_USE_ENDPOINT
}
})

test("rejection does not echo credentials carried in the endpoint value", () => {
// An endpoint can embed userinfo or a token in its query. Writing that into
// stderr on a typo is the same log leak this override exists to close, so the
// message may name the origin and nothing more.
process.env.BCODE_FETCH_USE_ENDPOINT = "http://user:hunter2@evil.example:8080/f?token=SECRET"
try {
let message = ""
try {
Effect.runSync(
Effect.gen(function* () {
return (yield* FetchUse.Service).enabled
}).pipe(Effect.provide(FetchUse.layer.pipe(Layer.provide(FetchHttpClient.layer)))),
)
} catch (e) {
message = String(e)
}
expect(message).toContain("https outside loopback")
expect(message).toContain("evil.example:8080")
expect(message).not.toContain("hunter2")
expect(message).not.toContain("SECRET")
} finally {
delete process.env.BCODE_FETCH_USE_ENDPOINT
}
})

test.each([
"https://proxy.example/fetch",
"http://127.0.0.1:7461/fetch",
"http://[::1]:7461/fetch",
// The whole 127/8 is loopback, and a rooted name is the same name.
"http://127.0.0.2:9/fetch",
"http://localhost./fetch",
])("accepts BCODE_FETCH_USE_ENDPOINT=%p", (value) => {
process.env.BCODE_FETCH_USE_ENDPOINT = value
try {
expect(() =>
Effect.runSync(
Effect.gen(function* () {
return (yield* FetchUse.Service).enabled
}).pipe(Effect.provide(FetchUse.layer.pipe(Layer.provide(FetchHttpClient.layer)))),
),
).not.toThrow()
} finally {
delete process.env.BCODE_FETCH_USE_ENDPOINT
}
},
)

test.skipIf(!haveKey)("live: fetches httpbin and returns body + content-type", async () => {
const result = await Effect.gen(function* () {
return yield* (yield* FetchUse.Service).fetch("https://httpbin.org/get", { timeoutMs: 30_000 })
Expand Down
4 changes: 4 additions & 0 deletions packages/bcode-browser/test/skills.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ test("resolveSkillsDir materializes skills with {{SKILLS_DIR}} substituted", asy
const browser = (await fs.readFile(path.join(dir, "browser-execute", "SKILL.md"), "utf8")).replaceAll("\\", "/")
expect(browser).not.toContain("{{SKILLS_DIR}}")
expect(browser).toContain(`${dir.replaceAll("\\", "/")}/`)
expect(browser).toContain('fetch("https://api.browser-use.com/api/v4/browsers"')
expect(browser).toContain("fetch(`https://api.browser-use.com/api/v4/browsers/${id}`")
expect(browser).toContain("https://docs.browser-use.com/cloud/openapi/v4.json")
expect(browser).not.toContain("https://api.browser-use.com/api/v3")
} finally {
await fs.rm(dataDir, { recursive: true, force: true })
}
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
}
},
"devDependencies": {
"@babel/core": "7.28.4",
"@babel/core": "7.29.6",
"@octokit/webhooks-types": "7.6.1",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/http-recorder": "workspace:*",
Expand Down
15 changes: 14 additions & 1 deletion packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1108,9 +1108,22 @@ const layer = Layer.effect(
(part) => part.type === "tool" && !part.metadata?.providerExecuted && !isOrphanedInterruptedTool(part),
) ?? false

// "unknown" is every mapper's fallback for a finish reason we could not
// interpret, and the AI SDK reports a stream that closed without any
// finish chunk at all as "other", which maps here too. Neither means the
// model was done, so resample the turn like "tool-calls" instead of
// exiting as a clean completion. Exiting here silently truncated runs
// mid-task: the error check below already excludes "unknown", so nothing
// was recorded anywhere.
if (lastAssistant?.finish === "unknown")
yield* Effect.logWarning("resampling turn that ended with an unmapped finish reason", {
"session.id": sessionID,
messageID: lastAssistant.id,
})

if (
lastAssistant?.finish &&
!["tool-calls"].includes(lastAssistant.finish) &&
!["tool-calls", "unknown"].includes(lastAssistant.finish) &&

@cubic-dev-ai cubic-dev-ai Bot Sep 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When the provider repeatedly emits an unsupported or missing finish reason, this branch keeps runLoop alive indefinitely. Bound unknown resamples and surface a terminal error after the retry budget.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/session/prompt.ts, line 1126:

<comment>When the provider repeatedly emits an unsupported or missing finish reason, this branch keeps `runLoop` alive indefinitely. Bound unknown resamples and surface a terminal error after the retry budget.</comment>

<file context>
@@ -1108,9 +1108,22 @@ const layer = Layer.effect(
           if (
             lastAssistant?.finish &&
-            !["tool-calls"].includes(lastAssistant.finish) &&
+            !["tool-calls", "unknown"].includes(lastAssistant.finish) &&
             !hasToolCalls &&
             lastAssistant.parentID === lastUser.id
</file context>
Fix with cubic

!hasToolCalls &&
lastAssistant.parentID === lastUser.id
) {
Expand Down
30 changes: 30 additions & 0 deletions packages/opencode/test/session/prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,36 @@ it.instance("loop continues when finish is tool-calls", () =>
}),
)

it.instance("loop resamples when the provider closes a stream without a finish reason", () =>
Effect.gen(function* () {
const { llm } = yield* useServerConfig(providerCfg)
const prompt = yield* SessionPrompt.Service
const sessions = yield* Session.Service
const session = yield* sessions.create({
title: "Pinned",
permission: [{ permission: "*", pattern: "*", action: "allow" }],
})
yield* prompt.prompt({
sessionID: session.id,
agent: "build",
noReply: true,
parts: [{ type: "text", text: "hello" }],
})
// No finish chunk: the AI SDK reports this as finishReason "other", which we
// map to "unknown". The loop must resample instead of ending the run.
yield* llm.push(reply().item())
yield* llm.text("second")

const result = yield* prompt.loop({ sessionID: session.id })
expect(yield* llm.calls).toBe(2)
expect(result.info.role).toBe("assistant")
if (result.info.role === "assistant") {
expect(result.parts.some((part) => part.type === "text" && part.text === "second")).toBe(true)
expect(result.info.finish).toBe("stop")
}
}),
)

it.instance("glob tool keeps instance context during prompt runs", () =>
Effect.gen(function* () {
const { dir, llm } = yield* useServerConfig(providerCfg)
Expand Down
2 changes: 1 addition & 1 deletion packages/session-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@solidjs/meta": "catalog:",
"@solidjs/router": "catalog:",
"diff": "catalog:",
"dompurify": "3.3.1",
"dompurify": "3.4.13",
"fuzzysort": "catalog:",
"katex": "0.16.27",
"luxon": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"@solid-primitives/resize-observer": "2.1.3",
"@shikijs/stream": "catalog:",
"diff": "catalog:",
"dompurify": "3.4.11",
"dompurify": "3.4.13",
"fuzzysort": "catalog:",
"katex": "0.16.27",
"luxon": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion packages/web/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default defineConfig({
// when unset (plain `astro build`) this is undefined and the adapter falls
// back to its default config detection.
configPath: process.env.SST_WRANGLER_PATH,
// @astrojs/cloudflare v13 defaults to prerendering in the workerd runtime,
// @astrojs/cloudflare v13+ defaults to prerendering in the workerd runtime,
// which lacks Node built-ins (path/fs/url) that Astro/Zod internals import.
// Prerender in Node instead, matching the previous (v12) behavior.
prerenderEnvironment: "node",
Expand Down
10 changes: 5 additions & 5 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/cloudflare": "13.7.0",
"@astrojs/markdown-remark": "7.2.0",
"@astrojs/solid-js": "6.0.1",
"@astrojs/starlight": "0.40.0",
"@astrojs/cloudflare": "14.1.7",
"@astrojs/markdown-remark": "7.2.1",
"@astrojs/solid-js": "7.0.2",
"@astrojs/starlight": "0.41.7",
"@fontsource/ibm-plex-mono": "5.2.5",
"@shikijs/transformers": "3.20.0",
"@solid-primitives/resize-observer": "2.1.5",
"@types/luxon": "catalog:",
"ai": "catalog:",
"astro": "6.4.8",
"astro": "7.1.0",
"diff": "catalog:",
"js-base64": "3.7.7",
"lang-map": "0.4.0",
Expand Down
Loading
Loading