Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c5d80af1d | ||
|
|
1e05f4770e | ||
|
|
b1c43aeb89 |
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.3",
|
||||
"description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -74,13 +74,13 @@
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"oh-my-opencode-darwin-arm64": "3.7.2",
|
||||
"oh-my-opencode-darwin-x64": "3.7.2",
|
||||
"oh-my-opencode-linux-arm64": "3.7.2",
|
||||
"oh-my-opencode-linux-arm64-musl": "3.7.2",
|
||||
"oh-my-opencode-linux-x64": "3.7.2",
|
||||
"oh-my-opencode-linux-x64-musl": "3.7.2",
|
||||
"oh-my-opencode-windows-x64": "3.7.2"
|
||||
"oh-my-opencode-darwin-arm64": "3.7.3",
|
||||
"oh-my-opencode-darwin-x64": "3.7.3",
|
||||
"oh-my-opencode-linux-arm64": "3.7.3",
|
||||
"oh-my-opencode-linux-arm64-musl": "3.7.3",
|
||||
"oh-my-opencode-linux-x64": "3.7.3",
|
||||
"oh-my-opencode-linux-x64-musl": "3.7.3",
|
||||
"oh-my-opencode-windows-x64": "3.7.3"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"@ast-grep/cli",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-darwin-arm64",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.3",
|
||||
"description": "Platform-specific binary for oh-my-opencode (darwin-arm64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-darwin-x64",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.3",
|
||||
"description": "Platform-specific binary for oh-my-opencode (darwin-x64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-arm64-musl",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.3",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-arm64-musl)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-arm64",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.3",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-arm64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-x64-musl",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.3",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-x64-musl)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-x64",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.3",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-x64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-windows-x64",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.3",
|
||||
"description": "Platform-specific binary for oh-my-opencode (windows-x64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1543,6 +1543,22 @@
|
||||
"created_at": "2026-02-17T14:18:29Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1889
|
||||
},
|
||||
{
|
||||
"name": "codeg-dev",
|
||||
"id": 12405078,
|
||||
"comment_id": 3915482750,
|
||||
"created_at": "2026-02-17T15:47:18Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1927
|
||||
},
|
||||
{
|
||||
"name": "codeg-dev",
|
||||
"id": 12405078,
|
||||
"comment_id": 3915952929,
|
||||
"created_at": "2026-02-17T17:11:11Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1927
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -95,6 +95,24 @@ describe("createServerConnection", () => {
|
||||
expect(mockServerClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("explicit port attaches when start fails because port became occupied", async () => {
|
||||
// given
|
||||
const signal = new AbortController().signal
|
||||
const port = 8080
|
||||
mockIsPortAvailable.mockResolvedValueOnce(true).mockResolvedValueOnce(false)
|
||||
mockCreateOpencode.mockRejectedValueOnce(new Error("Failed to start server on port 8080"))
|
||||
|
||||
// when
|
||||
const result = await createServerConnection({ port, signal })
|
||||
|
||||
// then
|
||||
expect(mockIsPortAvailable).toHaveBeenNthCalledWith(1, 8080, "127.0.0.1")
|
||||
expect(mockIsPortAvailable).toHaveBeenNthCalledWith(2, 8080, "127.0.0.1")
|
||||
expect(mockCreateOpencodeClient).toHaveBeenCalledWith({ baseUrl: "http://127.0.0.1:8080" })
|
||||
result.cleanup()
|
||||
expect(mockServerClose).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("explicit port attaches when port is occupied", async () => {
|
||||
// given
|
||||
const signal = new AbortController().signal
|
||||
@@ -133,6 +151,32 @@ describe("createServerConnection", () => {
|
||||
expect(mockServerClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("auto mode retries on next port when initial start fails", async () => {
|
||||
// given
|
||||
const signal = new AbortController().signal
|
||||
mockGetAvailableServerPort
|
||||
.mockResolvedValueOnce({ port: 4096, wasAutoSelected: false })
|
||||
.mockResolvedValueOnce({ port: 4097, wasAutoSelected: true })
|
||||
|
||||
mockCreateOpencode
|
||||
.mockRejectedValueOnce(new Error("Failed to start server on port 4096"))
|
||||
.mockResolvedValueOnce({
|
||||
client: { session: {} },
|
||||
server: { url: "http://127.0.0.1:4097", close: mockServerClose },
|
||||
})
|
||||
|
||||
// when
|
||||
const result = await createServerConnection({ signal })
|
||||
|
||||
// then
|
||||
expect(mockGetAvailableServerPort).toHaveBeenNthCalledWith(1, 4096, "127.0.0.1")
|
||||
expect(mockGetAvailableServerPort).toHaveBeenNthCalledWith(2, 4097, "127.0.0.1")
|
||||
expect(mockCreateOpencode).toHaveBeenNthCalledWith(1, { signal, port: 4096, hostname: "127.0.0.1" })
|
||||
expect(mockCreateOpencode).toHaveBeenNthCalledWith(2, { signal, port: 4097, hostname: "127.0.0.1" })
|
||||
result.cleanup()
|
||||
expect(mockServerClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("invalid port throws error", async () => {
|
||||
// given
|
||||
const signal = new AbortController().signal
|
||||
|
||||
@@ -5,6 +5,24 @@ import { getAvailableServerPort, isPortAvailable, DEFAULT_SERVER_PORT } from "..
|
||||
import { withWorkingOpencodePath } from "./opencode-binary-resolver"
|
||||
import { prependResolvedOpencodeBinToPath } from "./opencode-bin-path"
|
||||
|
||||
function isPortStartFailure(error: unknown, port: number): boolean {
|
||||
if (!(error instanceof Error)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return error.message.includes(`Failed to start server on port ${port}`)
|
||||
}
|
||||
|
||||
async function startServer(options: { signal: AbortSignal, port: number }): Promise<ServerConnection> {
|
||||
const { signal, port } = options
|
||||
const { client, server } = await withWorkingOpencodePath(() =>
|
||||
createOpencode({ signal, port, hostname: "127.0.0.1" }),
|
||||
)
|
||||
|
||||
console.log(pc.dim("Server listening at"), pc.cyan(server.url))
|
||||
return { client, cleanup: () => server.close() }
|
||||
}
|
||||
|
||||
export async function createServerConnection(options: {
|
||||
port?: number
|
||||
attach?: string
|
||||
@@ -29,11 +47,22 @@ export async function createServerConnection(options: {
|
||||
|
||||
if (available) {
|
||||
console.log(pc.dim("Starting server on port"), pc.cyan(port.toString()))
|
||||
const { client, server } = await withWorkingOpencodePath(() =>
|
||||
createOpencode({ signal, port, hostname: "127.0.0.1" }),
|
||||
)
|
||||
console.log(pc.dim("Server listening at"), pc.cyan(server.url))
|
||||
return { client, cleanup: () => server.close() }
|
||||
try {
|
||||
return await startServer({ signal, port })
|
||||
} catch (error) {
|
||||
if (!isPortStartFailure(error, port)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
const stillAvailable = await isPortAvailable(port, "127.0.0.1")
|
||||
if (stillAvailable) {
|
||||
throw error
|
||||
}
|
||||
|
||||
console.log(pc.dim("Port"), pc.cyan(port.toString()), pc.dim("became occupied, attaching to existing server"))
|
||||
const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}` })
|
||||
return { client, cleanup: () => {} }
|
||||
}
|
||||
}
|
||||
|
||||
console.log(pc.dim("Port"), pc.cyan(port.toString()), pc.dim("is occupied, attaching to existing server"))
|
||||
@@ -47,9 +76,16 @@ export async function createServerConnection(options: {
|
||||
} else {
|
||||
console.log(pc.dim("Starting server on port"), pc.cyan(selectedPort.toString()))
|
||||
}
|
||||
const { client, server } = await withWorkingOpencodePath(() =>
|
||||
createOpencode({ signal, port: selectedPort, hostname: "127.0.0.1" }),
|
||||
)
|
||||
console.log(pc.dim("Server listening at"), pc.cyan(server.url))
|
||||
return { client, cleanup: () => server.close() }
|
||||
|
||||
try {
|
||||
return await startServer({ signal, port: selectedPort })
|
||||
} catch (error) {
|
||||
if (!isPortStartFailure(error, selectedPort)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
const { port: retryPort } = await getAvailableServerPort(selectedPort + 1, "127.0.0.1")
|
||||
console.log(pc.dim("Retrying server start on port"), pc.cyan(retryPort.toString()))
|
||||
return await startServer({ signal, port: retryPort })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user