size.start()}>
diff --git a/packages/app/src/pages/session/helpers.test.ts b/packages/app/src/pages/session/helpers.test.ts
index 95f7cd384db8..1723410efbe8 100644
--- a/packages/app/src/pages/session/helpers.test.ts
+++ b/packages/app/src/pages/session/helpers.test.ts
@@ -8,8 +8,17 @@ import {
focusTerminalById,
getTabReorderIndex,
shouldFocusTerminalOnKeyDown,
+ shouldShowFileTree,
} from "./helpers"
+describe("shouldShowFileTree", () => {
+ test("does not reserve space for a disabled v2 file tree", () => {
+ expect(shouldShowFileTree({ desktopV2: true, showFileTree: false, opened: true })).toBe(false)
+ expect(shouldShowFileTree({ desktopV2: false, showFileTree: false, opened: true })).toBe(true)
+ expect(shouldShowFileTree({ desktopV2: true, showFileTree: true, opened: true })).toBe(true)
+ })
+})
+
describe("createOpenReviewFile", () => {
test("opens and loads selected review file", () => {
const calls: string[] = []
diff --git a/packages/app/src/pages/session/helpers.ts b/packages/app/src/pages/session/helpers.ts
index e136ba9991bb..a1797e554d3d 100644
--- a/packages/app/src/pages/session/helpers.ts
+++ b/packages/app/src/pages/session/helpers.ts
@@ -20,6 +20,10 @@ type TabsInput = {
export const getSessionKey = (dir: string | undefined, id: string | undefined) => `${dir ?? ""}${id ? `/${id}` : ""}`
+export function shouldShowFileTree(input: { desktopV2: boolean; showFileTree: boolean; opened: boolean }) {
+ return input.opened && (!input.desktopV2 || input.showFileTree)
+}
+
export const createSessionTabs = (input: TabsInput) => {
const review = input.review ?? (() => false)
const hasReview = input.hasReview ?? (() => false)
diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx
index d5a3311917da..bff7c3e26f9a 100644
--- a/packages/app/src/pages/session/session-side-panel.tsx
+++ b/packages/app/src/pages/session/session-side-panel.tsx
@@ -24,7 +24,13 @@ import { useSettings } from "@/context/settings"
import { useSync } from "@/context/sync"
import { createFileTabListSync } from "@/pages/session/file-tab-scroll"
import { FileTabContent } from "@/pages/session/file-tabs"
-import { createOpenSessionFileTab, createSessionTabs, getTabReorderIndex, type Sizing } from "@/pages/session/helpers"
+import {
+ createOpenSessionFileTab,
+ createSessionTabs,
+ getTabReorderIndex,
+ shouldShowFileTree,
+ type Sizing,
+} from "@/pages/session/helpers"
import { setSessionHandoff } from "@/pages/session/handoff"
import { useSessionLayout } from "@/pages/session/session-layout"
@@ -59,10 +65,18 @@ export function SessionSidePanel(props: {
const isDesktop = createMediaQuery("(min-width: 768px)")
const desktopV2 = () => platform.platform === "desktop" && settings.general.newLayoutDesigns()
- const shown = createMemo(() => (desktopV2() ? settings.general.showFileTree() : true))
+ const shown = createMemo(() => !desktopV2() || settings.general.showFileTree())
const reviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened())
- const fileOpen = createMemo(() => isDesktop() && shown() && layout.fileTree.opened())
+ const fileOpen = createMemo(
+ () =>
+ isDesktop() &&
+ shouldShowFileTree({
+ desktopV2: desktopV2(),
+ showFileTree: settings.general.showFileTree(),
+ opened: layout.fileTree.opened(),
+ }),
+ )
const open = createMemo(() => reviewOpen() || fileOpen())
const reviewTab = createMemo(() => isDesktop())
const panelWidth = createMemo(() => {
diff --git a/packages/app/src/utils/persist.test.ts b/packages/app/src/utils/persist.test.ts
index 90a8d01dc89b..d8b822d856bb 100644
--- a/packages/app/src/utils/persist.test.ts
+++ b/packages/app/src/utils/persist.test.ts
@@ -166,6 +166,24 @@ describe("persist localStorage resilience", () => {
expect(storage.getItem(`${target.legacyStorageNames![0]}:${target.key}`)).toBeNull()
})
+ test("draft target isolates storage per draft and namespaces keys", () => {
+ const a = Persist.draft("draft-a", "prompt")
+ const b = Persist.draft("draft-b", "prompt")
+
+ expect(a.key).toBe("draft:prompt")
+ expect(a.storage).not.toBe(b.storage)
+ expect(a.storage).not.toBe(Persist.workspace("/home/luke/repo", "prompt").storage)
+ })
+
+ test("removes draft storage when removing persisted target", () => {
+ const target = Persist.draft("draft-a", "prompt")
+ storage.setItem(`${target.storage}:${target.key}`, '{"value":1}')
+
+ removePersisted(target)
+
+ expect(storage.getItem(`${target.storage}:${target.key}`)).toBeNull()
+ })
+
test("server workspace target preserves local storage and isolates remote storage", () => {
const local = Persist.serverWorkspace(ServerScope.local, "/home/luke/repo", "prompt")
const windows = Persist.serverWorkspace("https://windows.example" as ServerScope, "/home/luke/repo", "prompt")
diff --git a/packages/app/src/utils/persist.ts b/packages/app/src/utils/persist.ts
index 590d19e96975..7c0fe28fbd5e 100644
--- a/packages/app/src/utils/persist.ts
+++ b/packages/app/src/utils/persist.ts
@@ -341,6 +341,12 @@ function workspaceStorage(dir: string) {
return `opencode.workspace.${head}.${sum}.dat`
}
+function draftStorage(draftID: string) {
+ const head = (draftID.slice(0, 12) || "draft").replace(/[^a-zA-Z0-9._-]/g, "-")
+ const sum = checksum(draftID) ?? "0"
+ return `opencode.draft.${head}.${sum}.dat`
+}
+
function legacyWorkspaceStorage(dir: string) {
const storage = workspaceStorage(pathKey(dir))
const result = new Set
()
@@ -450,6 +456,12 @@ function localStorageDirect(): SyncStorage {
}
}
+const DRAFT_PERSISTED_KEYS = ["prompt", "comments", "model-selection", "file-view", "layout"]
+
+export function draftPersistedKeys() {
+ return DRAFT_PERSISTED_KEYS
+}
+
export const PersistTesting = {
localStorageDirect,
localStorageWithPrefix,
@@ -462,6 +474,9 @@ export const Persist = {
global(key: string, legacy?: string[]): PersistTarget {
return { storage: GLOBAL_STORAGE, key, legacy }
},
+ draft(draftID: string, key: string, legacy?: string[]): PersistTarget {
+ return { storage: draftStorage(draftID), key: `draft:${key}`, legacy }
+ },
serverGlobal(scope: ServerScopeValue, key: string, legacy?: string[]): PersistTarget {
if (scope === ServerScope.local) return Persist.global(key, legacy)
return { storage: GLOBAL_STORAGE, key: ScopedKey.from(scope, key) }
diff --git a/packages/app/src/wsl/dialog-add-server.tsx b/packages/app/src/wsl/dialog-add-server.tsx
index 5673d58b14b2..6c79824136fa 100644
--- a/packages/app/src/wsl/dialog-add-server.tsx
+++ b/packages/app/src/wsl/dialog-add-server.tsx
@@ -71,6 +71,17 @@ export function DialogAddWslServer(props: DialogWslServerProps = {}) {
if (!distro) return null
return current()?.opencodeChecks[distro] ?? null
})
+ const wslReady = createMemo(() => !!current()?.runtime?.available && !current()?.pendingRestart)
+ const distroReady = createMemo(() => {
+ const probe = selectedProbe()
+ if (!probe || !selectedDistro()) return false
+ if (selectedInstalled()?.version === 1) return false
+ return probe.canExecute && probe.hasBash && probe.hasCurl
+ })
+ const opencodeReady = createMemo(() => {
+ const check = opencodeCheck()
+ return !!check?.resolvedPath && !check.error
+ })
const distroWarningProbe = createMemo(() => {
const probe = selectedProbe()
if (!probe) return null
@@ -106,17 +117,6 @@ export function DialogAddWslServer(props: DialogWslServerProps = {}) {
const job = current()?.job
return job?.kind === "install-opencode" && job.distro === selectedDistro()
})
- const wslReady = createMemo(() => !!current()?.runtime?.available && !current()?.pendingRestart)
- const distroReady = createMemo(() => {
- const probe = selectedProbe()
- if (!probe || !selectedDistro()) return false
- if (selectedInstalled()?.version === 1) return false
- return probe.canExecute && probe.hasBash && probe.hasCurl
- })
- const opencodeReady = createMemo(() => {
- const check = opencodeCheck()
- return !!check?.resolvedPath && !check.error
- })
const allReady = createMemo(() => wslReady() && distroReady() && opencodeReady())
const addDisabled = createMemo(() => {
const job = current()?.job
diff --git a/packages/app/src/wsl/settings.tsx b/packages/app/src/wsl/settings.tsx
index 0a3d56fef35c..746a5861c52c 100644
--- a/packages/app/src/wsl/settings.tsx
+++ b/packages/app/src/wsl/settings.tsx
@@ -76,11 +76,7 @@ export function WslServerSettings(props: {
}))
const remove = (key: ServerConnection.Key) => {
- if (!api) return
- request.mutate(async () => {
- await api.removeServer(key)
- await props.controller.handleRemove(key)
- })
+ request.mutate(() => props.controller.handleRemove(key))
}
return (
diff --git a/packages/console/app/src/lib/stats-proxy.ts b/packages/console/app/src/lib/stats-proxy.ts
index c8c576ddcf35..399bf0efd88b 100644
--- a/packages/console/app/src/lib/stats-proxy.ts
+++ b/packages/console/app/src/lib/stats-proxy.ts
@@ -1,6 +1,8 @@
import type { APIEvent } from "@solidjs/start/server"
import { Resource } from "@opencode-ai/console-resource"
+const dataPath = "/data"
+
export async function statsProxy(evt: APIEvent) {
const req = evt.request.clone()
const targetUrl = new URL(req.url)
@@ -8,8 +10,8 @@ export async function statsProxy(evt: APIEvent) {
targetUrl.hostname = Resource.App.stage === "production" ? "stats.opencode.ai" : "stats.dev.opencode.ai"
targetUrl.port = ""
- if (targetUrl.pathname.startsWith("/stats/_build/") || targetUrl.pathname === "/stats/banner.png") {
- targetUrl.pathname = targetUrl.pathname.slice("/stats".length)
+ if (targetUrl.pathname.startsWith(`${dataPath}/_build/`) || targetUrl.pathname === `${dataPath}/banner.jpg`) {
+ targetUrl.pathname = targetUrl.pathname.slice(dataPath.length)
}
const response = await fetch(targetUrl, {
@@ -32,6 +34,17 @@ export async function statsProxy(evt: APIEvent) {
})
}
+export function statsRedirect(evt: APIEvent) {
+ const url = new URL(evt.request.url)
+ url.pathname = `${dataPath}${url.pathname.slice("/stats".length)}`
+ return new Response(null, {
+ status: 308,
+ headers: {
+ Location: url.toString(),
+ },
+ })
+}
+
function rewriteStatsHtml(html: string) {
- return html.replaceAll('"/_build/', '"/stats/_build/').replaceAll("'/_build/", "'/stats/_build/")
+ return html.replaceAll('"/_build/', `"${dataPath}/_build/`).replaceAll("'/_build/", `'${dataPath}/_build/`)
}
diff --git a/packages/console/app/src/routes/data/[...path].ts b/packages/console/app/src/routes/data/[...path].ts
new file mode 100644
index 000000000000..d1899215df0b
--- /dev/null
+++ b/packages/console/app/src/routes/data/[...path].ts
@@ -0,0 +1,8 @@
+import { statsProxy } from "~/lib/stats-proxy"
+
+export const GET = statsProxy
+export const POST = statsProxy
+export const PUT = statsProxy
+export const DELETE = statsProxy
+export const OPTIONS = statsProxy
+export const PATCH = statsProxy
diff --git a/packages/console/app/src/routes/data/index.ts b/packages/console/app/src/routes/data/index.ts
new file mode 100644
index 000000000000..d1899215df0b
--- /dev/null
+++ b/packages/console/app/src/routes/data/index.ts
@@ -0,0 +1,8 @@
+import { statsProxy } from "~/lib/stats-proxy"
+
+export const GET = statsProxy
+export const POST = statsProxy
+export const PUT = statsProxy
+export const DELETE = statsProxy
+export const OPTIONS = statsProxy
+export const PATCH = statsProxy
diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx
index a4b1eb1bb78b..fee2dec3fb07 100644
--- a/packages/console/app/src/routes/go/index.tsx
+++ b/packages/console/app/src/routes/go/index.tsx
@@ -65,7 +65,7 @@ function LimitsGraph(props: { href: string }) {
{ id: "glm-5.1", name: "GLM-5.1", req: 880, d: "100ms" },
{ id: "qwen3.7-max", name: "Qwen3.7 Max", req: 950, d: "110ms" },
{ id: "kimi-k2.6", name: "Kimi K2.6", req: 1150, d: "150ms" },
- { id: "minimax-m3", name: "MiniMax M3", req: 1400, d: "200ms" },
+ { id: "minimax-m3", name: "MiniMax M3", req: 3200, d: "200ms" },
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", req: 3250, d: "210ms" },
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", req: 3300, d: "220ms" },
{ id: "minimax-m2.7", name: "MiniMax M2.7", req: 3400, d: "230ms" },
diff --git a/packages/console/app/src/routes/legal/privacy-policy/index.tsx b/packages/console/app/src/routes/legal/privacy-policy/index.tsx
index 42bb71aa3953..4426e0ddd08d 100644
--- a/packages/console/app/src/routes/legal/privacy-policy/index.tsx
+++ b/packages/console/app/src/routes/legal/privacy-policy/index.tsx
@@ -501,7 +501,7 @@ export default function PrivacyPolicy() {
otherwise use the Services or send us any Personal Data. If we learn we have collected Personal Data
from a child under 18 years of age, we will delete that information as quickly as possible. If you
believe that a child under 18 years of age may have provided Personal Data to us, please contact us at{" "}
- contact@anoma.ly.
+ help@anoma.ly.
California Resident Rights
@@ -520,7 +520,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are a California resident, the portion that is more protective of Personal Data shall control to the
extent of such conflict. If you have any questions about this section or whether any of the following
- rights apply to you, please contact us at contact@anoma.ly.
+ rights apply to you, please contact us at help@anoma.ly.
Access
@@ -605,7 +605,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are a Colorado resident, the portion that is more protective of Personal Data shall control to the
extent of such conflict. If you have any questions about this section or whether any of the following
- rights apply to you, please contact us at contact@anoma.ly.
+ rights apply to you, please contact us at help@anoma.ly.
Access and Portability
@@ -676,7 +676,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are a Connecticut resident, the portion that is more protective of Personal Data shall control to the
extent of such conflict. If you have any questions about this section or whether any of the following
- rights apply to you, please contact us at contact@anoma.ly.
+ rights apply to you, please contact us at help@anoma.ly.
Access and Portability
@@ -745,7 +745,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are a Delaware resident, the portion that is more protective of Personal Data shall control to the
extent of such conflict. If you have any questions about this section or whether any of the following
- rights apply to you, please contact us at contact@anoma.ly.
+ rights apply to you, please contact us at help@anoma.ly.
Access and Portability
@@ -818,7 +818,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are an Iowa resident, the portion that is more protective of Personal Data shall control to the extent
of such conflict. If you have any questions about this section or whether any of the following rights
- apply to you, please contact us at contact@anoma.ly.
+ apply to you, please contact us at help@anoma.ly.
Access and Portability
@@ -864,7 +864,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are a Montana resident, the portion that is more protective of Personal Data shall control to the extent
of such conflict. If you have any questions about this section or whether any of the following rights
- apply to you, please contact us at contact@anoma.ly.
+ apply to you, please contact us at help@anoma.ly.
Access and Portability
@@ -937,7 +937,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are a Nebraska resident, the portion that is more protective of Personal Data shall control to the
extent of such conflict. If you have any questions about this section or whether any of the following
- rights apply to you, please contact us at contact@anoma.ly.
+ rights apply to you, please contact us at help@anoma.ly.
Access and Portability
@@ -1007,7 +1007,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are a New Hampshire resident, the portion that is more protective of Personal Data shall control to the
extent of such conflict. If you have any questions about this section or whether any of the following
- rights apply to you, please contact us at contact@anoma.ly.
+ rights apply to you, please contact us at help@anoma.ly.
Access and Portability
@@ -1078,7 +1078,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are a New Jersey resident, the portion that is more protective of Personal Data shall control to the
extent of such conflict. If you have any questions about this section or whether any of the following
- rights apply to you, please contact us at contact@anoma.ly.
+ rights apply to you, please contact us at help@anoma.ly.
Access and Portability
@@ -1151,7 +1151,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are an Oregon resident, the portion that is more protective of Personal Data shall control to the extent
of such conflict. If you have any questions about this section or whether any of the following rights
- apply to you, please contact us at contact@anoma.ly.
+ apply to you, please contact us at help@anoma.ly.
Access and Portability
@@ -1225,7 +1225,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are a Texas resident, the portion that is more protective of Personal Data shall control to the extent
of such conflict. If you have any questions about this section or whether any of the following rights
- apply to you, please contact us at contact@anoma.ly.
+ apply to you, please contact us at help@anoma.ly.
Access and Portability
@@ -1293,7 +1293,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are a Utah resident, the portion that is more protective of Personal Data shall control to the extent of
such conflict. If you have any questions about this section or whether any of the following rights apply
- to you, please contact us at contact@anoma.ly.
+ to you, please contact us at help@anoma.ly.
Access and Portability
@@ -1339,7 +1339,7 @@ export default function PrivacyPolicy() {
If there are any conflicts between this section and any other provision of this Privacy Policy and you
are a Virginia resident, the portion that is more protective of Personal Data shall control to the
extent of such conflict. If you have any questions about this section or whether any of the following
- rights apply to you, please contact us at contact@anoma.ly.
+ rights apply to you, please contact us at help@anoma.ly.
Access and Portability
@@ -1418,7 +1418,7 @@ export default function PrivacyPolicy() {
@@ -1430,7 +1430,7 @@ export default function PrivacyPolicy() {
@@ -1457,7 +1457,7 @@ export default function PrivacyPolicy() {
@@ -1474,8 +1474,8 @@ export default function PrivacyPolicy() {
Under California Civil Code Sections 1798.83-1798.84, California residents are entitled to contact us to
prevent disclosure of Personal Data to third parties for such third parties' direct marketing purposes;
- in order to submit such a request, please contact us at{" "}
- contact@anoma.ly.
+ in order to submit such a request, please contact us at help@anoma.ly
+ .
@@ -1500,7 +1500,7 @@ export default function PrivacyPolicy() {
-
- Email: contact@anoma.ly
+ Email: help@anoma.ly
- Phone: +1 415 794-0209
- Address: 2443 Fillmore St #380-6343, San Francisco, CA 94115, United States
diff --git a/packages/console/app/src/routes/legal/terms-of-service/index.tsx b/packages/console/app/src/routes/legal/terms-of-service/index.tsx
index 55a9fd42f111..7847c44bdc83 100644
--- a/packages/console/app/src/routes/legal/terms-of-service/index.tsx
+++ b/packages/console/app/src/routes/legal/terms-of-service/index.tsx
@@ -30,7 +30,7 @@ export default function TermsOfService() {
- Email: contact@anoma.ly
+ Email: help@anoma.ly
@@ -114,7 +114,7 @@ export default function TermsOfService() {
attempt to register for or otherwise use the Services or send us any personal information. If we learn
we have collected personal information from a child under 13 years of age, we will delete that
information as quickly as possible. If you believe that a child under 13 years of age may have provided
- us personal information, please contact us at contact@anoma.ly.
+ us personal information, please contact us at help@anoma.ly.
What are the basics of using OpenCode?
@@ -315,7 +315,7 @@ export default function TermsOfService() {
specified time of the trial. You must stop using a Paid Service before the end of the trial period in
order to avoid being charged for that Paid Service. If you cancel prior to the end of the trial period
and are inadvertently charged for a Paid Service, please contact us at{" "}
- contact@anoma.ly.
+ help@anoma.ly.
What if I want to stop using the Services?
diff --git a/packages/console/app/src/routes/stats/[...path].ts b/packages/console/app/src/routes/stats/[...path].ts
index d1899215df0b..4a387c039ca5 100644
--- a/packages/console/app/src/routes/stats/[...path].ts
+++ b/packages/console/app/src/routes/stats/[...path].ts
@@ -1,8 +1,8 @@
-import { statsProxy } from "~/lib/stats-proxy"
+import { statsRedirect } from "~/lib/stats-proxy"
-export const GET = statsProxy
-export const POST = statsProxy
-export const PUT = statsProxy
-export const DELETE = statsProxy
-export const OPTIONS = statsProxy
-export const PATCH = statsProxy
+export const GET = statsRedirect
+export const POST = statsRedirect
+export const PUT = statsRedirect
+export const DELETE = statsRedirect
+export const OPTIONS = statsRedirect
+export const PATCH = statsRedirect
diff --git a/packages/console/app/src/routes/stats/index.ts b/packages/console/app/src/routes/stats/index.ts
index d1899215df0b..4a387c039ca5 100644
--- a/packages/console/app/src/routes/stats/index.ts
+++ b/packages/console/app/src/routes/stats/index.ts
@@ -1,8 +1,8 @@
-import { statsProxy } from "~/lib/stats-proxy"
+import { statsRedirect } from "~/lib/stats-proxy"
-export const GET = statsProxy
-export const POST = statsProxy
-export const PUT = statsProxy
-export const DELETE = statsProxy
-export const OPTIONS = statsProxy
-export const PATCH = statsProxy
+export const GET = statsRedirect
+export const POST = statsRedirect
+export const PUT = statsRedirect
+export const DELETE = statsRedirect
+export const OPTIONS = statsRedirect
+export const PATCH = statsRedirect
diff --git a/packages/console/app/src/routes/stripe/webhook.ts b/packages/console/app/src/routes/stripe/webhook.ts
index dfff3bd809b4..e1e4e6cbd39f 100644
--- a/packages/console/app/src/routes/stripe/webhook.ts
+++ b/packages/console/app/src/routes/stripe/webhook.ts
@@ -226,7 +226,7 @@ export async function POST(input: APIEvent) {
expand: ["discounts", "payments"],
})
const paymentID = invoice.payments?.data[0]?.payment.payment_intent as string
- const couponID = (invoice.discounts[0] as Stripe.Discount).coupon?.id as string
+ const couponID = (invoice.discounts[0] as Stripe.Discount)?.coupon?.id as string
if (!paymentID) {
// payment id can be undefined when using coupon
if (!couponID) throw new Error("Payment ID not found")
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx
index 4d9b0cabd547..90280635ff7e 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx
@@ -143,7 +143,7 @@ export function BillingSection() {
{i18n.t("workspace.billing.title")}
{i18n.t("workspace.billing.subtitle.beforeLink")}{" "}
- {i18n.t("workspace.billing.contactUs")}{" "}
+ {i18n.t("workspace.billing.contactUs")}{" "}
{i18n.t("workspace.billing.subtitle.afterLink")}
diff --git a/packages/core/package.json b/packages/core/package.json
index e720d9357c0d..482d66bb648d 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -9,8 +9,7 @@
"db": "bun drizzle-kit",
"migration": "bun run script/migration.ts",
"fix-node-pty": "bun run script/fix-node-pty.ts",
- "test": "bun test",
- "test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
+ "test": "bun test --only-failures",
"typecheck": "tsgo --noEmit"
},
"bin": {
diff --git a/packages/core/src/config/plugin/reference.ts b/packages/core/src/config/plugin/reference.ts
new file mode 100644
index 000000000000..81e0804b4e9e
--- /dev/null
+++ b/packages/core/src/config/plugin/reference.ts
@@ -0,0 +1,65 @@
+export * as ConfigReferencePlugin from "./reference"
+
+import path from "path"
+import { Effect } from "effect"
+import { Config } from "../../config"
+import { ConfigReference } from "../reference"
+import { Global } from "../../global"
+import { Location } from "../../location"
+import { PluginV2 } from "../../plugin"
+import { Reference } from "../../reference"
+import { AbsolutePath } from "../../schema"
+
+export const Plugin = {
+ id: PluginV2.ID.make("core/config-reference"),
+ effect: Effect.gen(function* () {
+ const config = yield* Config.Service
+ const global = yield* Global.Service
+ const location = yield* Location.Service
+ const references = yield* Reference.Service
+ const update = yield* references.transform()
+ const entries = new Map