From 3d3d68f37d91c68d4b952191c5e805ac2256d386 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 28 May 2026 15:47:06 +0200 Subject: [PATCH 1/2] refactor(cli): remove legacy Snapshot facade --- packages/opencode/src/snapshot/index.ts | 13 --- .../test/kilocode/snapshot-cache.test.ts | 89 ++++++++++------- .../kilocode/snapshot-freeze-repro.test.ts | 96 ++++++++++--------- script/check-opencode-promise-facades.ts | 1 - 4 files changed, 106 insertions(+), 93 deletions(-) diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 5826f6947a..0ddbd6f8b4 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -3,7 +3,6 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { formatPatch, structuredPatch } from "diff" import path from "path" import z from "zod" -import { makeRuntime } from "@/effect/run-service" // kilocode_change import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { InstanceState } from "@/effect/instance-state" import { AppFileSystem } from "@opencode-ai/core/filesystem" @@ -851,16 +850,4 @@ export const defaultLayer = layer.pipe( Layer.provide(Config.defaultLayer), ) -// kilocode_change start - legacy promise helpers for Kilo callsites -const { runPromise } = makeRuntime(Service, defaultLayer) -export const track = () => runPromise((svc) => svc.track()) -export const patch = (hash: string) => runPromise((svc) => svc.patch(hash)) -export const restore = (snapshot: string) => runPromise((svc) => svc.restore(snapshot)) -export const revert = (patches: Patch[]) => runPromise((svc) => svc.revert(patches)) -export const diff = (hash: string) => runPromise((svc) => svc.diff(hash)) -export const diffFull = (from: string, to: string) => runPromise((svc) => svc.diffFull(from, to)) -export const cleanup = () => runPromise((svc) => svc.cleanup()) -export const init = () => runPromise((svc) => svc.init()) -// kilocode_change end - export * as Snapshot from "." diff --git a/packages/opencode/test/kilocode/snapshot-cache.test.ts b/packages/opencode/test/kilocode/snapshot-cache.test.ts index c326c59460..6fb9e86dbb 100644 --- a/packages/opencode/test/kilocode/snapshot-cache.test.ts +++ b/packages/opencode/test/kilocode/snapshot-cache.test.ts @@ -1,12 +1,13 @@ import { test, expect } from "bun:test" import { $ } from "bun" +import { Effect } from "effect" import { Snapshot } from "../../src/snapshot" import { WithInstance } from "../../src/project/with-instance" import { Filesystem } from "../../src/util/filesystem" import * as Log from "@opencode-ai/core/util/log" import { tmpdir } from "../fixture/fixture" -Log.init({ print: false }) +void Log.init({ print: false }) async function bootstrap() { return tmpdir({ @@ -20,26 +21,33 @@ async function bootstrap() { }) } +function run(body: (snapshot: Snapshot.Interface) => Effect.Effect) { + return Effect.runPromise(Snapshot.Service.use(body).pipe(Effect.provide(Snapshot.defaultLayer))) +} + test("diffFull returns cached result for same hash pair", async () => { await using tmp = await bootstrap() await WithInstance.provide({ directory: tmp.path, - fn: async () => { - const before = await Snapshot.track() - expect(before).toBeTruthy() + fn: () => + run((snapshot) => + Effect.gen(function* () { + const before = yield* snapshot.track() + expect(before).toBeTruthy() - await Filesystem.write(`${tmp.path}/a.txt`, "MODIFIED") - const after = await Snapshot.track() - expect(after).toBeTruthy() - expect(after).not.toBe(before) + yield* Effect.promise(() => Filesystem.write(`${tmp.path}/a.txt`, "MODIFIED")) + const after = yield* snapshot.track() + expect(after).toBeTruthy() + expect(after).not.toBe(before) - const first = await Snapshot.diffFull(before!, after!) - const second = await Snapshot.diffFull(before!, after!) + const first = yield* snapshot.diffFull(before!, after!) + const second = yield* snapshot.diffFull(before!, after!) - // Should be the exact same array reference (cached) - expect(second).toBe(first) - expect(first.length).toBeGreaterThan(0) - }, + // Should be the exact same array reference (cached) + expect(second).toBe(first) + expect(first.length).toBeGreaterThan(0) + }), + ), }) }) @@ -47,13 +55,16 @@ test("diffFull returns empty array when from === to", async () => { await using tmp = await bootstrap() await WithInstance.provide({ directory: tmp.path, - fn: async () => { - const hash = await Snapshot.track() - expect(hash).toBeTruthy() + fn: () => + run((snapshot) => + Effect.gen(function* () { + const hash = yield* snapshot.track() + expect(hash).toBeTruthy() - const result = await Snapshot.diffFull(hash!, hash!) - expect(result).toEqual([]) - }, + const result = yield* snapshot.diffFull(hash!, hash!) + expect(result).toEqual([]) + }), + ), }) }) @@ -61,24 +72,30 @@ test("diffFull concurrent calls for same pair share one result", async () => { await using tmp = await bootstrap() await WithInstance.provide({ directory: tmp.path, - fn: async () => { - const before = await Snapshot.track() - expect(before).toBeTruthy() + fn: () => + run((snapshot) => + Effect.gen(function* () { + const before = yield* snapshot.track() + expect(before).toBeTruthy() - await Filesystem.write(`${tmp.path}/a.txt`, "CONCURRENT") - const after = await Snapshot.track() - expect(after).toBeTruthy() + yield* Effect.promise(() => Filesystem.write(`${tmp.path}/a.txt`, "CONCURRENT")) + const after = yield* snapshot.track() + expect(after).toBeTruthy() - // Fire multiple concurrent calls — they should all resolve to the same object - const results = await Promise.all([ - Snapshot.diffFull(before!, after!), - Snapshot.diffFull(before!, after!), - Snapshot.diffFull(before!, after!), - ]) + // Fire multiple concurrent calls, they should all resolve to the same object. + const results = yield* Effect.all( + [ + snapshot.diffFull(before!, after!), + snapshot.diffFull(before!, after!), + snapshot.diffFull(before!, after!), + ], + { concurrency: "unbounded" }, + ) - expect(results[0]).toBe(results[1]) - expect(results[1]).toBe(results[2]) - expect(results[0].length).toBeGreaterThan(0) - }, + expect(results[0]).toBe(results[1]) + expect(results[1]).toBe(results[2]) + expect(results[0].length).toBeGreaterThan(0) + }), + ), }) }) diff --git a/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts b/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts index 4234efe98b..68b1ceb72b 100644 --- a/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts +++ b/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts @@ -14,6 +14,7 @@ import { test, expect, afterEach, mock } from "bun:test" import { $ } from "bun" +import { Effect, Fiber } from "effect" import { WithInstance } from "../../src/project/with-instance" import { Server } from "../../src/server/server" import { Session } from "../../src/session/session" @@ -22,7 +23,11 @@ import { Filesystem } from "../../src/util/filesystem" import * as Log from "@opencode-ai/core/util/log" import { disposeAllInstances, tmpdir } from "../fixture/fixture" -Log.init({ print: false }) +void Log.init({ print: false }) + +function run(body: (snapshot: Snapshot.Interface) => Effect.Effect) { + return Effect.runPromise(Snapshot.Service.use(body).pipe(Effect.provide(Snapshot.defaultLayer))) +} afterEach(async () => { mock.restore() @@ -47,55 +52,60 @@ test("pathological diffFull workload finishes quickly and does not block abort", await WithInstance.provide({ directory: tmp.path, - fn: async () => { - const session = await Session.create({}) + fn: () => + run((snapshot) => + Effect.gen(function* () { + const session = yield* Effect.promise(() => Session.create({})) - const before = await Snapshot.track() - expect(before).toBeTruthy() + const before = yield* snapshot.track() + expect(before).toBeTruthy() - await Filesystem.write(`${tmp.path}/fat.json`, v2) - const after = await Snapshot.track() - expect(after).toBeTruthy() + yield* Effect.promise(() => Filesystem.write(`${tmp.path}/fat.json`, v2)) + const after = yield* snapshot.track() + expect(after).toBeTruthy() - // Kick off a diffFull that exercises the freeze path. - const diffPromise = Snapshot.diffFull(before!, after!) + // Kick off a diffFull that exercises the freeze path. + const diff = yield* snapshot.diffFull(before!, after!).pipe(Effect.forkChild({ startImmediately: true })) - // Concurrently keep a tick counter running. If the event loop blocks we - // will see this count fall behind wall-clock elapsed. - let ticks = 0 - const start = Date.now() - const timer = setInterval(() => { - ticks++ - }, 25) + // Concurrently keep a tick counter running. If the event loop blocks we + // will see this count fall behind wall-clock elapsed. + let ticks = 0 + const start = Date.now() + const timer = setInterval(() => { + ticks++ + }, 25) - // Fire an abort request against the Hono app in the middle of the diff. - const app = Server.Default().app - const abortStart = Date.now() - const res = await app.request(`/session/${session.id}/abort`, { method: "POST" }) - const abortLatency = Date.now() - abortStart - expect(res.status).toBe(200) - // The abort endpoint must respond well under a second even under load. - expect(abortLatency).toBeLessThan(2000) + // Fire an abort request against the Hono app in the middle of the diff. + const app = Server.Default().app + const abortStart = Date.now() + const res = yield* Effect.promise(() => + Promise.resolve(app.request(`/session/${session.id}/abort`, { method: "POST" })), + ) + const abortLatency = Date.now() - abortStart + expect(res.status).toBe(200) + // The abort endpoint must respond well under a second even under load. + expect(abortLatency).toBeLessThan(2000) - const diffs = await diffPromise - clearInterval(timer) - const total = Date.now() - start + const diffs = yield* Fiber.join(diff) + clearInterval(timer) + const total = Date.now() - start - // The freeze workload must finish in bounded time. Five seconds is - // generous even for a slow CI box; without the fix this hangs. - expect(total).toBeLessThan(5000) - // And we must have ticked at least a few times during the work — proves - // the event loop stayed responsive (ESC would actually arrive). - expect(ticks).toBeGreaterThan(0) + // The freeze workload must finish in bounded time. Five seconds is + // generous even for a slow CI box; without the fix this hangs. + expect(total).toBeLessThan(5000) + // And we must have ticked at least a few times during the work, proving + // the event loop stayed responsive (ESC would actually arrive). + expect(ticks).toBeGreaterThan(0) - // With git-based diff the patch is a real unified diff, not empty. - const hit = diffs.find((d) => d.file === "fat.json") - expect(hit).toBeDefined() - expect(hit!.patch).toMatch(/^diff --git /m) - expect(hit!.patch).toContain("-v1_line_0") - expect(hit!.patch).toContain("+v2_line_0") - expect(hit!.additions).toBeGreaterThan(0) - expect(hit!.deletions).toBeGreaterThan(0) - }, + // With git-based diff the patch is a real unified diff, not empty. + const hit = diffs.find((d) => d.file === "fat.json") + expect(hit).toBeDefined() + expect(hit!.patch).toMatch(/^diff --git /m) + expect(hit!.patch).toContain("-v1_line_0") + expect(hit!.patch).toContain("+v2_line_0") + expect(hit!.additions).toBeGreaterThan(0) + expect(hit!.deletions).toBeGreaterThan(0) + }), + ), }) }) diff --git a/script/check-opencode-promise-facades.ts b/script/check-opencode-promise-facades.ts index f7319c53d1..fe36cc5d33 100644 --- a/script/check-opencode-promise-facades.ts +++ b/script/check-opencode-promise-facades.ts @@ -28,7 +28,6 @@ const allow: Record = { "session/prompt.ts": "transitional facade tracked by #10655", "session/session.ts": "transitional facade tracked by #10655", "session/summary.ts": "transitional facade removed by #10620", - "snapshot/index.ts": "transitional facade tracked by #10660", "storage/storage.ts": "transitional facade tracked by #10659", "sync/index.ts": "sync event runtime boundary", "tool/registry.ts": "transitional facade removed by #10620", From b48e7c53c8902fb837d7c59e2fc103f58da0a426 Mon Sep 17 00:00:00 2001 From: "kilo-maintainer[bot]" Date: Thu, 28 May 2026 14:17:05 +0000 Subject: [PATCH 2/2] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index e8171c93a6..db28ef6641 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-EbXkvlbexL1Gyy4vvGG5j+JWjv7SsVk6T5YnDfo3AQY=", - "aarch64-linux": "sha256-+MuJE4XGr0dArD/HuozaoK9Oymfgvx1CisE/Sm4ZstY=", - "aarch64-darwin": "sha256-Qdk+tZLydGld471ApL1cxfd85QQNFelTfqq7uAznfK4=", - "x86_64-darwin": "sha256-Fo6W65MfAcXaulBGCaE7+GfHtq3VVJyVDJpIENvR7lQ=" + "x86_64-linux": "sha256-vI06afIL8mL/Rt33Wk2S2kLzrlR3EHqv5kfy0qgO2Zg=", + "aarch64-linux": "sha256-68uA7dKxOXmIRHJ3BI2K2wc1Pkag2cWpp9fLtbS6Ehk=", + "aarch64-darwin": "sha256-oRy74XjENuCxOmPrfEmM4UXoZQQXM5VQlLXMx5SmX+k=", + "x86_64-darwin": "sha256-TjcaVBh40HZk2kKwAunrrl/KYrvEk13bLiBdcUzWSQA=" } }