diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 96b9280fb9..9c44efe1b3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -64,6 +64,12 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - uses: actions/setup-node@v4 with: node-version: "24" diff --git a/STATS.md b/STATS.md index 8a3e0d5538..1a1081d3ac 100644 --- a/STATS.md +++ b/STATS.md @@ -168,3 +168,5 @@ | 2025-12-10 | 1,025,891 (+14,403) | 991,708 (+17,786) | 2,017,599 (+32,189) | | 2025-12-11 | 1,045,110 (+19,219) | 1,010,559 (+18,851) | 2,055,669 (+38,070) | | 2025-12-12 | 1,061,340 (+16,230) | 1,030,838 (+20,279) | 2,092,178 (+36,509) | +| 2025-12-13 | 1,073,561 (+12,221) | 1,044,608 (+13,770) | 2,118,169 (+25,991) | +| 2025-12-14 | 1,082,042 (+8,481) | 1,052,425 (+7,817) | 2,134,467 (+16,298) | diff --git a/bun.lock b/bun.lock index 5bc89cf56d..c709bd83b2 100644 --- a/bun.lock +++ b/bun.lock @@ -20,7 +20,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -48,7 +48,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -75,7 +75,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -99,7 +99,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -123,7 +123,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -133,6 +133,7 @@ "@solid-primitives/active-element": "2.1.3", "@solid-primitives/audio": "1.4.2", "@solid-primitives/event-bus": "1.1.2", + "@solid-primitives/media": "2.3.3", "@solid-primitives/resize-observer": "2.1.3", "@solid-primitives/scroll": "2.1.3", "@solid-primitives/storage": "4.3.3", @@ -169,7 +170,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -198,7 +199,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "22.0.0", @@ -214,7 +215,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.0.152", + "version": "1.0.153", "bin": { "opencode": "./bin/opencode", }, @@ -306,7 +307,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -326,7 +327,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.0.152", + "version": "1.0.153", "devDependencies": { "@hey-api/openapi-ts": "0.88.1", "@tsconfig/node22": "catalog:", @@ -337,7 +338,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -350,7 +351,7 @@ }, "packages/tauri": { "name": "@opencode-ai/tauri", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@opencode-ai/desktop": "workspace:*", "@tauri-apps/api": "^2", @@ -375,7 +376,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -410,7 +411,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "zod": "catalog:", }, @@ -421,7 +422,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -477,7 +478,7 @@ "@tailwindcss/vite": "4.1.11", "@tsconfig/bun": "1.0.9", "@tsconfig/node22": "22.0.2", - "@types/bun": "1.3.3", + "@types/bun": "1.3.4", "@types/luxon": "3.7.1", "@types/node": "22.13.9", "@typescript/native-preview": "7.0.0-dev.20251207.1", @@ -1703,7 +1704,7 @@ "@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="], - "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], + "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="], "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], @@ -2009,7 +2010,7 @@ "bun-pty": ["bun-pty@0.4.2", "", {}, "sha512-sHImDz6pJDsHAroYpC9ouKVgOyqZ7FP3N+stX5IdMddHve3rf9LIZBDomQcXrACQ7sQDNuwZQHG8BKR7w8krkQ=="], - "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], + "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="], "bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="], diff --git a/github/action.yml b/github/action.yml index f52f14d80e..fe6a32206b 100644 --- a/github/action.yml +++ b/github/action.yml @@ -17,6 +17,11 @@ inputs: description: "Custom prompt to override the default prompt" required: false + use_github_token: + description: "Use GITHUB_TOKEN directly instead of OpenCode App token exchange. When true, skips OIDC and uses the GITHUB_TOKEN env var." + required: false + default: "false" + runs: using: "composite" steps: @@ -51,3 +56,4 @@ runs: MODEL: ${{ inputs.model }} SHARE: ${{ inputs.share }} PROMPT: ${{ inputs.prompt }} + USE_GITHUB_TOKEN: ${{ inputs.use_github_token }} diff --git a/nix/hashes.json b/nix/hashes.json index e28f98d051..5f3baf1919 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,3 +1,3 @@ { - "nodeModules": "sha256-nWSAnQEm/t1ESZe23dr4JnIOJQ0JLN0w4NVoMJajbVQ=" + "nodeModules": "sha256-lgPsYtNJT7a+mDk5cTiEJLlBnTMTjxZCl8bw5WxcuaM=" } diff --git a/package.json b/package.json index 39733b931a..ca2a10f784 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "AI-powered development tool", "private": true, "type": "module", - "packageManager": "bun@1.3.3", + "packageManager": "bun@1.3.4", "scripts": { "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", "typecheck": "bun turbo typecheck", @@ -20,7 +20,7 @@ "packages/slack" ], "catalog": { - "@types/bun": "1.3.3", + "@types/bun": "1.3.4", "@hono/zod-validator": "0.4.2", "ulid": "3.0.1", "@kobalte/core": "0.13.11", diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 96cd611f43..4fcaff7012 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.0.152", + "version": "1.0.153", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 6fd87c2f89..f68f704363 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.0.152", + "version": "1.0.153", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 22322aa243..53a41670d9 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.0.152", + "version": "1.0.153", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index f26d54d358..a03e3843b5 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.0.152", + "version": "1.0.153", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 91e04af08f..0f5afeaa9b 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/desktop", - "version": "1.0.152", + "version": "1.0.153", "description": "", "type": "module", "exports": { @@ -37,6 +37,7 @@ "@solid-primitives/active-element": "2.1.3", "@solid-primitives/audio": "1.4.2", "@solid-primitives/event-bus": "1.1.2", + "@solid-primitives/media": "2.3.3", "@solid-primitives/resize-observer": "2.1.3", "@solid-primitives/scroll": "2.1.3", "@solid-primitives/storage": "4.3.3", diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx index 70ee0a7397..114e6d49dc 100644 --- a/packages/desktop/src/components/prompt-input.tsx +++ b/packages/desktop/src/components/prompt-input.tsx @@ -1,18 +1,7 @@ import { useFilteredList } from "@opencode-ai/ui/hooks" -import { - createEffect, - on, - Component, - Show, - For, - onMount, - onCleanup, - Switch, - Match, - createSignal, - createMemo, -} from "solid-js" +import { createEffect, on, Component, Show, For, onMount, onCleanup, Switch, Match, createMemo } from "solid-js" import { createStore } from "solid-js/store" +import { makePersisted } from "@solid-primitives/storage" import { createFocusSignal } from "@solid-primitives/active-element" import { useLocal } from "@/context/local" import { ContentPart, DEFAULT_PROMPT, isPromptEqual, Prompt, useSession } from "@/context/session" @@ -81,22 +70,85 @@ export const PromptInput: Component = (props) => { const [store, setStore] = createStore<{ popoverIsOpen: boolean + historyIndex: number + savedPrompt: Prompt | null + placeholder: number }>({ popoverIsOpen: false, + historyIndex: -1, + savedPrompt: null, + placeholder: Math.floor(Math.random() * PLACEHOLDERS.length), }) - const [placeholder, setPlaceholder] = createSignal(Math.floor(Math.random() * PLACEHOLDERS.length)) + const MAX_HISTORY = 100 + const [history, setHistory] = makePersisted( + createStore<{ + entries: Prompt[] + }>({ + entries: [], + }), + { + name: "prompt-history.v1", + }, + ) - onMount(() => { - const interval = setInterval(() => { - setPlaceholder((prev) => (prev + 1) % PLACEHOLDERS.length) - }, 6500) - onCleanup(() => clearInterval(interval)) - }) + const clonePromptParts = (prompt: Prompt): Prompt => + prompt.map((part) => + part.type === "text" + ? { ...part } + : { + ...part, + selection: part.selection ? { ...part.selection } : undefined, + }, + ) + + const promptLength = (prompt: Prompt) => prompt.reduce((len, part) => len + part.content.length, 0) + + const applyHistoryPrompt = (prompt: Prompt, position: "start" | "end") => { + const length = position === "start" ? 0 : promptLength(prompt) + session.prompt.set(prompt, length) + requestAnimationFrame(() => { + editorRef.focus() + setCursorPosition(editorRef, length) + }) + } + + const getCaretLineState = () => { + const selection = window.getSelection() + if (!selection || selection.rangeCount === 0) return { collapsed: false, onFirstLine: false, onLastLine: false } + const range = selection.getRangeAt(0) + const rect = range.getBoundingClientRect() + const editorRect = editorRef.getBoundingClientRect() + const style = window.getComputedStyle(editorRef) + const paddingTop = parseFloat(style.paddingTop) || 0 + const paddingBottom = parseFloat(style.paddingBottom) || 0 + let lineHeight = parseFloat(style.lineHeight) + if (!Number.isFinite(lineHeight)) lineHeight = parseFloat(style.fontSize) || 16 + const scrollTop = editorRef.scrollTop + let relativeTop = rect.top - editorRect.top - paddingTop + scrollTop + if (!Number.isFinite(relativeTop)) relativeTop = scrollTop + relativeTop = Math.max(0, relativeTop) + let caretHeight = rect.height + if (!caretHeight || !Number.isFinite(caretHeight)) caretHeight = lineHeight + const relativeBottom = relativeTop + caretHeight + const contentHeight = Math.max(caretHeight, editorRef.scrollHeight - paddingTop - paddingBottom) + const threshold = Math.max(2, lineHeight / 2) + + return { + collapsed: selection.isCollapsed, + onFirstLine: relativeTop <= threshold, + onLastLine: contentHeight - relativeBottom <= threshold, + } + } createEffect(() => { session.id editorRef.focus() + if (session.id) return + const interval = setInterval(() => { + setStore("placeholder", (prev) => (prev + 1) % PLACEHOLDERS.length) + }, 6500) + onCleanup(() => clearInterval(interval)) }) const isFocused = createFocusSignal(() => editorRef) @@ -129,17 +181,12 @@ export const PromptInput: Component = (props) => { addPart({ type: "file", path, content: "@" + path, start: 0, end: 0 }) } - const { flat, active, onInput, onKeyDown, refetch } = useFilteredList({ + const { flat, active, onInput, onKeyDown } = useFilteredList({ items: local.file.searchFilesAndDirectories, key: (x) => x, onSelect: handleFileSelect, }) - createEffect(() => { - local.model.recent() - refetch() - }) - createEffect( on( () => session.prompt.current(), @@ -221,6 +268,11 @@ export const PromptInput: Component = (props) => { setStore("popoverIsOpen", false) } + if (store.historyIndex >= 0) { + setStore("historyIndex", -1) + setStore("savedPrompt", null) + } + session.prompt.set(rawParts, cursorPosition) } @@ -296,12 +348,100 @@ export const PromptInput: Component = (props) => { sessionID: session.id!, }) + const addToHistory = (prompt: Prompt) => { + const text = prompt + .map((p) => p.content) + .join("") + .trim() + if (!text) return + + const entry = clonePromptParts(prompt) + const lastEntry = history.entries[0] + if (lastEntry) { + const lastText = lastEntry.map((p) => p.content).join("") + if (lastText === text) return + } + + setHistory("entries", (entries) => [entry, ...entries].slice(0, MAX_HISTORY)) + } + + const navigateHistory = (direction: "up" | "down") => { + const entries = history.entries + const current = store.historyIndex + + if (direction === "up") { + if (entries.length === 0) return false + if (current === -1) { + setStore("savedPrompt", clonePromptParts(session.prompt.current())) + setStore("historyIndex", 0) + applyHistoryPrompt(entries[0], "start") + return true + } + if (current < entries.length - 1) { + const next = current + 1 + setStore("historyIndex", next) + applyHistoryPrompt(entries[next], "start") + return true + } + return false + } + + if (current > 0) { + const next = current - 1 + setStore("historyIndex", next) + applyHistoryPrompt(entries[next], "end") + return true + } + if (current === 0) { + setStore("historyIndex", -1) + const saved = store.savedPrompt + if (saved) { + applyHistoryPrompt(saved, "end") + setStore("savedPrompt", null) + return true + } + applyHistoryPrompt(DEFAULT_PROMPT, "end") + return true + } + + return false + } + const handleKeyDown = (event: KeyboardEvent) => { if (store.popoverIsOpen && (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter")) { onKeyDown(event) event.preventDefault() return } + + if (event.key === "ArrowUp" || event.key === "ArrowDown") { + const { collapsed, onFirstLine, onLastLine } = getCaretLineState() + if (!collapsed) return + const cursorPos = getCursorPosition(editorRef) + const textLength = promptLength(session.prompt.current()) + const inHistory = store.historyIndex >= 0 + const isStart = cursorPos === 0 + const isEnd = cursorPos === textLength + const atAbsoluteStart = onFirstLine && isStart + const atAbsoluteEnd = onLastLine && isEnd + const allowUp = (inHistory && isEnd) || atAbsoluteStart + const allowDown = (inHistory && isStart) || atAbsoluteEnd + + if (event.key === "ArrowUp") { + if (!allowUp) return + if (navigateHistory("up")) { + event.preventDefault() + } + return + } + + if (!allowDown) return + if (navigateHistory("down")) { + event.preventDefault() + } + return + } + if (event.key === "Enter" && !event.shiftKey) { handleSubmit(event) } @@ -323,6 +463,10 @@ export const PromptInput: Component = (props) => { return } + addToHistory(prompt) + setStore("historyIndex", -1) + setStore("savedPrompt", null) + let existing = session.info() if (!existing) { const created = await sdk.client.session.create() @@ -461,7 +605,7 @@ export const PromptInput: Component = (props) => { />
- Ask anything... "{PLACEHOLDERS[placeholder()]}" + Ask anything... "{PLACEHOLDERS[store.placeholder]}"
@@ -507,6 +651,7 @@ export const PromptInput: Component = (props) => { items={models} current={local.model.current()} filterKeys={["provider.name", "name", "id"]} + sortBy={(a, b) => a.name.localeCompare(b.name)} // groupBy={(x) => (local.model.recent().includes(x) ? "Recent" : x.provider.name)} groupBy={(x) => x.provider.name} sortGroupsBy={(a, b) => { diff --git a/packages/desktop/src/components/terminal.tsx b/packages/desktop/src/components/terminal.tsx index 15302f1521..865d9b30ff 100644 --- a/packages/desktop/src/components/terminal.tsx +++ b/packages/desktop/src/components/terminal.tsx @@ -1,8 +1,9 @@ import { Ghostty, Terminal as Term, FitAddon } from "ghostty-web" -import { ComponentProps, onCleanup, onMount, splitProps } from "solid-js" +import { ComponentProps, createEffect, onCleanup, onMount, splitProps } from "solid-js" import { useSDK } from "@/context/sdk" import { SerializeAddon } from "@/addons/serialize" import { LocalPTY } from "@/context/session" +import { usePrefersDark } from "@solid-primitives/media" export interface TerminalProps extends ComponentProps<"div"> { pty: LocalPTY @@ -21,6 +22,7 @@ export const Terminal = (props: TerminalProps) => { let serializeAddon: SerializeAddon let fitAddon: FitAddon let handleResize: () => void + const prefersDark = usePrefersDark() onMount(async () => { ghostty = await Ghostty.load() @@ -31,10 +33,17 @@ export const Terminal = (props: TerminalProps) => { fontSize: 14, fontFamily: "TX-02, monospace", allowTransparency: true, - theme: { - background: "#191515", - foreground: "#d4d4d4", - }, + theme: prefersDark() + ? { + background: "#191515", + foreground: "#d4d4d4", + cursor: "#d4d4d4", + } + : { + background: "#fcfcfc", + foreground: "#211e1e", + cursor: "#211e1e", + }, scrollback: 10_000, ghostty, }) diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 7da920c5f4..52a3bd6ab1 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -189,11 +189,13 @@ export default function Layout(props: ParentProps) { const hasError = createMemo(() => notifications().some((n) => n.type === "error")) const name = createMemo(() => getFilename(props.project.worktree)) const mask = "radial-gradient(circle 5px at calc(100% - 2px) 2px, transparent 5px, black 5.5px)" + const opencode = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750" + return (
- -
+ -
+ ) }} - - -
-
- - New session - -
+
diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index f265b3b276..d0589587b5 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.0.152", + "version": "1.0.153", "private": true, "type": "module", "scripts": { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 649233b996..52f9486558 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.0.152" +version = "1.0.153" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/sst/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.152/opencode-darwin-arm64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.153/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.152/opencode-darwin-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.153/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.152/opencode-linux-arm64.tar.gz" +archive = "https://github.com/sst/opencode/releases/download/v1.0.153/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.152/opencode-linux-x64.tar.gz" +archive = "https://github.com/sst/opencode/releases/download/v1.0.153/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.152/opencode-windows-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.153/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 42baa27871..c2ee790ce3 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.0.152", + "version": "1.0.153", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/Dockerfile b/packages/opencode/Dockerfile index 99f593581c..f92b48a6d1 100644 --- a/packages/opencode/Dockerfile +++ b/packages/opencode/Dockerfile @@ -1,10 +1,18 @@ -FROM alpine +FROM alpine AS base # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful ARG BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=${BUN_RUNTIME_TRANSPILER_CACHE_PATH} RUN apk add libgcc libstdc++ ripgrep -ADD ./dist/opencode-linux-x64-baseline-musl/bin/opencode /usr/local/bin/opencode + +FROM base AS build-amd64 +COPY dist/opencode-linux-x64-baseline-musl/bin/opencode /usr/local/bin/opencode + +FROM base AS build-arm64 +COPY dist/opencode-linux-arm64-musl/bin/opencode /usr/local/bin/opencode + +ARG TARGETARCH +FROM build-${TARGETARCH} RUN opencode --version ENTRYPOINT ["opencode"] diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 362f5b1f28..bbeb9ae0ab 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.0.152", + "version": "1.0.153", "name": "opencode", "type": "module", "private": true, diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index 5a6ac25848..a85fde9e20 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -117,6 +117,9 @@ for (const item of targets) { compile: { autoloadBunfig: false, autoloadDotenv: false, + //@ts-ignore (bun types aren't up to date) + autoloadTsconfig: true, + autoloadPackageJson: true, target: name.replace(pkg.name, "bun") as any, outfile: `dist/${name}/bin/opencode`, execArgv: [`--user-agent=opencode/${Script.version}`, "--"], diff --git a/packages/opencode/script/publish.ts b/packages/opencode/script/publish.ts index ff75bbb8da..72632992f7 100755 --- a/packages/opencode/script/publish.ts +++ b/packages/opencode/script/publish.ts @@ -244,8 +244,8 @@ if (!Script.preview) { await $`cd ./dist/homebrew-tap && git push` const image = "ghcr.io/sst/opencode" - await $`docker build -t ${image}:${Script.version} .` - await $`docker push ${image}:${Script.version}` - await $`docker tag ${image}:${Script.version} ${image}:latest` - await $`docker push ${image}:latest` + const platforms = "linux/amd64,linux/arm64" + const tags = [`${image}:${Script.version}`, `${image}:latest`] + const tagFlags = tags.flatMap((t) => ["-t", t]) + await $`docker buildx build --platform ${platforms} ${tagFlags} --push .` } diff --git a/packages/opencode/src/bun/index.ts b/packages/opencode/src/bun/index.ts index c0f90e6ca7..5456d0a5b9 100644 --- a/packages/opencode/src/bun/index.ts +++ b/packages/opencode/src/bun/index.ts @@ -85,47 +85,16 @@ export namespace BunProc { version, }) - const total = 3 - const wait = 500 - - const runInstall = async (count: number = 1): Promise => { - log.info("bun install attempt", { - pkg, - version, - attempt: count, - total, - }) - await BunProc.run(args, { - cwd: Global.Path.cache, - }).catch(async (error) => { - log.warn("bun install failed", { - pkg, - version, - attempt: count, - total, - error, - }) - if (count >= total) { - throw new InstallFailedError( - { pkg, version }, - { - cause: error, - }, - ) - } - const delay = wait * count - log.info("bun install retrying", { - pkg, - version, - next: count + 1, - delay, - }) - await Bun.sleep(delay) - return runInstall(count + 1) - }) - } - - await runInstall() + await BunProc.run(args, { + cwd: Global.Path.cache, + }).catch((e) => { + throw new InstallFailedError( + { pkg, version }, + { + cause: e, + }, + ) + }) // Resolve actual version from installed package when using "latest" // This ensures subsequent starts use the cached version until explicitly updated diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 55d9fb19de..dab1196f47 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -411,17 +411,30 @@ export const GithubRunCommand = cmd({ let exitCode = 0 type PromptFiles = Awaited>["promptFiles"] const triggerCommentId = payload.comment.id + const useGithubToken = normalizeUseGithubToken() try { - const actionToken = isMock ? args.token! : await getOidcToken() - appToken = await exchangeForAppToken(actionToken) + if (useGithubToken) { + const githubToken = process.env["GITHUB_TOKEN"] + if (!githubToken) { + throw new Error( + "GITHUB_TOKEN environment variable is not set. When using use_github_token, you must provide GITHUB_TOKEN.", + ) + } + appToken = githubToken + } else { + const actionToken = isMock ? args.token! : await getOidcToken() + appToken = await exchangeForAppToken(actionToken) + } octoRest = new Octokit({ auth: appToken }) octoGraph = graphql.defaults({ headers: { authorization: `token ${appToken}` }, }) const { userPrompt, promptFiles } = await getUserPrompt() - await configureGit(appToken) + if (!useGithubToken) { + await configureGit(appToken) + } await assertPermissions() await addReaction() @@ -514,8 +527,10 @@ export const GithubRunCommand = cmd({ // Also output the clean error message for the action to capture //core.setOutput("prepare_error", e.message); } finally { - await restoreGitConfig() - await revokeAppToken() + if (!useGithubToken) { + await restoreGitConfig() + await revokeAppToken() + } } process.exit(exitCode) @@ -544,6 +559,14 @@ export const GithubRunCommand = cmd({ throw new Error(`Invalid share value: ${value}. Share must be a boolean.`) } + function normalizeUseGithubToken() { + const value = process.env["USE_GITHUB_TOKEN"] + if (!value) return false + if (value === "true") return true + if (value === "false") return false + throw new Error(`Invalid use_github_token value: ${value}. Must be a boolean.`) + } + function isIssueCommentEvent( event: IssueCommentEvent | PullRequestReviewCommentEvent, ): event is IssueCommentEvent { diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 28e8411224..18a3428189 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -218,7 +218,7 @@ function App() { let continued = false createEffect(() => { if (continued || sync.status !== "complete" || !args.continue) return - const match = sync.data.session.at(0)?.id + const match = sync.data.session.find((x) => x.parentID === undefined)?.id if (match) { continued = true route.navigate({ type: "session", sessionID: match }) diff --git a/packages/opencode/src/cli/cmd/tui/attach.ts b/packages/opencode/src/cli/cmd/tui/attach.ts index 7da6507ea0..5d1a4ded20 100644 --- a/packages/opencode/src/cli/cmd/tui/attach.ts +++ b/packages/opencode/src/cli/cmd/tui/attach.ts @@ -14,12 +14,17 @@ export const AttachCommand = cmd({ .option("dir", { type: "string", description: "directory to run in", + }) + .option("session", { + alias: ["s"], + type: "string", + describe: "session id to continue", }), handler: async (args) => { if (args.dir) process.chdir(args.dir) await tui({ url: args.url, - args: {}, + args: { sessionID: args.session }, }) }, }) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 1ea701c83d..5cc757ac2e 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -705,8 +705,8 @@ export function Prompt(props: PromptProps) { >