mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
feat: session to terminal sync // new cli kilo theme
This commit is contained in:
@@ -9,13 +9,18 @@ import type {
|
||||
ConfigSourcesResponse,
|
||||
FormatterStatusResponse,
|
||||
GlobalHealthResponse,
|
||||
GlobalEvent,
|
||||
LspStatusResponse,
|
||||
McpStatusResponse,
|
||||
Pty as PtyInfo,
|
||||
PermissionRequest,
|
||||
Project as KiloProject,
|
||||
QuestionRequest,
|
||||
ProviderAuthAuthorization,
|
||||
ProviderAuthResponse,
|
||||
ProviderListResponse,
|
||||
Session as KiloSession,
|
||||
SessionStatus,
|
||||
ToolIdsResponse,
|
||||
ToolListResponse,
|
||||
TuiConfigGetResponse,
|
||||
@@ -55,7 +60,11 @@ export type ProjectDiffItem = WorktreeDiffItem
|
||||
export type ProjectPtyInfo = PtyInfo
|
||||
export type ProjectTerminalItem = ProjectPtyInfo & {
|
||||
directory: string
|
||||
session?: KiloSession
|
||||
sessionStatus?: SessionStatus
|
||||
attention?: "permission" | "question"
|
||||
}
|
||||
export type ProjectConsoleEvent = GlobalEvent
|
||||
|
||||
export type Snapshot = {
|
||||
health: GlobalHealthResponse
|
||||
@@ -339,8 +348,46 @@ export async function resetProjectWorktree(input: Query, dir: string) {
|
||||
|
||||
export async function loadProjectTerminals(input: ProjectQuery, dir: string): Promise<ProjectTerminalItem[]> {
|
||||
const sdk = client({ url: input.url, dir })
|
||||
const result = await sdk.pty.list({ directory: dir })
|
||||
return demand("Terminals", result).map((item) => ({ ...item, directory: dir }))
|
||||
const [result, statusResult, permissionResult, questionResult] = await Promise.all([
|
||||
sdk.pty.list({ directory: dir }),
|
||||
maybe("Session status", sdk.session.status({ directory: dir })),
|
||||
maybe("Pending permissions", sdk.permission.list({ directory: dir })),
|
||||
maybe("Pending questions", sdk.question.list({ directory: dir })),
|
||||
])
|
||||
const rows = demand("Terminals", result)
|
||||
const status = statusResult ?? {}
|
||||
const permissions = pending(permissionResult ?? [])
|
||||
const questions = pending(questionResult ?? [])
|
||||
const sessions = new Map<string, KiloSession>()
|
||||
|
||||
await Promise.all(
|
||||
rows.map(async (item) => {
|
||||
if (!item.sessionID || sessions.has(item.sessionID)) return
|
||||
const session = await maybe("Session", sdk.session.get({ directory: dir, sessionID: item.sessionID }))
|
||||
if (session) sessions.set(item.sessionID, session)
|
||||
}),
|
||||
)
|
||||
|
||||
return rows.map((item) => {
|
||||
const session = item.sessionID ? sessions.get(item.sessionID) : undefined
|
||||
return {
|
||||
...item,
|
||||
directory: dir,
|
||||
session,
|
||||
sessionStatus: item.sessionID ? status[item.sessionID] : undefined,
|
||||
attention: item.sessionID ? attention(item.sessionID, permissions, questions) : undefined,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function pending(items: Array<PermissionRequest | QuestionRequest>) {
|
||||
return new Set(items.map((item) => item.sessionID))
|
||||
}
|
||||
|
||||
function attention(id: string, permissions: Set<string>, questions: Set<string>) {
|
||||
if (permissions.has(id)) return "permission"
|
||||
if (questions.has(id)) return "question"
|
||||
return undefined
|
||||
}
|
||||
|
||||
export async function loadProjectDiff(input: Query, dir: string): Promise<ProjectDiffItem[]> {
|
||||
@@ -361,6 +408,33 @@ export async function createProjectPty(input: Query, dir: string, title = "Kilo
|
||||
return demand("Create terminal", result)
|
||||
}
|
||||
|
||||
export async function removeProjectPty(input: Query, pty: string) {
|
||||
const sdk = client({ url: input.url, dir: input.dir })
|
||||
const result = await sdk.pty.remove({ directory: input.dir, ptyID: pty })
|
||||
return demand("Remove terminal", result)
|
||||
}
|
||||
|
||||
export async function viewProjectSessions(input: ProjectQuery, focused: string[], open: string[]) {
|
||||
const sdk = client(input)
|
||||
const result = await sdk.session.viewed({ directory: input.dir, focused, open })
|
||||
return demand("Viewed sessions", result)
|
||||
}
|
||||
|
||||
export function subscribeProjectEvents(input: ProjectQuery, handler: (event: ProjectConsoleEvent) => void) {
|
||||
const sdk = client(input)
|
||||
const ctl = new AbortController()
|
||||
void (async () => {
|
||||
const events = await sdk.global.event({ signal: ctl.signal, sseMaxRetryAttempts: 0 })
|
||||
for await (const event of events.stream) {
|
||||
if (ctl.signal.aborted) return
|
||||
handler(event)
|
||||
}
|
||||
})().catch((err) => {
|
||||
if (!ctl.signal.aborted) console.warn(`Project events: ${message(err)}`)
|
||||
})
|
||||
return () => ctl.abort()
|
||||
}
|
||||
|
||||
export async function resizeProjectPty(input: Query, pty: string, cols: number, rows: number) {
|
||||
const sdk = client({ url: input.url, dir: input.dir })
|
||||
const result = await sdk.pty.update({ directory: input.dir, ptyID: pty, size: { cols, rows } })
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { A, useLocation, useParams } from "@solidjs/router"
|
||||
import { createEffect, createMemo, createResource, createSignal, For, Show } from "solid-js"
|
||||
import { createEffect, createMemo, createResource, createSignal, For, onCleanup, Show } from "solid-js"
|
||||
import { Card } from "@kilocode/kilo-web-ui/card"
|
||||
import { Icon } from "@kilocode/kilo-web-ui/icon"
|
||||
import {
|
||||
createProjectPty,
|
||||
createProjectWorktree,
|
||||
@@ -11,9 +12,13 @@ import {
|
||||
loadProjectConsole,
|
||||
loadProjectDiff,
|
||||
loadProjectDiffFile,
|
||||
removeProjectPty,
|
||||
removeProjectWorktree,
|
||||
resetProjectWorktree,
|
||||
saveCached,
|
||||
subscribeProjectEvents,
|
||||
viewProjectSessions,
|
||||
type ProjectConsoleEvent,
|
||||
type ProjectConsoleQuery,
|
||||
type ProjectTerminalItem,
|
||||
type Query,
|
||||
@@ -53,6 +58,51 @@ function title(input: string) {
|
||||
return friendly(repo(input))
|
||||
}
|
||||
|
||||
function record(input: unknown): input is Record<string, unknown> {
|
||||
return typeof input === "object" && input !== null
|
||||
}
|
||||
|
||||
function nested(input: unknown, key: string) {
|
||||
if (!record(input)) return undefined
|
||||
const value = input[key]
|
||||
if (!record(value)) return undefined
|
||||
return value
|
||||
}
|
||||
|
||||
function eventSession(event: ProjectConsoleEvent) {
|
||||
const payload = event.payload
|
||||
const props: unknown = "properties" in payload ? payload.properties : "data" in payload ? payload.data : undefined
|
||||
if (!record(props)) return undefined
|
||||
const id = props["sessionID"]
|
||||
if (typeof id === "string") return id
|
||||
const info = nested(props, "info")
|
||||
if (typeof info?.["sessionID"] === "string") return info["sessionID"]
|
||||
if (typeof info?.["id"] === "string") return info["id"]
|
||||
const part = nested(props, "part")
|
||||
if (typeof part?.["sessionID"] === "string") return part["sessionID"]
|
||||
return undefined
|
||||
}
|
||||
|
||||
function eventType(event: ProjectConsoleEvent) {
|
||||
const payload = event.payload
|
||||
if (payload.type !== "sync") return payload.type
|
||||
return payload.name
|
||||
}
|
||||
|
||||
function messageEvent(event: ProjectConsoleEvent) {
|
||||
return eventType(event).startsWith("message.")
|
||||
}
|
||||
|
||||
function refreshEvent(event: ProjectConsoleEvent) {
|
||||
const type = eventType(event)
|
||||
if (type.startsWith("pty.")) return true
|
||||
if (type.startsWith("session.")) return true
|
||||
if (type.startsWith("permission.")) return true
|
||||
if (type.startsWith("question.")) return true
|
||||
if (type.startsWith("message.")) return true
|
||||
return false
|
||||
}
|
||||
|
||||
export function ProjectConsoleRoute() {
|
||||
const loc = useLocation()
|
||||
const params = useParams()
|
||||
@@ -65,6 +115,9 @@ export function ProjectConsoleRoute() {
|
||||
const [file, setFile] = createSignal<string | undefined>()
|
||||
const [saving, setSaving] = createSignal<string | undefined>()
|
||||
const [failure, setFailure] = createSignal<string | undefined>()
|
||||
const [unread, setUnread] = createSignal(new Set<string>())
|
||||
const [labelRev, setLabelRev] = createSignal(0)
|
||||
const events = { timer: undefined as number | undefined }
|
||||
const project = () => params.project ?? ""
|
||||
const query = createMemo<ProjectConsoleQuery | undefined>(() => {
|
||||
const target = clean(url()) || fallback()
|
||||
@@ -83,10 +136,10 @@ export function ProjectConsoleRoute() {
|
||||
})
|
||||
const terminals = createMemo(() => {
|
||||
const items = new Map<string, ProjectTerminalItem>()
|
||||
for (const item of snap()?.terminals ?? []) {
|
||||
for (const item of local()) {
|
||||
if (item.status === "running") items.set(item.id, item)
|
||||
}
|
||||
for (const item of local()) {
|
||||
for (const item of snap()?.terminals ?? []) {
|
||||
if (item.status === "running") items.set(item.id, item)
|
||||
}
|
||||
return Array.from(items.values())
|
||||
@@ -139,6 +192,74 @@ export function ProjectConsoleRoute() {
|
||||
return `/projects/${encodeURIComponent(project())}/settings${q ? `?${q}` : ""}`
|
||||
})
|
||||
|
||||
function projectInput(): Query | undefined {
|
||||
const base = query()
|
||||
const data = snap()
|
||||
if (!base || !data) return undefined
|
||||
return { url: base.url, dir: data.project.worktree, scope: "project" }
|
||||
}
|
||||
|
||||
function labelKey(dir: string) {
|
||||
return `kilo.console.${project()}.worktree.${encodeURIComponent(dir)}.label`
|
||||
}
|
||||
|
||||
function displayLabel(item: Context) {
|
||||
labelRev()
|
||||
return window.localStorage.getItem(labelKey(item.dir))?.trim() || item.label
|
||||
}
|
||||
|
||||
function currentLabel() {
|
||||
const item = current()
|
||||
if (!item) return "Project"
|
||||
return displayLabel(item)
|
||||
}
|
||||
|
||||
function sessionID(item: ProjectTerminalItem | undefined) {
|
||||
return item?.sessionID ?? item?.session?.id
|
||||
}
|
||||
|
||||
function activeSessionID() {
|
||||
return sessionID(activeTerminal())
|
||||
}
|
||||
|
||||
function terminalName(item: ProjectTerminalItem) {
|
||||
return item.session?.title || item.title || `Terminal ${item.id.slice(-4)}`
|
||||
}
|
||||
|
||||
function terminalState(item: ProjectTerminalItem) {
|
||||
if (item.attention) return "attention"
|
||||
if (item.sessionStatus && item.sessionStatus.type !== "idle") return "busy"
|
||||
const id = sessionID(item)
|
||||
if (id && unread().has(id)) return "unread"
|
||||
return "idle"
|
||||
}
|
||||
|
||||
function clearUnread(item: ProjectTerminalItem) {
|
||||
const id = sessionID(item)
|
||||
if (!id || !unread().has(id)) return
|
||||
setUnread((old) => {
|
||||
const next = new Set(old)
|
||||
next.delete(id)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
function markUnread(id: string) {
|
||||
if (id === activeSessionID()) return
|
||||
setUnread((old) => {
|
||||
if (old.has(id)) return old
|
||||
return new Set([...old, id])
|
||||
})
|
||||
}
|
||||
|
||||
function scheduleRefetch() {
|
||||
if (events.timer) return
|
||||
events.timer = window.setTimeout(() => {
|
||||
events.timer = undefined
|
||||
void refetch()
|
||||
}, 150)
|
||||
}
|
||||
|
||||
function terminalsFor(dir: string) {
|
||||
return grouped().get(dir) ?? []
|
||||
}
|
||||
@@ -164,6 +285,7 @@ export function ProjectConsoleRoute() {
|
||||
setSelected(item.directory)
|
||||
setActive(item.id)
|
||||
setFile(undefined)
|
||||
clearUnread(item)
|
||||
remember(item.directory, item.id)
|
||||
}
|
||||
|
||||
@@ -177,21 +299,21 @@ export function ProjectConsoleRoute() {
|
||||
}
|
||||
|
||||
function addWorktree() {
|
||||
const input = target()
|
||||
const input = projectInput()
|
||||
const data = snap()
|
||||
if (!input || !data) return
|
||||
const name = window.prompt("Worktree name") ?? undefined
|
||||
run("Creating worktree", async () => {
|
||||
const next = await createProjectWorktree({ ...input, dir: data.project.worktree }, name)
|
||||
const next = await createProjectWorktree(input, name)
|
||||
setSelected(next.directory)
|
||||
window.localStorage.setItem(`kilo.console.${project()}.dir`, next.directory)
|
||||
})
|
||||
}
|
||||
|
||||
function addSession() {
|
||||
const input = target()
|
||||
const item = current()
|
||||
if (!input || !item) return
|
||||
function addSession(item = current()) {
|
||||
const base = query()
|
||||
if (!base || !item) return
|
||||
const input = { url: base.url, dir: item.dir, scope: "project" as const }
|
||||
const label = `Kilo ${terminalsFor(item.dir).length + 1}`
|
||||
setSaving("Creating session")
|
||||
setFailure(undefined)
|
||||
@@ -208,34 +330,65 @@ export function ProjectConsoleRoute() {
|
||||
.finally(() => setSaving(undefined))
|
||||
}
|
||||
|
||||
function dropTerminal(id: string) {
|
||||
function forgetTerminal(id: string) {
|
||||
setLocal((rows) => rows.filter((row) => row.id !== id))
|
||||
if (active() === id) {
|
||||
setActive("")
|
||||
window.localStorage.removeItem(`kilo.console.${project()}.pty`)
|
||||
}
|
||||
}
|
||||
|
||||
function dropTerminal(id: string) {
|
||||
forgetTerminal(id)
|
||||
void refetch()
|
||||
}
|
||||
|
||||
function removeSelected() {
|
||||
const input = target()
|
||||
const item = current()
|
||||
const data = snap()
|
||||
if (!input || !item || !data || item.kind === "local") return
|
||||
if (!window.confirm(`Remove worktree ${item.label}?`)) return
|
||||
run("Removing worktree", async () => {
|
||||
await removeProjectWorktree({ ...input, dir: data.project.worktree }, item.dir)
|
||||
setSelected(data.project.worktree)
|
||||
function closeTerminal(item: ProjectTerminalItem) {
|
||||
const input = { url: query()?.url ?? "", dir: item.directory, scope: "project" as const }
|
||||
if (!input.url) return
|
||||
run("Closing terminal", async () => {
|
||||
await removeProjectPty(input, item.id)
|
||||
forgetTerminal(item.id)
|
||||
})
|
||||
}
|
||||
|
||||
function resetSelected() {
|
||||
const input = target()
|
||||
function renameWorktree(item: Context) {
|
||||
if (item.kind === "local") return
|
||||
const input = window.prompt("Worktree label", displayLabel(item))
|
||||
if (input === null) return
|
||||
const next = input.trim()
|
||||
if (next) window.localStorage.setItem(labelKey(item.dir), next)
|
||||
else window.localStorage.removeItem(labelKey(item.dir))
|
||||
setLabelRev((value) => value + 1)
|
||||
}
|
||||
|
||||
function removeWorktree(item: Context) {
|
||||
const input = projectInput()
|
||||
if (!input || item.kind === "local") return
|
||||
if (!window.confirm(`Remove worktree ${displayLabel(item)}?`)) return
|
||||
run("Removing worktree", async () => {
|
||||
await removeProjectWorktree(input, item.dir)
|
||||
window.localStorage.removeItem(labelKey(item.dir))
|
||||
setLabelRev((value) => value + 1)
|
||||
if (selected() === item.dir) {
|
||||
setSelected(input.dir)
|
||||
remember(input.dir)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function removeSelected() {
|
||||
const item = current()
|
||||
const data = snap()
|
||||
if (!input || !item || !data || item.kind === "local") return
|
||||
if (!window.confirm(`Reset worktree ${item.label}?`)) return
|
||||
run("Resetting worktree", async () => resetProjectWorktree({ ...input, dir: data.project.worktree }, item.dir))
|
||||
if (!item) return
|
||||
removeWorktree(item)
|
||||
}
|
||||
|
||||
function resetSelected() {
|
||||
const input = projectInput()
|
||||
const item = current()
|
||||
if (!input || !item || item.kind === "local") return
|
||||
if (!window.confirm(`Reset worktree ${displayLabel(item)}?`)) return
|
||||
run("Resetting worktree", async () => resetProjectWorktree(input, item.dir))
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
@@ -287,6 +440,41 @@ export function ProjectConsoleRoute() {
|
||||
remember(item.dir, pty?.id)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const item = activeTerminal()
|
||||
if (item) clearUnread(item)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const base = query()
|
||||
const data = snap()
|
||||
if (!base || !data) return
|
||||
const focused = activeSessionID()
|
||||
const open = terminals().flatMap((item) => {
|
||||
const id = sessionID(item)
|
||||
return id ? [id] : []
|
||||
})
|
||||
void viewProjectSessions({ url: base.url, dir: data.project.worktree }, focused ? [focused] : [], open).catch(() => {})
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const base = query()
|
||||
const data = snap()
|
||||
if (!base || !data) return
|
||||
const dirs = new Set([data.project.worktree, ...data.worktrees])
|
||||
const stop = subscribeProjectEvents({ url: base.url, dir: data.project.worktree }, (event) => {
|
||||
if (event.directory !== "global" && !dirs.has(event.directory)) return
|
||||
const id = eventSession(event)
|
||||
if (id && messageEvent(event)) markUnread(id)
|
||||
if (refreshEvent(event)) scheduleRefetch()
|
||||
})
|
||||
onCleanup(stop)
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
if (events.timer) window.clearTimeout(events.timer)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!snap.error || !discoverable(search())) return
|
||||
const cached = loadCached()
|
||||
@@ -315,45 +503,116 @@ export function ProjectConsoleRoute() {
|
||||
</div>
|
||||
<div class="project-console-scroll">
|
||||
<section class="project-sidebar-group">
|
||||
<div class="project-panel-heading">Actions</div>
|
||||
<div class="project-console-actions">
|
||||
<button type="button" onClick={addWorktree} disabled={!target() || !!saving()}>
|
||||
New worktree
|
||||
</button>
|
||||
<button type="button" onClick={addSession} disabled={!target() || !!saving()}>
|
||||
New session
|
||||
<div class="project-panel-heading project-panel-heading-row">
|
||||
<span>Worktrees</span>
|
||||
<button
|
||||
type="button"
|
||||
class="project-heading-action"
|
||||
onClick={addWorktree}
|
||||
disabled={!projectInput() || !!saving()}
|
||||
title="Create worktree"
|
||||
aria-label="Create worktree"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="project-sidebar-group">
|
||||
<div class="project-panel-heading">Worktrees</div>
|
||||
<nav class="project-contexts" aria-label="Worktrees">
|
||||
<For each={contexts()}>
|
||||
{(item) => (
|
||||
<div class="project-worktree-block">
|
||||
<button
|
||||
type="button"
|
||||
class="project-context"
|
||||
classList={{ active: current()?.dir === item.dir && !activeTerminal() }}
|
||||
onClick={() => select(item)}
|
||||
title={item.dir}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
<small>{item.kind === "local" ? "project" : repo(item.dir)}</small>
|
||||
</button>
|
||||
<div class="project-context-row" classList={{ active: current()?.dir === item.dir && !activeTerminal() }}>
|
||||
<button
|
||||
type="button"
|
||||
class="project-context"
|
||||
classList={{ active: current()?.dir === item.dir && !activeTerminal() }}
|
||||
onClick={() => select(item)}
|
||||
title={item.dir}
|
||||
>
|
||||
<span>{displayLabel(item)}</span>
|
||||
<small>{item.kind === "local" ? "project" : repo(item.dir)}</small>
|
||||
</button>
|
||||
<div class="project-row-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="project-inline-action"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
addSession(item)
|
||||
}}
|
||||
disabled={!query() || !!saving()}
|
||||
title={`New session in ${displayLabel(item)}`}
|
||||
aria-label={`New session in ${displayLabel(item)}`}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<Show when={item.kind === "worktree"}>
|
||||
<button
|
||||
type="button"
|
||||
class="project-inline-action"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
renameWorktree(item)
|
||||
}}
|
||||
disabled={!!saving()}
|
||||
title={`Rename ${displayLabel(item)}`}
|
||||
aria-label={`Rename ${displayLabel(item)}`}
|
||||
>
|
||||
<Icon name="edit" size="small" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="project-inline-action danger"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
removeWorktree(item)
|
||||
}}
|
||||
disabled={!!saving()}
|
||||
title={`Delete ${displayLabel(item)}`}
|
||||
aria-label={`Delete ${displayLabel(item)}`}
|
||||
>
|
||||
<Icon name="trash" size="small" />
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={terminalsFor(item.dir).length > 0}>
|
||||
<div class="project-terminal-list">
|
||||
<For each={terminalsFor(item.dir)}>
|
||||
{(pty) => (
|
||||
<button
|
||||
type="button"
|
||||
class="project-terminal-row"
|
||||
classList={{ active: active() === pty.id }}
|
||||
onClick={() => selectTerminal(pty)}
|
||||
title={`${pty.title} (${pty.pid})`}
|
||||
>
|
||||
<span>{pty.title || `Terminal ${pty.id.slice(-4)}`}</span>
|
||||
</button>
|
||||
<div class="project-terminal-item" classList={{ active: active() === pty.id }}>
|
||||
<button
|
||||
type="button"
|
||||
class="project-terminal-row"
|
||||
classList={{ active: active() === pty.id }}
|
||||
onClick={() => selectTerminal(pty)}
|
||||
title={`${terminalName(pty)} (${pty.pid})`}
|
||||
>
|
||||
<span
|
||||
class="project-console-dot"
|
||||
classList={{
|
||||
"project-console-dot-idle": terminalState(pty) === "idle",
|
||||
"project-console-dot-busy": terminalState(pty) === "busy",
|
||||
"project-console-dot-attention": terminalState(pty) === "attention",
|
||||
"project-console-dot-unread": terminalState(pty) === "unread",
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{terminalName(pty)}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="project-terminal-close"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
closeTerminal(pty)
|
||||
}}
|
||||
disabled={!!saving()}
|
||||
title={`Close ${terminalName(pty)}`}
|
||||
aria-label={`Close ${terminalName(pty)}`}
|
||||
>
|
||||
x
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
@@ -400,7 +659,7 @@ export function ProjectConsoleRoute() {
|
||||
<Show when={!terminal() && !snap.loading && !snap.error && !failure()}>
|
||||
<div class="project-terminal-empty">
|
||||
<strong>No terminal session selected</strong>
|
||||
<span>Use New session to start Kilo CLI in this worktree.</span>
|
||||
<span>Use + next to a worktree to start Kilo CLI.</span>
|
||||
</div>
|
||||
</Show>
|
||||
</main>
|
||||
@@ -408,7 +667,7 @@ export function ProjectConsoleRoute() {
|
||||
<aside class="project-console-info" aria-label="Project details">
|
||||
<div class="project-info-card">
|
||||
<div class="project-panel-heading">Context</div>
|
||||
<strong>{current()?.label ?? "Project"}</strong>
|
||||
<strong>{currentLabel()}</strong>
|
||||
<code>{current()?.dir ?? snap()?.project.worktree ?? project()}</code>
|
||||
<Show when={current()?.kind === "worktree"}>
|
||||
<div class="project-info-actions">
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
}
|
||||
|
||||
.kilo-console .project-console-sidebar {
|
||||
--project-action-rail: 5rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@@ -99,6 +101,7 @@
|
||||
.kilo-console .project-panel-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
min-height: 1.25rem;
|
||||
color: var(--text-weaker);
|
||||
font-size: 0.625rem;
|
||||
@@ -108,6 +111,17 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.kilo-console .project-panel-heading-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) var(--project-action-rail);
|
||||
gap: 0.25rem;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.kilo-console .project-panel-heading-row .project-heading-action {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.kilo-console .project-console-sidebar button,
|
||||
.kilo-console .project-settings-link {
|
||||
display: flex;
|
||||
@@ -143,6 +157,99 @@
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.kilo-console .project-console-sidebar .project-heading-action,
|
||||
.kilo-console .project-console-sidebar .project-inline-action,
|
||||
.kilo-console .project-console-sidebar .project-terminal-close {
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
width: auto;
|
||||
min-width: 1.35rem;
|
||||
min-height: 1.35rem;
|
||||
padding: 0 0.35rem;
|
||||
color: var(--text-weaker);
|
||||
font-size: 0.625rem;
|
||||
}
|
||||
|
||||
.kilo-console .project-console-sidebar .project-inline-action {
|
||||
max-width: 3.5rem;
|
||||
}
|
||||
|
||||
.kilo-console .project-console-sidebar .project-inline-action [data-component="icon"] {
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.kilo-console .project-console-sidebar .project-inline-action.danger:hover,
|
||||
.kilo-console .project-console-sidebar .project-inline-action.danger:focus-visible {
|
||||
color: var(--destructive, #ef4444);
|
||||
}
|
||||
|
||||
.kilo-console .project-context-row,
|
||||
.kilo-console .project-terminal-item {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.kilo-console .project-context-row:hover,
|
||||
.kilo-console .project-context-row:focus-within,
|
||||
.kilo-console .project-context-row.active,
|
||||
.kilo-console .project-terminal-item:hover,
|
||||
.kilo-console .project-terminal-item:focus-within,
|
||||
.kilo-console .project-terminal-item.active {
|
||||
background: var(--muted);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.kilo-console .project-context-row .project-context:hover,
|
||||
.kilo-console .project-context-row .project-context:focus-visible,
|
||||
.kilo-console .project-context-row .project-context.active,
|
||||
.kilo-console .project-context-row .project-inline-action:hover,
|
||||
.kilo-console .project-context-row .project-inline-action:focus-visible,
|
||||
.kilo-console .project-terminal-item .project-terminal-row:hover,
|
||||
.kilo-console .project-terminal-item .project-terminal-row:focus-visible,
|
||||
.kilo-console .project-terminal-item .project-terminal-row.active,
|
||||
.kilo-console .project-terminal-item .project-terminal-close:hover,
|
||||
.kilo-console .project-terminal-item .project-terminal-close:focus-visible {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.kilo-console .project-context-row:hover .project-context,
|
||||
.kilo-console .project-context-row:focus-within .project-context,
|
||||
.kilo-console .project-context-row.active .project-context {
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.kilo-console .project-terminal-item:hover .project-terminal-row,
|
||||
.kilo-console .project-terminal-item:focus-within .project-terminal-row,
|
||||
.kilo-console .project-terminal-item.active .project-terminal-row {
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
|
||||
.kilo-console .project-context-row .project-context {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.kilo-console .project-row-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.125rem;
|
||||
width: var(--project-action-rail);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 120ms ease;
|
||||
}
|
||||
|
||||
.kilo-console .project-context-row:hover .project-row-actions,
|
||||
.kilo-console .project-context-row:focus-within .project-row-actions,
|
||||
.kilo-console .project-terminal-item:hover .project-terminal-close,
|
||||
.kilo-console .project-terminal-item:focus-within .project-terminal-close {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.kilo-console .project-context span,
|
||||
.kilo-console .project-terminal-row span,
|
||||
.kilo-console .project-settings-link span,
|
||||
@@ -166,12 +273,41 @@
|
||||
}
|
||||
|
||||
.kilo-console .project-terminal-row {
|
||||
display: grid !important;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 0.375rem;
|
||||
min-height: 1.5rem !important;
|
||||
color: var(--muted-foreground) !important;
|
||||
font-size: 0.6875rem !important;
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
.kilo-console .project-terminal-close {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 120ms ease;
|
||||
}
|
||||
|
||||
.kilo-console .project-terminal-row .project-console-dot-idle {
|
||||
background: var(--muted-foreground);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.kilo-console .project-terminal-row .project-console-dot-busy {
|
||||
background: var(--primary);
|
||||
box-shadow: 0 0 0 0.1875rem color-mix(in oklab, var(--primary) 18%, transparent);
|
||||
}
|
||||
|
||||
.kilo-console .project-terminal-row .project-console-dot-attention {
|
||||
background: #f97316;
|
||||
box-shadow: 0 0 0 0.1875rem color-mix(in oklab, #f97316 18%, transparent);
|
||||
}
|
||||
|
||||
.kilo-console .project-terminal-row .project-console-dot-unread {
|
||||
background: #22c55e;
|
||||
box-shadow: 0 0 0 0.1875rem color-mix(in oklab, #22c55e 18%, transparent);
|
||||
}
|
||||
|
||||
.kilo-console .project-terminal-row.active {
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import github from "./theme/github.json" with { type: "json" }
|
||||
import gruvbox from "./theme/gruvbox.json" with { type: "json" }
|
||||
import kanagawa from "./theme/kanagawa.json" with { type: "json" }
|
||||
import kilo from "./theme/kilo.json" with { type: "json" } // kilocode_change
|
||||
import kilo1 from "./theme/kilo-v1.json" with { type: "json" } // kilocode_change
|
||||
import material from "./theme/material.json" with { type: "json" }
|
||||
import matrix from "./theme/matrix.json" with { type: "json" }
|
||||
import mercury from "./theme/mercury.json" with { type: "json" }
|
||||
@@ -102,6 +103,7 @@ export const DEFAULT_THEMES: Record<string, ThemeJson> = {
|
||||
gruvbox,
|
||||
kanagawa,
|
||||
kilo, // kilocode_change
|
||||
["kilo-v1"]: kilo1, // kilocode_change
|
||||
material,
|
||||
matrix,
|
||||
mercury,
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/theme.json",
|
||||
"defs": {
|
||||
"darkStep1": "#1e1e1e",
|
||||
"darkStep2": "#252526",
|
||||
"darkStep3": "#2d2d30",
|
||||
"darkStep4": "#333333",
|
||||
"darkStep5": "#3c3c3c",
|
||||
"darkStep6": "#4a4a4a",
|
||||
"darkStep7": "#3c3c3c",
|
||||
"darkStep8": "#007fd4",
|
||||
"darkStep9": "#faf74f",
|
||||
"darkStep10": "#fbf86f",
|
||||
"darkStep11": "#858585",
|
||||
"darkStep12": "#cccccc",
|
||||
"darkSecondary": "#007acc",
|
||||
"darkAccent": "#007fd4",
|
||||
"darkRed": "#f48771",
|
||||
"darkOrange": "#cca700",
|
||||
"darkGreen": "#89d185",
|
||||
"darkCyan": "#3794ff",
|
||||
"darkYellow": "#faf74f",
|
||||
"lightStep1": "#ffffff",
|
||||
"lightStep2": "#fafafa",
|
||||
"lightStep3": "#f5f5f5",
|
||||
"lightStep4": "#ebebeb",
|
||||
"lightStep5": "#e1e1e1",
|
||||
"lightStep6": "#d4d4d4",
|
||||
"lightStep7": "#cecece",
|
||||
"lightStep8": "#0090f1",
|
||||
"lightStep9": "#616161",
|
||||
"lightStep10": "#717171",
|
||||
"lightStep11": "#717171",
|
||||
"lightStep12": "#616161",
|
||||
"lightSecondary": "#007acc",
|
||||
"lightAccent": "#0090f1",
|
||||
"lightRed": "#a1260d",
|
||||
"lightOrange": "#bf8803",
|
||||
"lightGreen": "#388a34",
|
||||
"lightCyan": "#1a85ff",
|
||||
"lightYellow": "#616161"
|
||||
},
|
||||
"theme": {
|
||||
"primary": {
|
||||
"dark": "darkStep9",
|
||||
"light": "lightStep9"
|
||||
},
|
||||
"secondary": {
|
||||
"dark": "darkSecondary",
|
||||
"light": "lightSecondary"
|
||||
},
|
||||
"accent": {
|
||||
"dark": "darkAccent",
|
||||
"light": "lightAccent"
|
||||
},
|
||||
"error": {
|
||||
"dark": "darkRed",
|
||||
"light": "lightRed"
|
||||
},
|
||||
"warning": {
|
||||
"dark": "darkOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"success": {
|
||||
"dark": "darkGreen",
|
||||
"light": "lightGreen"
|
||||
},
|
||||
"info": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"text": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
},
|
||||
"textMuted": {
|
||||
"dark": "darkStep11",
|
||||
"light": "lightStep11"
|
||||
},
|
||||
"background": {
|
||||
"dark": "darkStep1",
|
||||
"light": "lightStep1"
|
||||
},
|
||||
"backgroundPanel": {
|
||||
"dark": "darkStep2",
|
||||
"light": "lightStep2"
|
||||
},
|
||||
"backgroundElement": {
|
||||
"dark": "darkStep3",
|
||||
"light": "lightStep3"
|
||||
},
|
||||
"border": {
|
||||
"dark": "darkStep7",
|
||||
"light": "lightStep7"
|
||||
},
|
||||
"borderActive": {
|
||||
"dark": "darkStep8",
|
||||
"light": "lightStep8"
|
||||
},
|
||||
"borderSubtle": {
|
||||
"dark": "darkStep6",
|
||||
"light": "lightStep6"
|
||||
},
|
||||
"diffAdded": {
|
||||
"dark": "#4fd6be",
|
||||
"light": "#1e725c"
|
||||
},
|
||||
"diffRemoved": {
|
||||
"dark": "#c53b53",
|
||||
"light": "#c53b53"
|
||||
},
|
||||
"diffContext": {
|
||||
"dark": "#828bb8",
|
||||
"light": "#7086b5"
|
||||
},
|
||||
"diffHunkHeader": {
|
||||
"dark": "#828bb8",
|
||||
"light": "#7086b5"
|
||||
},
|
||||
"diffHighlightAdded": {
|
||||
"dark": "#b8db87",
|
||||
"light": "#4db380"
|
||||
},
|
||||
"diffHighlightRemoved": {
|
||||
"dark": "#e26a75",
|
||||
"light": "#f52a65"
|
||||
},
|
||||
"diffAddedBg": {
|
||||
"dark": "#1a2a30",
|
||||
"light": "#d5e5d5"
|
||||
},
|
||||
"diffRemovedBg": {
|
||||
"dark": "#2a1a24",
|
||||
"light": "#f7d8db"
|
||||
},
|
||||
"diffContextBg": {
|
||||
"dark": "darkStep2",
|
||||
"light": "lightStep2"
|
||||
},
|
||||
"diffLineNumber": {
|
||||
"dark": "darkStep3",
|
||||
"light": "lightStep3"
|
||||
},
|
||||
"diffAddedLineNumberBg": {
|
||||
"dark": "#152328",
|
||||
"light": "#c5d5c5"
|
||||
},
|
||||
"diffRemovedLineNumberBg": {
|
||||
"dark": "#23151c",
|
||||
"light": "#e7c8cb"
|
||||
},
|
||||
"markdownText": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
},
|
||||
"markdownHeading": {
|
||||
"dark": "darkYellow",
|
||||
"light": "lightYellow"
|
||||
},
|
||||
"markdownLink": {
|
||||
"dark": "darkStep9",
|
||||
"light": "lightStep9"
|
||||
},
|
||||
"markdownLinkText": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"markdownCode": {
|
||||
"dark": "darkGreen",
|
||||
"light": "lightGreen"
|
||||
},
|
||||
"markdownBlockQuote": {
|
||||
"dark": "darkSecondary",
|
||||
"light": "lightSecondary"
|
||||
},
|
||||
"markdownEmph": {
|
||||
"dark": "darkSecondary",
|
||||
"light": "lightSecondary"
|
||||
},
|
||||
"markdownStrong": {
|
||||
"dark": "darkYellow",
|
||||
"light": "lightYellow"
|
||||
},
|
||||
"markdownHorizontalRule": {
|
||||
"dark": "darkStep11",
|
||||
"light": "lightStep11"
|
||||
},
|
||||
"markdownListItem": {
|
||||
"dark": "darkStep9",
|
||||
"light": "lightStep9"
|
||||
},
|
||||
"markdownListEnumeration": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"markdownImage": {
|
||||
"dark": "darkStep9",
|
||||
"light": "lightStep9"
|
||||
},
|
||||
"markdownImageText": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"markdownCodeBlock": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
},
|
||||
"syntaxComment": {
|
||||
"dark": "darkStep11",
|
||||
"light": "lightStep11"
|
||||
},
|
||||
"syntaxKeyword": {
|
||||
"dark": "darkAccent",
|
||||
"light": "lightAccent"
|
||||
},
|
||||
"syntaxFunction": {
|
||||
"dark": "darkStep9",
|
||||
"light": "lightStep9"
|
||||
},
|
||||
"syntaxVariable": {
|
||||
"dark": "darkRed",
|
||||
"light": "lightRed"
|
||||
},
|
||||
"syntaxString": {
|
||||
"dark": "darkGreen",
|
||||
"light": "lightGreen"
|
||||
},
|
||||
"syntaxNumber": {
|
||||
"dark": "darkOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"syntaxType": {
|
||||
"dark": "darkSecondary",
|
||||
"light": "lightSecondary"
|
||||
},
|
||||
"syntaxOperator": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"syntaxPunctuation": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,45 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/theme.json",
|
||||
"defs": {
|
||||
"darkStep1": "#1e1e1e",
|
||||
"darkStep2": "#252526",
|
||||
"darkStep3": "#2d2d30",
|
||||
"darkStep4": "#333333",
|
||||
"darkStep5": "#3c3c3c",
|
||||
"darkStep6": "#4a4a4a",
|
||||
"darkStep7": "#3c3c3c",
|
||||
"darkStep8": "#007fd4",
|
||||
"darkStep9": "#faf74f",
|
||||
"darkStep10": "#fbf86f",
|
||||
"darkStep11": "#858585",
|
||||
"darkStep12": "#cccccc",
|
||||
"darkSecondary": "#007acc",
|
||||
"darkAccent": "#007fd4",
|
||||
"darkRed": "#f48771",
|
||||
"darkBase": "#0c0a09",
|
||||
"darkPanel": "#1c1917",
|
||||
"darkElement": "#292524",
|
||||
"darkBorderSubtle": "#292524",
|
||||
"darkBorder": "#44403b",
|
||||
"darkBorderActive": "#f9f76f",
|
||||
"darkText": "#fafaf9",
|
||||
"darkTextMuted": "#a6a09b",
|
||||
"darkTextWeaker": "#79716b",
|
||||
"darkPrimary": "#f9f76f",
|
||||
"darkPrimaryForeground": "#0c0a09",
|
||||
"darkSecondary": "#a6a09b",
|
||||
"darkAccent": "#f9f76f",
|
||||
"darkRed": "#ff6467",
|
||||
"darkOrange": "#cca700",
|
||||
"darkGreen": "#89d185",
|
||||
"darkCyan": "#3794ff",
|
||||
"darkYellow": "#faf74f",
|
||||
"lightStep1": "#ffffff",
|
||||
"lightStep2": "#fafafa",
|
||||
"lightStep3": "#f5f5f5",
|
||||
"lightStep4": "#ebebeb",
|
||||
"lightStep5": "#e1e1e1",
|
||||
"lightStep6": "#d4d4d4",
|
||||
"lightStep7": "#cecece",
|
||||
"lightStep8": "#0090f1",
|
||||
"lightStep9": "#616161",
|
||||
"lightStep10": "#717171",
|
||||
"lightStep11": "#717171",
|
||||
"lightStep12": "#616161",
|
||||
"lightSecondary": "#007acc",
|
||||
"lightAccent": "#0090f1",
|
||||
"lightRed": "#a1260d",
|
||||
"lightOrange": "#bf8803",
|
||||
"lightBase": "#ffffff",
|
||||
"lightPanel": "#fafaf9",
|
||||
"lightElement": "#f5f5f4",
|
||||
"lightBorderSubtle": "#e7e5e4",
|
||||
"lightBorder": "#d6d3d1",
|
||||
"lightBorderActive": "#6f6500",
|
||||
"lightText": "#0c0a09",
|
||||
"lightTextMuted": "#79716b",
|
||||
"lightTextWeaker": "#57534d",
|
||||
"lightPrimary": "#6f6500",
|
||||
"lightPrimaryForeground": "#ffffff",
|
||||
"lightSecondary": "#57534d",
|
||||
"lightAccent": "#6f6500",
|
||||
"lightRed": "#e7000b",
|
||||
"lightOrange": "#8a6d00",
|
||||
"lightGreen": "#388a34",
|
||||
"lightCyan": "#1a85ff",
|
||||
"lightYellow": "#616161"
|
||||
"lightCyan": "#1a85ff"
|
||||
},
|
||||
"theme": {
|
||||
"primary": {
|
||||
"dark": "darkStep9",
|
||||
"light": "lightStep9"
|
||||
"dark": "darkPrimary",
|
||||
"light": "lightPrimary"
|
||||
},
|
||||
"secondary": {
|
||||
"dark": "darkSecondary",
|
||||
@@ -70,96 +66,104 @@
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"text": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
"dark": "darkText",
|
||||
"light": "lightText"
|
||||
},
|
||||
"textMuted": {
|
||||
"dark": "darkStep11",
|
||||
"light": "lightStep11"
|
||||
"dark": "darkTextMuted",
|
||||
"light": "lightTextMuted"
|
||||
},
|
||||
"background": {
|
||||
"dark": "darkStep1",
|
||||
"light": "lightStep1"
|
||||
"dark": "darkBase",
|
||||
"light": "lightBase"
|
||||
},
|
||||
"backgroundPanel": {
|
||||
"dark": "darkStep2",
|
||||
"light": "lightStep2"
|
||||
"dark": "darkPanel",
|
||||
"light": "lightPanel"
|
||||
},
|
||||
"backgroundElement": {
|
||||
"dark": "darkStep3",
|
||||
"light": "lightStep3"
|
||||
"dark": "darkElement",
|
||||
"light": "lightElement"
|
||||
},
|
||||
"backgroundMenu": {
|
||||
"dark": "darkPanel",
|
||||
"light": "lightPanel"
|
||||
},
|
||||
"border": {
|
||||
"dark": "darkStep7",
|
||||
"light": "lightStep7"
|
||||
"dark": "darkBorder",
|
||||
"light": "lightBorder"
|
||||
},
|
||||
"borderActive": {
|
||||
"dark": "darkStep8",
|
||||
"light": "lightStep8"
|
||||
"dark": "darkBorderActive",
|
||||
"light": "lightBorderActive"
|
||||
},
|
||||
"borderSubtle": {
|
||||
"dark": "darkStep6",
|
||||
"light": "lightStep6"
|
||||
"dark": "darkBorderSubtle",
|
||||
"light": "lightBorderSubtle"
|
||||
},
|
||||
"selectedListItemText": {
|
||||
"dark": "darkPrimaryForeground",
|
||||
"light": "lightPrimaryForeground"
|
||||
},
|
||||
"diffAdded": {
|
||||
"dark": "#4fd6be",
|
||||
"light": "#1e725c"
|
||||
"dark": "darkGreen",
|
||||
"light": "lightGreen"
|
||||
},
|
||||
"diffRemoved": {
|
||||
"dark": "#c53b53",
|
||||
"light": "#c53b53"
|
||||
"dark": "darkRed",
|
||||
"light": "lightRed"
|
||||
},
|
||||
"diffContext": {
|
||||
"dark": "#828bb8",
|
||||
"light": "#7086b5"
|
||||
"dark": "darkTextMuted",
|
||||
"light": "lightTextMuted"
|
||||
},
|
||||
"diffHunkHeader": {
|
||||
"dark": "#828bb8",
|
||||
"light": "#7086b5"
|
||||
"dark": "darkPrimary",
|
||||
"light": "lightPrimary"
|
||||
},
|
||||
"diffHighlightAdded": {
|
||||
"dark": "#b8db87",
|
||||
"light": "#4db380"
|
||||
"light": "#2f7d32"
|
||||
},
|
||||
"diffHighlightRemoved": {
|
||||
"dark": "#e26a75",
|
||||
"light": "#f52a65"
|
||||
"dark": "#ff8587",
|
||||
"light": "#c00009"
|
||||
},
|
||||
"diffAddedBg": {
|
||||
"dark": "#1a2a30",
|
||||
"light": "#d5e5d5"
|
||||
"dark": "#122318",
|
||||
"light": "#e8f5e9"
|
||||
},
|
||||
"diffRemovedBg": {
|
||||
"dark": "#2a1a24",
|
||||
"light": "#f7d8db"
|
||||
"dark": "#2a1214",
|
||||
"light": "#ffe4e6"
|
||||
},
|
||||
"diffContextBg": {
|
||||
"dark": "darkStep2",
|
||||
"light": "lightStep2"
|
||||
"dark": "darkPanel",
|
||||
"light": "lightPanel"
|
||||
},
|
||||
"diffLineNumber": {
|
||||
"dark": "darkStep3",
|
||||
"light": "lightStep3"
|
||||
"dark": "darkTextWeaker",
|
||||
"light": "lightTextWeaker"
|
||||
},
|
||||
"diffAddedLineNumberBg": {
|
||||
"dark": "#152328",
|
||||
"light": "#c5d5c5"
|
||||
"dark": "#17291b",
|
||||
"light": "#d7ecd8"
|
||||
},
|
||||
"diffRemovedLineNumberBg": {
|
||||
"dark": "#23151c",
|
||||
"light": "#e7c8cb"
|
||||
"dark": "#35181a",
|
||||
"light": "#ffd4d8"
|
||||
},
|
||||
"markdownText": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
"dark": "darkText",
|
||||
"light": "lightText"
|
||||
},
|
||||
"markdownHeading": {
|
||||
"dark": "darkYellow",
|
||||
"light": "lightYellow"
|
||||
"dark": "darkPrimary",
|
||||
"light": "lightPrimary"
|
||||
},
|
||||
"markdownLink": {
|
||||
"dark": "darkStep9",
|
||||
"light": "lightStep9"
|
||||
"dark": "darkPrimary",
|
||||
"light": "lightPrimary"
|
||||
},
|
||||
"markdownLinkText": {
|
||||
"dark": "darkCyan",
|
||||
@@ -170,52 +174,52 @@
|
||||
"light": "lightGreen"
|
||||
},
|
||||
"markdownBlockQuote": {
|
||||
"dark": "darkSecondary",
|
||||
"light": "lightSecondary"
|
||||
"dark": "darkTextMuted",
|
||||
"light": "lightTextMuted"
|
||||
},
|
||||
"markdownEmph": {
|
||||
"dark": "darkSecondary",
|
||||
"light": "lightSecondary"
|
||||
"dark": "darkOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"markdownStrong": {
|
||||
"dark": "darkYellow",
|
||||
"light": "lightYellow"
|
||||
"dark": "darkPrimary",
|
||||
"light": "lightPrimary"
|
||||
},
|
||||
"markdownHorizontalRule": {
|
||||
"dark": "darkStep11",
|
||||
"light": "lightStep11"
|
||||
"dark": "darkBorder",
|
||||
"light": "lightBorder"
|
||||
},
|
||||
"markdownListItem": {
|
||||
"dark": "darkStep9",
|
||||
"light": "lightStep9"
|
||||
"dark": "darkPrimary",
|
||||
"light": "lightPrimary"
|
||||
},
|
||||
"markdownListEnumeration": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"markdownImage": {
|
||||
"dark": "darkStep9",
|
||||
"light": "lightStep9"
|
||||
"dark": "darkPrimary",
|
||||
"light": "lightPrimary"
|
||||
},
|
||||
"markdownImageText": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"markdownCodeBlock": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
"dark": "darkText",
|
||||
"light": "lightText"
|
||||
},
|
||||
"syntaxComment": {
|
||||
"dark": "darkStep11",
|
||||
"light": "lightStep11"
|
||||
"dark": "darkTextMuted",
|
||||
"light": "lightTextMuted"
|
||||
},
|
||||
"syntaxKeyword": {
|
||||
"dark": "darkAccent",
|
||||
"light": "lightAccent"
|
||||
},
|
||||
"syntaxFunction": {
|
||||
"dark": "darkStep9",
|
||||
"light": "lightStep9"
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"syntaxVariable": {
|
||||
"dark": "darkRed",
|
||||
@@ -238,8 +242,9 @@
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"syntaxPunctuation": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
}
|
||||
"dark": "darkText",
|
||||
"light": "lightText"
|
||||
},
|
||||
"thinkingOpacity": 0.72
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,10 +67,27 @@ export function useSessionEffects(deps: {
|
||||
sdk: ReturnType<typeof useSDK>
|
||||
sync: ReturnType<typeof useSync>
|
||||
}) {
|
||||
const pty = process.env.KILO_PTY_ID
|
||||
const state = { prev: "" }
|
||||
|
||||
// Notify server which session the user is viewing
|
||||
createEffect(() => {
|
||||
const sessionID = deps.route.data.type === "session" ? deps.route.data.sessionID : undefined
|
||||
deps.sdk.client.session.viewed({ focused: sessionID ? [sessionID] : [] }).catch(() => {})
|
||||
|
||||
if (!pty) return
|
||||
const session = sessionID ? deps.sync.session.get(sessionID) : undefined
|
||||
const key = [sessionID ?? "", session?.title ?? ""].join("\n")
|
||||
if (key === state.prev) return
|
||||
state.prev = key
|
||||
|
||||
deps.sdk.client.pty
|
||||
.update({
|
||||
ptyID: pty,
|
||||
sessionID: sessionID ?? null,
|
||||
...(session?.title ? { title: session.title } : {}),
|
||||
})
|
||||
.catch(() => {})
|
||||
})
|
||||
|
||||
// Evict per-session data from store when navigating away
|
||||
|
||||
@@ -13,6 +13,7 @@ import { PtyID } from "./schema"
|
||||
import { Effect, Layer, Context, Schema, Types } from "effect"
|
||||
import { zod } from "@/util/effect-zod"
|
||||
import { NonNegativeInt, PositiveInt, withStatics } from "@/util/schema"
|
||||
import { SessionID } from "@/session/schema" // kilocode_change
|
||||
|
||||
const log = Log.create({ service: "pty" })
|
||||
|
||||
@@ -63,6 +64,7 @@ export const Info = Schema.Struct({
|
||||
cwd: Schema.String,
|
||||
status: Schema.Literals(["running", "exited"]),
|
||||
pid: PositiveInt,
|
||||
sessionID: Schema.optional(Schema.NullOr(SessionID)), // kilocode_change
|
||||
})
|
||||
.annotate({ identifier: "Pty" })
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
@@ -81,6 +83,7 @@ export type CreateInput = Types.DeepMutable<Schema.Schema.Type<typeof CreateInpu
|
||||
|
||||
export const UpdateInput = Schema.Struct({
|
||||
title: Schema.optional(Schema.String),
|
||||
sessionID: Schema.optional(Schema.NullOr(SessionID)), // kilocode_change
|
||||
size: Schema.optional(
|
||||
Schema.Struct({
|
||||
rows: PositiveInt,
|
||||
@@ -176,14 +179,16 @@ export const layer = Layer.effect(
|
||||
|
||||
const create = Effect.fn("Pty.create")(function* (input: CreateInput) {
|
||||
const s = yield* InstanceState.get(state)
|
||||
// kilocode_change start
|
||||
const bridge = yield* EffectBridge.make()
|
||||
const cfg = yield* config.get()
|
||||
const id = PtyID.ascending()
|
||||
// kilocode_change end
|
||||
const resolved = KiloPtySelfCommand.resolve(input) // kilocode_change
|
||||
const command = resolved.command || Shell.preferred(cfg.shell)
|
||||
const args = resolved.args || []
|
||||
const args = resolved.args || [] // kilocode_change
|
||||
if (Shell.login(command)) {
|
||||
args.push("-l")
|
||||
args.push("-l") // kilocode_change
|
||||
}
|
||||
|
||||
const cwd = resolved.cwd || s.dir // kilocode_change
|
||||
@@ -194,6 +199,7 @@ export const layer = Layer.effect(
|
||||
...shell.env,
|
||||
TERM: "xterm-256color",
|
||||
KILO_TERMINAL: "1",
|
||||
KILO_PTY_ID: id, // kilocode_change
|
||||
} as Record<string, string>
|
||||
// kilocode_change start
|
||||
// Don't leak the kilo server's auth credential into user shells.
|
||||
@@ -283,6 +289,11 @@ export const layer = Layer.effect(
|
||||
if (input.title) {
|
||||
session.info.title = input.title
|
||||
}
|
||||
// kilocode_change start
|
||||
if ("sessionID" in input) {
|
||||
session.info.sessionID = input.sessionID ?? undefined
|
||||
}
|
||||
// kilocode_change end
|
||||
if (input.size) {
|
||||
session.process.resize(input.size.cols, input.size.rows)
|
||||
}
|
||||
|
||||
@@ -1347,6 +1347,7 @@ export class Pty extends HeyApiClient {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
title?: string
|
||||
sessionID?: string | null
|
||||
size?: {
|
||||
rows: number
|
||||
cols: number
|
||||
@@ -1363,6 +1364,7 @@ export class Pty extends HeyApiClient {
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
{ in: "body", key: "title" },
|
||||
{ in: "body", key: "sessionID" },
|
||||
{ in: "body", key: "size" },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -688,6 +688,7 @@ export type Pty = {
|
||||
cwd: string
|
||||
status: "running" | "exited"
|
||||
pid: number
|
||||
sessionID?: string | null
|
||||
}
|
||||
|
||||
export type EventPtyCreated = {
|
||||
@@ -3319,6 +3320,7 @@ export type PtyGetResponse = PtyGetResponses[keyof PtyGetResponses]
|
||||
export type PtyUpdateData = {
|
||||
body?: {
|
||||
title?: string
|
||||
sessionID?: string | null
|
||||
size?: {
|
||||
rows: number
|
||||
cols: number
|
||||
|
||||
@@ -1335,6 +1335,17 @@
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"sessionID": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^ses.*"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"size": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -15081,6 +15092,17 @@
|
||||
"type": "integer",
|
||||
"exclusiveMinimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
},
|
||||
"sessionID": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^ses.*"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["id", "title", "command", "args", "cwd", "status", "pid"]
|
||||
|
||||
Reference in New Issue
Block a user