feat: promote Kilo mobile app to Cloud Agent users

Show a dismissible in-app notice pointing Cloud Agent (/remote) users at
the Kilo mobile app, in both the VS Code extension and the CLI.

- CLI: Notices.markCloudAgentUsed() (src/kilocode/notices.ts) persists a
  machine-global flag under Global.Path.state/notices.json when
  enableRemote() succeeds (src/kilo-sessions/kilo-sessions.ts). A new
  local kilo serve endpoint (kilocode.mobileAppNotice /
  kilocode.dismissMobileAppNotice) exposes/gates this flag for any
  client. The TUI session route shows the existing DialogRetryAction
  dialog and persists dismissal via the endpoint.
- VS Code: fetchAndSendNotifications() merges a locally-constructed
  notification into the existing KiloNotifications banner when the
  local kilo serve reports show=true, reusing the existing
  kilo.dismissedNotificationIds globalState dismiss flow and also
  propagating dismissal back to the CLI-side flag.
- Regenerated the SDK for the new kilocode.mobileAppNotice /
  dismissMobileAppNotice endpoints.
This commit is contained in:
arkadiykondrashov
2026-07-03 09:08:51 +00:00
parent 4c07a1db51
commit 68d36cd649
13 changed files with 1637 additions and 935 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"kilo-code": minor
"@kilocode/cli": minor
---
Show a dismissible notice promoting the Kilo mobile app to users who have previously used Cloud Agents (the `/remote` command in the CLI). The notice links to the mobile app announcement and stays hidden for everyone else, and once dismissed it never shows again.
@@ -33,6 +33,29 @@ export interface NotificationsContext {
notify: (id: string) => void
}
const MOBILE_APP_NOTICE_ID = "mobile-app-promo"
const MOBILE_APP_NOTICE_URL = "https://blog.kilo.ai/p/kilo-app-for-ios-and-android-is-live"
/**
* Purely local (non-server-fetched) notice promoting the Kilo mobile app. Gated by the
* local `kilo serve` instance's `kilocode.mobileAppNotice()` endpoint, which is only true
* for users who have previously enabled a Cloud Agent / remote session relay from the CLI
* (see `Notices.markCloudAgentUsed()` in `packages/opencode/src/kilocode/notices.ts`).
* Dismissal reuses the existing `kilo.dismissedNotificationIds` globalState flow below.
*/
async function localNotifications(client: KiloClient): Promise<NotificationItem[]> {
const res = await client.kilocode.mobileAppNotice().catch(() => null)
if (!res?.data?.show) return []
return [
{
id: MOBILE_APP_NOTICE_ID,
title: "Kilo Mobile App",
message: "Continue your /remote sessions in the Kilo mobile app.",
action: { actionText: "Open", actionURL: MOBILE_APP_NOTICE_URL },
},
]
}
export async function fetchAndSendNotifications(ctx: NotificationsContext): Promise<void> {
if (!ctx.client) {
const cached = ctx.cached()
@@ -48,8 +71,11 @@ export async function fetchAndSendNotifications(ctx: NotificationsContext): Prom
}
try {
const { data: all } = await retry(() => ctx.client!.kilo.notifications(undefined, { throwOnError: true }))
const notifications = all.filter((n) => !n.showIn || n.showIn.includes("extension"))
const [{ data: all }, local] = await Promise.all([
retry(() => ctx.client!.kilo.notifications(undefined, { throwOnError: true })),
localNotifications(ctx.client),
])
const notifications = [...local, ...all.filter((n) => !n.showIn || n.showIn.includes("extension"))]
const existing = ctx.context?.globalState.get<string[]>(KEY, []) ?? []
const active = new Set(notifications.map((n) => n.id))
const dismissedIds = notifications.length > 0 ? existing.filter((id) => active.has(id)) : existing
@@ -67,6 +93,10 @@ export async function dismissNotification(ctx: NotificationsContext, id: string)
const existing = ctx.context.globalState.get<string[]>(KEY, [])
if (!existing.includes(id)) await ctx.context.globalState.update(KEY, [...existing, id])
// Also persist dismissal on the local kilo serve instance so the CLI/TUI (which shares
// the same machine-global notice flag) stops showing it too.
if (id === MOBILE_APP_NOTICE_ID) await ctx.client?.kilocode.dismissMobileAppNotice().catch(() => undefined)
const cached = ctx.cached()
if (cached && !cached.dismissedIds.includes(id)) {
ctx.set({
@@ -0,0 +1,90 @@
import { describe, expect, it } from "bun:test"
import {
dismissNotification,
fetchAndSendNotifications,
type NotificationsContext,
} from "../../src/kilo-provider/notifications"
function makeContext(input: { show: boolean; initialDismissed?: string[] }) {
const flag = new Map<string, unknown>(
input.initialDismissed ? [["kilo.dismissedNotificationIds", input.initialDismissed]] : [],
)
let dismissMobileAppNoticeCalls = 0
let cached: NotificationsContext["cached"] extends () => infer R ? R : never = null
const posted: unknown[] = []
const client = {
kilo: {
notifications: async () => ({ data: [] }),
},
kilocode: {
mobileAppNotice: async () => ({ data: { show: input.show } }),
dismissMobileAppNotice: async () => {
dismissMobileAppNoticeCalls++
return { data: true }
},
},
}
const ctx: NotificationsContext = {
context: {
globalState: {
get: <T>(key: string, fallback?: T) => (flag.has(key) ? (flag.get(key) as T) : fallback),
update: async (key: string, value: unknown) => {
flag.set(key, value)
},
},
} as any,
client: client as any,
cached: () => cached,
set: (message) => {
cached = message
},
post: (message) => {
posted.push(message)
},
notify: () => {},
}
return { ctx, flag, posted, dismissMobileAppNoticeCallCount: () => dismissMobileAppNoticeCalls }
}
describe("mobile app promo notice", () => {
it("is included when the local kilo serve reports Cloud Agent usage", async () => {
const { ctx, posted } = makeContext({ show: true })
await fetchAndSendNotifications(ctx)
const message = posted[0] as { notifications: { id: string }[] }
expect(message.notifications.some((n) => n.id === "mobile-app-promo")).toBe(true)
})
it("is omitted for users who have never used a Cloud Agent / remote session", async () => {
const { ctx, posted } = makeContext({ show: false })
await fetchAndSendNotifications(ctx)
const message = posted[0] as { notifications: { id: string }[] }
expect(message.notifications.some((n) => n.id === "mobile-app-promo")).toBe(false)
})
it("persists dismissal in globalState and propagates it to the local kilo serve instance", async () => {
const { ctx, flag, dismissMobileAppNoticeCallCount } = makeContext({ show: true })
await fetchAndSendNotifications(ctx)
await dismissNotification(ctx, "mobile-app-promo")
expect(flag.get("kilo.dismissedNotificationIds")).toContain("mobile-app-promo")
expect(dismissMobileAppNoticeCallCount()).toBe(1)
})
it("keeps the notice dismissed across subsequent fetches even though the server still reports show=true", async () => {
const { ctx, posted } = makeContext({ show: true, initialDismissed: ["mobile-app-promo"] })
await fetchAndSendNotifications(ctx)
const message = posted[0] as { notifications: { id: string }[]; dismissedIds: string[] }
expect(message.notifications.some((n) => n.id === "mobile-app-promo")).toBe(true)
expect(message.dismissedIds).toContain("mobile-app-promo")
})
})
@@ -116,6 +116,7 @@ import { getScrollAcceleration } from "../../util/scroll"
import { collapseToolOutput } from "../../util/collapse-tool-output"
import { TuiPluginRuntime } from "@/cli/cmd/tui/plugin/runtime"
import { DialogRetryAction } from "../../component/dialog-retry-action"
import { useMobileAppNotice } from "@/kilocode/cli/cmd/tui/routes/session/mobile-app-notice" // kilocode_change
import { SessionRetry } from "@/session/retry"
import { getRevertDiffFiles } from "../../util/revert-diff"
import { KILO_BASE_MODE, useBindings, useCommandShortcut, useOpencodeKeymap } from "../../keymap"
@@ -460,6 +461,8 @@ export function Session() {
return exit.message.set(banner(title, session()?.id, UI.Style.TEXT_DIM, UI.Style.TEXT_NORMAL))
})
useMobileAppNotice({ sdk, dialog })
const [exitPress, setExitPress] = createSignal(0)
useBindings(() => ({
enabled: Boolean(session()?.parentID),
@@ -25,6 +25,7 @@ import { Vcs } from "@/project/vcs"
import simpleGit from "simple-git"
import { RemoteWS } from "@/kilo-sessions/remote-ws"
import { RemoteSender } from "@/kilo-sessions/remote-sender"
import { Notices } from "@/kilocode/notices" // kilocode_change
import { SessionStatus } from "@/session/status"
import { Telemetry } from "@kilocode/kilo-telemetry"
import { Question } from "@/question"
@@ -465,6 +466,9 @@ export namespace KiloSessions {
remote = { conn, sender }
log.info("remote connection enabled", { connected: conn.connected })
Telemetry.trackRemoteConnectionOpened()
// kilocode_change start - mark Cloud Agent (remote) usage so cross-surface promo notices can target these users
void Notices.markCloudAgentUsed().catch((err) => log.warn("failed to persist cloud agent usage", { error: String(err) }))
// kilocode_change end
void Bus.publish(Instance.current, Event.RemoteStatusChanged, { enabled: true, connected: conn.connected })
})()
.catch((err) => {
@@ -0,0 +1,43 @@
// kilocode_change - new file
/**
* "Continue your /remote sessions in the Kilo mobile app" promo notice.
*
* Only shown to users who have previously enabled a Cloud Agent / remote session relay
* (`Notices.markCloudAgentUsed()` in `@/kilocode/notices`, set from `enableRemote()` in
* `src/kilo-sessions/kilo-sessions.ts`). Persists until the user explicitly dismisses it
* via the "don't show again" action.
*/
import { onMount } from "solid-js"
import type { useDialog } from "@tui/ui/dialog"
import { DialogRetryAction } from "@tui/component/dialog-retry-action"
export const MOBILE_APP_NOTICE_URL = "https://blog.kilo.ai/p/kilo-app-for-ios-and-android-is-live"
// Loosely typed to avoid coupling this module to the exact generated SDK client shape.
type SDKLike = {
client: {
kilocode: {
mobileAppNotice: (...args: any[]) => Promise<{ data?: { show: boolean } }>
dismissMobileAppNotice: (...args: any[]) => Promise<unknown>
}
}
}
export function useMobileAppNotice(deps: { sdk: SDKLike; dialog: ReturnType<typeof useDialog> }) {
onMount(() => {
void (async () => {
if (deps.dialog.stack.length > 0) return
const res = await deps.sdk.client.kilocode.mobileAppNotice().catch(() => null)
if (!res?.data?.show) return
if (deps.dialog.stack.length > 0) return
const dontShowAgain = await DialogRetryAction.show(deps.dialog, {
title: "Kilo Mobile App",
message: "Continue your /remote sessions in the Kilo mobile app.",
label: "Open",
link: MOBILE_APP_NOTICE_URL,
})
if (dontShowAgain) await deps.sdk.client.kilocode.dismissMobileAppNotice().catch(() => undefined)
})()
})
}
+48
View File
@@ -0,0 +1,48 @@
/**
* Persisted, machine-global "notice" flags shared by every Kilo surface (TUI, VS Code
* extension, etc.) that talks to a local `kilo serve` instance.
*
* Backed by a small JSON file under `Global.Path.state` so it survives across CLI
* invocations and is visible to the VS Code extension's embedded `kilo serve` process,
* without requiring a database or per-project scoping.
*/
import path from "path"
import { Global } from "@opencode-ai/core/global"
import { Flock } from "@opencode-ai/core/util/flock"
import { Filesystem } from "@/util/filesystem"
const CLOUD_AGENT_USED = "cloud_agent_used"
const MOBILE_APP_NOTICE_DISMISSED = "mobile_app_notice_dismissed"
export namespace Notices {
const filePath = path.join(Global.Path.state, "notices.json")
const lock = `kilo-notices:${filePath}`
async function read(): Promise<Record<string, boolean | undefined>> {
return Filesystem.readJson<Record<string, boolean | undefined>>(filePath).catch(() => ({}))
}
async function update(patch: Record<string, boolean>): Promise<void> {
await Flock.withLock(lock, async () => {
const current = await read()
await Filesystem.writeJson(filePath, { ...current, ...patch })
})
}
/** Record that the user has enabled a remote/Cloud Agent session relay at least once. */
export async function markCloudAgentUsed(): Promise<void> {
const current = await read()
if (current[CLOUD_AGENT_USED]) return
await update({ [CLOUD_AGENT_USED]: true })
}
/** Whether the "continue in the Kilo mobile app" notice should be shown. */
export async function shouldShowMobileAppNotice(): Promise<boolean> {
const current = await read()
return !!current[CLOUD_AGENT_USED] && !current[MOBILE_APP_NOTICE_DISMISSED]
}
export async function dismissMobileAppNotice(): Promise<void> {
await update({ [MOBILE_APP_NOTICE_DISMISSED]: true })
}
}
@@ -25,6 +25,10 @@ export const RemoveSkillPayload = Schema.Struct({
location: Schema.String,
})
export const MobileAppNotice = Schema.Struct({
show: Schema.Boolean,
})
export const RemoveAgentPayload = Schema.Struct({
name: Schema.String,
})
@@ -45,6 +49,8 @@ export const KilocodePaths = {
notebookReply: `${root}/notebook/:requestID/reply`,
notebookReject: `${root}/notebook/:requestID/reject`,
sessionModelUsage: `/session/:sessionID/model-usage`,
mobileAppNotice: `${root}/notice/mobile-app`,
dismissMobileAppNotice: `${root}/notice/mobile-app/dismiss`,
} as const
export const KilocodeApi = HttpApi.make("kilocode")
@@ -145,6 +151,27 @@ export const KilocodeApi = HttpApi.make("kilocode")
description: "Get token usage and direct cost by model for the complete top-level session tree.",
}),
),
HttpApiEndpoint.get("mobileAppNotice", KilocodePaths.mobileAppNotice, {
query: WorkspaceRoutingQuery,
success: described(MobileAppNotice, "Mobile app notice visibility"),
}).annotateMerge(
OpenApi.annotations({
identifier: "kilocode.mobileAppNotice",
summary: "Get mobile app notice visibility",
description:
"Whether to show the 'continue your /remote sessions in the Kilo mobile app' notice. Only true for users who have previously used a Cloud Agent / remote session relay and have not dismissed the notice.",
}),
),
HttpApiEndpoint.post("dismissMobileAppNotice", KilocodePaths.dismissMobileAppNotice, {
query: WorkspaceRoutingQuery,
success: described(Schema.Boolean, "Notice dismissed"),
}).annotateMerge(
OpenApi.annotations({
identifier: "kilocode.dismissMobileAppNotice",
summary: "Dismiss mobile app notice",
description: "Permanently dismiss the Kilo mobile app promo notice for this machine.",
}),
),
)
.annotateMerge(
OpenApi.annotations({
@@ -4,10 +4,12 @@ import * as KiloAgent from "@/kilocode/agent"
import * as KiloSkill from "@/kilocode/skill-remove"
import { Agent } from "@/agent/agent"
import { Config } from "@/config/config"
import { EffectBridge } from "@/effect/bridge"
import { InstanceState } from "@/effect/instance-state"
import { HeapSnapshot } from "@/kilocode/cli/heap-snapshot"
import type { RequestID as NotebookRequestID } from "@/kilocode/notebook/protocol"
import { Notebook } from "@/kilocode/notebook/service"
import { Notices } from "@/kilocode/notices"
import { ModelUsage } from "@/kilocode/session/model-usage"
import { InstanceStore } from "@/project/instance-store"
import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api"
@@ -98,6 +100,16 @@ export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode"
return usage
})
const mobileAppNotice = Effect.fn("KilocodeHttpApi.mobileAppNotice")(function* () {
const show = yield* EffectBridge.fromPromise(() => Notices.shouldShowMobileAppNotice())
return { show }
})
const dismissMobileAppNotice = Effect.fn("KilocodeHttpApi.dismissMobileAppNotice")(function* () {
yield* EffectBridge.fromPromise(() => Notices.dismissMobileAppNotice())
return true
})
return handlers
.handle("heapSnapshot", heapSnapshot)
.handle("agentRequirements", agentRequirements)
@@ -107,5 +119,7 @@ export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode"
.handle("notebookReply", notebookReply)
.handle("notebookReject", notebookReject)
.handle("sessionModelUsage", sessionModelUsage)
.handle("mobileAppNotice", mobileAppNotice)
.handle("dismissMobileAppNotice", dismissMobileAppNotice)
}),
)
@@ -0,0 +1,45 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
import fs from "fs/promises"
import path from "path"
import { Global } from "@opencode-ai/core/global"
import { Notices } from "../../src/kilocode/notices"
const noticesPath = path.join(Global.Path.state, "notices.json")
beforeEach(async () => {
await fs.rm(noticesPath, { force: true }).catch(() => {})
})
afterEach(async () => {
await fs.rm(noticesPath, { force: true }).catch(() => {})
})
describe("Notices (mobile app promo targeting)", () => {
test("does not show the notice for users who have never used a Cloud Agent / remote session", async () => {
expect(await Notices.shouldShowMobileAppNotice()).toBe(false)
})
test("shows the notice once a Cloud Agent / remote session has been used", async () => {
await Notices.markCloudAgentUsed()
expect(await Notices.shouldShowMobileAppNotice()).toBe(true)
})
test("persists dismissal so the notice never shows again, even across separate reads", async () => {
await Notices.markCloudAgentUsed()
expect(await Notices.shouldShowMobileAppNotice()).toBe(true)
await Notices.dismissMobileAppNotice()
expect(await Notices.shouldShowMobileAppNotice()).toBe(false)
// Simulate re-marking cloud agent usage (e.g. re-enabling /remote) — must stay dismissed.
await Notices.markCloudAgentUsed()
expect(await Notices.shouldShowMobileAppNotice()).toBe(false)
})
test("is idempotent when Cloud Agent usage is marked more than once", async () => {
await Notices.markCloudAgentUsed()
await Notices.markCloudAgentUsed()
const raw = JSON.parse(await fs.readFile(noticesPath, "utf-8"))
expect(raw.cloud_agent_used).toBe(true)
})
})
+72
View File
@@ -160,8 +160,12 @@ import type {
KiloCloudSessionsResponses,
KilocodeAgentRequirementsErrors,
KilocodeAgentRequirementsResponses,
KilocodeDismissMobileAppNoticeErrors,
KilocodeDismissMobileAppNoticeResponses,
KilocodeHeapSnapshotErrors,
KilocodeHeapSnapshotResponses,
KilocodeMobileAppNoticeErrors,
KilocodeMobileAppNoticeResponses,
KilocodeNotebookListErrors,
KilocodeNotebookListResponses,
KilocodeNotebookRejectErrors,
@@ -7846,6 +7850,74 @@ export class Kilocode extends HeyApiClient {
})
}
/**
* Get mobile app notice visibility
*
* Whether to show the 'continue your /remote sessions in the Kilo mobile app' notice. Only true for users who have previously used a Cloud Agent / remote session relay and have not dismissed the notice.
*/
public mobileAppNotice<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
workspace?: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
],
},
],
)
return (options?.client ?? this.client).get<
KilocodeMobileAppNoticeResponses,
KilocodeMobileAppNoticeErrors,
ThrowOnError
>({
url: "/kilocode/notice/mobile-app",
...options,
...params,
})
}
/**
* Dismiss mobile app notice
*
* Permanently dismiss the Kilo mobile app promo notice for this machine.
*/
public dismissMobileAppNotice<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
workspace?: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
],
},
],
)
return (options?.client ?? this.client).post<
KilocodeDismissMobileAppNoticeResponses,
KilocodeDismissMobileAppNoticeErrors,
ThrowOnError
>({
url: "/kilocode/notice/mobile-app/dismiss",
...options,
...params,
})
}
private _heap?: Heap
get heap(): Heap {
return (this._heap ??= new Heap({ client: this.client }))
+295 -235
View File
@@ -5,20 +5,10 @@ export type ClientOptions = {
}
export type Event =
| EventServerInstanceDisposed
| EventServerConnected
| EventGlobalDisposed
| EventGlobalConfigUpdated
| EventTuiPromptAppend
| EventTuiCommandExecute
| EventTuiToastShow1
| EventTuiSessionSelect
| EventSandboxStatusChanged
| EventKilocodeAgentManagerStart
| EventKilocodeNotebookRequested
| EventKilocodeNotebookCancelled
| EventIndexingStatus
| EventIndexingWarning
| EventServerInstanceDisposed
| EventFileEdited
| EventFileWatcherUpdated
| EventQuestionAsked
@@ -26,6 +16,10 @@ export type Event =
| EventQuestionRejected
| EventLspClientDiagnostics
| EventLspUpdated
| EventTuiPromptAppend
| EventTuiCommandExecute
| EventTuiToastShow1
| EventTuiSessionSelect
| EventMcpToolsChanged
| EventMcpBrowserOpenFailed
| EventSessionNetworkAsked
@@ -42,6 +36,7 @@ export type Event =
| EventInteractiveTerminalDeleted
| EventSessionTurnOpen
| EventSessionTurnClose
| EventSandboxStatusChanged
| EventSessionDiff
| EventSessionError
| EventTodoUpdated
@@ -55,6 +50,9 @@ export type Event =
| EventCommandExecuted
| EventProjectUpdated
| EventSessionCompacted
| EventKilocodeAgentManagerStart
| EventKilocodeNotebookRequested
| EventKilocodeNotebookCancelled
| EventVcsBranchUpdated
| EventKiloSessionsRemoteStatusChanged
| EventWorkspaceReady
@@ -99,9 +97,11 @@ export type Event =
| EventSessionNextCompactionStarted
| EventSessionNextCompactionDelta
| EventSessionNextCompactionEnded
| EventIndexingStatus
| EventIndexingWarning
| EventModelsDevRefreshed
| EventPluginAdded
| EventCatalogModelUpdated
| EventModelsDevRefreshed
| EventAccountAdded
| EventAccountRemoved
| EventAccountSwitched
@@ -142,137 +142,6 @@ export type InvalidRequestError = {
field?: string
}
export type EventTuiPromptAppend = {
id: string
type: "tui.prompt.append"
properties: {
text: string
}
}
export type EventTuiCommandExecute = {
id: string
type: "tui.command.execute"
properties: {
command:
| "session.list"
| "session.new"
| "session.share"
| "session.interrupt"
| "session.compact"
| "session.page.up"
| "session.page.down"
| "session.line.up"
| "session.line.down"
| "session.half.page.up"
| "session.half.page.down"
| "session.first"
| "session.last"
| "prompt.clear"
| "prompt.submit"
| "agent.cycle"
| string
}
}
export type EventTuiToastShow = {
id: string
type: "tui.toast.show"
properties: {
title?: string
message: string
variant: "info" | "success" | "warning" | "error"
duration?: number
}
}
export type EventTuiSessionSelect = {
id: string
type: "tui.session.select"
properties: {
/**
* Session ID to navigate to
*/
sessionID: string
}
}
export type NotebookRequestId = string
export type NotebookReadRequest = {
id: NotebookRequestId
sessionID: string
path: string
operation: "read"
includeOutputs: boolean
}
export type NotebookEditRequest = {
id: NotebookRequestId
sessionID: string
path: string
operation: "edit"
/**
* Opaque notebook content revision; pass it back unchanged and do not parse or increment it
*/
expectedRevision?: string
/**
* Zero-based cell index
*/
index: number
edit:
| {
action: "insert"
kind: "code" | "markdown"
language?: string
source: string
}
| {
action: "replace"
kind: "code" | "markdown"
language?: string
source: string
}
| {
action: "delete"
}
| {
action: "create"
}
}
export type NotebookExecuteRequest = {
id: NotebookRequestId
sessionID: string
path: string
operation: "execute"
/**
* Opaque notebook content revision; pass it back unchanged and do not parse or increment it
*/
expectedRevision: string
/**
* Zero-based cell index
*/
index: number
}
export type NotebookRequest = NotebookReadRequest | NotebookEditRequest | NotebookExecuteRequest
export type IndexingStatusState = "Disabled" | "In Progress" | "Complete" | "Error" | "Standby"
export type IndexingStatus = {
state: IndexingStatusState
message: string
processedFiles: number
totalFiles: number
percent: number
}
export type IndexingWarning = {
code: "qdrant.version-incompatible" | "qdrant.version-unavailable"
message: string
}
export type QuestionOption = {
/**
* Display text (1-5 words, concise)
@@ -335,6 +204,61 @@ export type QuestionRejected = {
requestID: string
}
export type EventTuiPromptAppend = {
id: string
type: "tui.prompt.append"
properties: {
text: string
}
}
export type EventTuiCommandExecute = {
id: string
type: "tui.command.execute"
properties: {
command:
| "session.list"
| "session.new"
| "session.share"
| "session.interrupt"
| "session.compact"
| "session.page.up"
| "session.page.down"
| "session.line.up"
| "session.line.down"
| "session.half.page.up"
| "session.half.page.down"
| "session.first"
| "session.last"
| "prompt.clear"
| "prompt.submit"
| "agent.cycle"
| string
}
}
export type EventTuiToastShow = {
id: string
type: "tui.toast.show"
properties: {
title?: string
message: string
variant: "info" | "success" | "warning" | "error"
duration?: number
}
}
export type EventTuiSessionSelect = {
id: string
type: "tui.session.select"
properties: {
/**
* Session ID to navigate to
*/
sessionID: string
}
}
export type SessionNetworkWait = {
id: string
sessionID: string
@@ -584,6 +508,67 @@ export type Project = {
sandboxes: Array<string>
}
export type NotebookRequestId = string
export type NotebookReadRequest = {
id: NotebookRequestId
sessionID: string
path: string
operation: "read"
includeOutputs: boolean
}
export type NotebookEditRequest = {
id: NotebookRequestId
sessionID: string
path: string
operation: "edit"
/**
* Opaque notebook content revision; pass it back unchanged and do not parse or increment it
*/
expectedRevision?: string
/**
* Zero-based cell index
*/
index: number
edit:
| {
action: "insert"
kind: "code" | "markdown"
language?: string
source: string
}
| {
action: "replace"
kind: "code" | "markdown"
language?: string
source: string
}
| {
action: "delete"
}
| {
action: "create"
}
}
export type NotebookExecuteRequest = {
id: NotebookRequestId
sessionID: string
path: string
operation: "execute"
/**
* Opaque notebook content revision; pass it back unchanged and do not parse or increment it
*/
expectedRevision: string
/**
* Zero-based cell index
*/
index: number
}
export type NotebookRequest = NotebookReadRequest | NotebookEditRequest | NotebookExecuteRequest
export type Pty = {
id: string
title: string
@@ -1030,25 +1015,30 @@ export type Prompt = {
references?: Array<PromptReferenceAttachment>
}
export type IndexingStatusState = "Disabled" | "In Progress" | "Complete" | "Error" | "Standby"
export type IndexingStatus = {
state: IndexingStatusState
message: string
processedFiles: number
totalFiles: number
percent: number
}
export type IndexingWarning = {
code: "qdrant.version-incompatible" | "qdrant.version-unavailable"
message: string
}
export type GlobalEvent = {
directory: string
project?: string
workspace?: string
payload:
| EventServerInstanceDisposed
| EventServerConnected
| EventGlobalDisposed
| EventGlobalConfigUpdated
| EventTuiPromptAppend
| EventTuiCommandExecute
| EventTuiToastShow
| EventTuiSessionSelect
| EventSandboxStatusChanged
| EventKilocodeAgentManagerStart
| EventKilocodeNotebookRequested
| EventKilocodeNotebookCancelled
| EventIndexingStatus
| EventIndexingWarning
| EventServerInstanceDisposed
| EventFileEdited
| EventFileWatcherUpdated
| EventQuestionAsked
@@ -1056,6 +1046,10 @@ export type GlobalEvent = {
| EventQuestionRejected
| EventLspClientDiagnostics
| EventLspUpdated
| EventTuiPromptAppend
| EventTuiCommandExecute
| EventTuiToastShow
| EventTuiSessionSelect
| EventMcpToolsChanged
| EventMcpBrowserOpenFailed
| EventSessionNetworkAsked
@@ -1072,6 +1066,7 @@ export type GlobalEvent = {
| EventInteractiveTerminalDeleted
| EventSessionTurnOpen
| EventSessionTurnClose
| EventSandboxStatusChanged
| EventSessionDiff
| EventSessionError
| EventTodoUpdated
@@ -1085,6 +1080,9 @@ export type GlobalEvent = {
| EventCommandExecuted
| EventProjectUpdated
| EventSessionCompacted
| EventKilocodeAgentManagerStart
| EventKilocodeNotebookRequested
| EventKilocodeNotebookCancelled
| EventVcsBranchUpdated
| EventKiloSessionsRemoteStatusChanged
| EventWorkspaceReady
@@ -1129,9 +1127,11 @@ export type GlobalEvent = {
| EventSessionNextCompactionStarted
| EventSessionNextCompactionDelta
| EventSessionNextCompactionEnded
| EventIndexingStatus
| EventIndexingWarning
| EventModelsDevRefreshed
| EventPluginAdded
| EventCatalogModelUpdated
| EventModelsDevRefreshed
| EventAccountAdded
| EventAccountRemoved
| EventAccountSwitched
@@ -3338,6 +3338,14 @@ export type SyncEventSessionNextCompactionEnded = {
}
}
export type EventServerInstanceDisposed = {
id: string
type: "server.instance.disposed"
properties: {
directory: string
}
}
export type EventServerConnected = {
id: string
type: "server.connected"
@@ -3362,78 +3370,6 @@ export type EventGlobalConfigUpdated = {
}
}
export type EventSandboxStatusChanged = {
id: string
type: "sandbox.status.changed"
properties: {
sessionID: string
directory: string
enabled: boolean
available: boolean
reason?: string
version: number
}
}
export type EventKilocodeAgentManagerStart = {
id: string
type: "kilocode.agent_manager.start"
properties: {
requestID: string
sessionID: string
mode: "worktree" | "local"
versions?: boolean
tasks: Array<{
prompt?: string
name?: string
branchName?: string
model?: {
providerID: string
modelID: string
}
variant?: string
}>
}
}
export type EventKilocodeNotebookRequested = {
id: string
type: "kilocode.notebook.requested"
properties: NotebookRequest
}
export type EventKilocodeNotebookCancelled = {
id: string
type: "kilocode.notebook.cancelled"
properties: {
requestID: NotebookRequestId
sessionID: string
reason: "cancelled" | "disposed" | "timeout"
}
}
export type EventIndexingStatus = {
id: string
type: "indexing.status"
properties: {
status: IndexingStatus
}
}
export type EventIndexingWarning = {
id: string
type: "indexing.warning"
properties: IndexingWarning
}
export type EventServerInstanceDisposed = {
id: string
type: "server.instance.disposed"
properties: {
directory: string
}
}
export type EventFileEdited = {
id: string
type: "file.edited"
@@ -3630,6 +3566,19 @@ export type EventSessionTurnClose = {
}
}
export type EventSandboxStatusChanged = {
id: string
type: "sandbox.status.changed"
properties: {
sessionID: string
directory: string
enabled: boolean
available: boolean
reason?: string
version: number
}
}
export type EventSessionDiff = {
id: string
type: "session.diff"
@@ -3759,6 +3708,43 @@ export type EventSessionCompacted = {
}
}
export type EventKilocodeAgentManagerStart = {
id: string
type: "kilocode.agent_manager.start"
properties: {
requestID: string
sessionID: string
mode: "worktree" | "local"
versions?: boolean
tasks: Array<{
prompt?: string
name?: string
branchName?: string
model?: {
providerID: string
modelID: string
}
variant?: string
}>
}
}
export type EventKilocodeNotebookRequested = {
id: string
type: "kilocode.notebook.requested"
properties: NotebookRequest
}
export type EventKilocodeNotebookCancelled = {
id: string
type: "kilocode.notebook.cancelled"
properties: {
requestID: NotebookRequestId
sessionID: string
reason: "cancelled" | "disposed" | "timeout"
}
}
export type EventVcsBranchUpdated = {
id: string
type: "vcs.branch.updated"
@@ -4297,6 +4283,28 @@ export type EventSessionNextCompactionEnded = {
}
}
export type EventIndexingStatus = {
id: string
type: "indexing.status"
properties: {
status: IndexingStatus
}
}
export type EventIndexingWarning = {
id: string
type: "indexing.warning"
properties: IndexingWarning
}
export type EventModelsDevRefreshed = {
id: string
type: "models-dev.refreshed"
properties: {
[key: string]: unknown
}
}
export type EventPluginAdded = {
id: string
type: "plugin.added"
@@ -4411,14 +4419,6 @@ export type EventCatalogModelUpdated = {
}
}
export type EventModelsDevRefreshed = {
id: string
type: "models-dev.refreshed"
properties: {
[key: string]: unknown
}
}
export type AccountV2oAuthCredential = {
type: "oauth"
refresh: string
@@ -11337,6 +11337,66 @@ export type KilocodeSessionModelUsageResponses = {
export type KilocodeSessionModelUsageResponse =
KilocodeSessionModelUsageResponses[keyof KilocodeSessionModelUsageResponses]
export type KilocodeMobileAppNoticeData = {
body?: never
path?: never
query?: {
directory?: string
workspace?: string
}
url: "/kilocode/notice/mobile-app"
}
export type KilocodeMobileAppNoticeErrors = {
/**
* Bad request
*/
400: BadRequestError
}
export type KilocodeMobileAppNoticeError = KilocodeMobileAppNoticeErrors[keyof KilocodeMobileAppNoticeErrors]
export type KilocodeMobileAppNoticeResponses = {
/**
* Mobile app notice visibility
*/
200: {
show: boolean
}
}
export type KilocodeMobileAppNoticeResponse = KilocodeMobileAppNoticeResponses[keyof KilocodeMobileAppNoticeResponses]
export type KilocodeDismissMobileAppNoticeData = {
body?: never
path?: never
query?: {
directory?: string
workspace?: string
}
url: "/kilocode/notice/mobile-app/dismiss"
}
export type KilocodeDismissMobileAppNoticeErrors = {
/**
* Bad request
*/
400: BadRequestError
}
export type KilocodeDismissMobileAppNoticeError =
KilocodeDismissMobileAppNoticeErrors[keyof KilocodeDismissMobileAppNoticeErrors]
export type KilocodeDismissMobileAppNoticeResponses = {
/**
* Notice dismissed
*/
200: boolean
}
export type KilocodeDismissMobileAppNoticeResponse =
KilocodeDismissMobileAppNoticeResponses[keyof KilocodeDismissMobileAppNoticeResponses]
export type AnacondaDesktopStatusData = {
body?: never
path?: never
+958 -698
View File
File diff suppressed because it is too large Load Diff