mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-08-21 14:23:26 +00:00
fix(v2): isolate runtime defaults
Force the V2 service to use the opencode2 database under the current user's data directory so parallel V1 launches cannot redirect it through shared environment settings. Keep the bare opencode and opencode2 commands out of custom binary history while preserving real executable paths, and document the parallel desktop smoke procedure. Existing history entries are intentionally left untouched for manual cleanup. The server manager test and typecheck passed, and the Tauri package built successfully before the final UI-only simplification. No additional compilation was run per request.
This commit is contained in:
parent
c025bfcd78
commit
9a53e2f05c
6 changed files with 43 additions and 15 deletions
|
|
@ -82,6 +82,30 @@ The final validation should include:
|
|||
- `git diff --check`.
|
||||
- A real OpenCode V2 startup, session, event, Shell, and shutdown smoke test.
|
||||
|
||||
### Required Parallel UI Smoke
|
||||
|
||||
CodeNomad V1 is the working environment and must remain open and untouched. V2 always uses `~/.local/share/opencode2/opencode.db`; launch the V2 build beside V1 from PowerShell with a dedicated CDP port and WebView profile:
|
||||
|
||||
```powershell
|
||||
Start-Process `
|
||||
-FilePath 'D:\CodeNomad-worktrees\opencode-v2-foundation\packages\tauri-app\target\release\codenomad-tauri.exe' `
|
||||
-Environment @{
|
||||
WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS = '--remote-debugging-port=9223'
|
||||
WEBVIEW2_USER_DATA_FOLDER = "$env:TEMP\codenomad-webview-v2"
|
||||
}
|
||||
```
|
||||
|
||||
The smoke is complete only after all of these actions succeed in the visible V2 UI:
|
||||
|
||||
1. Confirm the binary selected in CodeNomad's OpenCode settings reports exactly the pinned protocol baseline above.
|
||||
2. Open `D:\CodeNomad` from Recent Folders or the folder picker.
|
||||
3. Open an existing session from the session list; direct API session creation is not a substitute.
|
||||
4. Send a prompt from the composer and receive its visible assistant response.
|
||||
5. Reload the V2 window and confirm the workspace and session list recover. While V1 owns cross-host restore, reopen the existing V2 session from the list and confirm its messages and pending state recover correctly.
|
||||
6. Exercise one PTY create/list/remove cycle through the workspace proxy, then close only the V2 process after collecting its logs. PTY creation is not currently exposed in the visible UI.
|
||||
|
||||
Do not count direct HTTP/CDP calls as validation for workspace, session, prompt, response, or reload behavior. CDP may inspect the V2 DOM and operate visible controls, but it must follow the same controls and state transitions as a user. The PTY protocol check is the sole exception until the UI exposes creation.
|
||||
|
||||
## Review Notes
|
||||
|
||||
- The pinned OpenCode V2 protocol client is experimental and may change; public `@opencode-ai/sdk` examples are not authoritative for this build.
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ class ControlledSharedService {
|
|||
assert.equal(options?.command?.[5], options?.contenderFile)
|
||||
assert.equal(options?.launcherRecordsPid, true)
|
||||
assert.equal(options?.environment?.XDG_STATE_HOME, stateRoot)
|
||||
assert.equal(options?.environment?.OPENCODE_DB, path.join(os.tmpdir(), "user-opencode.db"))
|
||||
assert.equal(options?.environment?.OPENCODE_DB, path.join(os.homedir(), ".local", "share", "opencode2", "opencode.db"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,14 +120,6 @@ function createHarness(service = new ControlledSharedService(), overrides: Recor
|
|||
}
|
||||
|
||||
describe("workspace manager shared service lifecycle", () => {
|
||||
it("fails before contacting the service when OPENCODE_DB is absent", async () => {
|
||||
const { manager, service } = createHarness()
|
||||
;(manager as any).options.settings = { getOwner: () => ({ environmentVariables: { OPENCODE_DB: "" } }) }
|
||||
await assert.rejects(manager.create(process.cwd()), /non-empty OPENCODE_DB/)
|
||||
assert.equal(service.validationCalls.length, 0)
|
||||
assert.equal((manager as any).workspaces.size, 0)
|
||||
})
|
||||
|
||||
it("translates a matching WSL UNC workspace for service API calls", () => {
|
||||
const { manager } = createHarness()
|
||||
assert.equal(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import path from "path"
|
||||
import os from "node:os"
|
||||
import { spawnSync } from "child_process"
|
||||
import { randomUUID } from "node:crypto"
|
||||
import type { Endpoint } from "@opencode-ai/client/service"
|
||||
|
|
@ -30,6 +31,7 @@ import { isPathOwnedByWorktree, resolveWorktreeSlugForDirectory } from "./worktr
|
|||
|
||||
const DEFAULT_LAUNCH_TIMEOUT_MS = 30_000
|
||||
const OPENCODE_SERVICE_VERSION = "0.0.0-next-17353"
|
||||
const OPENCODE_DATABASE = path.join(os.homedir(), ".local", "share", "opencode2", "opencode.db")
|
||||
const ORDINARY_CREATION_OWNER = ""
|
||||
const WORKSPACE_STATE = Symbol("workspaceState")
|
||||
const SERVICE_CONTENDER_FILE = path.join(SERVICE_STATE_ROOT, `contenders-${process.pid}-${randomUUID()}.txt`)
|
||||
|
|
@ -474,9 +476,8 @@ export class WorkspaceManager {
|
|||
if (this.options.nodeExtraCaCertsPath) configuredEnvironment.NODE_EXTRA_CA_CERTS = this.options.nodeExtraCaCertsPath
|
||||
configuredEnvironment.XDG_STATE_HOME = SERVICE_STATE_ROOT
|
||||
const serviceEnvironment = { ...process.env, ...configuredEnvironment }
|
||||
if (!serviceEnvironment.OPENCODE_DB?.trim()) {
|
||||
throw new Error("OpenCode V2 requires a non-empty OPENCODE_DB environment variable")
|
||||
}
|
||||
// ponytail: fixed V2 storage root until database selection needs to be configurable.
|
||||
serviceEnvironment.OPENCODE_DB = OPENCODE_DATABASE
|
||||
prepareServiceState(SERVICE_CONTENDER_FILE)
|
||||
const launch = buildServiceLaunchSpec(resolvedBinaryPath, ["serve", "--service"], {
|
||||
env: serviceEnvironment,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
|
|||
|
||||
const lastUsedBinary = () => serverSettings().opencodeBinary
|
||||
|
||||
const customBinaries = createMemo(() => binaries().filter((binary) => binary.path !== "opencode2"))
|
||||
const customBinaries = createMemo(() => binaries().filter((binary) => binary.path !== "opencode" && binary.path !== "opencode2"))
|
||||
|
||||
const binaryOptions = createMemo<BinaryOption[]>(() => [{ path: "opencode2", isDefault: true }, ...customBinaries()])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { buildSpeechPatch } from "../lib/speech-patch"
|
||||
import type { SpeechSettingsUpdate } from "./preferences"
|
||||
import { buildBinaryList, type SpeechSettingsUpdate } from "./preferences"
|
||||
|
||||
describe("buildBinaryList", () => {
|
||||
it("does not add built-in commands to custom binary history", () => {
|
||||
const custom = { path: "C:/tools/opencode.exe", lastUsed: 1 }
|
||||
const source = [{ path: "opencode2", lastUsed: 2 }, custom]
|
||||
|
||||
assert.deepEqual(buildBinaryList("opencode", undefined, source), source)
|
||||
assert.deepEqual(buildBinaryList("opencode2", undefined, source), source)
|
||||
})
|
||||
})
|
||||
|
||||
describe("buildSpeechPatch", () => {
|
||||
it("only includes fields that are explicitly provided", () => {
|
||||
|
|
|
|||
|
|
@ -489,7 +489,8 @@ function buildRecentFolderList(folderPath: string, source: RecentFolder[], alias
|
|||
return folders.slice(0, MAX_RECENT_FOLDERS)
|
||||
}
|
||||
|
||||
function buildBinaryList(binaryPath: string, version: string | undefined, source: OpenCodeBinary[]): OpenCodeBinary[] {
|
||||
export function buildBinaryList(binaryPath: string, version: string | undefined, source: OpenCodeBinary[]): OpenCodeBinary[] {
|
||||
if (binaryPath === "opencode" || binaryPath === "opencode2") return source
|
||||
const timestamp = Date.now()
|
||||
const existing = source.find((b) => b.path === binaryPath)
|
||||
if (existing) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue