Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/kiloclaw-kilo-chat-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"kilo-code": minor
"@kilocode/cli": minor
"@kilocode/kilo-gateway": minor
"@kilocode/sdk": minor
---

Migrate KiloClaw chat to the new kilo-chat backend. Replaces the single-channel Stream Chat integration with a multi-conversation experience that matches the web UX at app.kilo.ai/claw/kilo-chat: conversation list, reactions, typing indicators, editing, and action approvals. The TUI continues to render a single chat view backed by the user's primary conversation.
9 changes: 0 additions & 9 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,7 @@
"patchedDependencies": {
"@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch",
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
"stream-chat@9.38.0": "patches/stream-chat@9.38.0.patch"
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch"
},
"version": "7.2.30",
"peerDependencies": {}
Expand Down
18 changes: 18 additions & 0 deletions packages/kilo-gateway/src/api/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ export const DEFAULT_KILO_API_URL = "https://api.kilo.ai"
/** Base URL for Kilo API - can be overridden by KILO_API_URL env var */
export const KILO_API_BASE = process.env[ENV_KILO_API_URL] || DEFAULT_KILO_API_URL

/** Environment variable for custom Kilo Chat URL */
export const KILO_CHAT_URL_ENV = "KILO_CHAT_URL"

/** Default Kilo Chat URL (REST endpoint for messages, conversations, etc.) */
export const KILO_DEFAULT_CHAT_URL = "https://chat.kiloapps.io"

/** Base URL for Kilo Chat - can be overridden by KILO_CHAT_URL env var */
export const KILO_CHAT_URL = process.env[KILO_CHAT_URL_ENV] || KILO_DEFAULT_CHAT_URL

/** Environment variable for custom Event Service URL */
export const KILO_EVENT_SERVICE_URL_ENV = "EVENT_SERVICE_URL"

/** Default Event Service URL (WebSocket endpoint for kilo-chat events) */
export const KILO_DEFAULT_EVENT_SERVICE_URL = "wss://events.kiloapps.io"

/** Base URL for Event Service - can be overridden by EVENT_SERVICE_URL env var */
export const KILO_EVENT_SERVICE_URL = process.env[KILO_EVENT_SERVICE_URL_ENV] || KILO_DEFAULT_EVENT_SERVICE_URL

/** Default base URL for OpenRouter-compatible endpoint */
export const KILO_OPENROUTER_BASE = `${KILO_API_BASE}/api/openrouter`

Expand Down
72 changes: 37 additions & 35 deletions packages/kilo-gateway/src/server/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
import { fetchProfile, fetchBalance } from "../api/profile.js"
import { fetchKilocodeNotifications, KilocodeNotificationSchema } from "../api/notifications.js"
import { fetchOrganizationModes, clearModesCache } from "../api/modes.js"
import { KILO_API_BASE, HEADER_FEATURE, HEADER_ORGANIZATIONID } from "../api/constants.js"
import {
KILO_API_BASE,
KILO_CHAT_URL,
KILO_EVENT_SERVICE_URL,
HEADER_FEATURE,
HEADER_ORGANIZATIONID,
} from "../api/constants.js"
import { buildKiloHeaders } from "../headers.js"
import type { ImportDeps, DrizzleDb } from "../cloud-sessions.js"
import { fetchCloudSession, fetchCloudSessionForImport, importSessionToDb } from "../cloud-sessions.js"
Expand Down Expand Up @@ -536,6 +542,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
channelCount: z.number().optional(),
secretCount: z.number().optional(),
userId: z.string().optional(),
botName: z.string().nullable().optional(),
}),
),
},
Expand Down Expand Up @@ -578,57 +585,52 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
"/claw/chat-credentials",
describeRoute({
summary: "Get KiloClaw chat credentials",
description: "Fetch Stream Chat credentials for the user's KiloClaw instance",
description:
"Returns the bearer token and endpoint URLs the client uses to talk to the Kilo Chat worker " +
"and the Event Service. The bearer is the user's existing long-lived Kilo JWT — kilo-chat and " +
"event-service both verify it directly with NEXTAUTH_SECRET, so no separate token mint is needed.",
operationId: "kilo.claw.chatCredentials",
responses: {
200: {
description: "Stream Chat credentials or null",
description: "Kilo Chat credentials or null",
content: {
"application/json": {
schema: resolver(
z
.object({
apiKey: z.string(),
userId: z.string(),
userToken: z.string(),
channelId: z.string(),
token: z.string(),
expiresAt: z.string(),
kiloChatUrl: z.string(),
eventServiceUrl: z.string(),
})
.nullable(),
),
},
},
},
...errors(401, 502),
...errors(401),
},
}),
async (c: any) => {
try {
const auth = await Auth.get("kilo")
if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined
if (!token) return c.json({ error: "No valid token found" }, 401)

const organizationId = auth.type === "oauth" ? auth.accountId : undefined
const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
}
if (organizationId) {
headers[HEADER_ORGANIZATIONID] = organizationId
}

const response = await fetch(`${KILO_API_BASE}/api/kiloclaw/chat-credentials`, { headers })

if (!response.ok) {
const text = await response.text()
return c.json({ error: `KiloClaw request failed: ${response.status} ${text}` }, response.status as any)
}

return c.json(await response.json())
} catch (err: any) {
console.error("[Kilo Gateway] claw/chat-credentials: error", err?.message ?? err)
return c.json({ error: "Failed to reach KiloClaw" }, 502)
}
const auth = await Auth.get("kilo")
if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined
if (!token) return c.json({ error: "No valid token found" }, 401)

// For OAuth, expires is a millisecond epoch we already track. For
// API tokens we don't have a verified expiry locally — the JWT is
// signed by the cloud and validated by kilo-chat/event-service on
// every request. Use a far-future placeholder so the client cache
// doesn't refetch unnecessarily; on 401 the client clears the
// cache and prompts re-auth.
const expiresAtMs = auth.type === "oauth" ? auth.expires : Date.now() + 365 * 24 * 60 * 60 * 1000

return c.json({
token,
expiresAt: new Date(expiresAtMs).toISOString(),
kiloChatUrl: KILO_CHAT_URL,
eventServiceUrl: KILO_EVENT_SERVICE_URL,
})
},
)
.get(
Expand Down
1 change: 0 additions & 1 deletion packages/kilo-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,6 @@
"quick-lru": "^7.0.0",
"simple-git": "3.35.2",
"solid-js": "^1.9.11",
"stream-chat": "9.38.0",
"uri-js": "^4.4.1",
"virtua": "catalog:",
"web-tree-sitter": "^0.24.7",
Expand Down
Loading