Merge branch 'main' into fix/tool-approval-source-display

This commit is contained in:
bagatao@anaconda.com
2026-08-07 14:19:15 +02:00
committed by GitHub
18 changed files with 340 additions and 47 deletions
@@ -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))
+1
View File
@@ -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],
+13
View File
@@ -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 = {
+11 -1
View File
@@ -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()
+8
View File
@@ -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> {}
+14
View File
@@ -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" },
],
},
],
+14 -2
View File
@@ -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?: {
+60 -7
View File
@@ -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": {