Cloud Agent - Add kilo cloud command for running asynchronous cloud agent tasks (#11849)

This commit is contained in:
Evgeny Shurakov
2026-07-22 15:48:29 +02:00
committed by GitHub
parent 16988a5581
commit fe01f53e2b
30 changed files with 3814 additions and 2 deletions
@@ -73,6 +73,7 @@ mock.module("@/kilocode/help-command", () => ({
for (const path of [
"@/kilocode/cli/cmd/console",
"@/kilocode/cli/cmd/cloud",
"@/kilocode/cli/cmd/roll-call",
"@/kilocode/cli/cmd/profile",
"@/kilocode/cli/cmd/daemon",
@@ -82,6 +83,7 @@ for (const path of [
]) {
mock.module(path, () => ({
KiloConsoleCommand: { command: "console", handler() {} },
CloudCommand: { command: "cloud", handler() {} },
RollCallCommand: { command: "roll-call", handler() {} },
ProfileCommand: { command: "profile", handler() {} },
DaemonCommand: { command: "daemon", handler() {} },
@@ -0,0 +1,540 @@
import { expect } from "bun:test"
import { Agent } from "@/agent/agent"
import { Auth } from "@/auth"
import { Config } from "@/config/config"
import type { AgentSendRequest, AgentStartRequest, MessageResult } from "@/kilocode/cloud/contracts"
import { CloudCommands } from "@/kilocode/cloud/commands"
import { CloudError } from "@/kilocode/cloud/errors"
import { Git } from "@/git"
import { Effect, Layer } from "effect"
import { TestInstance } from "../../fixture/fixture"
import { testEffect } from "../../lib/effect"
const SESSION = "agent_12345678-1234-1234-1234-123456789abc"
const MESSAGE = "msg_018f1e2d3c4bAbCdEfGhIjKlMn"
const TOKEN = "command-test-token"
const ORG = "11111111-1111-4111-8111-111111111111"
const auth = Layer.mock(Auth.Service)({
get: (id) =>
Effect.succeed(
id === "kilo"
? new Auth.Oauth({
type: "oauth",
access: TOKEN,
refresh: "test-refresh",
expires: Date.now() + 60_000,
accountId: ORG,
})
: undefined,
),
})
const it = testEffect(Layer.mergeAll(Agent.defaultLayer, Config.defaultLayer, Git.defaultLayer, auth))
const run = Effect.fn("CloudCommandTest.git")(function* (cwd: string, ...args: string[]) {
const git = yield* Git.Service
const result = yield* git.run(args, { cwd })
if (result.exitCode === 0) return
yield* Effect.die(new Error(result.stderr.toString("utf8")))
})
it.instance(
"assembles the default start request from Kilo state and the current repository",
() =>
Effect.acquireUseRelease(
Effect.sync(() =>
Bun.serve({
hostname: "127.0.0.1",
port: 0,
fetch(request) {
const url = new URL(request.url)
if (url.pathname.endsWith("/models")) {
return Response.json({ data: [{ id: "anthropic/command-model", supported_parameters: ["tools"] }] })
}
if (url.pathname.endsWith("/defaults")) {
return Response.json({ defaultModel: "anthropic/command-model" })
}
return new Response(null, { status: 404 })
},
}),
),
(server) =>
Effect.gen(function* () {
const test = yield* TestInstance
yield* run(test.directory, "remote", "add", "origin", "git@github.com:Kilo-Org/kilocode.git")
const requests: AgentStartRequest[] = []
const keys: string[] = []
const output: string[] = []
const response = {
cloudAgentSessionId: SESSION,
kiloSessionId: "ses_command_test",
messageId: MESSAGE,
delivery: "queued" as const,
}
const result = yield* CloudCommands.start(
{
cwd: test.directory,
prompt: "Inspect the current repository",
},
{
env: { KILO_API_URL: server.url.origin },
make: (options) => {
keys.push(options.apiKey)
return {
async start(input) {
requests.push(input)
return response
},
async send() {
throw new Error("unused send")
},
async getMessageResult() {
throw new Error("unused result")
},
}
},
write: (text) => output.push(text),
},
)
expect(result).toEqual(response)
expect(keys).toEqual([TOKEN])
expect(requests).toHaveLength(1)
expect(requests[0]).toEqual({
message: { prompt: "Inspect the current repository" },
agent: { mode: "plan", model: "anthropic/command-model" },
repository: { type: "github", repo: "Kilo-Org/kilocode" },
options: {
createdOnPlatform: "kilo-cli",
kilocodeOrganizationId: ORG,
},
})
expect(requests[0]?.repository).not.toHaveProperty("branch")
expect(output).toEqual([JSON.stringify(response) + "\n"])
}),
(server) => Effect.promise(() => server.stop(true)),
),
{
git: true,
config: {
default_agent: "plan",
agent: { plan: { model: "kilo/anthropic/command-model" } },
},
},
)
it.instance(
"streams WebSocket events when --stream is passed",
() =>
Effect.acquireUseRelease(
Effect.sync(() =>
Bun.serve({
hostname: "127.0.0.1",
port: 0,
fetch(request) {
const url = new URL(request.url)
if (url.pathname.endsWith("/models")) {
return Response.json({ data: [{ id: "anthropic/command-model", supported_parameters: ["tools"] }] })
}
if (url.pathname.endsWith("/defaults")) {
return Response.json({ defaultModel: "anthropic/command-model" })
}
return new Response(null, { status: 404 })
},
}),
),
(server) =>
Effect.gen(function* () {
const test = yield* TestInstance
const output: string[] = []
const ticketCalls: { cloudAgentSessionId: string; organizationId?: string }[] = []
const streamCalls: string[] = []
const response = {
cloudAgentSessionId: SESSION,
kiloSessionId: "ses_stream_test",
messageId: MESSAGE,
delivery: "queued" as const,
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=inlined",
}
const result = yield* CloudCommands.start(
{
cwd: test.directory,
prompt: "Inspect the repository",
repo: "Kilo-Org/kilocode",
stream: true,
},
{
env: { KILO_API_URL: server.url.origin },
make: () => ({
async start() {
return response
},
async send() {
throw new Error("unused")
},
async getMessageResult() {
throw new Error("unused")
},
}),
createStreamTicketClient: () => ({
async fetchTicket(input) {
ticketCalls.push(input)
return { ticket: "should-not-be-used", expiresAt: 0 }
},
}),
streamAgentEvents: async (options) => {
streamCalls.push(options.streamUrl)
await options.writeLine('{"event":"one"}')
await options.writeLine('{"streamEventType":"complete","data":{"exitCode":0}}')
},
write: (text) => output.push(text),
},
)
expect(result).toEqual(response)
expect(ticketCalls).toEqual([])
expect(streamCalls).toEqual(["/stream?cloudAgentSessionId=agent_123&ticket=inlined"])
expect(output).toEqual([
JSON.stringify({ ...response, streamUrl: undefined }) + "\n",
'{"event":"one"}\n',
'{"streamEventType":"complete","data":{"exitCode":0}}\n',
])
}),
(server) => Effect.promise(() => server.stop(true)),
),
{
git: true,
config: {
default_agent: "plan",
agent: { plan: { model: "kilo/anthropic/command-model" } },
},
},
)
it.instance(
"fetches a stream ticket when the response omits streamUrl",
() =>
Effect.acquireUseRelease(
Effect.sync(() =>
Bun.serve({
hostname: "127.0.0.1",
port: 0,
fetch(request) {
const url = new URL(request.url)
if (url.pathname.endsWith("/models")) {
return Response.json({ data: [{ id: "anthropic/command-model", supported_parameters: ["tools"] }] })
}
if (url.pathname.endsWith("/defaults")) {
return Response.json({ defaultModel: "anthropic/command-model" })
}
return new Response(null, { status: 404 })
},
}),
),
(server) =>
Effect.gen(function* () {
const test = yield* TestInstance
const output: string[] = []
const ticketCalls: { cloudAgentSessionId: string; organizationId?: string }[] = []
const streamCalls: string[] = []
const response = {
cloudAgentSessionId: SESSION,
kiloSessionId: "ses_stream_test",
messageId: MESSAGE,
delivery: "queued" as const,
}
yield* CloudCommands.start(
{
cwd: test.directory,
prompt: "Inspect the repository",
repo: "Kilo-Org/kilocode",
orgID: ORG,
stream: true,
},
{
env: { KILO_API_URL: server.url.origin },
make: () => ({
async start() {
return response
},
async send() {
throw new Error("unused")
},
async getMessageResult() {
throw new Error("unused")
},
}),
createStreamTicketClient: () => ({
async fetchTicket(input) {
ticketCalls.push(input)
return { ticket: "derived-tok", expiresAt: 1234567890 }
},
}),
streamAgentEvents: async (options) => {
streamCalls.push(options.streamUrl)
await options.writeLine('{"event":"derived"}')
},
write: (text) => output.push(text),
},
)
expect(ticketCalls).toEqual([{ cloudAgentSessionId: SESSION, organizationId: ORG }])
expect(streamCalls).toEqual([`/stream?cloudAgentSessionId=${SESSION}&ticket=derived-tok`])
expect(output).toEqual([JSON.stringify(response) + "\n", '{"event":"derived"}\n'])
}),
(server) => Effect.promise(() => server.stop(true)),
),
{
git: true,
config: {
default_agent: "plan",
agent: { plan: { model: "kilo/anthropic/command-model" } },
},
},
)
it.instance(
"keeps admission successful when stream ticket acquisition fails",
() =>
Effect.acquireUseRelease(
Effect.sync(() =>
Bun.serve({
hostname: "127.0.0.1",
port: 0,
fetch(request) {
const url = new URL(request.url)
if (url.pathname.endsWith("/models")) {
return Response.json({ data: [{ id: "anthropic/command-model", supported_parameters: ["tools"] }] })
}
if (url.pathname.endsWith("/defaults")) {
return Response.json({ defaultModel: "anthropic/command-model" })
}
return new Response(null, { status: 404 })
},
}),
),
(server) =>
Effect.gen(function* () {
const test = yield* TestInstance
const output: string[] = []
const response = {
cloudAgentSessionId: SESSION,
kiloSessionId: "ses_stream_test",
messageId: MESSAGE,
delivery: "queued" as const,
}
const result = yield* CloudCommands.start(
{
cwd: test.directory,
prompt: "Inspect the repository",
repo: "Kilo-Org/kilocode",
stream: true,
},
{
env: { KILO_API_URL: server.url.origin },
make: () => ({
async start() {
return response
},
async send() {
throw new Error("unused")
},
async getMessageResult() {
throw new Error("unused")
},
}),
createStreamTicketClient: () => ({
async fetchTicket() {
throw new CloudError("Unable to obtain stream ticket")
},
}),
streamAgentEvents: async () => {
throw new Error("unused")
},
write: (text) => output.push(text),
},
)
expect(result).toEqual(response)
expect(output).toEqual([
JSON.stringify(response) + "\n",
JSON.stringify({ streamEventType: "error", data: { message: "Unable to obtain stream ticket" } }) + "\n",
])
}),
(server) => Effect.promise(() => server.stop(true)),
),
{
git: true,
config: {
default_agent: "plan",
agent: { plan: { model: "kilo/anthropic/command-model" } },
},
},
)
it.instance(
"keeps a successful admission successful when the follow-up stream fails",
() =>
Effect.acquireUseRelease(
Effect.sync(() =>
Bun.serve({
hostname: "127.0.0.1",
port: 0,
fetch(request) {
const url = new URL(request.url)
if (url.pathname.endsWith("/models")) {
return Response.json({ data: [{ id: "anthropic/command-model", supported_parameters: ["tools"] }] })
}
if (url.pathname.endsWith("/defaults")) {
return Response.json({ defaultModel: "anthropic/command-model" })
}
return new Response(null, { status: 404 })
},
}),
),
(server) =>
Effect.gen(function* () {
const test = yield* TestInstance
const output: string[] = []
const response = {
cloudAgentSessionId: SESSION,
kiloSessionId: "ses_stream_test",
messageId: MESSAGE,
delivery: "queued" as const,
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=inlined",
}
const result = yield* CloudCommands.start(
{
cwd: test.directory,
prompt: "Inspect the repository",
repo: "Kilo-Org/kilocode",
stream: true,
},
{
env: { KILO_API_URL: server.url.origin },
make: () => ({
async start() {
return response
},
async send() {
throw new Error("unused")
},
async getMessageResult() {
throw new Error("unused")
},
}),
streamAgentEvents: async () => {
throw new Error("stream failed after admission")
},
write: (text) => output.push(text),
},
)
expect(result).toEqual(response)
expect(output).toEqual([
JSON.stringify({ ...response, streamUrl: undefined }) + "\n",
JSON.stringify({ streamEventType: "error", data: { message: "Cloud Agent stream failed" } }) + "\n",
])
}),
(server) => Effect.promise(() => server.stop(true)),
),
{
git: true,
config: {
default_agent: "plan",
agent: { plan: { model: "kilo/anthropic/command-model" } },
},
},
)
it.instance("sends follow-ups, prints status without assistant content, and applies the result exit code", () =>
Effect.gen(function* () {
const output: string[] = []
const exits: number[] = []
const sends: AgentSendRequest[] = []
const sent = {
cloudAgentSessionId: SESSION,
status: "started" as const,
streamUrl: "wss://cloud-agent.example/stream",
messageId: MESSAGE,
delivery: "queued" as const,
}
const results: MessageResult[] = [
{
cloudAgentSessionId: SESSION,
messageId: MESSAGE,
status: "completed",
createdAt: 1,
terminalAt: 2,
assistant: { messageId: "assistant_1", text: "done" },
},
{
cloudAgentSessionId: SESSION,
messageId: MESSAGE,
status: "failed",
createdAt: 1,
terminalAt: 2,
failure: { retryable: false },
},
]
const deps = {
env: { KILO_ORG_ID: "not-relevant-to-existing-sessions" },
make: () => ({
async start() {
throw new Error("unused start")
},
async send(input: AgentSendRequest) {
sends.push(input)
return sent
},
async getMessageResult() {
const result = results.shift()
if (!result) throw new Error("missing test result")
return result
},
}),
write: (text: string) => output.push(text),
exit: (code: number) => exits.push(code),
}
yield* CloudCommands.send({ sessionID: SESSION, prompt: "Continue" }, deps)
yield* CloudCommands.status({ sessionID: SESSION, messageID: MESSAGE }, deps)
yield* CloudCommands.result({ sessionID: SESSION, messageID: MESSAGE }, deps)
expect(sends).toEqual([{ cloudAgentSessionId: SESSION, message: { prompt: "Continue" } }])
expect(output).toEqual([
JSON.stringify({ ...sent, streamUrl: undefined }) + "\n",
JSON.stringify({
cloudAgentSessionId: SESSION,
messageId: MESSAGE,
status: "completed",
createdAt: 1,
terminalAt: 2,
}) + "\n",
JSON.stringify({
cloudAgentSessionId: SESSION,
messageId: MESSAGE,
status: "failed",
createdAt: 1,
terminalAt: 2,
failure: { retryable: false },
}) + "\n",
])
expect(exits).toEqual([3])
const error = yield* CloudCommands.send(
{ sessionID: SESSION, prompt: "Do not duplicate" },
{ ...deps, write: () => Promise.reject(new Error("closed output")) },
).pipe(Effect.flip)
expect(error.message).toBe(
"Cloud Agent send was admitted but output could not be written; do not retry automatically",
)
}),
)
@@ -0,0 +1,436 @@
import { expect } from "bun:test"
import { Effect, Layer, Redacted, Ref } from "effect"
import { Agent } from "@/agent/agent"
import { Auth } from "@/auth"
import { Config } from "@/config/config"
import { CloudAuth } from "@/kilocode/cloud/auth"
import { CloudCatalog } from "@/kilocode/cloud/catalog"
import { CloudDefaults } from "@/kilocode/cloud/defaults"
import { MAX_CLOUD_AGENT_RESPONSE_BYTES } from "@/kilocode/cloud/response-json"
import { testEffect } from "../../lib/effect"
const it = testEffect(Layer.mergeAll(Agent.defaultLayer, Config.defaultLayer))
type RequestInfo = {
readonly authorization: string | null
readonly feature: string | null
readonly organization: string | null
readonly path: string
}
const oauth = (token: string, organizationID: string) =>
new Auth.Oauth({
type: "oauth",
access: token,
refresh: "test-refresh",
expires: Date.now() + 60_000,
accountId: organizationID,
})
const authLayer = (info: Auth.Info) =>
Layer.mock(Auth.Service)({
get: (id) => Effect.succeed(id === "kilo" ? info : undefined),
})
const stateLayer = (state: CloudDefaults.ModelStateInfo) =>
Layer.mock(CloudDefaults.ModelState)({
get: () => Effect.succeed(state),
})
const state = (input: Partial<CloudDefaults.ModelStateInfo> = {}): CloudDefaults.ModelStateInfo => ({
model: {},
recent: [],
favorite: [],
variant: {},
...input,
})
function withCatalog<A, E, R>(
models: readonly string[],
defaultModel: string,
use: (url: URL, requests: RequestInfo[]) => Effect.Effect<A, E, R>,
) {
const requests: RequestInfo[] = []
return Effect.acquireUseRelease(
Effect.sync(() =>
Bun.serve({
hostname: "127.0.0.1",
port: 0,
fetch(request) {
const url = new URL(request.url)
requests.push({
authorization: request.headers.get("authorization"),
feature: request.headers.get("x-kilocode-feature"),
organization: request.headers.get("x-kilocode-organizationid"),
path: url.pathname,
})
if (url.pathname.endsWith("/models")) {
return Response.json({ data: models.map((id) => ({ id, supported_parameters: ["tools"] })) })
}
if (url.pathname.endsWith("/defaults")) return Response.json({ defaultModel })
return new Response(null, { status: 404 })
},
}),
),
(server) => use(server.url, requests),
(server) => Effect.promise(() => server.stop(true)),
)
}
it.instance("routes URL-scoped credentials to their catalog origin", () => {
const token = "https://catalog.example.test:scoped-token"
const requests: RequestInfo[] = []
return Effect.gen(function* () {
const catalog = yield* CloudCatalog.Service
expect(yield* catalog.models({ token: Redacted.make(token) })).toEqual(["anthropic/scoped"])
expect(requests).toEqual([
{
authorization: `Bearer ${token}`,
feature: "kilo-cli",
organization: null,
path: "/api/openrouter/models",
},
])
}).pipe(
Effect.provide(
CloudCatalog.layer({
env: {},
fetch: async (request) => {
const url = new URL(request.url)
expect(url.origin).toBe("https://catalog.example.test")
requests.push({
authorization: request.headers.get("authorization"),
feature: request.headers.get("x-kilocode-feature"),
organization: request.headers.get("x-kilocode-organizationid"),
path: url.pathname,
})
return Response.json({ data: [{ id: "anthropic/scoped", supported_parameters: ["tools"] }] })
},
}),
),
)
})
it.instance("returns only tool-capable text-output models", () =>
Effect.gen(function* () {
const catalog = yield* CloudCatalog.Service
const models = yield* catalog.models({ token: Redacted.make("stored-token") })
expect(models).toEqual(["anthropic/code"])
}).pipe(
Effect.provide(
CloudCatalog.layer({
fetch: async () =>
Response.json({
data: [
{
id: "anthropic/code",
architecture: { output_modalities: ["text"] },
supported_parameters: ["tools"],
},
{
id: "image/generator",
architecture: { output_modalities: ["image"] },
supported_parameters: ["tools"],
},
{
id: "anthropic/chat",
architecture: { output_modalities: ["text"] },
supported_parameters: ["temperature"],
},
{ id: "anthropic/unknown" },
],
}),
}),
),
),
)
it.instance("rejects oversized catalog responses", () =>
Effect.gen(function* () {
const catalog = yield* CloudCatalog.Service
const error = yield* catalog.models({ token: Redacted.make("stored-token") }).pipe(Effect.flip)
expect(error).toMatchObject({ _tag: "CloudCatalogError", kind: "schema" })
}).pipe(
Effect.provide(
CloudCatalog.layer({
fetch: async () =>
Response.json({ data: [] }, { headers: { "content-length": String(MAX_CLOUD_AGENT_RESPONSE_BYTES + 1) } }),
}),
),
),
)
it.instance(
"explicit overrides beat environment and saved defaults without persisting",
() =>
withCatalog(["anthropic/explicit", "anthropic/default"], "anthropic/default", (url, requests) =>
Effect.gen(function* () {
const savedID = "22222222-2222-4222-8222-222222222222"
const envID = "33333333-3333-4333-8333-333333333333"
const explicitID = "44444444-4444-4444-8444-444444444444"
const stored = oauth("stored-token", savedID)
const current = yield* Ref.make<Auth.Info>(stored)
const auth = Layer.mock(Auth.Service)({
get: (id) => (id === "kilo" ? Ref.get(current) : Effect.succeed(undefined)),
set: (id, info) => (id === "kilo" ? Ref.set(current, info) : Effect.void),
})
const resolved = yield* Effect.gen(function* () {
const result = yield* CloudDefaults.resolve({
mode: "debug",
model: "kilo/anthropic/explicit",
orgID: explicitID,
env: {
KILO_API_KEY: "ignored-env-token",
KILO_ORG_ID: envID,
},
})
const service = yield* Auth.Service
expect(yield* service.get("kilo")).toEqual(stored)
return result
}).pipe(
Effect.provide(
Layer.mergeAll(
auth,
stateLayer(
state({
model: { debug: { providerID: "kilo", modelID: "anthropic/saved" } },
}),
),
CloudCatalog.layer({ env: { KILO_API_URL: url.origin } }),
),
),
)
expect(resolved).toMatchObject({
mode: "debug",
model: "anthropic/explicit",
organizationID: explicitID,
})
expect(requests.map((request) => request.path)).toEqual([`/api/organizations/${explicitID}/models`])
expect(
requests.every(
(request) =>
request.path.startsWith(`/api/organizations/${explicitID}/`) &&
request.authorization === "Bearer stored-token" &&
request.feature === "kilo-cli" &&
request.organization === explicitID,
),
).toBe(true)
}),
),
{
config: {
default_agent: "plan",
model: "kilo/anthropic/repository",
agent: { plan: { model: "kilo/anthropic/mode" } },
},
},
)
it.instance(
"skips a stale saved model and uses the available repository model",
() =>
withCatalog(
["anthropic/repository", "anthropic/recent", "anthropic/default"],
"anthropic/default",
(url, requests) =>
Effect.gen(function* () {
const resolved = yield* CloudDefaults.resolve()
expect(resolved.mode).toBe("code")
expect(resolved.model).toBe("anthropic/repository")
expect(requests.map((request) => request.path)).toEqual(["/api/openrouter/models"])
expect(requests.every((request) => request.organization === null)).toBe(true)
}).pipe(
Effect.provide(
Layer.mergeAll(
authLayer(new Auth.Api({ type: "api", key: "stored-api-token" })),
stateLayer(
state({
model: { code: { providerID: "kilo", modelID: "anthropic/stale" } },
recent: [{ providerID: "kilo", modelID: "anthropic/recent" }],
}),
),
CloudCatalog.layer({ env: { KILO_API_URL: url.origin } }),
),
),
),
),
{
config: {
model: "kilo/anthropic/repository",
agent: { code: { model: null } },
},
},
)
it.instance(
"uses the saved model for the resolved mode when it remains available",
() =>
withCatalog(["anthropic/saved", "anthropic/default"], "anthropic/default", (url) =>
CloudDefaults.resolve().pipe(
Effect.tap((resolved) =>
Effect.sync(() => {
expect(resolved.mode).toBe("code")
expect(resolved.model).toBe("anthropic/saved")
}),
),
Effect.provide(
Layer.mergeAll(
authLayer(new Auth.Api({ type: "api", key: "stored-api-token" })),
stateLayer(
state({
model: { code: { providerID: "kilo", modelID: "anthropic/saved" } },
}),
),
CloudCatalog.layer({ env: { KILO_API_URL: url.origin } }),
),
),
),
),
{
config: { agent: { code: { model: null } } },
},
)
it.instance(
"fetches the catalog default only when configured and saved candidates are unavailable",
() =>
withCatalog(["anthropic/default"], "anthropic/default", (url, requests) =>
CloudDefaults.resolve().pipe(
Effect.tap((resolved) =>
Effect.sync(() => {
expect(resolved.model).toBe("anthropic/default")
expect(requests.map((request) => request.path)).toEqual(["/api/openrouter/models", "/api/defaults"])
}),
),
Effect.provide(
Layer.mergeAll(
authLayer(new Auth.Api({ type: "api", key: "stored-api-token" })),
stateLayer(state()),
CloudCatalog.layer({ env: { KILO_API_URL: url.origin } }),
),
),
),
),
{
config: { agent: { code: { model: null } } },
},
)
it.instance(
"falls back from an inferred custom mode but rejects an explicit custom mode",
() =>
withCatalog(["anthropic/code", "anthropic/custom", "anthropic/default"], "anthropic/default", (url, requests) =>
Effect.gen(function* () {
const inferred = yield* CloudDefaults.resolve()
expect(inferred.mode).toBe("code")
expect(inferred.model).toBe("anthropic/code")
const error = yield* CloudDefaults.resolve({ mode: "custom" }).pipe(Effect.flip)
expect(error).toMatchObject({
_tag: "CloudDefaultsResolutionError",
kind: "mode",
})
expect(requests).toHaveLength(1)
}).pipe(
Effect.provide(
Layer.mergeAll(
authLayer(new Auth.Api({ type: "api", key: "stored-api-token" })),
stateLayer(state()),
CloudCatalog.layer({ env: { KILO_API_URL: url.origin } }),
),
),
),
),
{
config: {
default_agent: "custom",
agent: {
code: { model: "kilo/anthropic/code" },
custom: { mode: "primary", model: "kilo/anthropic/custom" },
},
},
},
)
it.instance("rejects invalid persisted organization state and insecure catalog origins", () =>
Effect.gen(function* () {
const invalid = authLayer(oauth("stored-token", "not-a-uuid"))
const token = yield* CloudAuth.token().pipe(Effect.provide(invalid))
expect(Redacted.value(token)).toBe("stored-token")
const org = yield* CloudAuth.resolve().pipe(Effect.provide(invalid), Effect.flip)
expect(org).toMatchObject({
_tag: "CloudAuthResolutionError",
kind: "organization",
})
const catalog = yield* CloudDefaults.resolve({ env: { KILO_API_URL: "http://example.com" } }).pipe(
Effect.provide(
Layer.mergeAll(
authLayer(new Auth.Api({ type: "api", key: "stored-api-token" })),
stateLayer(state()),
CloudCatalog.layer({
env: { KILO_API_URL: "http://example.com" },
fetch: () => Promise.reject(new Error("insecure catalog request must not run")),
}),
),
),
Effect.flip,
)
expect(catalog).toMatchObject({
_tag: "CloudCatalogError",
kind: "schema",
})
}),
)
it.instance(
"uses stored auth and the resolved mode model before lower-precedence defaults",
() =>
withCatalog(
["anthropic/mode", "anthropic/saved", "anthropic/repository", "anthropic/recent", "anthropic/default"],
"anthropic/default",
(url, requests) =>
Effect.gen(function* () {
const organizationID = "11111111-1111-4111-8111-111111111111"
const resolved = yield* CloudDefaults.resolve({
env: { KILO_API_KEY: "ignored-env-token" },
}).pipe(
Effect.provide(
Layer.mergeAll(
authLayer(oauth("stored-token", organizationID)),
stateLayer(
state({
model: { plan: { providerID: "kilo", modelID: "anthropic/saved" } },
recent: [{ providerID: "kilo", modelID: "anthropic/recent" }],
}),
),
CloudCatalog.layer({ env: { KILO_API_URL: url.origin } }),
),
),
)
expect(resolved).toMatchObject({
mode: "plan",
model: "anthropic/mode",
organizationID,
})
expect(requests.map((request) => request.path)).toEqual([`/api/organizations/${organizationID}/models`])
expect(
requests.every(
(request) => request.authorization === "Bearer stored-token" && request.organization === organizationID,
),
).toBe(true)
}),
),
{
config: {
default_agent: "plan",
model: "kilo/anthropic/repository",
agent: { plan: { model: "kilo/anthropic/mode" } },
},
},
)
@@ -0,0 +1,119 @@
import { describe, expect } from "bun:test"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { Effect, Layer } from "effect"
import { Git } from "../../../src/git"
import { CloudRepository } from "../../../src/kilocode/cloud/repository"
import { tmpdirScoped } from "../../fixture/fixture"
import { testEffect } from "../../lib/effect"
const it = testEffect(Layer.mergeAll(Git.defaultLayer, CrossSpawnSpawner.defaultLayer))
const run = Effect.fn("CloudRepositoryTest.git")(function* (cwd: string, ...args: string[]) {
const git = yield* Git.Service
const result = yield* git.run(args, { cwd })
if (result.exitCode === 0) return result.text().trim()
return yield* Effect.die(new Error(result.stderr.toString("utf8")))
})
describe("CloudRepository", () => {
it.live("resolves and validates an explicit repository branch outside Git", () =>
Effect.gen(function* () {
const cwd = yield* tmpdirScoped()
const result = yield* CloudRepository.resolve({
cwd,
repo: "kilo-org/kilo",
branch: "feature/cloud-start",
})
expect(result).toEqual({
type: "github",
repo: "kilo-org/kilo",
branch: "feature/cloud-start",
})
const error = yield* CloudRepository.resolve({ cwd, repo: "kilo-org/kilo", branch: "feature.lock" }).pipe(
Effect.flip,
)
expect(error).toBeInstanceOf(CloudRepository.InvalidBranchError)
const type = yield* CloudRepository.resolve({
cwd,
repo: "https://github.com/kilo-org/kilo.git",
type: "gitlab",
}).pipe(Effect.flip)
expect(type).toBeInstanceOf(CloudRepository.InvalidRepositoryError)
}),
)
it.live("rejects GitHub repositories that become dot segments after trimming .git", () =>
Effect.gen(function* () {
const cwd = yield* tmpdirScoped()
for (const repo of ["https://github.com/kilo-org/...git", "git@github.com:kilo-org/...git"]) {
const error = yield* CloudRepository.resolve({ cwd, repo }).pipe(Effect.flip)
expect(error).toBeInstanceOf(CloudRepository.InvalidRepositoryError)
}
}),
)
it.live("normalizes an inferred GitHub SCP remote without adding the current branch", () =>
Effect.gen(function* () {
const cwd = yield* tmpdirScoped({ git: true })
yield* run(cwd, "remote", "add", "origin", "git@github.com:kilo-org/ssh-repo.git")
yield* run(cwd, "checkout", "-b", "feature/not-in-output")
const result = yield* CloudRepository.resolve({ cwd })
expect(result).toEqual({ type: "github", repo: "kilo-org/ssh-repo" })
}),
)
it.live("rejects inferred local remotes that resemble GitHub shorthand", () =>
Effect.gen(function* () {
const cwd = yield* tmpdirScoped({ git: true })
yield* run(cwd, "remote", "add", "origin", "kilo-org/local-repo")
const error = yield* CloudRepository.resolve({ cwd }).pipe(Effect.flip)
expect(error).toBeInstanceOf(CloudRepository.InvalidRepositoryError)
}),
)
it.live("prefers the tracking remote, then origin, then a sole remote fetch URL", () =>
Effect.gen(function* () {
const cwd = yield* tmpdirScoped({ git: true })
const git = yield* Git.Service
const branch = yield* git.branch(cwd)
if (!branch) yield* Effect.die(new Error("temporary repository has no current branch"))
yield* run(cwd, "remote", "add", "origin", "https://github.com/kilo-org/origin.git")
yield* run(cwd, "remote", "add", "tracked", "https://github.com/kilo-org/tracked.git")
yield* run(cwd, "remote", "set-url", "--add", "--push", "tracked", "https://github.com/kilo-org/push.git")
yield* run(cwd, "config", `branch.${branch}.remote`, "tracked")
expect(yield* CloudRepository.resolve({ cwd })).toEqual({ type: "github", repo: "kilo-org/tracked" })
yield* run(cwd, "config", `branch.${branch}.remote`, "missing")
expect(yield* CloudRepository.resolve({ cwd })).toEqual({ type: "github", repo: "kilo-org/origin" })
yield* run(cwd, "remote", "remove", "origin")
expect(yield* CloudRepository.resolve({ cwd })).toEqual({ type: "github", repo: "kilo-org/tracked" })
}),
)
it.live("returns typed errors when no remote can be selected", () =>
Effect.gen(function* () {
const cwd = yield* tmpdirScoped({ git: true })
const none = yield* CloudRepository.resolve({ cwd }).pipe(Effect.flip)
expect(none).toBeInstanceOf(CloudRepository.NoRemoteError)
yield* run(cwd, "remote", "add", "alpha", "https://github.com/kilo-org/alpha.git")
yield* run(cwd, "remote", "add", "beta", "https://github.com/kilo-org/beta.git")
const ambiguous = yield* CloudRepository.resolve({ cwd }).pipe(Effect.flip)
expect(ambiguous).toBeInstanceOf(CloudRepository.AmbiguousRemoteError)
}),
)
})
@@ -0,0 +1,134 @@
import { describe, expect, test } from "bun:test"
import { createStreamTicketClient, type StreamTicketClient } from "@/kilocode/cloud/stream-ticket"
import { parseServiceOrigin } from "@/kilocode/cloud/origin"
function jsonResponse(body: unknown, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "content-type": "application/json" },
})
}
function client(options: { fetch: ReturnType<typeof mockFetch> }): StreamTicketClient {
return createStreamTicketClient({
origin: parseServiceOrigin("https://app.example"),
apiKey: "key",
fetch: options.fetch.fetch,
})
}
describe("createStreamTicketClient", () => {
test("fetches a stream ticket from the web app", async () => {
const fetch = mockFetch().resolved(jsonResponse({ ticket: "tok", expiresAt: 1234567890 }))
const result = await client({ fetch }).fetchTicket({ cloudAgentSessionId: "agent_123" })
expect(result).toEqual({ ticket: "tok", expiresAt: 1234567890 })
expect(fetch.calls).toHaveLength(1)
const [url, init] = fetch.calls[0]!
expect(url.toString()).toBe("https://app.example/api/cloud-agent-next/sessions/stream-ticket")
expect(init).toMatchObject({
method: "POST",
redirect: "error",
headers: expect.objectContaining({
authorization: "Bearer key",
"content-type": "application/json",
}),
body: JSON.stringify({ cloudAgentSessionId: "agent_123" }),
})
expect(init?.signal).toBeInstanceOf(AbortSignal)
})
test("includes organizationId when provided", async () => {
const fetch = mockFetch().resolved(jsonResponse({ ticket: "tok", expiresAt: 1234567890 }))
await client({ fetch }).fetchTicket({
cloudAgentSessionId: "agent_123",
organizationId: "123e4567-e89b-12d3-a456-426614174000",
})
expect(fetch.calls[0]![1]).toMatchObject({
body: JSON.stringify({
cloudAgentSessionId: "agent_123",
organizationId: "123e4567-e89b-12d3-a456-426614174000",
}),
})
})
test("throws on transport failure", async () => {
const fetch = mockFetch().rejected(new Error("network error"))
await expect(client({ fetch }).fetchTicket({ cloudAgentSessionId: "agent_123" })).rejects.toThrow(
"Unable to reach Web App stream ticket endpoint",
)
})
test("retries on 403/404 and succeeds once the session becomes visible", async () => {
const fetch = mockFetch()
.resolved(jsonResponse({ error: "Organization does not own this session" }, 403))
.resolved(jsonResponse({ error: "Organization does not own this session" }, 403))
.resolved(jsonResponse({ ticket: "tok", expiresAt: 1234567890 }))
const result = await client({ fetch }).fetchTicket({ cloudAgentSessionId: "agent_123" })
expect(result).toEqual({ ticket: "tok", expiresAt: 1234567890 })
expect(fetch.calls).toHaveLength(3)
})
test("retries when a 404 response has an invalid body", async () => {
const fetch = mockFetch()
.resolved(new Response("not json", { status: 404 }))
.resolved(jsonResponse({ ticket: "tok", expiresAt: 1234567890 }))
const result = await client({ fetch }).fetchTicket({ cloudAgentSessionId: "agent_123" })
expect(result).toEqual({ ticket: "tok", expiresAt: 1234567890 })
expect(fetch.calls).toHaveLength(2)
})
test("gives up after repeated 403 responses", async () => {
const fetch = mockFetch().repeated(() => jsonResponse({ error: "Denied" }, 403))
await expect(client({ fetch }).fetchTicket({ cloudAgentSessionId: "agent_123" })).rejects.toThrow("Denied")
expect(fetch.calls).toHaveLength(10)
}, 15_000)
test("throws on authentication failure", async () => {
const fetch = mockFetch().resolved(jsonResponse({ error: "Unauthorized" }, 401))
await expect(client({ fetch }).fetchTicket({ cloudAgentSessionId: "agent_123" })).rejects.toThrow("Unauthorized")
})
test("throws on invalid response", async () => {
const fetch = mockFetch().resolved(jsonResponse({ missing: "fields" }))
await expect(client({ fetch }).fetchTicket({ cloudAgentSessionId: "agent_123" })).rejects.toThrow(
"Web App returned an invalid stream ticket response",
)
})
})
function mockFetch() {
const calls: [URL | RequestInfo, RequestInit | undefined][] = []
const sequence: (() => Response | Promise<Response>)[] = []
let fallback: (() => Response | Promise<Response>) | undefined
const self = {
calls,
resolved(value: Response) {
sequence.push(() => value)
return self
},
repeated(factory: () => Response) {
fallback = factory
return self
},
rejected(error: unknown) {
sequence.push(() => Promise.reject(error))
return self
},
get fetch(): typeof fetch {
return ((input: URL | RequestInfo, init?: RequestInit) => {
calls.push([input, init])
const next = sequence.shift() ?? fallback
if (!next) throw new Error("unexpected fetch call")
return Promise.resolve(next())
}) as typeof fetch
},
}
return self
}
@@ -0,0 +1,260 @@
import { describe, expect, test } from "bun:test"
import {
AgentSendRequestSchema,
AgentStartRequestSchema,
GetMessageResultInputSchema,
MessageIdSchema,
} from "../../../src/kilocode/cloud/contracts"
import { parseServiceOrigin } from "../../../src/kilocode/cloud/origin"
import { MAX_CLOUD_AGENT_RESPONSE_BYTES } from "../../../src/kilocode/cloud/response-json"
import { createCloudAgentClient } from "../../../src/kilocode/cloud/trpc"
const SESSION = "agent_12345678-1234-1234-1234-123456789abc"
const OTHER_SESSION = "agent_abcdefab-cdef-4abc-8def-abcdefabcdef"
const MESSAGE = "msg_018f1e2d3c4bAbCdEfGhIjKlMn"
const OTHER_MESSAGE = "msg_018f1e2d3c4bZyXwVuTsRqPoNm"
const SESSION_MESSAGE = "msg_018f1e2d3c4bQrStUvWxYzAbCd"
const TOKEN = "secret-bearer-value"
type Seen = {
readonly url: string
readonly auth: string | null
readonly body: string
}
function success(data: unknown) {
return Response.json({ result: { data } })
}
describe("Cloud Agent transport", () => {
test("places bearer auth only in headers and correlates generated message identities", async () => {
const seen: Seen[] = []
const ids: string[] = []
const server = Bun.serve({
hostname: "127.0.0.1",
port: 0,
async fetch(request) {
const url = new URL(request.url)
const body = request.method === "POST" ? await request.text() : ""
seen.push({ url: url.toString(), auth: request.headers.get("authorization"), body })
if (url.pathname === "/trpc/start") {
const input = AgentStartRequestSchema.parse(JSON.parse(body) as unknown)
const id = MessageIdSchema.parse(input.message.id)
ids.push(id)
return success({
cloudAgentSessionId: input.message.prompt === "invalid-session" ? "invalid" : SESSION,
kiloSessionId: "ses_123",
messageId: input.message.prompt === "mismatch" ? OTHER_MESSAGE : id,
delivery: "queued",
})
}
if (url.pathname === "/trpc/send") {
const input = AgentSendRequestSchema.parse(JSON.parse(body) as unknown)
const id = MessageIdSchema.parse(input.message.id)
ids.push(id)
return success({
cloudAgentSessionId:
input.message.prompt === "mismatch-session" ? OTHER_SESSION : input.cloudAgentSessionId,
status: "started",
streamUrl: "wss://cloud-agent.example/stream",
messageId: input.message.prompt === "mismatch-send" ? OTHER_MESSAGE : id,
delivery: "queued",
})
}
if (url.pathname === "/trpc/getMessageResult") {
const raw = url.searchParams.get("input")
const input = GetMessageResultInputSchema.parse(JSON.parse(raw ?? "null") as unknown)
return success({
cloudAgentSessionId: input.messageId === SESSION_MESSAGE ? OTHER_SESSION : input.cloudAgentSessionId,
messageId: input.messageId === OTHER_MESSAGE ? MESSAGE : input.messageId,
status: "failed",
createdAt: 1,
terminalAt: 2,
completionSource: "delivery_failure",
failure: {
stage: "pre_dispatch",
code: "workspace_setup_failed",
subtype: "git_clone_timeout",
attempts: 1,
message: "Repository clone timed out",
retryable: true,
},
})
}
return new Response(null, { status: 404 })
},
})
try {
const agent = createCloudAgentClient({
origin: parseServiceOrigin(server.url.origin, { allowHttpLoopback: true }),
apiKey: TOKEN,
})
const start = await agent.start({
message: { prompt: "Inspect the repository" },
agent: { mode: "code", model: "anthropic/claude-sonnet-4" },
repository: { type: "github", repo: "Kilo-Org/kilocode" },
options: { createdOnPlatform: "kilo-cli" },
})
const send = await agent.send({
cloudAgentSessionId: start.cloudAgentSessionId,
message: { prompt: "Continue" },
})
const result = await agent.getMessageResult({
cloudAgentSessionId: send.cloudAgentSessionId,
messageId: send.messageId,
})
expect(MessageIdSchema.safeParse(start.messageId).success).toBe(true)
expect(MessageIdSchema.safeParse(send.messageId).success).toBe(true)
expect(ids).toEqual([start.messageId, send.messageId])
expect(result.failure).toEqual({
stage: "pre_dispatch",
code: "workspace_setup_failed",
subtype: "git_clone_timeout",
attempts: 1,
message: "Repository clone timed out",
retryable: true,
})
expect(seen).toHaveLength(3)
expect(seen.every((request) => request.auth === `Bearer ${TOKEN}`)).toBe(true)
expect(seen.every((request) => !request.url.includes(TOKEN) && !request.body.includes(TOKEN))).toBe(true)
const error = await agent
.start({
message: { prompt: "mismatch" },
agent: { mode: "code", model: "anthropic/claude-sonnet-4" },
repository: { type: "github", repo: "Kilo-Org/kilocode" },
options: { createdOnPlatform: "kilo-cli" },
})
.then(
() => new Error("Expected start correlation to fail"),
(cause: unknown) => (cause instanceof Error ? cause : new Error("Non-error rejection")),
)
expect(error.message).toBe("Cloud Agent start outcome is unknown; do not retry automatically")
expect(error.message).not.toContain(TOKEN)
const malformed = await agent
.start({
message: { prompt: "invalid-session" },
agent: { mode: "code", model: "anthropic/claude-sonnet-4" },
repository: { type: "github", repo: "Kilo-Org/kilocode" },
options: { createdOnPlatform: "kilo-cli" },
})
.then(
() => new Error("Expected invalid session ID to fail"),
(cause: unknown) => (cause instanceof Error ? cause : new Error("Non-error rejection")),
)
expect(malformed.message).toBe("Cloud Agent start outcome is unknown; do not retry automatically")
const sendError = await agent.send({ cloudAgentSessionId: SESSION, message: { prompt: "mismatch-send" } }).then(
() => new Error("Expected send correlation to fail"),
(cause: unknown) => (cause instanceof Error ? cause : new Error("Non-error rejection")),
)
expect(sendError.message).toBe("Cloud Agent send outcome is unknown; do not retry automatically")
const resultError = await agent.getMessageResult({ cloudAgentSessionId: SESSION, messageId: OTHER_MESSAGE }).then(
() => new Error("Expected result correlation to fail"),
(cause: unknown) => (cause instanceof Error ? cause : new Error("Non-error rejection")),
)
expect(resultError.message).toBe("Cloud Agent returned an invalid response")
const sendSessionError = await agent
.send({ cloudAgentSessionId: SESSION, message: { prompt: "mismatch-session" } })
.then(
() => new Error("Expected send session correlation to fail"),
(cause: unknown) => (cause instanceof Error ? cause : new Error("Non-error rejection")),
)
expect(sendSessionError.message).toBe("Cloud Agent send outcome is unknown; do not retry automatically")
const resultSessionError = await agent
.getMessageResult({ cloudAgentSessionId: SESSION, messageId: SESSION_MESSAGE })
.then(
() => new Error("Expected result session correlation to fail"),
(cause: unknown) => (cause instanceof Error ? cause : new Error("Non-error rejection")),
)
expect(resultSessionError.message).toBe("Cloud Agent returned an invalid response")
} finally {
await server.stop(true)
}
})
test("rejects malformed send responses before they cross the client boundary", async () => {
const server = Bun.serve({
hostname: "127.0.0.1",
port: 0,
fetch() {
return success({
cloudAgentSessionId: "invalid",
status: "started",
streamUrl: "",
messageId: MESSAGE,
delivery: "queued",
})
},
})
try {
const agent = createCloudAgentClient({
origin: parseServiceOrigin(server.url.origin, { allowHttpLoopback: true }),
apiKey: TOKEN,
id: () => MESSAGE,
})
const error = await agent.send({ cloudAgentSessionId: SESSION, message: { prompt: "Continue" } }).then(
() => new Error("Expected send to fail"),
(cause: unknown) => (cause instanceof Error ? cause : new Error("Non-error rejection")),
)
expect(error.message).toBe("Cloud Agent send outcome is unknown; do not retry automatically")
} finally {
await server.stop(true)
}
})
test("treats redirects, oversized bodies, and malformed envelopes as unknown start outcomes", async () => {
const server = Bun.serve({
hostname: "127.0.0.1",
port: 0,
async fetch(request) {
const input = AgentStartRequestSchema.parse((await request.json()) as unknown)
if (input.message.prompt === "redirect") {
return new Response(null, { status: 302, headers: { location: "/elsewhere" } })
}
if (input.message.prompt === "oversized") {
return new Response(null, {
headers: { "content-length": String(MAX_CLOUD_AGENT_RESPONSE_BYTES + 1) },
})
}
if (input.message.prompt === "unavailable") return new Response(null, { status: 503 })
return Response.json({ invalid: true })
},
})
try {
const agent = createCloudAgentClient({
origin: parseServiceOrigin(server.url.origin, { allowHttpLoopback: true }),
apiKey: TOKEN,
})
const start = (prompt: string) =>
agent.start({
message: { prompt },
agent: { mode: "code", model: "anthropic/claude-sonnet-4" },
repository: { type: "github", repo: "Kilo-Org/kilocode" },
options: { createdOnPlatform: "kilo-cli" },
})
for (const prompt of ["redirect", "oversized", "malformed", "unavailable"]) {
const error = await start(prompt).then(
() => new Error("Expected start to fail"),
(cause: unknown) => (cause instanceof Error ? cause : new Error("Non-error rejection")),
)
expect(error.message).toBe("Cloud Agent start outcome is unknown; do not retry automatically")
}
} finally {
await server.stop(true)
}
})
})
@@ -0,0 +1,281 @@
import { describe, expect, test } from "bun:test"
import { streamAgentEvents } from "@/kilocode/cloud/websocket-stream"
function mockWebSocket(
events: ReadonlyArray<
{ type: "message"; data: string | ArrayBuffer } | { type: "error" } | { type: "close"; code?: number }
>,
options?: { onClose?: (code?: number) => void; triggerOnCloseOnClose?: boolean },
) {
return class MockWebSocket {
onmessage: ((event: MessageEvent) => void) | null = null
onerror: (() => void) | null = null
onclose: ((event: CloseEvent) => void) | null = null
constructor(_url: string) {
queueMicrotask(() => {
for (const event of events) {
if (event.type === "message") {
this.onmessage?.(new MessageEvent("message", { data: event.data }))
} else if (event.type === "error") {
this.onerror?.()
return
} else if (event.type === "close") {
this.onclose?.({ code: event.code ?? 1000 } as CloseEvent)
return
}
}
})
}
close(code?: number) {
options?.onClose?.(code)
if (options?.triggerOnCloseOnClose) {
queueMicrotask(() => {
this.onclose?.({ code: code ?? 1000 } as CloseEvent)
})
}
}
}
}
describe("streamAgentEvents", () => {
test("writes WebSocket text messages as lines", async () => {
const lines: string[] = []
const Socket = mockWebSocket([
{ type: "message", data: '{"event":"one"}' },
{ type: "message", data: '{"event":"two"}' },
{ type: "close" },
])
await streamAgentEvents({
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=tok",
origin: "https://agent.example",
writeLine: (line) => {
lines.push(line)
},
WebSocket: Socket as unknown as typeof WebSocket,
})
expect(lines).toEqual(['{"event":"one"}', '{"event":"two"}'])
})
test("resolves an absolute wss URL", async () => {
const Socket = mockWebSocket([{ type: "close" }])
const connectUrl: string[] = []
class Tracked extends Socket {
constructor(url: string) {
super(url)
connectUrl.push(url)
}
}
await streamAgentEvents({
streamUrl: "wss://agent.example/stream?ticket=tok",
origin: "https://agent.example",
writeLine: () => {},
WebSocket: Tracked as unknown as typeof WebSocket,
})
expect(connectUrl).toEqual(["wss://agent.example/stream?ticket=tok"])
})
test("rejects an absolute stream URL on another origin", async () => {
const Socket = mockWebSocket([{ type: "close" }])
await expect(
streamAgentEvents({
streamUrl: "wss://other.example/stream?ticket=tok",
origin: "https://agent.example",
writeLine: () => {},
WebSocket: Socket as unknown as typeof WebSocket,
}),
).rejects.toThrow("Invalid stream URL origin")
})
test("converts a relative URL to an absolute wss URL", async () => {
const Socket = mockWebSocket([{ type: "close" }])
const connectUrl: string[] = []
class Tracked extends Socket {
constructor(url: string) {
super(url)
connectUrl.push(url)
}
}
await streamAgentEvents({
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=tok",
origin: "https://agent.example",
writeLine: () => {},
WebSocket: Tracked as unknown as typeof WebSocket,
})
expect(connectUrl).toEqual(["wss://agent.example/stream?cloudAgentSessionId=agent_123&ticket=tok"])
})
test("rejects when the WebSocket errors", async () => {
const Socket = mockWebSocket([{ type: "error" }])
await expect(
streamAgentEvents({
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=tok",
origin: "https://agent.example",
writeLine: () => {},
WebSocket: Socket as unknown as typeof WebSocket,
}),
).rejects.toThrow("WebSocket stream connection failed")
})
test("rejects when the WebSocket closes abnormally", async () => {
const Socket = mockWebSocket([{ type: "close", code: 1011 }])
await expect(
streamAgentEvents({
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=tok",
origin: "https://agent.example",
writeLine: () => {},
WebSocket: Socket as unknown as typeof WebSocket,
}),
).rejects.toThrow("WebSocket stream closed unexpectedly (1011)")
})
test("rejects when the WebSocket stream stalls", async () => {
const Socket = mockWebSocket([])
await expect(
streamAgentEvents({
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=tok",
origin: "https://agent.example",
writeLine: () => {},
WebSocket: Socket as unknown as typeof WebSocket,
timeoutMs: 1,
}),
).rejects.toThrow("WebSocket stream timed out")
})
test("resolves 3 seconds after receiving a complete event", async () => {
const lines: string[] = []
const codes: Array<number | undefined> = []
const Socket = mockWebSocket(
[
{ type: "message", data: '{"event":"running"}' },
{ type: "message", data: '{"streamEventType":"complete","data":{"exitCode":0}}' },
],
{ onClose: (code) => codes.push(code), triggerOnCloseOnClose: true },
)
const start = Date.now()
await streamAgentEvents({
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=tok",
origin: "https://agent.example",
writeLine: (line) => {
lines.push(line)
},
WebSocket: Socket as unknown as typeof WebSocket,
})
expect(Date.now() - start).toBeGreaterThanOrEqual(3000)
expect(codes).toEqual([1000])
expect(lines).toEqual(['{"event":"running"}', '{"streamEventType":"complete","data":{"exitCode":0}}'])
}, 10_000)
test("flushes slow writes in order before resolving", async () => {
const lines: string[] = []
const Socket = mockWebSocket([
{ type: "message", data: '{"event":"one"}' },
{ type: "message", data: '{"event":"two"}' },
{ type: "close" },
])
await streamAgentEvents({
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=tok",
origin: "https://agent.example",
writeLine: async (line) => {
await new Promise((resolve) => setTimeout(resolve, 10))
lines.push(line)
},
WebSocket: Socket as unknown as typeof WebSocket,
})
expect(lines).toEqual(['{"event":"one"}', '{"event":"two"}'])
})
test("flushes slow writes in order before rejecting a transport failure", async () => {
const lines: string[] = []
const Socket = mockWebSocket([
{ type: "message", data: '{"event":"one"}' },
{ type: "message", data: '{"event":"two"}' },
{ type: "close", code: 1011 },
])
await expect(
streamAgentEvents({
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=tok",
origin: "https://agent.example",
writeLine: async (line) => {
await new Promise((resolve) => setTimeout(resolve, 10))
lines.push(line)
},
WebSocket: Socket as unknown as typeof WebSocket,
}),
).rejects.toThrow("WebSocket stream closed unexpectedly (1011)")
expect(lines).toEqual(['{"event":"one"}', '{"event":"two"}'])
})
test("bounds transport failure draining when an output write stalls", async () => {
const Socket = mockWebSocket([
{ type: "message", data: '{"event":"one"}' },
{ type: "close", code: 1011 },
])
await expect(
streamAgentEvents({
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=tok",
origin: "https://agent.example",
writeLine: () => new Promise(() => {}),
WebSocket: Socket as unknown as typeof WebSocket,
timeoutMs: 10,
}),
).rejects.toThrow("WebSocket stream closed unexpectedly (1011)")
})
test("rejects when a stream output write fails", async () => {
const lines: string[] = []
const Socket = mockWebSocket([
{ type: "message", data: '{"event":"one"}' },
{ type: "message", data: '{"event":"two"}' },
{ type: "close" },
])
await expect(
streamAgentEvents({
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=tok",
origin: "https://agent.example",
writeLine: (line) => {
if (lines.length > 0) throw new Error("EPIPE")
lines.push(line)
},
WebSocket: Socket as unknown as typeof WebSocket,
}),
).rejects.toThrow("WebSocket stream output failed")
expect(lines).toEqual(['{"event":"one"}'])
})
test("rejects when queued stream output exceeds the memory bound", async () => {
const line = "x".repeat(1024)
const Socket = mockWebSocket(
Array.from({ length: 9000 }, () => ({ type: "message" as const, data: line })),
)
await expect(
streamAgentEvents({
streamUrl: "/stream?cloudAgentSessionId=agent_123&ticket=tok",
origin: "https://agent.example",
writeLine: () => new Promise(() => {}),
WebSocket: Socket as unknown as typeof WebSocket,
}),
).rejects.toThrow("WebSocket stream output consumer is too slow")
})
})
@@ -1,5 +1,6 @@
import { describe, test, expect } from "bun:test"
import path from "path"
import yargs from "yargs"
import { generateHelp, generateCommandTable } from "../../src/kilocode/help"
import { AcpCommand } from "../../src/cli/cmd/acp"
import { McpCommand } from "../../src/cli/cmd/mcp"
@@ -26,6 +27,7 @@ import { HelpCommand } from "../../src/kilocode/help-command"
import { ProfileCommand } from "../../src/kilocode/cli/cmd/profile"
import { DaemonCommand } from "../../src/kilocode/cli/cmd/daemon"
import { KiloConsoleCommand } from "../../src/kilocode/cli/cmd/console"
import { CloudCommand } from "../../src/kilocode/cli/cmd/cloud"
// Stand-in for TuiThreadCommand — the real one imports @opentui/solid which
// doesn't resolve in the test environment. Only command/describe matter here.
@@ -76,6 +78,7 @@ const commands = [
ProfileCommand,
DaemonCommand,
KiloConsoleCommand,
CloudCommand,
HelpCommand,
CompletionStub,
] as any[]
@@ -140,6 +143,29 @@ describe("kilo help <command>", () => {
})
})
describe("kilo cloud help", () => {
async function parser() {
const cli = yargs([])
.scriptName("kilo cloud")
.exitProcess(false)
.help()
.fail((msg, err) => {
throw err ?? new Error(msg)
})
if (typeof CloudCommand.builder !== "function") throw new Error("cloud command builder is missing")
return await CloudCommand.builder(cli)
}
test("requires a subcommand and exposes only the public Cloud Agent operations", async () => {
const bare = await parser()
await expect(Promise.resolve().then(() => bare.parseAsync([]))).rejects.toThrow()
const help = await (await parser()).getHelp()
const names = [...help.matchAll(/^\s*kilo cloud ([a-z][a-z-]*)\b/gm)].map((match) => match[1])
expect([...new Set(names)].sort()).toEqual(["result", "send", "start", "status"])
})
})
describe("edge cases", () => {
test("output contains no ANSI escape sequences", async () => {
const output = await generateHelp({ all: true, format: "md", commands })