Merge remote-tracking branch 'origin/johnnyeric/kilo-opencode-v1.18.0' into johnnyeric/kilo-opencode-v1.18.13

This commit is contained in:
Johnny Eric Amancio
2026-08-10 13:36:14 +02:00
8 changed files with 151 additions and 35 deletions
@@ -345,9 +345,7 @@ export namespace RemoteSender {
// bus listener count from inflating for senders that never handle
// attachments (the count would otherwise show up in unrelated tests
// that assert it stays at 0).
const attachments =
options.attachments ??
((sessionID: SessionID) => RemoteAttachments.create({ sessionID }))
const attachments = options.attachments ?? ((sessionID: SessionID) => RemoteAttachments.create({ sessionID }))
const attachmentCache = new Map<SessionID, RemoteAttachments.Result>()
const pending = new Map<SessionID, number>()
const retired = new Map<SessionID, RemoteAttachments.Result>()
@@ -886,27 +884,27 @@ export namespace RemoteSender {
return
}
// kilocode_change end
// kilocode_change start - sessionless list_models for the pre-session instance picker
if (msg.command === "list_models") {
const parsed = RemoteModelCatalog.Request.safeParse(msg.data)
const session = msg.sessionId ? decodeSessionID(msg.sessionId) : Option.none<SessionID>()
if (!parsed.success || Option.isNone(session)) {
options.conn.send({
type: "response",
id: msg.id,
error: "invalid list_models command",
})
// Accept an absent sessionId (the mobile instance-picker path asks for the
// instance's catalog before a session exists). A present but undecodable
// sessionId is still invalid.
const target = msg.sessionId ? decodeSessionID(msg.sessionId) : Option.none<SessionID>()
if (!parsed.success || (msg.sessionId && Option.isNone(target))) {
options.conn.send({ type: "response", id: msg.id, error: "invalid list_models command" })
return
}
const run = options.provide ?? provide
void (async () => {
try {
const info = await catalog.get(session.value)
const info = Option.isSome(target) ? await catalog.get(target.value) : undefined
const result = await run({
directory: info.directory,
directory: info?.directory ?? options.directory,
fn: async () => {
const [providers, messages, fallback] = await Promise.all([
catalog.providers(),
catalog.messages(info.id),
info ? catalog.messages(info.id) : Promise.resolve([]),
catalog.default().catch((err) => {
options.log.warn("default model lookup failed", { error: String(err) })
return undefined
@@ -914,7 +912,7 @@ export namespace RemoteSender {
])
return RemoteModelCatalog.build({
providers,
session: info,
session: info ?? {},
messages,
defaultModel: fallback,
})
@@ -928,6 +926,7 @@ export namespace RemoteSender {
})()
return
}
// kilocode_change end
if (msg.command === "send_message") {
const parsed = getRemotePromptInput().safeParse(msg.data)
if (!parsed.success) {
@@ -911,7 +911,9 @@ describe("config overlay routes", () => {
Permission.evaluate("edit", "*", after.find((item) => item.name === "code")?.permission ?? []).action,
).toBe("allow")
},
30_000,
// Cold Windows CI runs take ~32s (observed timeout at 30s); give the two
// instance create/dispose cycles of each iteration real headroom.
90_000,
)
}
@@ -1084,7 +1084,7 @@ describe("RemoteSender", () => {
expect(JSON.stringify(sent)).not.toContain("api-key")
})
test("list_models rejects unsupported versions and missing session IDs", () => {
test("list_models rejects unsupported versions and undecodable session IDs", () => {
const { conn, sent } = fakeConn()
const sender = RemoteSender.create({
conn,
@@ -1100,12 +1100,6 @@ describe("RemoteSender", () => {
sessionId: "ses_models",
data: { protocolVersion: 2 },
})
sender.handle({
type: "command",
id: "req_models_missing_session",
command: "list_models",
data: { protocolVersion: 1 },
})
sender.handle({
type: "command",
id: "req_models_invalid_session",
@@ -1116,11 +1110,72 @@ describe("RemoteSender", () => {
expect(sent).toEqual([
{ type: "response", id: "req_models_v2", error: "invalid list_models command" },
{ type: "response", id: "req_models_missing_session", error: "invalid list_models command" },
{ type: "response", id: "req_models_invalid_session", error: "invalid list_models command" },
])
})
test("list_models without a sessionId returns the instance catalog", async () => {
const { conn, sent } = fakeConn()
const dirs: string[] = []
const sender = RemoteSender.create({
conn,
directory: "/tmp/process-default",
log: nolog,
subscribe: fakeBus().subscribe,
provide: async <R>(input: { directory: string; init?: Effect.Effect<void>; fn: () => R }) => {
dirs.push(input.directory)
return input.fn()
},
catalog: {
get: async () => {
throw new Error("catalog.get must not be called without a sessionId")
},
messages: async () => {
throw new Error("catalog.messages must not be called without a sessionId")
},
providers: async () =>
({
custom: {
id: ProviderV2.ID.make("custom"),
name: "Custom Provider",
source: "config",
env: ["PRIVATE_API_KEY"],
key: "must-not-leak",
options: { apiKey: "must-not-leak" },
models: {
"deployment/model": catalogModel("custom", "deployment/model", "Deployment Model", true),
},
},
}) as any,
default: async () => ({
providerID: ProviderV2.ID.make("custom"),
modelID: ModelV2.ID.make("deployment/model"),
}),
},
})
sender.handle({
type: "command",
id: "req_models_sessionless",
command: "list_models",
data: { protocolVersion: 1 },
})
await new Promise((resolve) => setTimeout(resolve, 0))
expect(dirs).toEqual(["/tmp/process-default"])
expect(sent).toHaveLength(1)
expect(sent[0]?.type).toBe("response")
expect(sent[0]?.id).toBe("req_models_sessionless")
const result = sent[0]?.result as RemoteModelCatalog.Response
expect(result.protocolVersion).toBe(1)
expect(result.all).toHaveLength(1)
expect(result.all[0]?.id).toBe("custom")
expect(result.defaultModel).toEqual({ providerID: "custom", modelID: "deployment/model" })
expect(result).not.toHaveProperty("currentModel")
expect(JSON.stringify(result)).not.toContain("must-not-leak")
})
test("send_message with agent is accepted", async () => {
const { conn, sent } = fakeConn()
let resolveProvide: () => void
@@ -3066,7 +3121,9 @@ describe("RemoteSender slash commands", () => {
removeCalls.push(id)
},
},
attachSession: async () => { throw new Error("attach failed") },
attachSession: async () => {
throw new Error("attach failed")
},
})
const response = expectResponse(conn, sent, "req_spawn_failed")
@@ -3107,7 +3164,9 @@ describe("RemoteSender slash commands", () => {
throw new Error("cleanup secondary failure")
},
},
attachSession: async () => { throw new Error("attach failed") },
attachSession: async () => {
throw new Error("attach failed")
},
})
const response = expectResponse(conn, sent, "req_spawn_then_cleanup_fail")