fix: vscode extension messages

This commit is contained in:
Catriel Müller
2026-02-23 22:57:03 -03:00
parent 210b889bc5
commit 656d595fb6
7 changed files with 75 additions and 31 deletions
+7 -17
View File
@@ -486,9 +486,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
(event) => {
const sessionId = this.connectionService.resolveEventSessionId(event)
// message.part.updated is always session-scoped; if we can't determine the session, drop it.
// message.part.updated and message.part.delta are always session-scoped; drop if session unknown.
if (!sessionId) {
return event.type !== "message.part.updated"
return event.type !== "message.part.updated" && event.type !== "message.part.delta"
}
return this.trackedSessionIds.has(sessionId)
@@ -658,15 +658,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
})
.catch((err) => console.error("[Kilo New] KiloProvider: Failed to fetch session statuses:", err))
// Convert to webview format, including cost/tokens for assistant messages
const messages = messagesData.map((m) => ({
id: m.info.id,
sessionID: m.info.sessionID,
role: m.info.role,
...m.info,
parts: m.parts,
createdAt: new Date(m.info.time.created).toISOString(),
cost: m.info.cost,
tokens: m.info.tokens,
}))
for (const message of messages) {
@@ -705,13 +700,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
const messagesData = await this.httpClient.getMessages(sessionID, workspaceDir)
const messages = messagesData.map((m) => ({
id: m.info.id,
sessionID: m.info.sessionID,
role: m.info.role,
...m.info,
parts: m.parts,
createdAt: new Date(m.info.time.created).toISOString(),
cost: m.info.cost,
tokens: m.info.tokens,
}))
for (const message of messages) {
@@ -1445,8 +1436,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
// Events without sessionID (server.connected, server.heartbeat) → always forward
// Events with sessionID → only forward if this webview tracks that session
// message.part.updated is always session-scoped; if we can't determine the session, drop it to avoid cross-webview leakage.
if (!sessionID && event.type === "message.part.updated") {
// message.part.updated and message.part.delta are always session-scoped; drop if session unknown.
if (!sessionID && (event.type === "message.part.updated" || event.type === "message.part.delta")) {
return
}
if (sessionID && !this.trackedSessionIds.has(sessionID)) {
@@ -1568,8 +1559,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
const activeEditor = vscode.window.activeTextEditor
const activeRel =
activeEditor?.document.uri.scheme === "file" ? toRelative(activeEditor.document.uri.fsPath) : undefined
const activeFile =
activeRel && controller.validateAccess(activeEditor!.document.uri.fsPath) ? activeRel : undefined
const activeFile = activeRel && controller.validateAccess(activeEditor!.document.uri.fsPath) ? activeRel : undefined
// Shell
const shell = vscode.env.shell || undefined
@@ -40,7 +40,7 @@ export type WebviewMessage =
}
| {
type: "messageCreated"
message: { id: string; sessionID: string; role: string; createdAt: string; cost?: number; tokens?: unknown }
message: Record<string, unknown>
}
| { type: "sessionStatus"; sessionID: string; status: string; attempt?: number; message?: string; next?: number }
| {
@@ -75,18 +75,27 @@ export function mapSSEEventToWebviewMessage(event: SSEEvent, sessionID: string |
delta: event.properties.delta ? { type: "text-delta", textDelta: event.properties.delta } : undefined,
}
}
case "message.updated":
case "message.part.delta": {
const props = event.properties
if (!sessionID) return null
return {
type: "partUpdated",
sessionID: props.sessionID,
messageID: props.messageID,
part: { id: props.partID, type: "text", messageID: props.messageID, text: props.delta },
delta: { type: "text-delta", textDelta: props.delta },
}
}
case "message.updated": {
const info = event.properties.info
return {
type: "messageCreated",
message: {
id: event.properties.info.id,
sessionID: event.properties.info.sessionID,
role: event.properties.info.role,
createdAt: new Date(event.properties.info.time.created).toISOString(),
cost: event.properties.info.cost,
tokens: event.properties.info.tokens,
...info,
createdAt: new Date(info.time.created).toISOString(),
},
}
}
case "session.status": {
const info = event.properties.status
return {
@@ -32,6 +32,8 @@ export function resolveEventSessionId(
}
return lookupMessageSessionId(part.messageID)
}
case "message.part.delta":
return event.properties.sessionID
case "permission.asked":
case "permission.replied":
case "question.asked":
@@ -35,7 +35,15 @@ export interface MessageInfo {
created: number
completed?: number
}
// Present on assistant messages
agent?: string
providerID?: string
modelID?: string
model?: { providerID: string; modelID: string }
mode?: string
parentID?: string
path?: { cwd: string; root: string }
error?: { name: string; data?: Record<string, unknown> }
summary?: { title?: string; body?: string; diffs?: unknown[] } | boolean
cost?: number
tokens?: TokenUsage
}
@@ -76,6 +84,10 @@ export type SSEEvent =
| { type: "session.idle"; properties: { sessionID: string } }
| { type: "message.updated"; properties: { info: MessageInfo } }
| { type: "message.part.updated"; properties: { part: MessagePart; delta?: string } }
| {
type: "message.part.delta"
properties: { sessionID: string; messageID: string; partID: string; field: string; delta: string }
}
| { type: "permission.asked"; properties: PermissionRequest }
| {
type: "permission.replied"
+23 -4
View File
@@ -12,7 +12,7 @@ import Settings from "./components/Settings"
import ProfileView from "./components/ProfileView"
import { VSCodeProvider, useVSCode } from "./context/vscode"
import { ServerProvider, useServer } from "./context/server"
import { ProviderProvider } from "./context/provider"
import { ProviderProvider, useProvider } from "./context/provider"
import { ConfigProvider } from "./context/config"
import { SessionProvider, useSession } from "./context/session"
import { LanguageProvider } from "./context/language"
@@ -50,10 +50,12 @@ const DummyView: Component<{ title: string }> = (props) => {
export const DataBridge: Component<{ children: any }> = (props) => {
const session = useSession()
const vscode = useVSCode()
const prov = useProvider()
const data = createMemo(() => {
const id = session.currentSessionID()
const perms = id ? session.permissions().filter((p) => p.sessionID === id) : []
const qs = id ? session.questions() : []
return {
session: session.sessions().map((s) => ({ ...s, id: s.id, role: "user" as const })) as unknown as any[],
session_status: {} as Record<string, any>,
@@ -68,6 +70,12 @@ export const DataBridge: Component<{ children: any }> = (props) => {
)
: {},
permission: id ? { [id]: perms as unknown as any[] } : {},
question: id ? { [id]: qs as unknown as any[] } : {},
provider: {
all: Object.values(prov.providers()) as unknown as any[],
connected: prov.connected(),
default: prov.defaults(),
} as unknown as any,
}
})
@@ -75,8 +83,12 @@ export const DataBridge: Component<{ children: any }> = (props) => {
session.respondToPermission(input.permissionID, input.response)
}
const sync = (sessionID: string) => {
session.syncSession(sessionID)
const reply = (input: { requestID: string; answers: string[][] }) => {
session.replyToQuestion(input.requestID, input.answers)
}
const reject = (input: { requestID: string }) => {
session.rejectQuestion(input.requestID)
}
const open = (filePath: string, line?: number, column?: number) => {
@@ -84,7 +96,14 @@ export const DataBridge: Component<{ children: any }> = (props) => {
}
return (
<DataProvider data={data()} directory="" onPermissionRespond={respond} onSyncSession={sync} onOpenFile={open}>
<DataProvider
data={data()}
directory=""
onPermissionRespond={respond}
onQuestionReply={reply}
onQuestionReject={reject}
onOpenFile={open}
>
{props.children}
</DataProvider>
)
@@ -659,11 +659,13 @@ export const SessionProvider: ParentComponent = (props) => {
const sid = currentSessionID()
if (sid) {
const tempId = `optimistic-${crypto.randomUUID()}`
const now = Date.now()
const temp: Message = {
id: tempId,
sessionID: sid,
role: "user",
createdAt: new Date().toISOString(),
createdAt: new Date(now).toISOString(),
time: { created: now },
}
setStore("messages", sid, (msgs = []) => [...msgs, temp])
setStore("parts", tempId, [{ type: "text" as const, id: `${tempId}-text`, text }])
@@ -92,6 +92,16 @@ export interface Message {
content?: string
parts?: Part[]
createdAt: string
time?: { created: number; completed?: number }
agent?: string
model?: { providerID: string; modelID: string }
providerID?: string
modelID?: string
mode?: string
parentID?: string
path?: { cwd: string; root: string }
error?: { name: string; data?: Record<string, unknown> }
summary?: { title?: string; body?: string; diffs?: unknown[] } | boolean
cost?: number
tokens?: TokenUsage
}