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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions opencode/packages/opencode/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,36 @@ export namespace Server {
return headers[name] ?? headers[name.toLowerCase()] ?? headers[name.toUpperCase()]
}

export function formatListenError(opts: { port: number; hostname: string }, cause?: unknown) {
const reason =
cause instanceof Error
? cause.message
: typeof cause === "string"
? cause
: ""

const portTarget =
opts.port === 0
? `an available port on ${opts.hostname} (preferred 4096)`
: `${opts.hostname}:${opts.port}`

const addressInUse =
/EADDRINUSE|address already in use|in use/i.test(reason) ||
/failed to bind|failed to listen/i.test(reason)

if (!reason) {
return addressInUse
? `Failed to start server on ${portTarget}. The port appears to already be in use.`
: `Failed to start server on ${portTarget}.`
}

if (addressInUse) {
return `Failed to start server on ${portTarget}. The port appears to already be in use. Original error: ${reason}`
}

return `Failed to start server on ${portTarget}. Original error: ${reason}`
}

function hasForwardedHeader(headers: Record<string, string | undefined>) {
return getHeader(headers, "forwarded") !== undefined || getHeader(headers, "x-forwarded-for") !== undefined
}
Expand Down Expand Up @@ -858,12 +888,14 @@ export namespace Server {
const tryServe = (port: number) => {
try {
return Bun.serve({ ...args, port })
} catch {
} catch (error) {
lastServeError = error
return undefined
}
}
let lastServeError: unknown
const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port)
if (!server) throw new Error(`Failed to start server on port ${opts.port}`)
if (!server) throw new Error(formatListenError(opts, lastServeError))

_url = server.url
Schedule.init()
Expand Down
3 changes: 3 additions & 0 deletions opencode/packages/opencode/src/tool/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ Call this first to discover which browsers are available and get tab IDs.`,

if (status.user) {
lines.push(`User Browser: ${status.user.connected ? "Connected" : "Not connected"}`)
if (status.user.tabListSource && status.user.connected) {
lines.push(` Tab source: ${status.user.tabListSource}`)
}
if (status.user.tabs.length > 0) {
lines.push(" Tabs:")
for (const tab of status.user.tabs) {
Expand Down
24 changes: 24 additions & 0 deletions opencode/packages/opencode/test/server/listen-errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, test } from "bun:test"
import { Server } from "../../src/server/server"

describe("server listen error formatting", () => {
test("surfaces port-in-use hints with hostname and port", () => {
const message = Server.formatListenError(
{ hostname: "127.0.0.1", port: 4096 },
new Error("EADDRINUSE: address already in use 127.0.0.1:4096"),
)

expect(message).toContain("127.0.0.1:4096")
expect(message).toContain("already be in use")
})

test("describes ephemeral port fallback requests clearly", () => {
const message = Server.formatListenError(
{ hostname: "127.0.0.1", port: 0 },
new Error("bind failed"),
)

expect(message).toContain("available port")
expect(message).toContain("preferred 4096")
})
})
13 changes: 8 additions & 5 deletions packages/browser-extension/src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@ function createOpenNonce(): string {
return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
}

async function markSidePanelOpened(): Promise<void> {
await chrome.storage.sync.set({ [SIDE_PANEL_OPEN_NONCE_STORAGE_KEY]: createOpenNonce() })
async function markSidePanelOpened(openNonce: string): Promise<void> {
await chrome.storage.sync.set({ [SIDE_PANEL_OPEN_NONCE_STORAGE_KEY]: openNonce })
}

async function openSidePanel(windowId: number): Promise<void> {
await activateDedicatedNine1TabGroup(windowId).catch((error) => {
const openNonce = createOpenNonce()
await activateDedicatedNine1TabGroup(windowId, { openNonce }).catch((error) => {
console.warn('[Nine1Bot Browser Control] Failed to activate dedicated tab group:', error)
})
await markSidePanelOpened().catch((error) => {
await markSidePanelOpened(openNonce).catch((error) => {
console.warn('[Nine1Bot Browser Control] Failed to persist side panel open nonce:', error)
})
await chrome.sidePanel.open({ windowId })
Expand Down Expand Up @@ -77,7 +78,9 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
return true
}
if (message?.type === 'nine1bot-sidepanel-ensure-tab-group') {
activateDedicatedNine1TabGroup()
activateDedicatedNine1TabGroup(undefined, {
onlyIfMissing: Boolean(message.onlyIfMissing),
})
.then((result) => sendResponse({ ok: true, ...result }))
.catch((error) => {
sendResponse({ ok: false, error: error instanceof Error ? error.message : String(error) })
Expand Down
Loading
Loading