fix: harden upstream v1.14.42 integration

This commit is contained in:
marius-kilocode
2026-06-01 16:11:59 +02:00
parent 0f271a39d7
commit 78bfc700ea
10 changed files with 134 additions and 67 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
},
"permission": {
"edit": {
"packages/opencode/migration/*": "ask",
"packages/opencode/migration/*": "deny",
},
},
"mcp": {},
@@ -111,7 +111,8 @@ describe("cross-spawn spawner", () => {
ChildProcess.make(process.execPath, ["-e", "process.stdout.write(process.cwd())"], { cwd: tmp.path }),
),
)
expect(out).toBe(tmp.path)
const cwd = yield* Effect.promise(() => fs.realpath(tmp.path))
expect(out).toBe(cwd)
}),
)
+1 -1
View File
@@ -6,7 +6,7 @@ import { Global } from "@opencode-ai/core/global"
describe("global paths", () => {
test("tmp path is under the system temp directory", () => {
expect(Global.Path.tmp).toBe(path.join(os.tmpdir(), "opencode"))
expect(Global.Path.tmp).toBe(path.join(os.tmpdir(), "kilo")) // kilocode_change
expect(Global.make().tmp).toBe(Global.Path.tmp)
})
+1
View File
@@ -31,6 +31,7 @@ exclude = [
'^https?://ai-gateway\.vercel\.sh/v1/?$',
'^https?://api\.voyageai\.com/v1/embeddings/?$',
'^https?://generativelanguage\.googleapis\.com/v1beta/openai/?$',
'^https?://search\.parallel\.ai/mcp/?$',
# xAI API and OAuth endpoints require request parameters or non-GET methods.
'^https?://api\.x\.ai/v1/?$',
'^https?://auth\.x\.ai/?$',
@@ -92,6 +92,6 @@ data class CommandInfo(
data class SkillInfo(
val name: String,
val description: String,
val description: String?,
val location: String,
)
@@ -5,6 +5,6 @@ import kotlinx.serialization.Serializable
@Serializable
data class SkillDto(
val name: String,
val description: String,
val description: String? = null,
val location: String,
)
+62 -28
View File
@@ -5,6 +5,8 @@ const fs = require("fs")
const path = require("path")
const os = require("os")
const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP"]
// kilocode_change start - point packaged binaries at co-located tree-sitter WASM resources
function configureTreeSitterResources(target) {
const wasmDir = path.join(path.dirname(target), "tree-sitter")
@@ -14,42 +16,74 @@ function configureTreeSitterResources(target) {
}
// kilocode_change end
function run(target) {
function run(target, fallback) { // kilocode_change - preserve cached binary fallback
configureTreeSitterResources(target) // kilocode_change
const result = childProcess.spawnSync(target, process.argv.slice(2), {
stdio: "inherit",
})
if (result.error) {
console.error(result.error.message)
process.exit(1)
// kilocode_change start - fall through if the cached binary cannot be spawned
const child = (() => {
try {
return childProcess.spawn(target, process.argv.slice(2), {
stdio: "inherit",
})
} catch (error) {
if (fallback) {
run(fallback)
return
}
console.error(error.message)
process.exit(1)
}
})()
if (!child) return
// kilocode_change end
const forwarders = {}
const clear = () => { // kilocode_change - remove listeners before cached binary fallback
for (const signal of forwardedSignals) {
process.removeListener(signal, forwarders[signal])
}
}
const code = typeof result.status === "number" ? result.status : 0
process.exit(code)
child.on("error", (error) => {
clear() // kilocode_change
// kilocode_change start - fall through to findBinary() if cached binary fails
if (fallback) {
run(fallback)
return
}
// kilocode_change end
console.error(error.message)
process.exit(1)
})
for (const signal of forwardedSignals) {
forwarders[signal] = () => {
try {
child.kill(signal)
} catch {
// The child may have already exited.
}
}
process.on(signal, forwarders[signal])
}
child.on("exit", (code, signal) => {
clear() // kilocode_change
if (signal) {
process.kill(process.pid, signal)
return
}
process.exit(typeof code === "number" ? code : 0)
})
}
const envPath = process.env.KILO_BIN_PATH
if (envPath) {
run(envPath)
}
const scriptPath = fs.realpathSync(__filename)
const scriptDir = path.dirname(scriptPath)
// kilocode_change start - fall through to findBinary() if cached binary fails
const cached = path.join(scriptDir, ".kilo")
if (fs.existsSync(cached)) {
configureTreeSitterResources(cached)
const result = childProcess.spawnSync(cached, process.argv.slice(2), {
stdio: "inherit",
})
if (!result.error) {
const code = typeof result.status === "number" ? result.status : 0
process.exit(code)
}
// cached binary failed (e.g. wrong platform/arch, missing dynamic linker),
// fall through to findBinary() which has better variant detection
}
// kilocode_change end
const platformMap = {
darwin: "darwin",
@@ -186,7 +220,7 @@ function findBinary(startDir) {
}
}
const resolved = findBinary(scriptDir)
const resolved = envPath || (fs.existsSync(cached) ? cached : findBinary(scriptDir))
if (!resolved) {
console.error(
"It seems that your package manager failed to install the right version of the Kilo CLI for your platform. You can try manually installing " +
@@ -196,4 +230,4 @@ if (!resolved) {
process.exit(1)
}
run(resolved)
run(resolved, resolved === cached ? findBinary(scriptDir) : undefined) // kilocode_change - preserve cached binary fallback
@@ -9,6 +9,7 @@ import { isReviewCommand, parseReviewCommand } from "@/kilocode/review/command"
import * as Log from "@opencode-ai/core/util/log"
import { Effect } from "effect"
import { Flag } from "@opencode-ai/core/flag/flag"
import { EffectBridge } from "@/effect/bridge"
export type ReviewTelemetry = {
mode: "review"
@@ -127,7 +128,7 @@ export namespace KiloSessionProcessor {
return Effect.gen(function* () {
const msg = SessionNetwork.message(input.error)
const { id, promise } = yield* Effect.promise(() =>
const { id, promise } = yield* EffectBridge.fromPromise(() =>
SessionNetwork.ask({
sessionID: input.sessionID,
message: msg,
@@ -38,30 +38,37 @@ function eventData(data: unknown): Sse.Event {
}
function eventResponse(bus: Bus.Interface) {
const events = bus.subscribeAll().pipe(Stream.takeUntil((event) => event.type === Bus.InstanceDisposed.type))
const heartbeat = Stream.tick("10 seconds").pipe(
Stream.drop(1),
Stream.map(() => ({ id: Bus.createID(), type: "server.heartbeat", properties: {} })),
)
return Effect.gen(function* () {
const context = yield* Effect.context()
log.info("event connected")
return HttpServerResponse.stream(
Stream.make({ id: Bus.createID(), type: "server.connected", properties: {} }).pipe(
Stream.concat(events.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }))),
Stream.map(eventData),
Stream.pipeThroughChannel(Sse.encode()),
Stream.encodeText,
Stream.ensuring(Effect.sync(() => log.info("event disconnected"))),
),
{
contentType: "text/event-stream",
headers: {
"Cache-Control": "no-cache, no-transform",
"X-Accel-Buffering": "no",
"X-Content-Type-Options": "nosniff",
const events = bus.subscribeAll().pipe(
Stream.provideContext(context),
Stream.takeUntil((event) => event.type === Bus.InstanceDisposed.type),
)
const heartbeat = Stream.tick("10 seconds").pipe(
Stream.drop(1),
Stream.map(() => ({ id: Bus.createID(), type: "server.heartbeat", properties: {} })),
)
log.info("event connected")
return HttpServerResponse.stream(
Stream.make({ id: Bus.createID(), type: "server.connected", properties: {} }).pipe(
Stream.concat(events.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }))),
Stream.map(eventData),
Stream.pipeThroughChannel(Sse.encode()),
Stream.encodeText,
Stream.ensuring(Effect.sync(() => log.info("event disconnected"))),
),
{
contentType: "text/event-stream",
headers: {
"Cache-Control": "no-cache, no-transform",
"X-Accel-Buffering": "no",
"X-Content-Type-Options": "nosniff",
},
},
},
)
)
})
}
export const eventHandlers = HttpApiBuilder.group(EventApi, "event", (handlers) =>
@@ -70,7 +77,7 @@ export const eventHandlers = HttpApiBuilder.group(EventApi, "event", (handlers)
return handlers.handleRaw(
"subscribe",
Effect.fn("EventHttpApi.subscribe")(function* () {
return eventResponse(bus)
return yield* eventResponse(bus)
}),
)
}),
@@ -21,27 +21,36 @@ describe("bin/kilo tree-sitter resources", () => {
return { bin, log, wasm, wrapper: join(dir, "kilo") }
}
async function run(root: string, bin: string | undefined, log: string, wrapper?: string) {
async function run(root: string, bin: string | undefined, log: string, wrapper?: string, failCached?: boolean) {
const capture = `
const { EventEmitter } = require("events")
const kiloFs = require("fs")
const kiloChild = require("child_process")
const log = process.argv[1]
const wrapper = process.argv[2]
const failCached = process.argv[3] === "true"
const realpathSync = kiloFs.realpathSync
kiloFs.realpathSync = (file) => wrapper && file === __filename ? wrapper : realpathSync(file)
kiloChild.spawnSync = () => {
kiloFs.realpathSync = (file) => file === __filename ? wrapper || process.cwd() : realpathSync(file)
kiloChild.spawn = (target) => {
if (failCached && target.endsWith(".kilo")) throw new Error("cached binary failed")
kiloFs.writeFileSync(log, process.env.KILO_TREE_SITTER_WASM_DIR || "")
return { status: 0 }
const child = new EventEmitter()
child.kill = () => {}
process.nextTick(() => child.emit("exit", 0))
return child
}
`
const source = (await Bun.file(script).text()).replace(/^#!.*\n/, "")
return Bun.spawnSync(["node", "--input-type=commonjs", "--eval", capture + source, log, wrapper ?? ""], {
cwd: root,
env: {
PATH: process.env.PATH ?? "",
...(bin ? { KILO_BIN_PATH: bin } : {}),
return Bun.spawnSync(
["node", "--input-type=commonjs", "--eval", capture + source, log, wrapper ?? "", String(failCached)],
{
cwd: root,
env: {
PATH: process.env.PATH ?? "",
...(bin ? { KILO_BIN_PATH: bin } : {}),
},
},
})
)
}
test("exports co-located tree-sitter WASM dir for optional package binary", async () => {
@@ -69,4 +78,18 @@ kiloChild.spawnSync = () => {
await rm(root, { recursive: true, force: true })
}
})
test("falls back to the optional package when the cached binary cannot spawn", async () => {
const root = await mkdtemp(join(tmpdir(), "kilo-bin-tree-sitter-"))
try {
const cached = await setup(root, false)
const item = await setup(root, true)
const proc = await run(root, undefined, item.log, cached.wrapper, true)
expect(proc.exitCode).toBe(0)
expect(await Bun.file(item.log).text()).toBe(cached.wasm)
} finally {
await rm(root, { recursive: true, force: true })
}
})
})