mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-10-30 19:32:39 +08:00
Merge branch 'main' into fix/tool-approval-source-display
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep Agent Manager terminals aligned and correctly wrapped when the terminal panel is narrow.
|
||||
@@ -531,6 +531,7 @@ type Endpoint14_1Input = {
|
||||
readonly cwd?: Endpoint14_1Request["payload"]["cwd"]
|
||||
readonly title?: Endpoint14_1Request["payload"]["title"]
|
||||
readonly env?: Endpoint14_1Request["payload"]["env"]
|
||||
readonly size?: Endpoint14_1Request["payload"]["size"]
|
||||
}
|
||||
const Endpoint14_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint14_1Input) =>
|
||||
raw["pty.create"]({
|
||||
@@ -541,6 +542,7 @@ const Endpoint14_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint14_1Inpu
|
||||
cwd: input?.["cwd"],
|
||||
title: input?.["title"],
|
||||
env: input?.["env"],
|
||||
size: input?.["size"],
|
||||
},
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
|
||||
@@ -840,6 +840,7 @@ export function make(options: ClientOptions) {
|
||||
cwd: input?.["cwd"],
|
||||
title: input?.["title"],
|
||||
env: input?.["env"],
|
||||
size: input?.["size"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
|
||||
@@ -2632,6 +2632,7 @@ export type PtysCreateInput = {
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["command"]
|
||||
readonly args?: {
|
||||
readonly command?: string
|
||||
@@ -2639,6 +2640,7 @@ export type PtysCreateInput = {
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["args"]
|
||||
readonly cwd?: {
|
||||
readonly command?: string
|
||||
@@ -2646,6 +2648,7 @@ export type PtysCreateInput = {
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["cwd"]
|
||||
readonly title?: {
|
||||
readonly command?: string
|
||||
@@ -2653,6 +2656,7 @@ export type PtysCreateInput = {
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["title"]
|
||||
readonly env?: {
|
||||
readonly command?: string
|
||||
@@ -2660,7 +2664,16 @@ export type PtysCreateInput = {
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["env"]
|
||||
readonly size?: {
|
||||
readonly command?: string
|
||||
readonly args?: ReadonlyArray<string>
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["size"]
|
||||
}
|
||||
|
||||
export type PtysCreateOutput = {
|
||||
|
||||
@@ -214,7 +214,17 @@ const layer = Layer.effect(
|
||||
}
|
||||
yield* Effect.logInfo("creating session", { id, cmd: command, args, cwd })
|
||||
const { spawn } = yield* Effect.promise(() => pty())
|
||||
const proc = yield* Effect.sync(() => spawn(command, args, { name: "xterm-256color", cwd, env }))
|
||||
// kilocode_change start - spawn with initial terminal dimensions
|
||||
const proc = yield* Effect.sync(() =>
|
||||
spawn(command, args, {
|
||||
name: "xterm-256color",
|
||||
cwd,
|
||||
env,
|
||||
cols: input.size?.cols,
|
||||
rows: input.size?.rows,
|
||||
}),
|
||||
)
|
||||
// kilocode_change end
|
||||
const info: Info = {
|
||||
id,
|
||||
title: input.title || `Terminal ${id.slice(-4)}`,
|
||||
|
||||
@@ -51,6 +51,7 @@ interface Entry {
|
||||
export class TerminalManager {
|
||||
private readonly entries = new Map<string, Entry>()
|
||||
private readonly restarts = new Map<string, Promise<void>>()
|
||||
private readonly pending = new Map<string, { cols: number; rows: number }>()
|
||||
|
||||
constructor(private readonly deps: TerminalManagerDeps) {}
|
||||
|
||||
@@ -67,7 +68,14 @@ export class TerminalManager {
|
||||
worktreeId: string | null
|
||||
cwd: string
|
||||
title: string
|
||||
cols?: number
|
||||
rows?: number
|
||||
}): Promise<{ terminalId: string; worktreeId: string | null; title: string; wsUrl: string }> {
|
||||
const initial =
|
||||
this.pending.get(params.terminalId) ??
|
||||
(params.cols !== undefined && params.rows !== undefined ? { cols: params.cols, rows: params.rows } : undefined)
|
||||
this.pending.delete(params.terminalId)
|
||||
|
||||
const client = this.deps.getClient()
|
||||
const { data, error } = await client.pty.create({
|
||||
directory: params.cwd,
|
||||
@@ -76,6 +84,7 @@ export class TerminalManager {
|
||||
// xterm's DOM renderer cannot draw the Unicode sextant glyphs used by
|
||||
// Kilo's modern wordmark, so use the compatible logo in embedded tabs.
|
||||
env,
|
||||
size: initial,
|
||||
})
|
||||
if (error || !data) {
|
||||
const err = error instanceof Error ? error.message : String(error ?? "unknown error")
|
||||
@@ -89,15 +98,39 @@ export class TerminalManager {
|
||||
title: data.title ?? params.title,
|
||||
}
|
||||
this.entries.set(params.terminalId, entry)
|
||||
// If a resize arrived while pty.create was in flight that differed from `initial`, apply it now.
|
||||
const latest = this.pending.get(params.terminalId)
|
||||
if (latest && (latest.cols !== initial?.cols || latest.rows !== initial?.rows)) {
|
||||
this.pending.delete(params.terminalId)
|
||||
const { error: resizeErr } = await client.pty.update({
|
||||
directory: entry.cwd,
|
||||
ptyID: entry.ptyID,
|
||||
size: latest,
|
||||
})
|
||||
if (resizeErr) {
|
||||
const err = resizeErr instanceof Error ? resizeErr.message : String(resizeErr)
|
||||
this.deps.log(`Initial terminal resize failed (${params.terminalId}): ${err}`)
|
||||
}
|
||||
}
|
||||
const wsUrl = this.deps.buildWsUrl(entry.ptyID, entry.cwd)
|
||||
this.deps.log(`Terminal created: ${params.terminalId} -> pty ${entry.ptyID} cwd=${entry.cwd}`)
|
||||
return { terminalId: params.terminalId, worktreeId: entry.worktreeId, title: entry.title, wsUrl }
|
||||
}
|
||||
|
||||
/** Forward a resize event to the backend PTY. Missing terminals are a no-op. */
|
||||
/**
|
||||
* Forward a resize event to the backend PTY.
|
||||
*
|
||||
* If the terminal creation is still in flight, dimensions are queued into
|
||||
* `pending` and applied during PTY initialization before the WebSocket
|
||||
* URL is returned.
|
||||
*/
|
||||
async resize(terminalId: string, cols: number, rows: number): Promise<void> {
|
||||
const entry = this.entries.get(terminalId)
|
||||
if (!entry) return
|
||||
if (!entry) {
|
||||
this.pending.set(terminalId, { cols, rows })
|
||||
return
|
||||
}
|
||||
this.pending.delete(terminalId)
|
||||
const client = this.deps.getClient()
|
||||
const { error } = await client.pty.update({
|
||||
directory: entry.cwd,
|
||||
@@ -126,6 +159,7 @@ export class TerminalManager {
|
||||
* failed delete would be silently logged as a successful close and
|
||||
* the server-side PTY would linger until `kilo serve` exits. */
|
||||
async close(terminalId: string): Promise<void> {
|
||||
this.pending.delete(terminalId)
|
||||
const entry = this.entries.get(terminalId)
|
||||
if (!entry) return
|
||||
this.entries.delete(terminalId)
|
||||
@@ -187,6 +221,7 @@ export class TerminalManager {
|
||||
* is sampled mid-shutdown.
|
||||
*/
|
||||
async dispose(): Promise<void> {
|
||||
this.pending.clear()
|
||||
const snapshot = [...this.entries.values()]
|
||||
if (snapshot.length === 0) {
|
||||
this.entries.clear()
|
||||
|
||||
@@ -88,7 +88,7 @@ export class TerminalRouter {
|
||||
handle(m: AgentManagerInMessage): boolean {
|
||||
if (!isTerminalMessage(m)) return false
|
||||
if (m.type === "agentManager.terminal.create") {
|
||||
void this.handleCreate(m.createId, m.placement, m.worktreeId)
|
||||
void this.handleCreate(m.createId, m.placement, m.worktreeId, m.cols, m.rows)
|
||||
return true
|
||||
}
|
||||
if (m.type === "agentManager.terminal.close") {
|
||||
@@ -132,7 +132,13 @@ export class TerminalRouter {
|
||||
return manager.dispose()
|
||||
}
|
||||
|
||||
private async handleCreate(createId: string, placement: TerminalPlacement, worktreeId: string | null): Promise<void> {
|
||||
private async handleCreate(
|
||||
createId: string,
|
||||
placement: TerminalPlacement,
|
||||
worktreeId: string | null,
|
||||
cols?: number,
|
||||
rows?: number,
|
||||
): Promise<void> {
|
||||
const generation = this.generation
|
||||
const manager = this.manager
|
||||
const cwd = this.resolveCwd(worktreeId)
|
||||
@@ -155,7 +161,7 @@ export class TerminalRouter {
|
||||
// Join the shared backend connection instead of racing its synchronous
|
||||
// client accessor when this is the first Kilo action in the window.
|
||||
await this.deps.getClientAsync()
|
||||
const created = await manager.create({ terminalId: createId, worktreeId, cwd, title })
|
||||
const created = await manager.create({ terminalId: createId, worktreeId, cwd, title, cols, rows })
|
||||
if (generation !== this.generation) {
|
||||
await manager.close(created.terminalId)
|
||||
return
|
||||
|
||||
@@ -946,6 +946,8 @@ interface TerminalCreateIn {
|
||||
placement: TerminalPlacement
|
||||
/** null for LOCAL, worktree id otherwise */
|
||||
worktreeId: string | null
|
||||
cols?: number
|
||||
rows?: number
|
||||
}
|
||||
|
||||
interface TerminalCloseIn {
|
||||
|
||||
@@ -39,6 +39,29 @@ test("does not refit hidden terminal buffers during resize", () => {
|
||||
expect(callback!.indexOf("if (!props.active) return")).toBeLessThan(callback!.indexOf("fit.fit()"))
|
||||
})
|
||||
|
||||
test("keeps raw PTY line endings and initializes Unicode widths before attaching", () => {
|
||||
expect(terminal).toContain("convertEol: false")
|
||||
expect(terminal).toContain('term.unicode.activeVersion = "15-graphemes"')
|
||||
expect(terminal.indexOf("term.loadAddon(new UnicodeGraphemesAddon())")).toBeLessThan(
|
||||
terminal.indexOf("open(props.wsUrl)"),
|
||||
)
|
||||
})
|
||||
|
||||
test("fits and forces the initial PTY dimensions before socket attach", () => {
|
||||
expect(terminal).toContain("const syncSize = (force = false)")
|
||||
expect(terminal).toContain("if (props.active) syncSize(true)")
|
||||
expect(terminal.indexOf("fitNow()\n open(props.wsUrl)")).toBeGreaterThan(-1)
|
||||
})
|
||||
|
||||
test("re-sends dimensions when an optimistic terminal receives its PTY", () => {
|
||||
const created = terminal.match(
|
||||
/if \(message\.terminalId === props\.terminalId && !ws\) \{([\s\S]*?)\n \}/,
|
||||
)?.[1]
|
||||
expect(created).toBeDefined()
|
||||
expect(created).toContain("fitNow()")
|
||||
expect(created!.indexOf("fitNow()")).toBeLessThan(created!.indexOf("open(message.wsUrl)"))
|
||||
})
|
||||
|
||||
test("clamps the restored inspector width to the shared layout bounds", () => {
|
||||
expect(clampPanelWidth(undefined, 1200)).toBe(600)
|
||||
expect(clampPanelWidth(500, 1200)).toBe(500)
|
||||
|
||||
@@ -343,4 +343,62 @@ describe("Agent Manager terminal routing", () => {
|
||||
})
|
||||
await router.dispose()
|
||||
})
|
||||
|
||||
it("applies initial create dimensions and queues resize messages before creation settles", async () => {
|
||||
const creates: Array<Record<string, unknown>> = []
|
||||
const updates: Array<{ ptyID: string; size?: { cols: number; rows: number } }> = []
|
||||
let createResolver: ((value: { data: { id: string; title: string } }) => void) | undefined
|
||||
const client = {
|
||||
pty: {
|
||||
create: (params: Record<string, unknown>) =>
|
||||
new Promise<{ data: { id: string; title: string } }>((resolve) => {
|
||||
creates.push(params)
|
||||
createResolver = resolve
|
||||
}),
|
||||
remove: async () => ({ data: true }),
|
||||
update: async (params: { ptyID: string; size?: { cols: number; rows: number } }) => {
|
||||
updates.push(params)
|
||||
return { data: true }
|
||||
},
|
||||
},
|
||||
} as unknown as KiloClient
|
||||
const router = new TerminalRouter({
|
||||
getClient: () => client,
|
||||
getClientAsync: async () => client,
|
||||
getServerConfig: () => ({ baseUrl: "http://127.0.0.1:4096", password: "secret" }),
|
||||
getRoot: () => "/workspace",
|
||||
getWorktreePath: () => undefined,
|
||||
getProjectId: () => "prj-1",
|
||||
log: () => undefined,
|
||||
post: () => undefined,
|
||||
getTerminalFont: () => font,
|
||||
})
|
||||
|
||||
router.handle({
|
||||
type: "agentManager.terminal.create",
|
||||
createId: "queued",
|
||||
placement: "side",
|
||||
worktreeId: null,
|
||||
cols: 60,
|
||||
rows: 20,
|
||||
})
|
||||
await wait()
|
||||
expect(creates).toHaveLength(1)
|
||||
expect(creates[0]?.size).toEqual({ cols: 60, rows: 20 })
|
||||
|
||||
// Send a resize before pty.create settles (optimistic side terminal layout)
|
||||
router.handle({
|
||||
type: "agentManager.terminal.resize",
|
||||
terminalId: "queued",
|
||||
cols: 55,
|
||||
rows: 18,
|
||||
})
|
||||
await wait()
|
||||
expect(updates).toHaveLength(0)
|
||||
|
||||
createResolver?.({ data: { id: "pty-queued", title: "Terminal 1" } })
|
||||
await wait()
|
||||
expect(updates).toEqual([{ directory: "/workspace", ptyID: "pty-queued", size: { cols: 55, rows: 18 } }])
|
||||
await router.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -152,7 +152,10 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
|
||||
onMount(() => {
|
||||
const term = new Terminal({
|
||||
convertEol: true,
|
||||
// PTY output already contains terminal line endings. Converting LF to
|
||||
// CRLF here corrupts raw PTY output and is especially visible when a
|
||||
// narrow terminal wraps and redraws the prompt.
|
||||
convertEol: false,
|
||||
cursorBlink: true,
|
||||
cursorInactiveStyle: "outline",
|
||||
fontFamily: props.font.fontFamily,
|
||||
@@ -164,17 +167,11 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
const fit = new FitAddon()
|
||||
term.loadAddon(fit)
|
||||
term.open(host)
|
||||
// Fit on the next frame — `host` might still have 0px dimensions
|
||||
// during the initial layout pass otherwise.
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
fit.fit()
|
||||
} catch (err) {
|
||||
// Host still detached at mount time. ResizeObserver will retry
|
||||
// once layout kicks in. Logged so regressions don't hide.
|
||||
log("initial fit() threw", err)
|
||||
}
|
||||
})
|
||||
// Unicode width must be configured before the first PTY bytes are parsed.
|
||||
// Loading it later can leave already-wrapped graphemes with stale cell
|
||||
// widths, which moves the cursor in narrow terminals.
|
||||
term.loadAddon(new UnicodeGraphemesAddon())
|
||||
term.unicode.activeVersion = "15-graphemes"
|
||||
|
||||
// Pass Agent Manager hotkeys through to the parent key handler so
|
||||
// ⌘T / ⌘⇧T / ⌘W / terminal cycling / ⌘⌥← still work while focused.
|
||||
@@ -356,11 +353,9 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
}
|
||||
const disposeData = term.onData(send)
|
||||
const disposeBinary = term.onBinary(send)
|
||||
open(props.wsUrl)
|
||||
|
||||
// These addons are not needed to paint the initial prompt. Defer them
|
||||
// until after the first frame so their startup work, especially the
|
||||
// Unicode 15 width tables, does not delay the shell connection.
|
||||
// until after the first frame so their startup work does not delay the
|
||||
// shell connection.
|
||||
const loadAddons = () => {
|
||||
deferred = undefined
|
||||
if (closed) return
|
||||
@@ -375,16 +370,8 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
)
|
||||
// OSC 52 clipboard support for shell programs such as tmux and neovim.
|
||||
term.loadAddon(new ClipboardAddon())
|
||||
// Use grapheme-aware width tables for newer emoji and ZWJ sequences.
|
||||
term.loadAddon(new UnicodeGraphemesAddon())
|
||||
term.unicode.activeVersion = "15-graphemes"
|
||||
term.refresh(0, Math.max(0, term.rows - 1))
|
||||
}
|
||||
frame = requestAnimationFrame(() => {
|
||||
frame = undefined
|
||||
deferred = requestAnimationFrame(loadAddons)
|
||||
})
|
||||
|
||||
const restarted = (url: string) => {
|
||||
open(url)
|
||||
}
|
||||
@@ -395,10 +382,12 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
let resizeTimer: ReturnType<typeof setTimeout> | undefined
|
||||
let lastCols = term.cols
|
||||
let lastRows = term.rows
|
||||
const syncSize = () => {
|
||||
if (term.cols === lastCols && term.rows === lastRows) return
|
||||
let synced = false
|
||||
const syncSize = (force = false) => {
|
||||
if (!force && synced && term.cols === lastCols && term.rows === lastRows) return
|
||||
lastCols = term.cols
|
||||
lastRows = term.rows
|
||||
synced = true
|
||||
vscode.postMessage({
|
||||
type: "agentManager.terminal.resize",
|
||||
terminalId: props.terminalId,
|
||||
@@ -406,6 +395,16 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
rows: term.rows,
|
||||
})
|
||||
}
|
||||
const fitNow = () => {
|
||||
try {
|
||||
fit.fit()
|
||||
if (props.active) syncSize(true)
|
||||
} catch (err) {
|
||||
// Host still detached at mount time. ResizeObserver will retry
|
||||
// once layout kicks in. Logged so regressions don't hide.
|
||||
log("fit() threw", err)
|
||||
}
|
||||
}
|
||||
const ro = new ResizeObserver(() => {
|
||||
if (!props.active) return
|
||||
try {
|
||||
@@ -422,6 +421,16 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
resizeTimer = setTimeout(syncSize, RESIZE_DEBOUNCE_MS)
|
||||
})
|
||||
ro.observe(host)
|
||||
// Wait for the first committed layout before attaching the socket. This
|
||||
// prevents the shell from emitting its first prompt at xterm's default
|
||||
// 80 columns, which is most visible in a narrow side panel.
|
||||
frame = requestAnimationFrame(() => {
|
||||
frame = undefined
|
||||
if (closed) return
|
||||
fitNow()
|
||||
open(props.wsUrl)
|
||||
deferred = requestAnimationFrame(loadAddons)
|
||||
})
|
||||
|
||||
// ---- Repaint recovery ----
|
||||
//
|
||||
@@ -452,7 +461,7 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
if (!isRenderable()) return
|
||||
try {
|
||||
fit.fit()
|
||||
syncSize()
|
||||
syncSize(!synced)
|
||||
} catch (err) {
|
||||
// Layout not settled yet; ResizeObserver retries on next change.
|
||||
log("repaint fit() threw", err)
|
||||
@@ -485,6 +494,9 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
if (message.terminalId === props.terminalId && !ws) {
|
||||
term.options.fontFamily = message.font.fontFamily
|
||||
term.options.fontSize = message.font.fontSize
|
||||
// Optimistic side terminals can fit before their backend PTY exists;
|
||||
// force the first resize again once the created response arrives.
|
||||
fitNow()
|
||||
scheduleRepaint()
|
||||
open(message.wsUrl)
|
||||
}
|
||||
|
||||
@@ -628,10 +628,32 @@ function newId(): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the close-terminal handler the main component wires to the
|
||||
* close button. Picks the next visible tab before dropping the entry
|
||||
* so focus flows naturally; notifies the extension last.
|
||||
* Estimate initial terminal geometry from the current DOM container.
|
||||
* Provides best-effort columns and rows so PTY spawn avoids the default
|
||||
* 80-column line width before the first xterm fit pass commits.
|
||||
*/
|
||||
function measureInitialDimensions(
|
||||
placement: TerminalPlacement,
|
||||
font: TerminalFont,
|
||||
): { cols: number; rows: number } | undefined {
|
||||
if (typeof document === "undefined") return undefined
|
||||
const selector =
|
||||
placement === "side"
|
||||
? ".am-side-terminal-layer, .am-side-terminal, .am-diff-panel-wrapper"
|
||||
: ".am-terminal-layer, .am-detail-stack"
|
||||
const host = document.querySelector(selector) as HTMLElement | null
|
||||
const rect = host?.getBoundingClientRect()
|
||||
if (!rect || rect.width <= 0 || rect.height <= 0) return undefined
|
||||
const cellWidth = font.fontSize > 0 ? font.fontSize * 0.6 : 7.2
|
||||
const cellHeight = font.fontSize > 0 ? font.fontSize * 1.2 : 14.4
|
||||
const availableWidth = Math.max(0, rect.width - 30)
|
||||
const availableHeight = Math.max(0, rect.height - 16)
|
||||
return {
|
||||
cols: Math.max(10, Math.floor(availableWidth / cellWidth)),
|
||||
rows: Math.max(3, Math.floor(availableHeight / cellHeight)),
|
||||
}
|
||||
}
|
||||
|
||||
export function createTerminalHandlers(deps: TerminalHandlerDeps) {
|
||||
const activate = (id: string) => {
|
||||
deps.state.setActiveId(id)
|
||||
@@ -645,11 +667,15 @@ export function createTerminalHandlers(deps: TerminalHandlerDeps) {
|
||||
const requestNew = () => {
|
||||
const sel = deps.getSelection()
|
||||
if (sel === null) return
|
||||
const font = deps.getFont()
|
||||
const dims = measureInitialDimensions("tab", font)
|
||||
deps.postMessage({
|
||||
type: "agentManager.terminal.create",
|
||||
createId: newId(),
|
||||
placement: "tab",
|
||||
worktreeId: sel === deps.LOCAL ? null : sel,
|
||||
cols: dims?.cols,
|
||||
rows: dims?.rows,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -659,12 +685,14 @@ export function createTerminalHandlers(deps: TerminalHandlerDeps) {
|
||||
// project-namespaced state key and must not leak into the message.
|
||||
const sel = deps.getSelection()
|
||||
const id = newId()
|
||||
const font = deps.getFont()
|
||||
const dims = measureInitialDimensions("side", font)
|
||||
deps.state.beginSide(key, id)
|
||||
deps.state.add(key === deps.LOCAL ? null : key, {
|
||||
id,
|
||||
title: "Terminal",
|
||||
wsUrl: "",
|
||||
font: deps.getFont(),
|
||||
font,
|
||||
placement: "side",
|
||||
})
|
||||
deps.state.setSideActive(key, id)
|
||||
@@ -674,6 +702,8 @@ export function createTerminalHandlers(deps: TerminalHandlerDeps) {
|
||||
createId: id,
|
||||
placement: "side",
|
||||
worktreeId: sel === null || sel === deps.LOCAL ? null : sel,
|
||||
cols: dims?.cols,
|
||||
rows: dims?.rows,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -813,6 +813,8 @@ export interface AgentManagerTerminalCreateRequest {
|
||||
createId: string
|
||||
placement: TerminalPlacement
|
||||
worktreeId: string | null
|
||||
cols?: number
|
||||
rows?: number
|
||||
}
|
||||
|
||||
// Close a terminal tab
|
||||
|
||||
@@ -81,11 +81,18 @@ describe("pty HttpApi bridge", () => {
|
||||
expect(list.status).toBe(200)
|
||||
expect(await list.json()).toEqual([])
|
||||
|
||||
// kilocode_change start - test initial spawn dimensions
|
||||
const created = await app().request(PtyPaths.create, {
|
||||
method: "POST",
|
||||
headers: { ...headers, "content-type": "application/json" },
|
||||
body: JSON.stringify({ command: "/usr/bin/env", args: ["sh", "-c", "sleep 5"], title: "demo" }),
|
||||
body: JSON.stringify({
|
||||
command: "/usr/bin/env",
|
||||
args: ["sh", "-c", "sleep 5"],
|
||||
title: "demo",
|
||||
size: { cols: 50, rows: 20 },
|
||||
}),
|
||||
})
|
||||
// kilocode_change end
|
||||
expect(created.status).toBe(200)
|
||||
const info = await created.json()
|
||||
|
||||
|
||||
@@ -45,6 +45,14 @@ export const CreateInput = Schema.Struct({
|
||||
cwd: optional(Schema.String),
|
||||
title: optional(Schema.String),
|
||||
env: optional(Schema.Record(Schema.String, Schema.String)),
|
||||
// kilocode_change start - spawn with initial terminal dimensions
|
||||
size: optional(
|
||||
Schema.Struct({
|
||||
rows: PositiveInt,
|
||||
cols: PositiveInt,
|
||||
}),
|
||||
),
|
||||
// kilocode_change end
|
||||
})
|
||||
export interface CreateInput extends Schema.Schema.Type<typeof CreateInput> {}
|
||||
|
||||
|
||||
@@ -3428,6 +3428,10 @@ export class Pty extends HeyApiClient {
|
||||
env?: {
|
||||
[key: string]: string
|
||||
}
|
||||
size?: {
|
||||
rows: number
|
||||
cols: number
|
||||
}
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -3443,6 +3447,7 @@ export class Pty extends HeyApiClient {
|
||||
{ in: "body", key: "cwd" },
|
||||
{ in: "body", key: "title" },
|
||||
{ in: "body", key: "env" },
|
||||
{ in: "body", key: "size" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -4430,6 +4435,8 @@ export class Session2 extends HeyApiClient {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
messageID?: string
|
||||
file?: string
|
||||
full?: "true" | "false"
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -4442,6 +4449,8 @@ export class Session2 extends HeyApiClient {
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
{ in: "query", key: "messageID" },
|
||||
{ in: "query", key: "file" },
|
||||
{ in: "query", key: "full" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -10831,6 +10840,10 @@ export class Pty2 extends HeyApiClient {
|
||||
env?: {
|
||||
[key: string]: string
|
||||
}
|
||||
size?: {
|
||||
rows: number
|
||||
cols: number
|
||||
}
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -10845,6 +10858,7 @@ export class Pty2 extends HeyApiClient {
|
||||
{ in: "body", key: "cwd" },
|
||||
{ in: "body", key: "title" },
|
||||
{ in: "body", key: "env" },
|
||||
{ in: "body", key: "size" },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -465,6 +465,8 @@ export type IndexingWarning = {
|
||||
export type SnapshotFileDiff = {
|
||||
file?: string
|
||||
patch?: string
|
||||
before?: string
|
||||
after?: string
|
||||
additions: number
|
||||
deletions: number
|
||||
status?: "added" | "deleted" | "modified"
|
||||
@@ -2888,11 +2890,11 @@ export type WorktreeResetInput = {
|
||||
export type WorktreeDiffItem = {
|
||||
file?: string
|
||||
patch?: string
|
||||
before: string
|
||||
after: string
|
||||
additions: number
|
||||
deletions: number
|
||||
status?: "added" | "deleted" | "modified"
|
||||
before: string
|
||||
after: string
|
||||
tracked: boolean
|
||||
generatedLike: boolean
|
||||
summarized: boolean
|
||||
@@ -12326,6 +12328,10 @@ export type PtyCreateData = {
|
||||
env?: {
|
||||
[key: string]: string
|
||||
}
|
||||
size?: {
|
||||
rows: number
|
||||
cols: number
|
||||
}
|
||||
}
|
||||
path?: never
|
||||
query?: {
|
||||
@@ -13177,6 +13183,8 @@ export type SessionDiffData = {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
messageID?: string
|
||||
file?: string
|
||||
full?: "true" | "false"
|
||||
}
|
||||
url: "/session/{sessionID}/diff"
|
||||
}
|
||||
@@ -20244,6 +20252,10 @@ export type V2PtyCreateData = {
|
||||
env?: {
|
||||
[key: string]: string
|
||||
}
|
||||
size?: {
|
||||
rows: number
|
||||
cols: number
|
||||
}
|
||||
}
|
||||
path?: never
|
||||
query?: {
|
||||
|
||||
@@ -4539,6 +4539,21 @@
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rows": {
|
||||
"type": "integer",
|
||||
"exclusiveMinimum": 0
|
||||
},
|
||||
"cols": {
|
||||
"type": "integer",
|
||||
"exclusiveMinimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["rows", "cols"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -6587,6 +6602,23 @@
|
||||
"pattern": "^msg"
|
||||
},
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "file",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "full",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["true", "false"]
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -24318,6 +24350,21 @@
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rows": {
|
||||
"type": "integer",
|
||||
"exclusiveMinimum": 0
|
||||
},
|
||||
"cols": {
|
||||
"type": "integer",
|
||||
"exclusiveMinimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["rows", "cols"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -27026,6 +27073,12 @@
|
||||
"patch": {
|
||||
"type": "string"
|
||||
},
|
||||
"before": {
|
||||
"type": "string"
|
||||
},
|
||||
"after": {
|
||||
"type": "string"
|
||||
},
|
||||
"additions": {
|
||||
"type": "number"
|
||||
},
|
||||
@@ -34498,6 +34551,12 @@
|
||||
"patch": {
|
||||
"type": "string"
|
||||
},
|
||||
"before": {
|
||||
"type": "string"
|
||||
},
|
||||
"after": {
|
||||
"type": "string"
|
||||
},
|
||||
"additions": {
|
||||
"type": "number"
|
||||
},
|
||||
@@ -34508,12 +34567,6 @@
|
||||
"type": "string",
|
||||
"enum": ["added", "deleted", "modified"]
|
||||
},
|
||||
"before": {
|
||||
"type": "string"
|
||||
},
|
||||
"after": {
|
||||
"type": "string"
|
||||
},
|
||||
"tracked": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -34527,7 +34580,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["additions", "deletions", "before", "after", "tracked", "generatedLike", "summarized", "stamp"],
|
||||
"required": ["before", "after", "additions", "deletions", "tracked", "generatedLike", "summarized", "stamp"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"SnapshotSummaryFileDiff": {
|
||||
|
||||
Reference in New Issue
Block a user