mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
Merge branch 'main' into session/agent_889409bf-00dd-4caf-a3a0-4b1f84fe03ba
This commit is contained in:
+4
-4
@@ -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="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 "."
|
||||
|
||||
@@ -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<A>(body: (snapshot: Snapshot.Interface) => Effect.Effect<A>) {
|
||||
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)
|
||||
}),
|
||||
),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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<A>(body: (snapshot: Snapshot.Interface) => Effect.Effect<A>) {
|
||||
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)
|
||||
}),
|
||||
),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,7 +28,6 @@ const allow: Record<string, string> = {
|
||||
"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",
|
||||
|
||||
Reference in New Issue
Block a user