CodeNomad/packages/ui/src/stores/runtime-contract.test.ts
Pascal André 7869e609b3
feat(opencode): enforce stable runtime support and add managed setup
Require stable OpenCode >=2.0.11 <3.0.0 from authenticated daemon metadata before functional clients and plugin provisioning. Keep bounded contract recognition, connection-generation fencing and native ownership checks while retiring old request, response and event conversions.

Unify missing and outdated runtime recovery with Preferences. Install exact stable releases through bundled Node/npm into a verified versioned user prefix, preserve custom and WSL selections, and separate installation from explicit shared-service restart through the native-parent starter. Fence stale setup responses, retain pending folder opening and never replay prompts.

Certify isolated 2.0.3-to-2.0.11 history migration with executable hashes, untouched seed storage, paginated messages, forks, compaction checkpoints, location moves and pending inbox data. Add minimum/latest cross-platform migration and installation gates, packaged npm closure checks, real Solid recovery regressions and supported-contract authority tests.

Validated server/UI types, focused unit and reducer suites, browser captures, actual packaging-prepared Node 24.20.0 installation without system Node, and native location, automation and pruning fixtures. Document packaged-desktop, WSL, broader historical-state and remote-CI evidence as remaining release gates.
2026-09-20 16:09:36 +02:00

95 lines
5.4 KiB
TypeScript

import assert from "node:assert/strict"
import { test } from "node:test"
import type { OpenCodeEvent } from "@opencode/client"
import { applyOpenCodeDataEvent, destroyOpenCodeData } from "./opencode-data.ts"
import { sdkManager } from "../lib/sdk-manager.ts"
import { sseManager } from "../lib/sse-manager.ts"
import { addInstance, handleInstanceInvalidation, removeInstance } from "./instances.ts"
import { handleNativeSessionEvent, handleSessionIdle, handleSessionStatus } from "./session-events.ts"
import { messageStoreBus } from "./message-v2/bus.ts"
import { setActiveSession, setSessions } from "./session-state.ts"
test("the native reducer preserves canonical precise start times and durable identity", () => {
for (const started of [0, 45]) {
const instanceId = `runtime-started-${started}`
const original = {
id: "evt_step", type: "session.step.started", created: 100,
durable: { aggregateID: "s", seq: 2, version: 1 },
data: { sessionID: "s", assistantMessageID: "m", agent: "build", model: { providerID: "fixture", id: "fixture" },
started },
}
try {
const event = original as OpenCodeEvent
const data = applyOpenCodeDataEvent(instanceId, "/fixture", event)
assert.equal(data.session.message.get("s", "m")?.time.created, started)
assert.ok("durable" in event)
assert.deepEqual(event.durable, original.durable)
assert.equal(original.data.started, started, "normalization must not mutate the input event")
} finally { destroyOpenCodeData(instanceId) }
}
})
for (const following of ["none", "idle", "status-idle", "new-execution"] as const) {
test(`settled tools reach the visible store with following ${following}`, async () => {
const instanceId = `runtime-tool-settlement-${following}`, sessionId = "s"
const statuses = sseManager.getStatuses
sseManager.getStatuses = () => new Map([[instanceId, "connected"]])
let reads = 0
const completed = {
id: "m", type: "assistant", agent: "build", model: { providerID: "fixture", id: "fixture" },
time: { created: 1, completed: 5 }, content: [{ type: "tool", id: "tool", name: "read",
time: { created: 2, ran: 3, completed: 4 },
state: { status: "completed", input: { path: "fixture" }, content: [{ type: "text", text: "settled" }], metadata: {} },
}],
}
let release!: (value: Record<string, unknown>) => void
const active = new Promise<Record<string, unknown>>(resolve => { release = resolve })
const client = {
session: { active: () => active, get: async () => ({ id: sessionId, location: { directory: "/fixture" }, time: { created: 1, updated: 5 } }) },
message: { list: async () => { reads++; return { data: [completed], cursor: {} } } },
} as any
;(sdkManager as any).clients.set(`${instanceId}:/workspaces/${instanceId}/instance`, client)
addInstance({ id: instanceId, folder: "/fixture", port: 0, pid: 0, proxyPath: "", status: "ready", client })
setSessions(previous => new Map(previous).set(instanceId, new Map([[sessionId, {
id: sessionId, instanceId, title: "Fixture", parentId: null, location: { directory: "/fixture" },
status: "working", agent: "build", model: { providerId: "fixture", modelId: "fixture" }, time: { created: 1, updated: 1 },
} as any]])))
setActiveSession(instanceId, sessionId)
const emit = (type: string, data: Record<string, unknown>, created: number) => {
const event = { id: `evt_${created}`, type, created, data, location: { directory: "/fixture" } } as OpenCodeEvent
handleInstanceInvalidation(instanceId, event)
handleNativeSessionEvent(instanceId, event)
}
const state = () => (messageStoreBus.getOrCreate(instanceId).getMessage("m")?.parts.tool?.data as any)?.state
try {
const base = { sessionID: sessionId, assistantMessageID: "m" }
emit("session.step.started", { ...base, agent: "build", model: completed.model, started: 1 }, 1)
emit("session.tool.input.started", { ...base, id: "tool", name: "read" }, 2)
emit("session.tool.called", { ...base, id: "tool", input: { path: "fixture" } }, 3)
assert.equal(state()?.status, "running")
emit("session.execution.succeeded", { sessionID: sessionId }, 5)
if (following === "idle") handleSessionIdle(instanceId, { type: "session.idle", data: { sessionID: sessionId }, created: 6 } as any)
if (following === "status-idle") handleSessionStatus(instanceId, { type: "session.status", data: { sessionID: sessionId, status: { type: "idle" } }, created: 6 } as any)
if (following === "new-execution") emit("session.execution.started", { sessionID: sessionId }, 6)
release({})
if (following === "new-execution") {
await new Promise(resolve => setTimeout(resolve, 30))
assert.equal(state()?.status, "running", "a superseded terminal check cannot replace the current transcript")
return
}
for (let attempt = 0; attempt < 100 && state()?.status !== "completed"; attempt++) {
await new Promise(resolve => setTimeout(resolve, 10))
}
assert.ok(reads > 0, "settlement must consult native history")
assert.equal(state()?.status, "completed")
assert.equal(state()?.output, "settled")
} finally {
release({})
sseManager.getStatuses = statuses
destroyOpenCodeData(instanceId)
removeInstance(instanceId, { authoritative: false })
sdkManager.destroyClientsForInstance(instanceId)
if (messageStoreBus.getInstance(instanceId)) messageStoreBus.unregisterInstance(instanceId)
}
})
}