Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/desktop",
"version": "0.1.12",
"version": "0.1.13",
"private": true,
"type": "module",
"main": "dist-electron/main.cjs",
Expand Down
4 changes: 2 additions & 2 deletions apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "t3",
"version": "0.1.12",
"version": "0.1.13",
"license": "MIT",
"repository": {
"type": "git",
Expand Down Expand Up @@ -28,7 +28,7 @@
"@effect/platform-node-shared": "catalog:",
"@effect/sql-sqlite-bun": "catalog:",
"@ff-labs/fff-node": "0.9.4",
"@opencode-ai/sdk": "^1.3.15",
"@opencode-ai/sdk": "^1.17.8",
"@pierre/diffs": "catalog:",
"effect": "catalog:",
"node-pty": "^1.1.0"
Expand Down
49 changes: 46 additions & 3 deletions apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type MessageEntry = {
const runtimeMock = {
state: {
startCalls: [] as string[],
connectReuseLocalServer: [] as Array<boolean | undefined>,
sessionCreateUrls: [] as string[],
authHeaders: [] as Array<string | null>,
abortCalls: [] as string[],
Expand All @@ -66,6 +67,7 @@ const runtimeMock = {
},
reset() {
this.state.startCalls.length = 0;
this.state.connectReuseLocalServer.length = 0;
this.state.sessionCreateUrls.length = 0;
this.state.authHeaders.length = 0;
this.state.abortCalls.length = 0;
Expand Down Expand Up @@ -97,9 +99,11 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntimeShape = {
exitCode: Effect.never,
};
}),
connectToOpenCodeServer: ({ serverUrl }) =>
connectToOpenCodeServer: ({ serverUrl, reuseLocalServer }) =>
Effect.gen(function* () {
const url = serverUrl ?? "http://127.0.0.1:4301";
const externalServerUrl = serverUrl?.trim();
const url = externalServerUrl ? externalServerUrl : "http://127.0.0.1:4301";
runtimeMock.state.connectReuseLocalServer.push(reuseLocalServer);
// Unconditionally register a scope finalizer for test observability —
// preserves the `closeCalls` / `closeError` probes that the existing
// suites rely on. Production code never attaches a finalizer to an
Expand All @@ -115,7 +119,7 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntimeShape = {
return {
url,
exitCode: null,
external: Boolean(serverUrl),
external: Boolean(externalServerUrl),
};
}),
runOpenCodeCommand: () => Effect.succeed({ stdout: "", stderr: "", code: 0 }),
Expand Down Expand Up @@ -198,6 +202,10 @@ const openCodeAdapterTestSettings = Schema.decodeSync(OpenCodeSettings)({
serverUrl: "http://127.0.0.1:9999",
serverPassword: "secret-password",
});
const openCodeAdapterLocalServerTestSettings = Schema.decodeSync(OpenCodeSettings)({
binaryPath: "fake-opencode",
serverUrl: "",
});

const OpenCodeAdapterTestLayer = Layer.effect(
OpenCodeAdapter,
Expand All @@ -220,6 +228,26 @@ const OpenCodeAdapterTestLayer = Layer.effect(
Layer.provideMerge(NodeServices.layer),
);

const OpenCodeAdapterLocalServerTestLayer = Layer.effect(
OpenCodeAdapter,
makeOpenCodeAdapter(openCodeAdapterLocalServerTestSettings),
).pipe(
Layer.provideMerge(Layer.succeed(OpenCodeRuntime, OpenCodeRuntimeTestDouble)),
Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())),
Layer.provideMerge(
ServerSettingsService.layerTest({
providers: {
opencode: {
binaryPath: "fake-opencode",
serverUrl: "",
},
},
}),
),
Layer.provideMerge(providerSessionDirectoryTestLayer),
Layer.provideMerge(NodeServices.layer),
);

beforeEach(() => {
runtimeMock.reset();
});
Expand Down Expand Up @@ -248,6 +276,21 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
}),
);

it.effect("requests an exclusive local server lease for chat sessions", () =>
Effect.gen(function* () {
const adapter = yield* OpenCodeAdapter;

yield* adapter.startSession({
provider: ProviderDriverKind.make("opencode"),
threadId: asThreadId("thread-opencode-local-exclusive"),
runtimeMode: "full-access",
});

assert.deepEqual(runtimeMock.state.connectReuseLocalServer, [false]);
assert.deepEqual(runtimeMock.state.sessionCreateUrls, ["http://127.0.0.1:4301"]);
}).pipe(Effect.provide(OpenCodeAdapterLocalServerTestLayer)),
);

it.effect("stops a configured-server session without trying to own server lifecycle", () =>
Effect.gen(function* () {
const adapter = yield* OpenCodeAdapter;
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/provider/Layers/OpenCodeAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,7 @@ export function makeOpenCodeAdapter(
binaryPath,
serverUrl,
...(options?.environment ? { environment: options.environment } : {}),
reuseLocalServer: false,
});
const client = openCodeRuntime.createOpenCodeSdkClient({
baseUrl: server.url,
Expand Down
65 changes: 64 additions & 1 deletion apps/server/src/provider/opencodeRuntime.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { describe, expect, it } from "vitest";

import { buildOpenCodeServerEnvironment } from "./opencodeRuntime.ts";
import {
buildOpenCodeServerEnvironment,
isOpenCodeDatabaseLockedError,
OpenCodeRuntimeError,
openCodeLocalServerLockKey,
} from "./opencodeRuntime.ts";

describe("buildOpenCodeServerEnvironment", () => {
it("does not inject inline OpenCode config when OPENCODE_CONFIG points at a file", () => {
Expand All @@ -23,3 +28,61 @@ describe("buildOpenCodeServerEnvironment", () => {
expect(env.OPENCODE_CONFIG_CONTENT).toBe("{}");
});
});

describe("OpenCode local server locking", () => {
it("recognizes OpenCode database lock startup failures", () => {
const error = new OpenCodeRuntimeError({
operation: "startOpenCodeServerProcess",
detail:
"OpenCode server exited before startup completed (code: 1).\n\nstderr:\ndatabase is locked",
});

expect(isOpenCodeDatabaseLockedError(error)).toBe(true);
});

it("keys local server leases by OpenCode data scope", () => {
const first = openCodeLocalServerLockKey({
binaryPath: "/managed/opencode-a",
environment: {
OPENCODE_CONFIG: "/Users/test/.agents/ucsd/config/opencode/opencode.json",
XDG_DATA_HOME: "/Users/test/.agents/ucsd/data",
},
});
const second = openCodeLocalServerLockKey({
binaryPath: "/managed/opencode-b",
environment: {
OPENCODE_CONFIG: "/Users/test/.agents/ucsd/config/opencode/opencode.json",
XDG_DATA_HOME: "/Users/test/.agents/ucsd/data",
},
});
const isolated = openCodeLocalServerLockKey({
binaryPath: "/managed/opencode-a",
environment: {
OPENCODE_CONFIG: "/Users/test/.config/opencode/opencode.json",
XDG_DATA_HOME: "/Users/test/.local/share",
},
});

expect(first).toBe(second);
expect(first).not.toBe(isolated);
});

it("does not split default data-directory locks by config path", () => {
const first = openCodeLocalServerLockKey({
binaryPath: "/managed/opencode-a",
environment: {
HOME: "/Users/test",
OPENCODE_CONFIG: "/Users/test/.config/opencode/opencode.json",
},
});
const second = openCodeLocalServerLockKey({
binaryPath: "/managed/opencode-b",
environment: {
HOME: "/Users/test",
OPENCODE_CONFIG: "/Users/test/.agents/ucsd/config/opencode/opencode.json",
},
});

expect(first).toBe(second);
});
});
Loading
Loading