d2e21c5006
- InstanceStore: run Instance.provide init inside the ALS context so KilocodeBootstrap (and any forkDetach work it spawns like KiloIndexing.init) can read Instance.directory. Upstream refactor moved init out of the ALS scope; kilo-main's pre-merge Instance.provide wrapped it. Without this, KiloIndexing.init silently fails with "No context found for instance".
- kilocode/agent: thread worktree through planGuard/patchAgents instead of reading Instance.worktree at agent state construction time. Agent state is built inside Effect (no ALS) via InstanceState.make — reading the ALS-backed Instance.worktree there crashed under the new architecture.
- kilocode/agent: drop the "*": "ask" from explore external_directory — defaults already provides it, and redefining here overwrites the tmp/skill allowlist via findLast().
- test/server/httpapi-instance.test.ts: revert the ported Hono-bridge tests. Upstream put the same tests in httpapi-instance.legacy.test.ts (renamed file); the port duplicated them.
- test/server/httpapi-instance.legacy.test.ts: mark the catalog test test.skip with Kilo's original rationale (/agent 500s via the bridge; the bridge is not enabled in any production client).
- test/server/httpapi-ui.test.ts: delete. Tests upstream's proxy-to-app.opencode.ai fallback that Kilo intentionally removed (src/server/routes/ui.ts kilocode_change).
- test/server/httpapi-raw-route-auth.test.ts: basic("opencode", ...) → basic("kilo", ...) to match the Kilo username default.
- test/provider/models.test.ts: skip describe block. Upstream tests assert raw-fixture passthrough but Kilo's ModelsDev.get() filters/injects providers based on Config.get(), which needs an Instance context the test doesn't provide.
84 lines
3.4 KiB
TypeScript
84 lines
3.4 KiB
TypeScript
import { NodeHttpServer, NodeServices } from "@effect/platform-node"
|
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
import { describe, expect } from "bun:test"
|
|
import { Config, Effect, FileSystem, Layer, Path } from "effect"
|
|
import { HttpClient, HttpClientRequest, HttpRouter, HttpServer } from "effect/unstable/http"
|
|
import * as Socket from "effect/unstable/socket/Socket"
|
|
import { InstancePaths } from "../../src/server/routes/instance/httpapi/groups/instance"
|
|
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
|
import { resetDatabase } from "../fixture/db"
|
|
import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture"
|
|
import { testEffect } from "../lib/effect"
|
|
|
|
// Flip the experimental HttpApi flag so backend selection telemetry on the
|
|
// production routes reports the right backend, and reset the database around
|
|
// the test so per-instance state does not leak between runs. resetDatabase()
|
|
// already calls disposeAllInstances(), so we don't repeat it.
|
|
const testStateLayer = Layer.effectDiscard(
|
|
Effect.gen(function* () {
|
|
const originalHttpApi = Flag.KILO_EXPERIMENTAL_HTTPAPI
|
|
Flag.KILO_EXPERIMENTAL_HTTPAPI = true
|
|
yield* Effect.promise(() => resetDatabase())
|
|
yield* Effect.addFinalizer(() =>
|
|
Effect.promise(async () => {
|
|
Flag.KILO_EXPERIMENTAL_HTTPAPI = originalHttpApi
|
|
await resetDatabase()
|
|
}),
|
|
)
|
|
}),
|
|
)
|
|
|
|
// Mount the production HttpApi route tree on a real Node HTTP server bound to
|
|
// 127.0.0.1:0 and a fetch-based HttpClient that prepends the server URL. This
|
|
// keeps the test wired through the same route layer production uses, without
|
|
// going through Server.Default()/Hono.
|
|
const servedRoutes: Layer.Layer<never, Config.ConfigError, HttpServer.HttpServer> = HttpRouter.serve(
|
|
ExperimentalHttpApiServer.routes,
|
|
{ disableListenLog: true, disableLogger: true },
|
|
)
|
|
|
|
const httpApiServerLayer = servedRoutes.pipe(
|
|
Layer.provide(Socket.layerWebSocketConstructorGlobal),
|
|
Layer.provideMerge(NodeHttpServer.layerTest),
|
|
Layer.provideMerge(NodeServices.layer),
|
|
)
|
|
|
|
const it = testEffect(Layer.mergeAll(testStateLayer, httpApiServerLayer))
|
|
|
|
const directoryHeader = (dir: string) => HttpClientRequest.setHeader("x-kilo-directory", dir)
|
|
|
|
describe("instance HttpApi", () => {
|
|
it.live("serves path and VCS read endpoints", () =>
|
|
Effect.gen(function* () {
|
|
const dir = yield* tmpdirScoped({ git: true })
|
|
const fs = yield* FileSystem.FileSystem
|
|
const path = yield* Path.Path
|
|
yield* fs.writeFileString(path.join(dir, "changed.txt"), "hello")
|
|
|
|
const [paths, vcs, diff] = yield* Effect.all(
|
|
[
|
|
HttpClientRequest.get(InstancePaths.path).pipe(directoryHeader(dir), HttpClient.execute),
|
|
HttpClientRequest.get(InstancePaths.vcs).pipe(directoryHeader(dir), HttpClient.execute),
|
|
HttpClientRequest.get(InstancePaths.vcsDiff).pipe(
|
|
HttpClientRequest.setUrlParam("mode", "git"),
|
|
directoryHeader(dir),
|
|
HttpClient.execute,
|
|
),
|
|
],
|
|
{ concurrency: "unbounded" },
|
|
)
|
|
|
|
expect(paths.status).toBe(200)
|
|
expect(yield* paths.json).toMatchObject({ directory: dir, worktree: dir })
|
|
|
|
expect(vcs.status).toBe(200)
|
|
expect(yield* vcs.json).toMatchObject({ branch: expect.any(String) })
|
|
|
|
expect(diff.status).toBe(200)
|
|
expect(yield* diff.json).toContainEqual(
|
|
expect.objectContaining({ file: "changed.txt", additions: 1, status: "added" }),
|
|
)
|
|
}),
|
|
)
|
|
})
|