mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
fix(opencode): resolve race conditions and update tests for kilo compatibility
Remove eager publish call in indexing initialization that caused premature event emission. Add foreign key constraint tolerance in next-message projector to handle writes racing against deleted sessions. Fix test environment isolation for gateway headers and permission tests, update auth test assertions to use "kilo" username, and remove stale indexing route from httpapi-bridge expectations.
This commit is contained in:
@@ -203,7 +203,6 @@ export namespace KiloIndexing {
|
||||
await Bus.publish(Event, { status: current() })
|
||||
}
|
||||
|
||||
await publish()
|
||||
return {
|
||||
current,
|
||||
publish,
|
||||
|
||||
@@ -8,10 +8,22 @@ import { SyncEvent } from "@/sync"
|
||||
import { SessionMessageTable, SessionTable } from "./session.sql"
|
||||
import type { SessionID } from "./schema"
|
||||
import { Schema } from "effect"
|
||||
import { Log } from "@opencode-ai/core/util/log" // kilocode_change
|
||||
|
||||
const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Message)
|
||||
type SessionMessageData = NonNullable<(typeof SessionMessageTable.$inferInsert)["data"]>
|
||||
|
||||
// kilocode_change start - tolerate next-message writes that race deleted sessions
|
||||
const log = Log.create({ service: "session.projector.next" })
|
||||
|
||||
// Duplicated from projectors.ts to minimize merge conflicts and avoid a circular dependency.
|
||||
function foreign(err: unknown) {
|
||||
if (typeof err !== "object" || err === null) return false
|
||||
if ("code" in err && err.code === "SQLITE_CONSTRAINT_FOREIGNKEY") return true
|
||||
return "message" in err && typeof err.message === "string" && err.message.includes("FOREIGN KEY constraint failed")
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
function encodeDateTimes(value: unknown): unknown {
|
||||
if (DateTime.isDateTime(value)) return DateTime.toEpochMillis(value)
|
||||
if (Array.isArray(value)) return value.map(encodeDateTimes)
|
||||
@@ -115,7 +127,14 @@ function sqlite(db: Database.TxOrDb, sessionID: SessionID): SessionMessageUpdate
|
||||
}
|
||||
|
||||
function update(db: Database.TxOrDb, event: SessionEvent.Event) {
|
||||
SessionMessageUpdater.update(sqlite(db, event.data.sessionID), event)
|
||||
// kilocode_change start - tolerate next-message writes that race deleted sessions
|
||||
try {
|
||||
SessionMessageUpdater.update(sqlite(db, event.data.sessionID), event)
|
||||
} catch (err) {
|
||||
if (!foreign(err)) throw err
|
||||
log.warn("ignored late next-message update", { eventID: event.id, sessionID: event.data.sessionID })
|
||||
}
|
||||
// kilocode_change end
|
||||
}
|
||||
|
||||
export default [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, afterEach } from "bun:test"
|
||||
import { describe, it, expect, afterEach, beforeEach } from "bun:test"
|
||||
import { buildKiloHeaders, getFeatureHeader, getEditorNameHeader } from "@kilocode/kilo-gateway"
|
||||
import { HEADER_FEATURE, ENV_FEATURE, ENV_VERSION, DEFAULT_EDITOR_NAME } from "@kilocode/kilo-gateway"
|
||||
import { HEADER_FEATURE, ENV_FEATURE, ENV_EDITOR_NAME, ENV_VERSION, DEFAULT_EDITOR_NAME } from "@kilocode/kilo-gateway"
|
||||
|
||||
describe("getFeatureHeader", () => {
|
||||
const original = process.env[ENV_FEATURE]
|
||||
@@ -31,6 +31,11 @@ describe("getFeatureHeader", () => {
|
||||
|
||||
describe("getEditorNameHeader", () => {
|
||||
const originalVersion = process.env[ENV_VERSION]
|
||||
const originalEditor = process.env[ENV_EDITOR_NAME]
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env[ENV_EDITOR_NAME]
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (originalVersion === undefined) {
|
||||
@@ -38,6 +43,12 @@ describe("getEditorNameHeader", () => {
|
||||
} else {
|
||||
process.env[ENV_VERSION] = originalVersion
|
||||
}
|
||||
|
||||
if (originalEditor === undefined) {
|
||||
delete process.env[ENV_EDITOR_NAME]
|
||||
} else {
|
||||
process.env[ENV_EDITOR_NAME] = originalEditor
|
||||
}
|
||||
})
|
||||
|
||||
it("returns default editor name without version when KILOCODE_VERSION is not set", () => {
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Permission } from "../../../src/permission"
|
||||
import { PermissionID } from "../../../src/permission/schema"
|
||||
import { WithInstance } from "../../../src/project/with-instance"
|
||||
import { Session } from "../../../src/session/session"
|
||||
import { tmpdir } from "../../fixture/fixture"
|
||||
|
||||
const original = Flag.KILO_EXPERIMENTAL_HTTPAPI
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env["KILO_EXPERIMENTAL_HTTPAPI"]
|
||||
Flag.KILO_EXPERIMENTAL_HTTPAPI = original
|
||||
})
|
||||
|
||||
async function app() {
|
||||
async function app(experimental = false) {
|
||||
const { Server } = await import("../../../src/server/server")
|
||||
return Server.Default().app
|
||||
Flag.KILO_EXPERIMENTAL_HTTPAPI = experimental
|
||||
return experimental ? Server.Default().app : Server.Legacy().app
|
||||
}
|
||||
|
||||
describe("POST /permission/:requestID/reply", () => {
|
||||
@@ -119,13 +123,12 @@ describe("POST /permission/:requestID/reply", () => {
|
||||
})
|
||||
|
||||
test("returns 404 for unknown replies when experimental HttpApi is enabled", async () => {
|
||||
process.env["KILO_EXPERIMENTAL_HTTPAPI"] = "1"
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
|
||||
await WithInstance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const server = await app()
|
||||
const server = await app(true)
|
||||
|
||||
const response = await server.request("/permission/permission_missing/reply", {
|
||||
method: "POST",
|
||||
|
||||
@@ -22,12 +22,12 @@ describe("ServerAuth", () => {
|
||||
expect(ServerAuth.headers()).toBeUndefined()
|
||||
})
|
||||
|
||||
test("defaults to the opencode username", () => {
|
||||
test("defaults to the kilo username", () => { // kilocode_change
|
||||
Flag.KILO_SERVER_PASSWORD = "secret"
|
||||
Flag.KILO_SERVER_USERNAME = undefined
|
||||
|
||||
expect(ServerAuth.headers()).toEqual({
|
||||
Authorization: `Basic ${Buffer.from("opencode:secret").toString("base64")}`,
|
||||
Authorization: `Basic ${Buffer.from("kilo:secret").toString("base64")}`, // kilocode_change
|
||||
})
|
||||
})
|
||||
|
||||
@@ -54,6 +54,6 @@ describe("ServerAuth", () => {
|
||||
|
||||
expect(ServerAuth.required(config)).toBe(true)
|
||||
expect(ServerAuth.authorized({ username: "alice", password: Redacted.make("secret") }, config)).toBe(true)
|
||||
expect(ServerAuth.authorized({ username: "opencode", password: Redacted.make("secret") }, config)).toBe(false)
|
||||
expect(ServerAuth.authorized({ username: "kilo", password: Redacted.make("secret") }, config)).toBe(false) // kilocode_change
|
||||
})
|
||||
})
|
||||
|
||||
@@ -230,7 +230,6 @@ describe("HttpApi server", () => {
|
||||
"GET /api/session",
|
||||
"GET /api/session/{sessionID}/context",
|
||||
"GET /api/session/{sessionID}/message",
|
||||
"GET /indexing/status", // kilocode_change - Kilo Effect-only indexing route
|
||||
"POST /api/session/{sessionID}/compact",
|
||||
"POST /api/session/{sessionID}/prompt",
|
||||
"POST /api/session/{sessionID}/wait",
|
||||
|
||||
Reference in New Issue
Block a user