Merge branch 'dev' into feat/canceled-prompts-in-history

This commit is contained in:
Ariane Emory
2026-02-26 17:50:11 -05:00
committed by GitHub
158 changed files with 6514 additions and 606 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.2.14",
"version": "1.2.15",
"name": "opencode",
"type": "module",
"license": "MIT",
+6 -1
View File
@@ -56,7 +56,7 @@ const migrations = await Promise.all(
)
console.log(`Loaded ${migrations.length} migrations`)
const singleFlag = process.argv.includes("--single") || (!!process.env.CI && !process.argv.includes("--all"))
const singleFlag = process.argv.includes("--single")
const baselineFlag = process.argv.includes("--baseline")
const skipInstall = process.argv.includes("--skip-install")
@@ -103,6 +103,11 @@ const allTargets: {
os: "darwin",
arch: "x64",
},
{
os: "darwin",
arch: "x64",
avx2: false,
},
{
os: "win32",
arch: "x64",
@@ -16,9 +16,9 @@ import { useToast } from "../ui/toast"
const PROVIDER_PRIORITY: Record<string, number> = {
opencode: 0,
openai: 1,
"github-copilot": 2,
"opencode-go": 3,
"opencode-go": 1,
openai: 2,
"github-copilot": 3,
anthropic: 4,
google: 5,
}
@@ -38,7 +38,7 @@ export function createDialogProviderOptions() {
opencode: "(Recommended)",
anthropic: "(Claude Max or API key)",
openai: "(ChatGPT Plus/Pro or API key)",
"opencode-go": "(Low cost)",
"opencode-go": "Low cost subscription for everyone",
}[provider.id],
category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other",
async onSelect() {
+19 -85
View File
@@ -23,60 +23,6 @@ export namespace Pty {
close: (code?: number, reason?: string) => void
}
type Subscriber = {
id: number
token: unknown
}
const sockets = new WeakMap<object, number>()
const owners = new WeakMap<object, string>()
let socketCounter = 0
const tagSocket = (ws: Socket) => {
if (!ws || typeof ws !== "object") return
const next = (socketCounter = (socketCounter + 1) % Number.MAX_SAFE_INTEGER)
sockets.set(ws, next)
return next
}
const token = (ws: Socket) => {
const data = ws.data
if (data === undefined) return
if (data === null) return
if (typeof data !== "object") return data
const id = (data as { connId?: unknown }).connId
if (typeof id === "number" || typeof id === "string") return id
const href = (data as { href?: unknown }).href
if (typeof href === "string") return href
const url = (data as { url?: unknown }).url
if (typeof url === "string") return url
if (url && typeof url === "object") {
const href = (url as { href?: unknown }).href
if (typeof href === "string") return href
return url
}
const events = (data as { events?: unknown }).events
if (typeof events === "number" || typeof events === "string") return events
if (events && typeof events === "object") {
const id = (events as { connId?: unknown }).connId
if (typeof id === "number" || typeof id === "string") return id
const id2 = (events as { connection?: unknown }).connection
if (typeof id2 === "number" || typeof id2 === "string") return id2
const id3 = (events as { id?: unknown }).id
if (typeof id3 === "number" || typeof id3 === "string") return id3
return events
}
return data
}
// WebSocket control frame: 0x00 + UTF-8 JSON.
const meta = (cursor: number) => {
const json = JSON.stringify({ cursor })
@@ -141,7 +87,7 @@ export namespace Pty {
buffer: string
bufferCursor: number
cursor: number
subscribers: Map<Socket, Subscriber>
subscribers: Map<unknown, Socket>
}
const state = Instance.state(
@@ -151,9 +97,9 @@ export namespace Pty {
try {
session.process.kill()
} catch {}
for (const ws of session.subscribers.keys()) {
for (const [key, ws] of session.subscribers.entries()) {
try {
ws.close()
if (ws.data === key) ws.close()
} catch {
// ignore
}
@@ -224,26 +170,21 @@ export namespace Pty {
ptyProcess.onData((chunk) => {
session.cursor += chunk.length
for (const [ws, sub] of session.subscribers) {
for (const [key, ws] of session.subscribers.entries()) {
if (ws.readyState !== 1) {
session.subscribers.delete(ws)
session.subscribers.delete(key)
continue
}
if (typeof ws === "object" && sockets.get(ws) !== sub.id) {
session.subscribers.delete(ws)
continue
}
if (token(ws) !== sub.token) {
session.subscribers.delete(ws)
if (ws.data !== key) {
session.subscribers.delete(key)
continue
}
try {
ws.send(chunk)
} catch {
session.subscribers.delete(ws)
session.subscribers.delete(key)
}
}
@@ -256,9 +197,9 @@ export namespace Pty {
ptyProcess.onExit(({ exitCode }) => {
log.info("session exited", { id, exitCode })
session.info.status = "exited"
for (const ws of session.subscribers.keys()) {
for (const [key, ws] of session.subscribers.entries()) {
try {
ws.close()
if (ws.data === key) ws.close()
} catch {
// ignore
}
@@ -291,9 +232,9 @@ export namespace Pty {
try {
session.process.kill()
} catch {}
for (const ws of session.subscribers.keys()) {
for (const [key, ws] of session.subscribers.entries()) {
try {
ws.close()
if (ws.data === key) ws.close()
} catch {
// ignore
}
@@ -325,23 +266,16 @@ export namespace Pty {
}
log.info("client connected to session", { id })
const socketId = tagSocket(ws)
if (socketId === undefined) {
ws.close()
return
}
// Use ws.data as the unique key for this connection lifecycle.
// If ws.data is undefined, fallback to ws object.
const connectionKey = ws.data && typeof ws.data === "object" ? ws.data : ws
const previous = owners.get(ws)
if (previous && previous !== id) {
state().get(previous)?.subscribers.delete(ws)
}
owners.set(ws, id)
session.subscribers.set(ws, { id: socketId, token: token(ws) })
// Optionally cleanup if the key somehow exists
session.subscribers.delete(connectionKey)
session.subscribers.set(connectionKey, ws)
const cleanup = () => {
session.subscribers.delete(ws)
if (owners.get(ws) === id) owners.delete(ws)
session.subscribers.delete(connectionKey)
}
const start = session.bufferCursor