Merge remote-tracking branch 'upstream/dev' into pr-23407-merge
# Conflicts: # packages/desktop/src/index.tsx # packages/desktop/src/main/index.ts # packages/desktop/src/main/wsl-servers.ts # packages/desktop/src/main/wsl.ts
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.35",
|
||||
"version": "1.14.39",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { List } from "@opencode-ai/ui/list"
|
||||
import { Switch } from "@opencode-ai/ui/switch"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { loadMcpQuery } from "@/context/global-sync"
|
||||
import { mcpQueryKey } from "@/context/global-sync"
|
||||
|
||||
const statusLabels = {
|
||||
connected: "mcp.status.connected",
|
||||
@@ -32,7 +32,7 @@ export const DialogSelectMcp: Component = () => {
|
||||
if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name })
|
||||
else await sdk.client.mcp.connect({ name })
|
||||
},
|
||||
onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
|
||||
onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }),
|
||||
}))
|
||||
|
||||
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "@/context/prompt"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useComments } from "@/context/comments"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
@@ -102,6 +103,7 @@ const NON_EMPTY_TEXT = /[^\s\u200B]/
|
||||
|
||||
export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const sdk = useSDK()
|
||||
const globalSDK = useGlobalSDK()
|
||||
|
||||
const sync = useSync()
|
||||
const local = useLocal()
|
||||
@@ -1253,7 +1255,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
const [agentsQuery, globalProvidersQuery, providersQuery] = useQueries(() => ({
|
||||
queries: [loadAgentsQuery(sdk.directory), loadProvidersQuery(null), loadProvidersQuery(sdk.directory)],
|
||||
queries: [
|
||||
loadAgentsQuery(sdk.directory, sdk.client),
|
||||
loadProvidersQuery(null, globalSDK.client),
|
||||
loadProvidersQuery(sdk.directory, sdk.client),
|
||||
],
|
||||
}))
|
||||
|
||||
const agentsLoading = () => agentsQuery.isLoading
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useSDK } from "@/context/sdk"
|
||||
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
|
||||
import { loadMcpQuery } from "@/context/global-sync"
|
||||
import { mcpQueryKey } from "@/context/global-sync"
|
||||
|
||||
const pollMs = 10_000
|
||||
|
||||
@@ -145,7 +145,7 @@ const useMcpToggleMutation = () => {
|
||||
const status = sync.data.mcp[name]
|
||||
await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name }))
|
||||
},
|
||||
onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
|
||||
onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }),
|
||||
onError: (err) => {
|
||||
showToast({
|
||||
variant: "error",
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
clearProviderRev,
|
||||
loadGlobalConfigQuery,
|
||||
loadPathQuery,
|
||||
loadProjectsQuery,
|
||||
loadProvidersQuery,
|
||||
} from "./global-sync/bootstrap"
|
||||
import { createChildStoreManager } from "./global-sync/child-store"
|
||||
@@ -31,7 +30,7 @@ import { trimSessions } from "./global-sync/session-trim"
|
||||
import type { ProjectMeta } from "./global-sync/types"
|
||||
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
||||
import { formatServerError } from "@/utils/server-errors"
|
||||
import { queryOptions, skipToken, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query"
|
||||
import { queryOptions, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query"
|
||||
import { createRefreshQueue } from "./global-sync/queue"
|
||||
import { directoryKey } from "./global-sync/utils"
|
||||
|
||||
@@ -49,19 +48,22 @@ type GlobalStore = {
|
||||
reload: undefined | "pending" | "complete"
|
||||
}
|
||||
|
||||
export const loadSessionsQuery = (directory: string) =>
|
||||
queryOptions<null>({ queryKey: [directory, "loadSessions"], queryFn: skipToken })
|
||||
export const loadSessionsQueryKey = (directory: string) => [directory, "loadSessions"] as const
|
||||
|
||||
export const loadMcpQuery = (directory: string, sdk?: OpencodeClient) =>
|
||||
export const mcpQueryKey = (directory: string) => [directory, "mcp"] as const
|
||||
|
||||
export const loadMcpQuery = (directory: string, sdk: OpencodeClient) =>
|
||||
queryOptions({
|
||||
queryKey: [directory, "mcp"],
|
||||
queryFn: sdk ? () => sdk.mcp.status().then((r) => r.data ?? {}) : skipToken,
|
||||
queryKey: mcpQueryKey(directory),
|
||||
queryFn: () => sdk.mcp.status().then((r) => r.data ?? {}),
|
||||
})
|
||||
|
||||
export const loadLspQuery = (directory: string, sdk?: OpencodeClient) =>
|
||||
export const lspQueryKey = (directory: string) => [directory, "lsp"] as const
|
||||
|
||||
export const loadLspQuery = (directory: string, sdk: OpencodeClient) =>
|
||||
queryOptions({
|
||||
queryKey: [directory, "lsp"],
|
||||
queryFn: sdk ? () => sdk.lsp.status().then((r) => r.data ?? []) : skipToken,
|
||||
queryKey: lspQueryKey(directory),
|
||||
queryFn: () => sdk.lsp.status().then((r) => r.data ?? []),
|
||||
})
|
||||
|
||||
function createGlobalSync() {
|
||||
@@ -76,7 +78,11 @@ function createGlobalSync() {
|
||||
const sessionMeta = new Map<string, { limit: number }>()
|
||||
|
||||
const [configQuery, providerQuery, pathQuery] = useQueries(() => ({
|
||||
queries: [loadGlobalConfigQuery(), loadProvidersQuery(null), loadPathQuery(null), loadProjectsQuery()],
|
||||
queries: [
|
||||
loadGlobalConfigQuery(globalSDK.client),
|
||||
loadProvidersQuery(null, globalSDK.client),
|
||||
loadPathQuery(null, globalSDK.client),
|
||||
],
|
||||
}))
|
||||
|
||||
const [globalStore, setGlobalStore] = createStore<GlobalStore>({
|
||||
@@ -233,7 +239,7 @@ function createGlobalSync() {
|
||||
const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT)
|
||||
const promise = queryClient
|
||||
.fetchQuery({
|
||||
...loadSessionsQuery(key),
|
||||
queryKey: loadSessionsQueryKey(key),
|
||||
queryFn: () =>
|
||||
loadRootSessionsWithFallback({
|
||||
directory,
|
||||
|
||||
@@ -18,7 +18,7 @@ import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
||||
import type { State, VcsCache } from "./types"
|
||||
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
||||
import { formatServerError } from "@/utils/server-errors"
|
||||
import { QueryClient, queryOptions, skipToken } from "@tanstack/solid-query"
|
||||
import { QueryClient, queryOptions } from "@tanstack/solid-query"
|
||||
import { loadMcpQuery } from "../global-sync"
|
||||
|
||||
type GlobalStore = {
|
||||
@@ -83,44 +83,25 @@ function showErrors(input: {
|
||||
})
|
||||
}
|
||||
|
||||
export const loadGlobalConfigQuery = (
|
||||
sdk?: OpencodeClient,
|
||||
transform?: (x: Awaited<ReturnType<OpencodeClient["global"]["config"]["get"]>>) => void,
|
||||
) =>
|
||||
export const loadGlobalConfigQuery = (sdk: OpencodeClient) =>
|
||||
queryOptions({
|
||||
queryKey: ["config"],
|
||||
queryFn: sdk
|
||||
? () =>
|
||||
retry(() =>
|
||||
sdk.global.config.get().then((x) => {
|
||||
transform?.(x)
|
||||
return x.data!
|
||||
}),
|
||||
)
|
||||
: skipToken,
|
||||
queryFn: () => retry(() => sdk.global.config.get().then((x) => x.data!)),
|
||||
})
|
||||
|
||||
export const loadProjectsQuery = (
|
||||
sdk?: OpencodeClient,
|
||||
transform?: (x: Awaited<ReturnType<OpencodeClient["project"]["list"]>>["data"]) => void,
|
||||
) =>
|
||||
export const loadProjectsQuery = (sdk: OpencodeClient) =>
|
||||
queryOptions({
|
||||
queryKey: ["project"],
|
||||
queryFn: sdk
|
||||
? () =>
|
||||
retry(() =>
|
||||
sdk.project
|
||||
.list()
|
||||
.then((x) => {
|
||||
return (x.data ?? [])
|
||||
.filter((p) => !!p?.id)
|
||||
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
|
||||
.slice()
|
||||
.sort((a, b) => cmp(a.id, b.id))
|
||||
})
|
||||
.then(transform),
|
||||
)
|
||||
: skipToken,
|
||||
queryFn: () =>
|
||||
retry(() =>
|
||||
sdk.project.list().then((x) => {
|
||||
return (x.data ?? [])
|
||||
.filter((p) => !!p?.id)
|
||||
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
|
||||
.slice()
|
||||
.sort((a, b) => cmp(a.id, b.id))
|
||||
}),
|
||||
),
|
||||
})
|
||||
|
||||
export async function bootstrapGlobal(input: {
|
||||
@@ -136,9 +117,9 @@ export async function bootstrapGlobal(input: {
|
||||
() => input.queryClient.fetchQuery(loadProvidersQuery(null, input.globalSDK)),
|
||||
() => input.queryClient.fetchQuery(loadPathQuery(null, input.globalSDK)),
|
||||
() =>
|
||||
input.queryClient.fetchQuery(
|
||||
loadProjectsQuery(input.globalSDK, (data) => input.setGlobalStore("project", data ?? [])),
|
||||
),
|
||||
input.queryClient
|
||||
.fetchQuery(loadProjectsQuery(input.globalSDK))
|
||||
.then((data) => input.setGlobalStore("project", data)),
|
||||
]
|
||||
await runAll(slow)
|
||||
// showErrors({
|
||||
@@ -197,46 +178,22 @@ function warmSessions(input: {
|
||||
).then(() => undefined)
|
||||
}
|
||||
|
||||
export const loadProvidersQuery = (directory: string | null, sdk?: OpencodeClient) =>
|
||||
export const loadProvidersQuery = (directory: string | null, sdk: OpencodeClient) =>
|
||||
queryOptions({
|
||||
queryKey: [directory, "providers"],
|
||||
queryFn: sdk ? () => retry(() => sdk.provider.list().then((x) => normalizeProviderList(x.data!))) : skipToken,
|
||||
queryFn: () => retry(() => sdk.provider.list().then((x) => normalizeProviderList(x.data!))),
|
||||
})
|
||||
|
||||
export const loadAgentsQuery = (
|
||||
directory: string | null,
|
||||
sdk?: OpencodeClient,
|
||||
transform?: (x: Awaited<ReturnType<OpencodeClient["app"]["agents"]>>) => void,
|
||||
) =>
|
||||
export const loadAgentsQuery = (directory: string | null, sdk: OpencodeClient) =>
|
||||
queryOptions({
|
||||
queryKey: [directory, "agents"],
|
||||
queryFn: sdk
|
||||
? () =>
|
||||
retry(() =>
|
||||
sdk.app.agents().then((x) => {
|
||||
transform?.(x)
|
||||
return x.data!
|
||||
}),
|
||||
)
|
||||
: skipToken,
|
||||
queryFn: () => retry(() => sdk.app.agents().then((x) => normalizeAgentList(x.data))),
|
||||
})
|
||||
|
||||
export const loadPathQuery = (
|
||||
directory: string | null,
|
||||
sdk?: OpencodeClient,
|
||||
transform?: (x: Awaited<ReturnType<OpencodeClient["path"]["get"]>>) => void,
|
||||
) =>
|
||||
export const loadPathQuery = (directory: string | null, sdk: OpencodeClient) =>
|
||||
queryOptions<Path>({
|
||||
queryKey: [directory, "path"],
|
||||
queryFn: sdk
|
||||
? () =>
|
||||
retry(() =>
|
||||
sdk.path.get().then(async (x) => {
|
||||
transform?.(x)
|
||||
return x.data!
|
||||
}),
|
||||
)
|
||||
: skipToken,
|
||||
queryFn: () => retry(() => sdk.path.get().then((x) => x.data!)),
|
||||
})
|
||||
|
||||
export async function bootstrapDirectory(input: {
|
||||
@@ -271,9 +228,9 @@ export async function bootstrapDirectory(input: {
|
||||
const slow = [
|
||||
() => Promise.resolve(input.loadSessions(input.directory)),
|
||||
() =>
|
||||
input.queryClient.ensureQueryData(
|
||||
loadAgentsQuery(input.directory, input.sdk, (x) => input.setStore("agent", normalizeAgentList(x.data))),
|
||||
),
|
||||
input.queryClient
|
||||
.ensureQueryData(loadAgentsQuery(input.directory, input.sdk))
|
||||
.then((data) => input.setStore("agent", data)),
|
||||
() =>
|
||||
retry(() => input.sdk.config.get().then((x) => input.setStore("config", reconcile(x.data!, { merge: false })))),
|
||||
() => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
|
||||
@@ -281,12 +238,10 @@ export async function bootstrapDirectory(input: {
|
||||
(() => retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id))),
|
||||
!seededPath &&
|
||||
(() =>
|
||||
input.queryClient.ensureQueryData(
|
||||
loadPathQuery(input.directory, input.sdk, (x) => {
|
||||
const next = projectID(x.data?.directory ?? input.directory, input.global.project)
|
||||
if (next) input.setStore("project", next)
|
||||
}),
|
||||
)),
|
||||
input.queryClient.ensureQueryData(loadPathQuery(input.directory, input.sdk)).then((data) => {
|
||||
const next = projectID(data.directory ?? input.directory, input.global.project)
|
||||
if (next) input.setStore("project", next)
|
||||
})),
|
||||
() =>
|
||||
retry(() =>
|
||||
input.sdk.vcs.get().then((x) => {
|
||||
|
||||
@@ -14,12 +14,12 @@ import { Spinner } from "@opencode-ai/ui/spinner"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { type Session } from "@opencode-ai/sdk/v2/client"
|
||||
import { type LocalProject } from "@/context/layout"
|
||||
import { loadSessionsQuery, useGlobalSync } from "@/context/global-sync"
|
||||
import { loadSessionsQueryKey, useGlobalSync } from "@/context/global-sync"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { pathKey } from "@/utils/path-key"
|
||||
import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items"
|
||||
import { sortedRootSessions } from "./helpers"
|
||||
import { useQuery } from "@tanstack/solid-query"
|
||||
import { useIsFetching } from "@tanstack/solid-query"
|
||||
|
||||
type InlineEditorComponent = (props: {
|
||||
id: string
|
||||
@@ -320,9 +320,9 @@ export const SortableWorkspace = (props: {
|
||||
const boot = createMemo(() => open() || active())
|
||||
const count = createMemo(() => sessions()?.length ?? 0)
|
||||
const hasMore = createMemo(() => workspaceStore.sessionTotal > count())
|
||||
const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) }))
|
||||
const fetching = useIsFetching(() => ({ queryKey: loadSessionsQueryKey(props.directory) }))
|
||||
const busy = createMemo(() => props.ctx.isBusy(props.directory))
|
||||
const loading = () => query.isLoading && count() === 0
|
||||
const loading = () => fetching() > 0 && count() === 0
|
||||
const touch = createMediaQuery("(hover: none)")
|
||||
const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id)))
|
||||
const loadMore = async () => {
|
||||
@@ -427,7 +427,7 @@ export const SortableWorkspace = (props: {
|
||||
mobile={props.mobile}
|
||||
ctx={props.ctx}
|
||||
showNew={showNew}
|
||||
loading={() => query.isLoading && count() === 0}
|
||||
loading={loading}
|
||||
sessions={sessions}
|
||||
hasMore={hasMore}
|
||||
loadMore={loadMore}
|
||||
@@ -454,9 +454,9 @@ export const LocalWorkspace = (props: {
|
||||
const slug = createMemo(() => base64Encode(props.project.worktree))
|
||||
const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
|
||||
const count = createMemo(() => sessions()?.length ?? 0)
|
||||
const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) }))
|
||||
const fetching = useIsFetching(() => ({ queryKey: loadSessionsQueryKey(props.project.worktree) }))
|
||||
const hasMore = createMemo(() => workspace().store.sessionTotal > count())
|
||||
const loading = () => query.isLoading && count() === 0
|
||||
const loading = () => fetching() > 0 && count() === 0
|
||||
const loadMore = async () => {
|
||||
workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
|
||||
await globalSync.project.loadSessions(props.project.worktree)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.14.35",
|
||||
"version": "1.14.39",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -31,6 +31,7 @@
|
||||
"solid-js": "catalog:",
|
||||
"solid-list": "0.3.0",
|
||||
"solid-stripe": "0.8.1",
|
||||
"svix": "1.92.2",
|
||||
"vite": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { Resource } from "@opencode-ai/console-resource"
|
||||
import { Webhook } from "svix"
|
||||
|
||||
type Incident = {
|
||||
mode?: "test" | "standard"
|
||||
name?: string
|
||||
permalink?: string
|
||||
summary?: string
|
||||
}
|
||||
|
||||
type IncidentWebhookPayload = {
|
||||
event_type?: string
|
||||
"public_incident.incident_created_v2"?: Incident
|
||||
}
|
||||
|
||||
const verifyWebhook = async (request: Request) => {
|
||||
const body = await request.text()
|
||||
try {
|
||||
return new Webhook(Resource.INCIDENT_WEBHOOK_SIGNING_SECRET.value).verify(
|
||||
body,
|
||||
Object.fromEntries(request.headers.entries()),
|
||||
) as IncidentWebhookPayload
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const postDiscordMessage = async (incident: Incident) => {
|
||||
return fetch(Resource.DISCORD_INCIDENT_WEBHOOK_URL.value, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: [
|
||||
`**${incident.mode === "test" ? "[TEST] " : ""}${incident.name ?? "Incident has been created"}**`,
|
||||
incident.summary,
|
||||
"",
|
||||
"@everyone",
|
||||
"",
|
||||
incident.permalink,
|
||||
]
|
||||
.filter((line) => line !== undefined)
|
||||
.join("\n"),
|
||||
allowed_mentions: {
|
||||
parse: ["everyone"],
|
||||
},
|
||||
flags: 4,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export async function POST(input: APIEvent) {
|
||||
const payload = await verifyWebhook(input.request)
|
||||
if (!payload) {
|
||||
return Response.json({ message: "invalid signature" }, { status: 401 })
|
||||
}
|
||||
|
||||
if (payload.event_type !== "public_incident.incident_created_v2") {
|
||||
return Response.json({ message: "ignored event" }, { status: 200 })
|
||||
}
|
||||
|
||||
const incident = payload["public_incident.incident_created_v2"]
|
||||
if (!incident) {
|
||||
return Response.json({ message: "missing incident" }, { status: 400 })
|
||||
}
|
||||
|
||||
const response = await postDiscordMessage(incident)
|
||||
if (!response.ok) {
|
||||
return Response.json({ message: "discord webhook failed" }, { status: 502 })
|
||||
}
|
||||
|
||||
return Response.json({ message: "sent" }, { status: 200 })
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.14.35",
|
||||
"version": "1.14.39",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -35,6 +35,10 @@ declare module "sst" {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"DISCORD_INCIDENT_WEBHOOK_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_BOT_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
@@ -87,6 +91,10 @@ declare module "sst" {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"INCIDENT_WEBHOOK_SIGNING_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.14.35",
|
||||
"version": "1.14.39",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -35,6 +35,10 @@ declare module "sst" {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"DISCORD_INCIDENT_WEBHOOK_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_BOT_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
@@ -87,6 +91,10 @@ declare module "sst" {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"INCIDENT_WEBHOOK_SIGNING_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.14.35",
|
||||
"version": "1.14.39",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -35,6 +35,10 @@ declare module "sst" {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"DISCORD_INCIDENT_WEBHOOK_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_BOT_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
@@ -87,6 +91,10 @@ declare module "sst" {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"INCIDENT_WEBHOOK_SIGNING_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.14.35",
|
||||
"version": "1.14.39",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
out/
|
||||
|
||||
resources/opencode-cli*
|
||||
resources/icons
|
||||
@@ -1,4 +0,0 @@
|
||||
# Desktop package notes
|
||||
|
||||
- Renderer process should only call `window.api` from `src/preload`.
|
||||
- Main process should register IPC handlers in `src/main/ipc.ts`.
|
||||
@@ -1,32 +0,0 @@
|
||||
# OpenCode Desktop
|
||||
|
||||
Native OpenCode desktop app, built with Tauri v2.
|
||||
|
||||
## Development
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
bun run --cwd packages/desktop tauri dev
|
||||
```
|
||||
|
||||
This starts the Vite dev server on http://localhost:1420 and opens the native window.
|
||||
|
||||
If you only want the web dev server (no native shell):
|
||||
|
||||
```bash
|
||||
bun run --cwd packages/desktop dev
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
To create a production `dist/` and build the native app bundle:
|
||||
|
||||
```bash
|
||||
bun run --cwd packages/desktop tauri build
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Running the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions.
|
||||
@@ -1,68 +0,0 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"private": true,
|
||||
"version": "1.14.35",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
"author": {
|
||||
"name": "OpenCode",
|
||||
"email": "hello@opencode.ai"
|
||||
},
|
||||
"scripts": {
|
||||
"typecheck": "tsgo -b",
|
||||
"predev": "bun ./scripts/predev.ts",
|
||||
"dev": "electron-vite dev",
|
||||
"prebuild": "bun ./scripts/prebuild.ts",
|
||||
"build": "electron-vite build",
|
||||
"preview": "electron-vite preview",
|
||||
"package": "electron-builder --config electron-builder.config.ts",
|
||||
"package:mac": "electron-builder --mac --config electron-builder.config.ts",
|
||||
"package:win": "electron-builder --win --config electron-builder.config.ts",
|
||||
"package:linux": "electron-builder --linux --config electron-builder.config.ts",
|
||||
"native:build": "bun install --cwd native"
|
||||
},
|
||||
"main": "./out/main/index.js",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
"electron-context-menu": "4.1.2",
|
||||
"electron-log": "^5",
|
||||
"electron-store": "^10",
|
||||
"electron-updater": "^6",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"drizzle-orm": "catalog:",
|
||||
"marked": "^15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/artifact": "4.0.0",
|
||||
"@lydell/node-pty": "catalog:",
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@solid-primitives/i18n": "2.2.1",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"@valibot/to-json-schema": "1.6.0",
|
||||
"electron": "41.2.1",
|
||||
"electron-builder": "^26",
|
||||
"electron-vite": "^5",
|
||||
"solid-js": "catalog:",
|
||||
"sury": "11.0.0-alpha.4",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "catalog:",
|
||||
"zod-openapi": "5.4.6"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@lydell/node-pty-darwin-arm64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-darwin-x64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-linux-arm64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-linux-x64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-win32-arm64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-win32-x64": "1.2.0-beta.10"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { $ } from "bun"
|
||||
import * as path from "node:path"
|
||||
|
||||
import { RUST_TARGET } from "./utils"
|
||||
|
||||
if (!RUST_TARGET) throw new Error("RUST_TARGET not defined")
|
||||
|
||||
const BUNDLE_DIR = "dist"
|
||||
const BUNDLES_OUT_DIR = path.join(process.cwd(), "dist/bundles")
|
||||
|
||||
await $`mkdir -p ${BUNDLES_OUT_DIR}`
|
||||
await $`cp -r ${BUNDLE_DIR}/* ${BUNDLES_OUT_DIR}`
|
||||
@@ -1,5 +0,0 @@
|
||||
import { $ } from "bun"
|
||||
|
||||
await $`bun ./scripts/copy-icons.ts ${process.env.OPENCODE_CHANNEL ?? "dev"}`
|
||||
|
||||
await $`cd ../opencode && bun script/build-node.ts`
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
import { Script } from "@opencode-ai/script"
|
||||
|
||||
await import("./prebuild")
|
||||
|
||||
const pkg = await Bun.file("./package.json").json()
|
||||
pkg.version = Script.version
|
||||
await Bun.write("./package.json", JSON.stringify(pkg, null, 2) + "\n")
|
||||
console.log(`Updated package.json version to ${Script.version}`)
|
||||
@@ -1,77 +0,0 @@
|
||||
import { $ } from "bun"
|
||||
|
||||
export type Channel = "dev" | "beta" | "prod"
|
||||
|
||||
export function resolveChannel(): Channel {
|
||||
const raw = Bun.env.OPENCODE_CHANNEL
|
||||
if (raw === "dev" || raw === "beta" || raw === "prod") return raw
|
||||
return "dev"
|
||||
}
|
||||
|
||||
export const SIDECAR_BINARIES: Array<{ rustTarget: string; ocBinary: string; assetExt: string }> = [
|
||||
{
|
||||
rustTarget: "aarch64-apple-darwin",
|
||||
ocBinary: "opencode-darwin-arm64",
|
||||
assetExt: "zip",
|
||||
},
|
||||
{
|
||||
rustTarget: "x86_64-apple-darwin",
|
||||
ocBinary: "opencode-darwin-x64-baseline",
|
||||
assetExt: "zip",
|
||||
},
|
||||
{
|
||||
rustTarget: "aarch64-pc-windows-msvc",
|
||||
ocBinary: "opencode-windows-arm64",
|
||||
assetExt: "zip",
|
||||
},
|
||||
{
|
||||
rustTarget: "x86_64-pc-windows-msvc",
|
||||
ocBinary: "opencode-windows-x64-baseline",
|
||||
assetExt: "zip",
|
||||
},
|
||||
{
|
||||
rustTarget: "x86_64-unknown-linux-gnu",
|
||||
ocBinary: "opencode-linux-x64-baseline",
|
||||
assetExt: "tar.gz",
|
||||
},
|
||||
{
|
||||
rustTarget: "aarch64-unknown-linux-gnu",
|
||||
ocBinary: "opencode-linux-arm64",
|
||||
assetExt: "tar.gz",
|
||||
},
|
||||
]
|
||||
|
||||
export const RUST_TARGET = Bun.env.RUST_TARGET
|
||||
|
||||
function nativeTarget() {
|
||||
const { platform, arch } = process
|
||||
if (platform === "darwin") return arch === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin"
|
||||
if (platform === "win32") return arch === "arm64" ? "aarch64-pc-windows-msvc" : "x86_64-pc-windows-msvc"
|
||||
if (platform === "linux") return arch === "arm64" ? "aarch64-unknown-linux-gnu" : "x86_64-unknown-linux-gnu"
|
||||
throw new Error(`Unsupported platform: ${platform}/${arch}`)
|
||||
}
|
||||
|
||||
export function getCurrentSidecar(target = RUST_TARGET ?? nativeTarget()) {
|
||||
const binaryConfig = SIDECAR_BINARIES.find((b) => b.rustTarget === target)
|
||||
if (!binaryConfig) throw new Error(`Sidecar configuration not available for Rust target '${target}'`)
|
||||
|
||||
return binaryConfig
|
||||
}
|
||||
|
||||
export async function copyBinaryToSidecarFolder(source: string) {
|
||||
const dir = `resources`
|
||||
await $`mkdir -p ${dir}`
|
||||
const dest = windowsify(`${dir}/opencode-cli`)
|
||||
await $`cp ${source} ${dest}`
|
||||
if (process.platform === "win32" && process.env.GITHUB_ACTIONS === "true") {
|
||||
await $`pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File ../../script/sign-windows.ps1 ${dest}`
|
||||
}
|
||||
if (process.platform === "darwin") await $`codesign --force --sign - ${dest}`
|
||||
|
||||
console.log(`Copied ${source} to ${dest}`)
|
||||
}
|
||||
|
||||
export function windowsify(path: string) {
|
||||
if (path.endsWith(".exe")) return path
|
||||
return `${path}${process.platform === "win32" ? ".exe" : ""}`
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/* This file is auto-generated by SST. Do not edit. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* deno-fmt-ignore-file */
|
||||
/* biome-ignore-all lint: auto-generated */
|
||||
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true,
|
||||
"strict": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"emitDeclarationOnly": false,
|
||||
"outDir": "node_modules/.ts-dist",
|
||||
"types": ["vite/client", "node", "electron"]
|
||||
},
|
||||
"references": [{ "path": "../app" }],
|
||||
"include": ["src", "package.json"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
||||
@@ -22,3 +22,7 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
out/
|
||||
|
||||
resources/opencode-cli*
|
||||
resources/icons
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Desktop package notes
|
||||
|
||||
- Never call `invoke` manually in this package.
|
||||
- Use the generated bindings in `packages/desktop/src/bindings.ts` for core commands/events.
|
||||
- Renderer process should only call `window.api` from `src/preload`.
|
||||
- Main process should register IPC handlers in `src/main/ipc.ts`.
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
Native OpenCode desktop app, built with Tauri v2.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Building the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions.
|
||||
|
||||
## Development
|
||||
|
||||
From the repo root:
|
||||
@@ -15,18 +11,22 @@ bun install
|
||||
bun run --cwd packages/desktop tauri dev
|
||||
```
|
||||
|
||||
This starts the Vite dev server on http://localhost:1420 and opens the native window.
|
||||
|
||||
If you only want the web dev server (no native shell):
|
||||
|
||||
```bash
|
||||
bun run --cwd packages/desktop dev
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
To create a production `dist/` and build the native app bundle:
|
||||
|
||||
```bash
|
||||
bun run --cwd packages/desktop tauri build
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
## Prerequisites
|
||||
|
||||
### Rust compiler not found
|
||||
|
||||
If you see errors about Rust not being found, install it via [rustup](https://rustup.rs/):
|
||||
|
||||
```bash
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
```
|
||||
Running the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions.
|
||||
|
||||
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 687 B After Width: | Height: | Size: 687 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 582 KiB After Width: | Height: | Size: 582 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |