refactor: improve implementation

This commit is contained in:
Catriel Müller
2026-05-21 13:58:30 -03:00
parent cd009c3d8f
commit 06a6bf715d
19 changed files with 344 additions and 277 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Show detected ports for tracked background processes in the TUI sidebar and process detail dialog.
@@ -654,6 +654,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
if (!next[item.sessionID]) next[item.sessionID] = []
next[item.sessionID].push(item)
}
for (const list of Object.values(next)) list.sort((a, b) => a.id.localeCompare(b.id))
setStore("background_process", reconcile(next))
}),
// kilocode_change end
@@ -276,18 +276,36 @@ export function Session() {
const editor = useEditorContext()
// kilocode_change start - background processes are scoped to the visible session
function processGroup(sessionID: string) {
const info = sync.session.get(sessionID)
return info?.parentID ?? info?.id ?? sessionID
}
function processSessions(sessionID: string) {
const group = processGroup(sessionID)
const ids = new Set([sessionID, group])
for (const item of sync.data.session) {
if (item.id === group || item.parentID === group) ids.add(item.id)
}
return Array.from(ids)
}
function stopProcesses(sessionID: string) {
void sdk.client.backgroundProcess.stopSession({ sessionID }).catch((err) => {
Log.Default.warn("failed to stop session background processes", { sessionID, err })
})
for (const id of processSessions(sessionID)) {
void sdk.client.backgroundProcess.stopSession({ sessionID: id }).catch((err) => {
Log.Default.warn("failed to stop session background processes", { sessionID: id, err })
})
}
}
let processSessionID = route.sessionID
createEffect(() => {
const next = route.sessionID
if (processSessionID === next) return
stopProcesses(processSessionID)
const prev = processSessionID
processSessionID = next
if (processGroup(prev) === processGroup(next)) return
stopProcesses(prev)
})
onCleanup(() => {
stopProcesses(processSessionID)
@@ -14,6 +14,7 @@ import { Context, Effect, Layer, Schema, Types } from "effect"
import net from "net"
import path from "path"
import z from "zod"
import * as Ports from "./ports"
export namespace BackgroundProcess {
const log = Log.create({ service: "background-process" })
@@ -21,6 +22,7 @@ export namespace BackgroundProcess {
const KILL_MS = 3_000
const READY_MS = 30_000
const PUBLISH_MS = 500
const PORT_MS = 2_000
const idSchema = Schema.String.annotate({ [ZodOverride]: z.string().startsWith("bgp") }).pipe(
Schema.brand("BackgroundProcessID"),
@@ -61,6 +63,7 @@ export namespace BackgroundProcess {
command: Schema.String,
cwd: Schema.String,
description: optionalOmitUndefined(Schema.String),
ports: Schema.mutable(Schema.Array(PositiveInt)),
status: Status,
ready: Schema.Boolean,
exitCode: optionalOmitUndefined(Schema.NullOr(NonNegativeInt)),
@@ -122,6 +125,9 @@ export namespace BackgroundProcess {
pattern?: RegExp
resolve?: (ready: boolean) => void
notify?: ReturnType<typeof setTimeout>
poll?: ReturnType<typeof setTimeout>
scan?: Promise<boolean>
disposed?: boolean
}
type State = {
@@ -137,6 +143,7 @@ export namespace BackgroundProcess {
function clone(info: Info): Info {
return {
...info,
ports: [...info.ports],
time: { ...info.time },
}
}
@@ -153,7 +160,26 @@ export namespace BackgroundProcess {
return buf.subarray(start).toString("utf-8")
}
function publish(active: Active) {
function same(a: number[], b: number[]) {
return a.length === b.length && a.every((port, index) => port === b[index])
}
async function refresh(active: Active) {
const pid = active.proc.pid
if (!pid || terminal(active.info.status)) {
const changed = active.info.ports.length > 0
active.info.ports = []
return changed
}
const fallback = active.info.ready && active.start.ready?.port ? [active.start.ready.port] : []
const next = Array.from(new Set([...(await Ports.list(pid)), ...fallback])).toSorted((a, b) => a - b)
if (same(active.info.ports, next)) return false
active.info.ports = next
active.info.time.updated = Date.now()
return true
}
function emit(active: Active) {
Instance.restore(active.ctx, () => {
void Bus.publish(Event.Updated, { info: clone(active.info) }).catch((err) => {
log.warn("failed to publish process update", { err, id: active.info.id })
@@ -161,7 +187,50 @@ export namespace BackgroundProcess {
})
}
function publish(active: Active) {
if (active.disposed) return
active.scan = (active.scan ?? refresh(active))
.then(() => {
active.scan = undefined
emit(active)
poll(active)
return false
})
.catch((err) => {
active.scan = undefined
log.debug("failed to refresh process ports", { err, id: active.info.id })
emit(active)
poll(active)
return false
})
}
function poll(active: Active) {
if (active.disposed) return
if (terminal(active.info.status)) return
if (active.poll) return
active.poll = setTimeout(() => {
active.poll = undefined
if (active.disposed) return
if (terminal(active.info.status)) return
active.scan = (active.scan ?? refresh(active))
.then((changed) => {
active.scan = undefined
if (changed) emit(active)
poll(active)
return changed
})
.catch((err) => {
active.scan = undefined
log.debug("failed to refresh process ports", { err, id: active.info.id })
poll(active)
return false
})
}, PORT_MS)
}
function schedule(active: Active) {
if (active.disposed) return
if (active.notify) return
active.notify = setTimeout(() => {
active.notify = undefined
@@ -170,6 +239,7 @@ export namespace BackgroundProcess {
}
function ready(active: Active) {
if (active.disposed) return
if (active.info.ready) return
active.info.ready = true
active.info.status = "ready"
@@ -180,6 +250,7 @@ export namespace BackgroundProcess {
}
function append(active: Active, chunk: string) {
if (active.disposed) return
active.info.output = clamp(active.info.output + chunk)
active.info.time.updated = Date.now()
if (active.pattern?.test(active.info.output)) ready(active)
@@ -187,11 +258,15 @@ export namespace BackgroundProcess {
}
function exited(active: Active, code: number | null, signal: NodeJS.Signals | null) {
if (active.disposed) return
if (terminal(active.info.status)) return
if (active.notify) clearTimeout(active.notify)
if (active.poll) clearTimeout(active.poll)
active.notify = undefined
active.poll = undefined
active.info.exitCode = code
active.info.signal = signal
active.info.ports = []
active.info.ready = active.info.ready && code === 0
active.info.status = active.info.status === "stopping" ? "stopped" : code === 0 ? "exited" : "failed"
active.info.time.updated = Date.now()
@@ -202,10 +277,20 @@ export namespace BackgroundProcess {
}
function failed(active: Active, err: unknown) {
if (active.disposed) return
append(active, `\n${err instanceof Error ? err.message : String(err)}\n`)
exited(active, 1, null)
}
function pattern(input?: string) {
if (!input) return
try {
return new RegExp(input)
} catch (err) {
throw new Error(`Invalid ready pattern: ${err instanceof Error ? err.message : String(err)}`)
}
}
function connected(port: number) {
return new Promise<boolean>((resolve) => {
const socket = net.createConnection({ port, host: "127.0.0.1" })
@@ -325,8 +410,10 @@ export namespace BackgroundProcess {
if (!terminal(active.info.status)) exited(active, active.proc.exitCode, active.proc.signalCode)
}
if (!opts?.remove) return
active.disposed = true
state.processes.delete(active.info.id)
if (active.notify) clearTimeout(active.notify)
if (active.poll) clearTimeout(active.poll)
active.resolve?.(false)
active.resolve = undefined
if (opts.silent) return
@@ -340,7 +427,7 @@ export namespace BackgroundProcess {
async function launch(state: State, input: StartInput, id = ID.ascending()) {
const sh = Shell.acceptable()
const cwd = path.resolve(state.dir, input.cwd ?? state.dir)
const pattern = input.ready?.pattern ? new RegExp(input.ready.pattern) : undefined
const readyPattern = pattern(input.ready?.pattern)
const args = Shell.args(sh, input.command, cwd)
const proc = spawn(sh, args, {
cwd,
@@ -359,6 +446,7 @@ export namespace BackgroundProcess {
command: input.command,
cwd,
description: input.description,
ports: [],
status: input.ready ? "starting" : "running",
ready: false,
output: "",
@@ -369,7 +457,7 @@ export namespace BackgroundProcess {
},
proc,
start: { ...input, cwd },
pattern,
pattern: readyPattern,
}
state.processes.set(id, active)
proc.stdout?.on("data", (chunk) => append(active, chunk.toString("utf-8")))
@@ -393,7 +481,9 @@ export namespace BackgroundProcess {
yield* Effect.addFinalizer(() =>
Effect.promise(async () => {
await Promise.all(
Array.from(state.processes.values()).map((active) => terminate(state, active, { silent: true })),
Array.from(state.processes.values()).map((active) =>
terminate(state, active, { remove: true, silent: true }),
),
)
state.processes.clear()
}),
@@ -0,0 +1,138 @@
import { Process } from "@/util/process"
import fs from "fs/promises"
import path from "path"
function sorted(items: Iterable<number>) {
return Array.from(items).toSorted((a, b) => a - b)
}
function parse(addr: string) {
const raw = addr.split(":").at(-1)
if (!raw) return
const port = Number.parseInt(raw, 16)
if (!Number.isFinite(port) || port <= 0) return
return port
}
async function ppid(pid: number) {
const text = await fs.readFile(`/proc/${pid}/stat`, "utf8")
const match = text.match(/^\d+ \(.+\) \S+ (\d+)/)
if (!match) return
return Number(match[1])
}
async function tree(root: number) {
const names = await fs.readdir("/proc")
const rows = await Promise.all(
names
.filter((name) => /^\d+$/.test(name))
.map(async (name) => {
const pid = Number(name)
const parent = await ppid(pid).catch(() => undefined)
if (!parent) return
return { pid, parent }
}),
)
const children = new Map<number, number[]>()
for (const row of rows) {
if (!row) continue
children.set(row.parent, [...(children.get(row.parent) ?? []), row.pid])
}
const result = new Set([root])
const stack = [root]
while (stack.length > 0) {
const pid = stack.pop()
if (!pid) continue
for (const child of children.get(pid) ?? []) {
if (result.has(child)) continue
result.add(child)
stack.push(child)
}
}
return result
}
async function sockets(pids: Set<number>) {
const all = await Promise.all(
Array.from(pids).map(async (pid) => {
const dir = `/proc/${pid}/fd`
const files = await fs.readdir(dir).catch(() => [])
const links = await Promise.allSettled(files.map((file) => fs.readlink(path.join(dir, file))))
return links.flatMap((item) => {
if (item.status !== "fulfilled") return []
const match = item.value.match(/^socket:\[(\d+)\]$/)
return match ? [match[1]] : []
})
}),
)
return new Set(all.flat())
}
async function file(name: string, inodes: Set<string>) {
const text = await fs.readFile(name, "utf8").catch(() => "")
return text
.trim()
.split(/\r?\n/)
.slice(1)
.flatMap((line) => {
const parts = line.trim().split(/\s+/)
if (parts[3] !== "0A") return []
if (!inodes.has(parts[9])) return []
const port = parse(parts[1])
return port ? [port] : []
})
}
async function linux(root: number) {
const pids = await tree(root)
const found = await sockets(pids)
if (found.size === 0) return []
const ports = await Promise.all([file("/proc/net/tcp", found), file("/proc/net/tcp6", found)])
return sorted(new Set(ports.flat()))
}
async function ps(root: number) {
const rows = await Process.lines(["ps", "-axo", "pid=,ppid="], { nothrow: true })
const children = new Map<number, number[]>()
for (const row of rows) {
const [pid, parent] = row.trim().split(/\s+/).map(Number)
if (!pid || !parent) continue
children.set(parent, [...(children.get(parent) ?? []), pid])
}
const result = new Set([root])
const stack = [root]
while (stack.length > 0) {
const pid = stack.pop()
if (!pid) continue
for (const child of children.get(pid) ?? []) {
if (result.has(child)) continue
result.add(child)
stack.push(child)
}
}
return result
}
async function lsof(root: number) {
const pids = await ps(root).catch(() => new Set([root]))
const rows = await Process.lines(["lsof", "-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", Array.from(pids).join(",")], {
nothrow: true,
})
return sorted(
new Set(
rows.flatMap((row) => {
const match = row.match(/:(\d+)\s+\(LISTEN\)$/)
return match ? [Number(match[1])] : []
}),
),
)
}
export async function list(root: number) {
if (process.platform === "linux") {
const ports = await linux(root).catch(() => [])
if (ports.length > 0) return ports
}
if (process.platform === "win32") return []
return lsof(root).catch(() => [])
}
@@ -23,7 +23,7 @@ type Scope = "session" | "all"
type Kind = "stop" | "restart"
type Theme = ReturnType<typeof useTheme>["theme"]
const stopKey = Keybind.parse("ctrl+x")[0]
const stopKey = Keybind.parse("ctrl+o")[0]
const restartKey = Keybind.parse("ctrl+r")[0]
const allKey = Keybind.parse("ctrl+a")[0]
@@ -49,14 +49,14 @@ function label(item: Info) {
return item.description?.trim() || item.command
}
function last(item: Info) {
return stripAnsi(item.output).trim().split(/\r?\n/).filter(Boolean).at(-1) ?? ""
}
function short(text: string, max = 64) {
return Locale.truncate(text, max)
}
function ports(item: Info) {
return item.ports.length > 0 ? item.ports.join(", ") : "none"
}
function useActions() {
const sdk = useSDK()
const toast = useToast()
@@ -142,21 +142,14 @@ export function DialogProcessList() {
const options = createMemo<DialogSelectOption<string>[]>(() => {
const busy = actions.busy()
return list().map((item) => {
const title = label(item)
const tail = last(item)
const note = mode() === "all" ? session(sync, item.sessionID) : undefined
const footer =
busy?.id === item.id
? `${busy.kind === "stop" ? "stopping" : "restarting"}...`
: `${item.status}${item.pid ? ` pid:${item.pid}` : ""}`
const command = note ? `(${short(note, 28)}) ${short(item.command, 40)}` : short(item.command, 58)
const footer = busy?.id === item.id ? `${busy.kind === "stop" ? "stopping" : "restarting"}...` : item.pid?.toString()
const title = `${note ? `(${note}) ` : ""}${label(item)} - ${item.command}`
return {
title: short(title, 61),
title: short(title, 92),
value: item.id,
description: tail ? `${command} > ${short(tail, 32)}` : command,
footer,
category: item.status,
gutter: () => <StatusMark status={item.status} />,
}
})
@@ -239,7 +232,7 @@ function DialogProcessDetail(props: { id: string; back: () => void }) {
props.back()
return
}
if (keybind.match("ctrl+x", evt)) {
if (keybind.match("ctrl+o", evt)) {
evt.preventDefault()
evt.stopPropagation()
if (proc) void actions.run("stop", proc)
@@ -278,23 +271,31 @@ function DialogProcessDetail(props: { id: string; back: () => void }) {
{(proc) => (
<>
<box>
<text fg={theme.textMuted}>
status{" "}
<span style={{ fg: tone(proc().status, theme), attributes: TextAttributes.BOLD }}>{proc().status}</span>
{proc().pid ? ` pid ${proc().pid}` : ""}
{proc().exitCode !== undefined ? ` exit ${proc().exitCode}` : ""}
{proc().signal ? ` signal ${proc().signal}` : ""}
<text fg={theme.textMuted} wrapMode="word">
Name: {label(proc())}
</text>
<text fg={theme.textMuted}>started {Locale.datetime(proc().time.started)}</text>
<text fg={theme.textMuted}>updated {Locale.datetime(proc().time.updated)}</text>
<text fg={theme.textMuted}>
Status:{" "}
<span style={{ fg: tone(proc().status, theme), attributes: TextAttributes.BOLD }}>{proc().status}</span>
</text>
<text fg={theme.textMuted}>PID: {proc().pid ?? "none"}</text>
<text fg={theme.textMuted}>Ports: {ports(proc())}</text>
<Show when={proc().exitCode !== undefined}>
<text fg={theme.textMuted}>Exit: {proc().exitCode}</text>
</Show>
<Show when={proc().signal}>
{(signal) => <text fg={theme.textMuted}>Signal: {signal()}</text>}
</Show>
<text fg={theme.textMuted}>Started: {Locale.datetime(proc().time.started)}</text>
<text fg={theme.textMuted}>Updated: {Locale.datetime(proc().time.updated)}</text>
<Show when={proc().time.ended}>
{(ended) => <text fg={theme.textMuted}>ended {Locale.datetime(ended())}</text>}
{(ended) => <text fg={theme.textMuted}>Ended: {Locale.datetime(ended())}</text>}
</Show>
<text fg={theme.textMuted} wrapMode="word">
cwd {proc().cwd}
CWD: {proc().cwd}
</text>
<text fg={theme.textMuted} wrapMode="word">
command {proc().command}
Command: {proc().command}
</text>
</box>
<box>
@@ -8,10 +8,6 @@ function short(text: string, max = 34) {
return text.slice(0, max - 3) + "..."
}
function last(text: string) {
return text.trim().split(/\r?\n/).filter(Boolean).at(-1) ?? ""
}
function tone(item: TuiSidebarBackgroundProcessItem, api: TuiPluginApi) {
const theme = api.theme.current
if (item.status === "ready" || item.status === "running") return theme.success
@@ -21,7 +17,7 @@ function tone(item: TuiSidebarBackgroundProcessItem, api: TuiPluginApi) {
}
function label(item: TuiSidebarBackgroundProcessItem) {
return item.description || item.command
return item.description?.trim() || item.command
}
function View(props: { api: TuiPluginApi; session_id: string }) {
@@ -47,13 +43,12 @@ function View(props: { api: TuiPluginApi; session_id: string }) {
<text fg={theme().textMuted} wrapMode="none">
<span style={{ fg: tone(item, props.api) }}></span> {short(label(item))}
</text>
<text fg={theme().textMuted}>
{item.status}
{item.pid ? ` pid:${item.pid}` : ""}
</text>
<text fg={theme().textMuted}>{short(item.command)}</text>
<Show when={last(item.output)}>
<text fg={theme().textMuted}>{"> " + short(last(item.output))}</text>
<Show when={item.pid}>
{(pid) => <text fg={theme().textMuted}>PID: {pid()}</text>}
</Show>
<Show when={item.ports.length > 0}>
<text fg={theme().textMuted}>PORTS: {item.ports.join(", ")}</text>
</Show>
</box>
)}
@@ -11,7 +11,6 @@ const root = "/background-process"
export const BackgroundProcessPaths = {
list: root,
create: root,
get: `${root}/:processID`,
logs: `${root}/:processID/logs`,
stop: `${root}/:processID/stop`,
@@ -35,17 +34,6 @@ export const BackgroundProcessApi = HttpApi.make("background-process")
description: "List tracked background processes for the current instance.",
}),
),
HttpApiEndpoint.post("create", BackgroundProcessPaths.create, {
payload: BackgroundProcess.StartInput,
success: described(BackgroundProcess.Info, "Created background process"),
error: HttpApiError.BadRequest,
}).annotateMerge(
OpenApi.annotations({
identifier: "backgroundProcess.create",
summary: "Create background process",
description: "Start a tracked background process.",
}),
),
HttpApiEndpoint.get("get", BackgroundProcessPaths.get, {
params: { processID: BackgroundProcess.ID },
success: described(BackgroundProcess.Info, "Background process info"),
@@ -12,12 +12,6 @@ export const backgroundProcessHandlers = HttpApiBuilder.group(InstanceHttpApi, "
return yield* Effect.promise(() => BackgroundProcess.list())
})
const create = Effect.fn("BackgroundProcessHttpApi.create")(function* (ctx: {
payload: BackgroundProcess.StartInput
}) {
return yield* Effect.promise(() => BackgroundProcess.start(ctx.payload))
})
const get = Effect.fn("BackgroundProcessHttpApi.get")(function* (ctx: {
params: { processID: BackgroundProcess.ID }
}) {
@@ -59,7 +53,6 @@ export const backgroundProcessHandlers = HttpApiBuilder.group(InstanceHttpApi, "
return handlers
.handle("list", list)
.handle("create", create)
.handle("get", get)
.handle("logs", logs)
.handle("stop", stop)
@@ -14,7 +14,6 @@ type Handler = (request: Request, context: Context.Context<unknown>) => Promise<
export function register(app: Hono, handler: Handler, context: Context.Context<unknown>) {
app.get(BackgroundProcessPaths.list, (c) => handler(c.req.raw, context))
app.post(BackgroundProcessPaths.create, (c) => handler(c.req.raw, context))
app.get(BackgroundProcessPaths.get, (c) => handler(c.req.raw, context))
app.get(BackgroundProcessPaths.logs, (c) => handler(c.req.raw, context))
app.post(BackgroundProcessPaths.stop, (c) => handler(c.req.raw, context))
@@ -24,23 +24,6 @@ export const BackgroundProcessRoutes = lazy(() =>
}),
async (c) => c.json(await BackgroundProcess.list()),
)
.post(
"/",
describeRoute({
summary: "Create background process",
description: "Start a tracked background process.",
operationId: "backgroundProcess.create",
responses: {
200: {
description: "Created background process",
content: { "application/json": { schema: resolver(BackgroundProcess.Info.zod) } },
},
...errors(400),
},
}),
validator("json", BackgroundProcess.StartInput.zod),
async (c) => c.json(await BackgroundProcess.start(c.req.valid("json") as BackgroundProcess.StartInput)),
)
.get(
"/:processID",
describeRoute({
@@ -70,6 +70,15 @@ function invalid(action: Action, message: string) {
}
}
function pattern(ready?: BackgroundProcess.Ready) {
if (!ready?.pattern) return
try {
new RegExp(ready.pattern)
} catch (err) {
return `Invalid ready pattern: ${err instanceof Error ? err.message : String(err)}`
}
}
export const BackgroundProcessTool = Tool.define<typeof Params, Meta, never, "background_process">(
"background_process",
Effect.succeed({
@@ -121,6 +130,8 @@ export const BackgroundProcessTool = Tool.define<typeof Params, Meta, never, "ba
const command = params.command
if (!command?.trim()) return invalid(params.action, "Missing command")
const err = pattern(params.ready)
if (err) return invalid(params.action, err)
const inst = yield* InstanceState.context
const cwd = path.resolve(inst.directory, params.workdir ?? inst.directory)
if (!containsPath(cwd, inst)) {
+1 -1
View File
@@ -285,7 +285,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
count: opts.state?.session?.count ?? (() => 0),
diff: opts.state?.session?.diff ?? (() => []),
todo: opts.state?.session?.todo ?? (() => []),
processes: opts.state?.session?.processes ?? (() => []),
processes: opts.state?.session?.processes ?? (() => []), // kilocode_change
messages: opts.state?.session?.messages ?? (() => []),
status: opts.state?.session?.status ?? (() => undefined),
permission: opts.state?.session?.permission ?? (() => []),
@@ -89,4 +89,30 @@ describe("BackgroundProcess", () => {
}
}),
)
it.instance("rejects invalid readiness patterns before launching", () =>
Effect.gen(function* () {
const test = yield* TestInstance
const sessionID = SessionID.descending()
const err = yield* Effect.promise(async () => {
try {
await BackgroundProcess.start({
sessionID,
command: "printf 'ready\n'",
cwd: test.directory,
ready: { pattern: "[", timeout: 1_000 },
})
} catch (err) {
return err
}
})
expect(err).toBeInstanceOf(Error)
expect((err as Error).message).toContain("Invalid ready pattern")
const list = yield* Effect.promise(() => BackgroundProcess.list({ sessionID }))
expect(list).toEqual([])
}),
)
})
@@ -81,7 +81,6 @@ describe("Kilo HttpApi bridge", () => {
const effect = new Set(openApiRouteKeys(effectOpenApi()))
const kilo = [
`GET ${BackgroundProcessPaths.list}`,
`POST ${BackgroundProcessPaths.create}`,
"GET /background-process/{processID}",
"GET /background-process/{processID}/logs",
"POST /background-process/{processID}/stop",
@@ -119,6 +118,8 @@ describe("Kilo HttpApi bridge", () => {
expect(kilo.filter((route) => !hono.has(route))).toEqual([])
expect(kilo.filter((route) => !effect.has(route))).toEqual([])
expect(hono.has("POST /background-process")).toBe(false)
expect(effect.has("POST /background-process")).toBe(false)
expect(effect.has("GET /indexing/status")).toBe(true)
})
+1 -1
View File
@@ -316,7 +316,7 @@ export type TuiSidebarTodoItem = Pick<Todo, "content" | "status">
export type TuiSidebarBackgroundProcessItem = Pick<
BackgroundProcessInfo,
"id" | "pid" | "command" | "cwd" | "description" | "status" | "output"
"id" | "pid" | "command" | "cwd" | "description" | "ports" | "status" | "output"
>
export type TuiSidebarFileItem = {
-44
View File
@@ -13,8 +13,6 @@ import type {
AuthRemoveResponses,
AuthSetErrors,
AuthSetResponses,
BackgroundProcessCreateErrors,
BackgroundProcessCreateResponses,
BackgroundProcessGetErrors,
BackgroundProcessGetResponses,
BackgroundProcessListResponses,
@@ -22,7 +20,6 @@ import type {
BackgroundProcessLogsResponses,
BackgroundProcessRestartErrors,
BackgroundProcessRestartResponses,
BackgroundProcessStartInput,
BackgroundProcessStopErrors,
BackgroundProcessStopResponses,
BackgroundProcessStopSessionResponses,
@@ -5006,47 +5003,6 @@ export class BackgroundProcess extends HeyApiClient {
})
}
/**
* Create background process
*
* Start a tracked background process.
*/
public create<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
workspace?: string
backgroundProcessStartInput?: BackgroundProcessStartInput
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
{ key: "backgroundProcessStartInput", map: "body" },
],
},
],
)
return (options?.client ?? this.client).post<
BackgroundProcessCreateResponses,
BackgroundProcessCreateErrors,
ThrowOnError
>({
url: "/background-process",
...options,
...params,
headers: {
"Content-Type": "application/json",
...options?.headers,
...params.headers,
},
})
}
/**
* Get background process
*
+1 -45
View File
@@ -269,6 +269,7 @@ export type BackgroundProcessInfo = {
command: string
cwd: string
description?: string
ports: Array<number>
status: "starting" | "running" | "ready" | "exited" | "failed" | "stopping" | "stopped"
ready: boolean
exitCode?: number
@@ -1953,23 +1954,6 @@ export type Workspace = {
projectID: string
}
export type BackgroundProcessReady = {
pattern?: string
port?: number
timeout?: number
}
export type BackgroundProcessStartInput = {
sessionID: string
/**
* Command to run in the configured shell
*/
command: string
cwd?: string
description?: string
ready?: BackgroundProcessReady
}
export type BackgroundProcessLogs = {
id: string
sessionID: string
@@ -7323,34 +7307,6 @@ export type BackgroundProcessListResponses = {
export type BackgroundProcessListResponse = BackgroundProcessListResponses[keyof BackgroundProcessListResponses]
export type BackgroundProcessCreateData = {
body?: BackgroundProcessStartInput
path?: never
query?: {
directory?: string
workspace?: string
}
url: "/background-process"
}
export type BackgroundProcessCreateErrors = {
/**
* Bad request
*/
400: BadRequestError
}
export type BackgroundProcessCreateError = BackgroundProcessCreateErrors[keyof BackgroundProcessCreateErrors]
export type BackgroundProcessCreateResponses = {
/**
* Created background process
*/
200: BackgroundProcessInfo
}
export type BackgroundProcessCreateResponse = BackgroundProcessCreateResponses[keyof BackgroundProcessCreateResponses]
export type BackgroundProcessGetData = {
body?: never
path: {
+8 -102
View File
@@ -8995,67 +8995,6 @@
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.backgroundProcess.list({\n ...\n})"
}
]
},
"post": {
"tags": ["background-process"],
"operationId": "backgroundProcess.create",
"parameters": [
{
"name": "directory",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "workspace",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Created background process",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BackgroundProcessInfo"
}
}
}
},
"400": {
"description": "Bad request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BadRequestError"
}
}
}
}
},
"description": "Start a tracked background process.",
"summary": "Create background process",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BackgroundProcessStartInput"
}
}
}
},
"x-codeSamples": [
{
"lang": "js",
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.backgroundProcess.create({\n ...\n})"
}
]
}
},
"/background-process/{processID}": {
@@ -13246,6 +13185,13 @@
"description": {
"type": "string"
},
"ports": {
"type": "array",
"items": {
"type": "integer",
"exclusiveMinimum": 0
}
},
"status": {
"type": "string",
"enum": ["starting", "running", "ready", "exited", "failed", "stopping", "stopped"]
@@ -13283,7 +13229,7 @@
"additionalProperties": false
}
},
"required": ["id", "sessionID", "command", "cwd", "status", "ready", "output", "time"],
"required": ["id", "sessionID", "command", "cwd", "ports", "status", "ready", "output", "time"],
"additionalProperties": false
},
"SnapshotFileDiff": {
@@ -18132,46 +18078,6 @@
"required": ["id", "type", "name", "branch", "directory", "extra", "projectID"],
"additionalProperties": false
},
"BackgroundProcessReady": {
"type": "object",
"properties": {
"pattern": {
"type": "string"
},
"port": {
"type": "integer",
"exclusiveMinimum": 0
},
"timeout": {
"type": "integer",
"exclusiveMinimum": 0
}
},
"additionalProperties": false
},
"BackgroundProcessStartInput": {
"type": "object",
"properties": {
"sessionID": {
"type": "string"
},
"command": {
"type": "string",
"description": "Command to run in the configured shell"
},
"cwd": {
"type": "string"
},
"description": {
"type": "string"
},
"ready": {
"$ref": "#/components/schemas/BackgroundProcessReady"
}
},
"required": ["sessionID", "command"],
"additionalProperties": false
},
"BackgroundProcessLogs": {
"type": "object",
"properties": {