Merge pull request #11146 from Kilo-Org/feat/gitkilo

Polish VS Code tool call previews
This commit is contained in:
Marius
2026-06-17 17:11:40 +02:00
committed by GitHub
38 changed files with 1168 additions and 165 deletions
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Polish chat tool-call previews for shell, search, and background-process output.
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:129ea4c06998d8418bdb8edc8e61e094849835edb3f40cf6e1ddc34568168e61
size 52396
oid sha256:b9d8327c530c9d071b5eb07ebbf48441ea8d64b8a4cd7201665a8fa037ca82ed
size 50349
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a943ba7617204c720540078a5c741511de9be1e0534d96188e18ddf6e942972b
size 41054
oid sha256:7b0cb661ae199182341c0034495298cb939ed5487ed8d1dcd41bb558eda8d0e3
size 45709
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:95f83279f09c34994294cef4a33c9007d6a7b7e84f553558edf6bdac62711216
size 37558
oid sha256:3f104b9ceef97c0dd5f5522745fb573189ba562a60b1f73196565e1b5a4b7dc6
size 37225
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f419ed4b9435f9a01c94dca80ef5b252cc108ccbb0e4d7ba978acf0fbf02bbe4
size 32635
oid sha256:4cf97da43aa741ac4227026dbb4f16a3a2e44a8394a4f0ab321a0938a5d5fb6e
size 32313
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0ac3951c6ce87f25da24a146dd5420528392720e081688646b67f33632bb6152
size 28951
oid sha256:bb4ff7b2909397b635c29603e93956ef16f12ffb1c3ac77f9f4c5c3fd7a07389
size 28876
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:381b72a63cc256f520e671460431f9dc9950d27fd727d0779d1a6b558c7593cd
size 18195
oid sha256:e74a00c414bf627c41e0841380dcd359cc7398111caa0545aafb7681cf76ea7e
size 18128
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5a0460abb79716b44ec7e9eb42c8d364c2e6737fc79042ca6b715dffe33b1a74
size 24548
oid sha256:6e4e9484d65b9dfab86a970db0242366566c2f345f13cab6fb523cc1a625873b
size 24479
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7cabfff215cef485ec913b9a082aaac844275e506e88ba0fab112d95ff6de329
size 18200
oid sha256:fc84c90016236e0c08dea8989c2124092d167d1d94136c33484ece3a54525250
size 18881
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:18d394d726ebee312e54db5d45f55cca3a1728d2b41f9f73bb9b610096bf53e0
size 41004
oid sha256:209002988d276858f6e6adae0083ff84c9a1b853ef396bbe4c8121e678121065
size 44104
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b5584b7b6d9cc3b80082e1d6fb24eda43d084fb437cf10c15fa3117e99ad9e89
size 4300
oid sha256:222790e6c32346cf6b6cc52a0859ee01d364b52a1ef8b9f385a6e9798adfccfb
size 4256
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:067a5c757dfa32d27955dd634b8dea52edef2141111a5eecee0161df36c0ed8b
size 7061
oid sha256:acccbf1fa9a76076f324031ed4d7e9ac317059a127c238cf9eaa9e4569dd38b2
size 7023
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c583144b11ac9608ec755e9a683dced8ae6ffae9a6f3833e1c0eec6b664f358e
size 7720
oid sha256:516db3d182497211aa32a32d8b33de309967be93e56a348f0397ad8eaef57eec
size 7014
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f0af006973871a81b4c0ae2fc6c98c88151655afbd9e63d77e970c18db861355
size 26371
oid sha256:c3ebab71919a7bafe609933ba8909f22e68171d3546473cca7ad5e3d859787ef
size 30655
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6c05d543611aff7bf10ef406de42844fa1aa825275e380c6ded05d03170aa9b0
size 8057
oid sha256:76b359a64ad4dede420eb4669f973f80f87be63b5d0a1dfe36829f0c19698381
size 7486
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b1ebe718b7747f0b0db029396c728ae859f388f11cf206bb020fcd7027f3b0a0
size 5046
oid sha256:cba6f84ec6138fc59be088cdf84d2ebe57a55f8514df58eed693a84969ff7a90
size 4794
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9b47f6adfc72399cb1535be3a69661b027eec3068880aba35821d5240e0c3492
size 16739
oid sha256:1adee4d2de92696f86183a38fabd14dc38be1b818100092f2014017e4de12b8c
size 16710
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5a308ad1c07d7af681670853d5e0f40b01ee88ad4e863dd5b84c0ab511757cdf
size 6134
oid sha256:56c7610136e6a015393f5e19b239ff446ec78ad34c75d3dda1e6d6870964f501
size 6340
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d4d7ac9d6322d2cf673a6353cb07cf9979ad55351769ba9d90eed386170f6c42
size 40854
oid sha256:146055492419facf8997d352f47ee80e9f205b60cc5345cf266683bc10518a8f
size 37718
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1d21dc25072b1c52cacaf0a91369f45e89f43ccc181d643e6b65030b829e5b1e
size 8591
oid sha256:0539bd8e7296f0a57af343b48b311f35292dd3b8eef257fb713cef457ea63a74
size 7150
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3fb9398991f40c60fb16d73b05ba4dd85af3475928df65917fefb9af1c4aaf1
size 7836
oid sha256:9233fec056782bc365cfbbeca8533f63a287e8363455826821d6c4c2fb99d841
size 7619
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a8576fc52a9122900230f545f9b0886280251b3c636a7c2416432730c2844398
size 8727
oid sha256:8296de6d4c8196db8566f622037cd2f1559bcc4523294fec2e1d84602edf23e8
size 8469
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:342c39c1ff3d02cef37e076209ddd1d3e1fe0f9eb207097b408d2078c0f05c58
size 181042
+237 -15
View File
@@ -35,18 +35,29 @@
[data-slot="basic-tool-tool-subtitle"] {
font-size: var(--kilo-font-size-12);
color: var(--text-weak);
}
[data-slot="basic-tool-tool-title"] {
font-size: var(--kilo-font-size-14);
font-size: var(--kilo-font-size-12);
font-weight: var(--font-weight-regular);
color: var(--text-weak);
}
[data-slot="message-part-title-text"] {
font-size: var(--kilo-font-size-14);
font-size: var(--kilo-font-size-12);
font-weight: var(--font-weight-regular);
color: var(--text-weak);
}
[data-slot="basic-tool-tool-arg"] {
font-size: var(--kilo-font-size-12);
color: var(--text-weak);
}
[data-slot="message-part-title-filename"] {
font-size: var(--kilo-font-size-12);
color: var(--text-weak);
}
[data-slot="message-part-meta-line"] {
@@ -58,6 +69,7 @@
min-width: 0;
flex-shrink: 1;
font-size: var(--kilo-font-size-12);
color: var(--text-weak);
&.clickable {
cursor: pointer;
@@ -108,37 +120,93 @@ html[data-theme="kilo-vscode"] [data-component="tool-trigger"] {
align-items: center;
justify-content: center;
flex-shrink: 0;
color: var(--icon-base);
color: var(--text-weak);
[data-component="icon"] {
display: flex;
color: inherit;
}
}
}
/* Card styling for tool calls in the VS Code sidebar theme */
html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-type="tool"] {
border-radius: var(--radius-md);
border: 1px solid var(--border-weak-base, var(--vscode-panel-border));
overflow: hidden;
border-radius: 0;
border: 0;
overflow: visible;
/* Header trigger — add bottom border when the collapsible is open */
[data-component="collapsible"].tool-collapsible {
gap: 0px;
gap: 0;
border-radius: 0;
&:has([data-slot="collapsible-trigger"][aria-expanded="true"]) {
gap: 6px;
}
[data-slot="collapsible-trigger"] {
padding: 0 8px;
height: 36px;
background-color: var(--surface-inset-base, var(--vscode-sideBar-background));
margin-inline: -6px;
width: calc(100% + 12px);
padding: 2px 6px;
min-height: 24px;
height: auto;
background-color: transparent;
&:hover {
background-color: var(--surface-inset-base-hover, var(--vscode-list-hoverBackground));
background-color: transparent;
}
}
[data-slot="basic-tool-tool-trigger-content"] {
align-self: center;
gap: 6px;
}
[data-slot="basic-tool-tool-info-structured"],
[data-slot="basic-tool-tool-info-main"] {
gap: 6px;
}
[data-slot="basic-tool-tool-title"]
+ :is(
[data-slot="basic-tool-tool-subtitle"],
[data-slot="basic-tool-tool-arg"],
[data-slot="message-part-meta-line"],
[data-slot="webfetch-meta"],
[data-component="shell-submessage"]
)::before {
content: "·";
margin-right: 6px;
color: var(--text-weak);
}
[data-slot="message-part-title-text"]
+ :is([data-slot="message-part-meta-line"], [data-slot="basic-tool-tool-subtitle"])::before {
content: "·";
margin-right: 6px;
color: var(--text-weak);
}
[data-slot="basic-tool-tool-title"],
[data-slot="basic-tool-tool-subtitle"],
[data-slot="basic-tool-tool-arg"],
[data-slot="message-part-title-text"],
[data-slot="message-part-title-filename"],
[data-slot="message-part-directory-inline"],
[data-slot="message-part-meta-line"] {
line-height: var(--kilo-font-size-16);
}
[data-slot="collapsible-trigger"][aria-expanded="true"] {
border-bottom: 1px solid var(--border-weak-base, var(--vscode-panel-border));
border-bottom: 0;
}
[data-slot="collapsible-trigger"]:has([data-slot="collapsible-arrow"]) {
border-radius: 4px;
cursor: pointer;
transition:
background-color 0.15s ease,
color 0.15s ease;
}
/* Arrow always visible (no fade-on-hover) */
@@ -146,13 +214,32 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty
opacity: 1;
}
/* Arrow: DOWN when collapsed, UP when expanded (matches question-dock toggle) */
/* Arrow: RIGHT when collapsed, DOWN when expanded. */
[data-slot="collapsible-arrow-icon"] {
transform: translateZ(0) rotate(0deg);
transform: translateZ(0) rotate(-90deg);
}
[data-slot="collapsible-trigger"][aria-expanded="true"] [data-slot="collapsible-arrow-icon"] {
transform: translateZ(0) rotate(180deg);
transform: translateZ(0) rotate(0deg);
}
[data-slot="collapsible-trigger"]:has([data-slot="collapsible-arrow"]):hover {
background-color: var(--vscode-list-hoverBackground, var(--surface-raised-base-hover));
[data-slot="basic-tool-tool-title"],
[data-slot="basic-tool-tool-subtitle"],
[data-slot="basic-tool-tool-arg"],
[data-slot="message-part-title-text"],
[data-slot="message-part-title-filename"],
[data-slot="message-part-directory-inline"],
[data-slot="message-part-meta-line"] {
color: var(--vscode-list-hoverForeground, var(--vscode-foreground, var(--text-strong)));
}
[data-slot="basic-tool-icon"],
[data-slot="collapsible-arrow-icon"] {
color: var(--vscode-list-hoverForeground, var(--vscode-foreground, var(--icon-base)));
}
}
}
@@ -174,6 +261,100 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty
margin-top: 4px;
}
[data-slot="mcp-section-label"] + [data-component="tool-output"] {
margin-inline: -6px;
width: calc(100% + 12px);
max-height: 240px;
padding: 8px 12px 10px;
border: 0;
border-radius: var(--radius-sm);
background: var(--surface-tool-output-base);
color: var(--text-weak);
font-size: var(--kilo-font-size-12);
line-height: var(--kilo-font-size-16);
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
[data-component="markdown"] {
color: inherit;
font-size: inherit;
line-height: inherit;
}
[data-component="markdown"] :is(a, code, span, [data-highlight="file"]) {
color: inherit;
}
}
[data-component="context-tool-group-trigger"] {
min-height: 20px;
[data-slot="context-tool-group-title"] {
gap: 6px;
font-size: var(--kilo-font-size-12);
font-weight: var(--font-weight-regular);
line-height: var(--kilo-font-size-16);
color: var(--text-weak);
}
[data-slot="context-tool-group-summary"] {
color: var(--text-weak);
}
[data-slot="context-tool-group-summary"]::before {
content: "·";
margin-right: 6px;
color: var(--text-weak);
}
}
[data-component="context-tool-expanded-list"] {
max-height: 180px;
overflow-y: auto;
padding: 4px 0;
border: 1px solid var(--border-weak-base);
background: color-mix(in srgb, var(--vscode-terminal-background, var(--vscode-panel-background)) 92%, black 8%);
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
[data-component="context-tool-expanded-row"],
[data-component="context-tool-rolling-row"] {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
align-items: baseline;
column-gap: 8px;
min-height: 20px;
font-size: var(--kilo-font-size-12);
line-height: var(--kilo-font-size-16);
}
[data-component="context-tool-expanded-row"] {
padding: 2px 12px;
}
[data-slot="context-tool-expanded-action"],
[data-slot="context-tool-rolling-action"] {
color: var(--text-weak);
}
[data-slot="context-tool-expanded-detail"],
[data-slot="context-tool-rolling-detail"] {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text-weak);
font-family: var(--font-family-mono);
font-feature-settings: var(--font-family-mono--font-feature-settings);
}
/* Expandable tool output content */
[data-component="tool-output"] {
padding: 8px 12px;
@@ -186,4 +367,45 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty
overflow-y: auto;
}
}
[data-component="tool-output"][data-variant="preview"] {
margin-inline: -6px;
width: calc(100% + 12px);
max-height: 240px;
padding: 8px 12px 10px;
border: 0;
border-radius: var(--radius-sm);
background: var(--surface-tool-output-base);
color: var(--text-weak);
font-family: var(--font-family-mono);
font-feature-settings: var(--font-family-mono--font-feature-settings);
font-size: var(--kilo-font-size-12);
line-height: var(--line-height-large);
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: normal;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
[data-component="markdown"] {
color: inherit;
font: inherit;
line-height: inherit;
white-space: inherit;
overflow-wrap: inherit;
}
[data-component="markdown"] :is(a, code, span, [data-highlight="file"]) {
color: inherit;
}
[data-component="markdown"] > *,
[data-component="markdown"] p,
[data-component="markdown"] pre {
margin: 0;
}
}
}
@@ -6,13 +6,13 @@
}
}
/* VS Code theme: tool-collapsible arrows point DOWN (collapsed) and UP (expanded), never right */
/* VS Code theme: tool-collapsible arrows point RIGHT when collapsed and DOWN when expanded. */
html[data-theme="kilo-vscode"] [data-component="collapsible"].tool-collapsible {
[data-slot="collapsible-arrow-icon"] {
transform: translateZ(0) rotate(0deg);
transform: translateZ(0) rotate(-90deg);
}
[data-slot="collapsible-trigger"][aria-expanded="true"] [data-slot="collapsible-arrow-icon"] {
transform: translateZ(0) rotate(180deg);
transform: translateZ(0) rotate(0deg);
}
}
+183 -17
View File
@@ -210,30 +210,72 @@
html[data-theme="kilo-vscode"] [data-component="bash-output"] {
border-radius: 0;
background: var(--vscode-terminal-background, var(--vscode-panel-background));
border: 0;
background: transparent;
overflow: visible;
[data-slot="bash-pre"] code {
color: var(--vscode-terminal-foreground, var(--vscode-editor-foreground));
[data-slot="bash-terminal"] {
border: 0;
border-radius: var(--radius-sm);
background: var(--surface-tool-output-base);
}
[data-slot="bash-divider"] {
background: var(--vscode-panel-border, var(--border-weak-base));
[data-slot="bash-pre"] code,
.shiki code {
color: var(--text-weak);
}
}
/* Bash tool: section layout (command + output) */
/* Bash tool: static terminal preview with separate command/output surfaces. */
[data-component="bash-output"] {
display: flex;
flex-direction: column;
gap: 4px;
margin-inline: -6px;
width: calc(100% + 12px);
border: 0;
border-radius: 0;
background: transparent;
overflow: visible;
}
[data-component="bash-output"] [data-slot="bash-terminal"] {
border-radius: 0;
border: 1px solid var(--border-weak-base);
background: var(--surface-raised-base);
overflow: hidden;
}
[data-component="bash-output"] [data-slot="bash-section"] {
position: relative;
}
[data-component="bash-output"] [data-slot="bash-section"][data-kind="command"] {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
align-items: start;
column-gap: 8px;
padding: 8px 12px 7px;
}
[data-component="bash-output"] [data-slot="bash-section"][data-kind="output"] {
padding: 8px 12px 10px;
}
[data-component="bash-output"] [data-slot="bash-prompt"] {
color: var(--text-weak, var(--vscode-descriptionForeground));
font-family: var(--font-family-mono);
font-feature-settings: var(--font-family-mono--font-feature-settings);
font-size: var(--kilo-font-size-12);
line-height: var(--line-height-large);
user-select: none;
}
[data-component="bash-output"] [data-slot="bash-section-code"] {
min-width: 0;
overflow-y: auto;
overflow-x: hidden;
max-height: 240px;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
[data-component="bash-output"] [data-slot="bash-section-actions"] {
@@ -254,25 +296,29 @@ html[data-theme="kilo-vscode"] [data-component="bash-output"] {
pointer-events: auto;
}
/* Edge-to-edge divider (matches MCP tool divider) */
[data-component="bash-output"] [data-slot="bash-divider"] {
height: 1px;
background: var(--border-weak-base);
[data-component="bash-output"] [data-slot="bash-pre"] {
margin: 0;
padding: 0;
}
[data-component="bash-output"] .shiki {
margin: 0;
padding: 0;
background: transparent !important;
}
/* Shiki syntax-highlighted shell output */
[data-component="bash-output"] .shiki,
[data-component="shell-expanded-output"] .shiki {
margin: 0;
padding: 12px;
background: transparent !important;
}
[data-component="bash-output"] [data-slot="bash-pre"] code,
[data-component="bash-output"] .shiki code,
[data-component="shell-expanded-output"] .shiki code {
font-family: var(--font-family-mono);
font-feature-settings: var(--font-family-mono--font-feature-settings);
font-size: var(--font-size-small);
font-size: var(--kilo-font-size-12);
line-height: var(--line-height-large);
white-space: pre-wrap;
overflow-wrap: anywhere;
@@ -380,6 +426,32 @@ html[data-theme="kilo-vscode"] [data-component="bash-output"] {
}
}
html[data-theme="kilo-vscode"] [data-component="todos"] {
margin-inline: -6px;
width: calc(100% + 12px);
padding: 8px 12px 10px;
border: 0;
border-radius: var(--radius-sm);
background: var(--surface-tool-output-base);
color: var(--text-weak);
[data-slot="checkbox-checkbox-label"] {
color: var(--text-weak);
font-size: var(--kilo-font-size-12);
line-height: var(--kilo-font-size-16);
}
[data-slot="message-part-todo-hidden"] {
color: var(--text-weaker);
font-size: var(--kilo-font-size-12);
line-height: var(--kilo-font-size-16);
}
[data-slot="message-part-todo-content"][data-changed="changed"] {
color: var(--text-base);
}
}
/* User message copy button: collapse wrapper when idle, show on hover.
* The wrapper must be tall enough to fully contain the button (20px)
* so that hovering the button keeps [data-component="user-message"]
@@ -514,6 +586,100 @@ html[data-theme="kilo-vscode"] [data-component="bash-output"] {
}
}
html[data-theme="kilo-vscode"] [data-component="text-part"] {
margin-top: 0;
}
html[data-theme="kilo-vscode"] [data-component="reasoning-part"] {
border-left: 0;
border-radius: 0;
overflow: visible;
[data-component="collapsible"].tool-collapsible {
gap: 0;
border-radius: 0;
&:has([data-slot="collapsible-trigger"][aria-expanded="true"]) {
gap: 6px;
}
}
[data-slot="collapsible-trigger"] {
margin-inline: -6px;
width: calc(100% + 12px);
min-height: 24px;
height: auto;
padding: 2px 6px;
background: transparent;
border-radius: 4px;
transition:
background-color 0.15s ease,
color 0.15s ease;
&:hover {
background-color: var(--vscode-list-hoverBackground, var(--surface-raised-base-hover));
}
}
[data-slot="collapsible-arrow"] {
opacity: 1;
}
[data-slot="reasoning-header"] {
gap: 6px;
color: var(--text-weak);
font-size: var(--kilo-font-size-12);
font-weight: var(--font-weight-regular);
line-height: var(--kilo-font-size-16);
[data-component="icon"] {
color: var(--text-weak);
}
}
[data-slot="reasoning-label"],
[data-slot="reasoning-title"] {
color: var(--text-weak);
font-weight: var(--font-weight-regular);
line-height: var(--kilo-font-size-16);
}
[data-slot="reasoning-label"] + [data-slot="reasoning-title"]::before {
content: "·";
margin-right: 6px;
color: var(--text-weak);
}
[data-slot="collapsible-trigger"]:hover {
[data-slot="reasoning-label"],
[data-slot="reasoning-title"] {
color: var(--vscode-list-hoverForeground, var(--vscode-foreground, var(--text-strong)));
}
[data-slot="reasoning-header"] [data-component="icon"],
[data-slot="collapsible-arrow-icon"] {
color: var(--vscode-list-hoverForeground, var(--vscode-foreground, var(--icon-base)));
}
}
[data-slot="collapsible-content"] {
margin-inline: -6px;
width: calc(100% + 12px);
}
[data-slot="reasoning-content"] {
padding: 8px 12px 10px;
border: 0;
border-radius: var(--radius-sm);
background: var(--surface-tool-output-base);
[data-component="markdown"] {
font-size: var(--kilo-font-size-12);
line-height: 160%;
}
}
}
@keyframes reasoning-pulse {
0%,
100% {
@@ -1852,7 +1852,7 @@ ToolRegistry.register({
>
<Show when={props.output}>
{(output) => (
<div data-component="tool-output" data-scrollable>
<div data-component="tool-output" data-variant="preview" data-scrollable>
<Markdown text={output()} />
</div>
)}
@@ -1883,7 +1883,7 @@ ToolRegistry.register({
>
<Show when={props.output}>
{(output) => (
<div data-component="tool-output" data-scrollable>
<div data-component="tool-output" data-variant="preview" data-scrollable>
<Markdown text={output()} />
</div>
)}
@@ -1917,7 +1917,7 @@ ToolRegistry.register({
>
<Show when={props.output}>
{(output) => (
<div data-component="tool-output" data-scrollable>
<div data-component="tool-output" data-variant="preview" data-scrollable>
<Markdown text={output()} />
</div>
)}
@@ -2162,34 +2162,36 @@ function BashHighlightedOutput(props: { cmd: string; output: string; outputPath?
return (
<div data-component="bash-output">
<Show when={props.cmd}>
<div data-slot="mcp-section-label">{i18n.t("ui.messagePart.shell.command")}</div>
<div data-slot="bash-section">
<div data-slot="bash-section-code" ref={cmdRef} />
<div data-slot="bash-section-actions">
<BashCopyButton value={() => props.cmd} label={i18n.t("ui.message.copy")} />
<div data-slot="bash-terminal" data-kind="command">
<div data-slot="bash-section" data-kind="command">
<span data-slot="bash-prompt" aria-hidden="true">
$
</span>
<div data-slot="bash-section-code" data-scrollable ref={cmdRef} />
<div data-slot="bash-section-actions">
<BashCopyButton value={() => props.cmd} label={i18n.t("ui.message.copy")} />
</div>
</div>
</div>
</Show>
<Show when={props.cmd && props.output}>
<div data-slot="bash-divider" />
</Show>
<Show when={props.output}>
<div data-slot="mcp-section-label">{i18n.t("ui.messagePart.shell.output")}</div>
<div data-slot="bash-section">
<div data-slot="bash-section-code" data-scrollable ref={outRef} />
<div data-slot="bash-section-actions">
<Show when={data.openContent || (props.outputPath && data.openFile)}>
<Tooltip value={i18n.t("ui.messagePart.openInEditor")} placement="bottom" gutter={4}>
<IconButton
icon="square-arrow-top-right"
size="small"
variant="ghost"
onClick={openInEditor}
aria-label={i18n.t("ui.messagePart.openInEditor")}
/>
</Tooltip>
</Show>
<BashCopyButton value={() => props.output} label={i18n.t("ui.message.copy")} />
<div data-slot="bash-terminal" data-kind="output">
<div data-slot="bash-section" data-kind="output">
<div data-slot="bash-section-code" data-scrollable ref={outRef} />
<div data-slot="bash-section-actions">
<Show when={data.openContent || (props.outputPath && data.openFile)}>
<Tooltip value={i18n.t("ui.messagePart.openInEditor")} placement="bottom" gutter={4}>
<IconButton
icon="square-arrow-top-right"
size="small"
variant="ghost"
onClick={openInEditor}
aria-label={i18n.t("ui.messagePart.openInEditor")}
/>
</Tooltip>
</Show>
<BashCopyButton value={() => props.output} label={i18n.t("ui.message.copy")} />
</div>
</div>
</div>
</Show>
@@ -2229,11 +2231,11 @@ ToolRegistry.register({
<BasicTool
{...props}
icon="console"
animated
hasDetails
defaultOpen={props.defaultOpen ?? true}
onOpenChange={setOpen}
allowPendingToggle
animated
trigger={
<div data-slot="basic-tool-tool-info-structured">
<div data-slot="basic-tool-tool-info-main">
@@ -48,6 +48,7 @@ html[data-theme="kilo-vscode"] {
--surface-weak: var(--vscode-input-background, var(--vscode-list-hoverBackground));
--surface-weaker: var(--vscode-editor-inactiveSelectionBackground);
--surface-strong: var(--vscode-editorWidget-background);
--surface-tool-output-base: color-mix(in srgb, var(--vscode-sideBar-background, var(--surface-base)) 64%, black 36%);
--surface-brand-base: var(--vscode-button-background);
--surface-brand-hover: var(--vscode-button-hoverBackground);
@@ -338,3 +339,8 @@ html[data-theme="kilo-vscode"] {
--avatar-text-cyan: var(--vscode-charts-blue);
--avatar-text-lime: var(--vscode-charts-green);
}
body.vscode-light,
body.vscode-high-contrast-light {
--surface-tool-output-base: color-mix(in srgb, var(--vscode-sideBar-background, var(--surface-base)) 96%, black 4%);
}
@@ -102,6 +102,22 @@ describe("webview font-size architecture", () => {
).toEqual([])
})
it("uses scalable line heights in polished tool previews", () => {
const files = [
path.join(REPO, "packages/kilo-ui/src/components/basic-tool.css"),
path.join(REPO, "packages/kilo-ui/src/components/message-part.css"),
]
const violations = files.flatMap((file) => {
const src = stripComments(fs.readFileSync(file, "utf-8"))
return Array.from(
src.matchAll(/line-height\s*:\s*\d+(?:\.\d+)?px\b/g),
(match) => `${rel(file)}:${line(src, match.index ?? 0)}`,
)
})
expect(violations).toEqual([])
})
it("injects and live-broadcasts the webview font-size setting to all webview providers", () => {
const util = fs.readFileSync(path.join(ROOT, "src/utils.ts"), "utf-8")
expect(util, "buildWebviewHtml must seed webview font tokens before app code runs").toContain("getWebviewFontSize")
@@ -23,6 +23,13 @@ const BASIC_TOOL_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/bas
const DATA_CONTEXT_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/context/data.tsx")
const MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/message-part.tsx")
const KILO_MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/kilo-ui/src/components/message-part.tsx")
const KILO_MESSAGE_PART_CSS_FILE = path.join(MONOREPO_ROOT, "packages/kilo-ui/src/components/message-part.css")
const SHELL_ROLLING_FILE = path.join(MONOREPO_ROOT, "packages/kilo-ui/src/components/shell-rolling-results.tsx")
const ASSISTANT_MESSAGE_FILE = path.join(
MONOREPO_ROOT,
"packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx",
)
const CHAT_LAYOUT_FILE = path.join(MONOREPO_ROOT, "packages/kilo-vscode/webview-ui/src/styles/chat-layout.css")
function check(code: string): { ok: boolean; output: string } {
const result = Bun.spawnSync(["bun", "--conditions=browser", "-e", code], {
@@ -185,7 +192,7 @@ describe("Write and apply_patch patch rendering contracts (source)", () => {
})
})
describe("Bash tool syntax highlighting and section labels (source)", () => {
describe("Bash tool static terminal preview (source)", () => {
const src = fs.readFileSync(KILO_MESSAGE_PART_FILE, "utf-8")
const block =
src.match(/ToolRegistry\.register\(\{\s*name:\s*"bash"[\s\S]*?(?=ToolRegistry\.register\(|$)/)?.[0] ?? ""
@@ -194,25 +201,38 @@ describe("Bash tool syntax highlighting and section labels (source)", () => {
expect(block).toContain("BashHighlightedOutput")
})
it("BashHighlightedOutput uses shellscript grammar for commands without $ prefix", () => {
// The command should be highlighted as shellscript, but the $ prompt must
// NOT be inside the highlighted code (it breaks Shiki's parse context)
expect(src).toMatch(/data-lang="shellscript">\$\{escapeHtml\(cmd\)\}/)
expect(src).not.toMatch(/data-lang="shellscript">\$\s/)
it("animates expanded bash details", () => {
expect(block).toMatch(/allowPendingToggle\s+animated\s+trigger=/)
})
it("BashHighlightedOutput uses log grammar for output", () => {
expect(src).toMatch(/data-lang="log"/)
it("BashHighlightedOutput syntax highlights the command next to the prompt", () => {
expect(src).toContain('data-slot="bash-terminal" data-kind="command"')
expect(src).toContain('data-slot="bash-prompt"')
expect(src).toContain('data-slot="bash-section-code" data-scrollable ref={cmdRef}')
expect(src).toContain('data-lang="shellscript"')
expect(src).toContain("escapeHtml(cmd)")
})
it("BashHighlightedOutput renders section labels matching MCP tool pattern", () => {
// Must use the same data-slot as MCP tools for consistent styling
expect(src).toMatch(/data-slot="mcp-section-label".*shell\.command/)
expect(src).toMatch(/data-slot="mcp-section-label".*shell\.output/)
it("BashHighlightedOutput syntax highlights log output", () => {
expect(src).toContain('data-slot="bash-terminal" data-kind="output"')
expect(src).toContain('data-slot="bash-section-code" data-scrollable ref={outRef}')
expect(src).toContain('data-lang="log"')
expect(src).toContain("escapeHtml(out)")
})
it("BashHighlightedOutput has edge-to-edge divider between sections", () => {
expect(src).toContain('data-slot="bash-divider"')
it("BashHighlightedOutput highlights only while expanded", () => {
expect(src).toContain("if (!props.active) return")
expect(block).toContain("active={open()}")
})
it("BashHighlightedOutput keeps command and output in separate terminal containers", () => {
const slots = src.match(/data-slot="bash-terminal"/g) ?? []
expect(slots).toHaveLength(2)
})
it("BashHighlightedOutput does not render shell section labels or a divider", () => {
expect(src).not.toMatch(/data-slot="mcp-section-label".*shell\./)
expect(src).not.toContain('data-slot="bash-divider"')
})
it("BashHighlightedOutput supports openContent for opening output in editor", () => {
@@ -232,6 +252,23 @@ describe("Bash tool syntax highlighting and section labels (source)", () => {
})
})
describe("Expanded tool motion and typography (source)", () => {
it("animates completed rolling shell details", () => {
const src = fs.readFileSync(SHELL_ROLLING_FILE, "utf-8")
expect(src).toContain("useCollapsible({")
expect(src).toContain("content: () => contentRef")
expect(src).toContain("body: () => bodyRef")
})
it("uses the assistant markdown line-height ratio for reasoning output", () => {
const css = fs.readFileSync(KILO_MESSAGE_PART_CSS_FILE, "utf-8")
const block = css.match(
/html\[data-theme="kilo-vscode"\] \[data-component="reasoning-part"\][\s\S]*?(?=@keyframes reasoning-pulse)/,
)?.[0]
expect(block).toMatch(/\[data-component="markdown"\]\s*\{[^}]*line-height:\s*160%;/)
})
})
describe("HighlightedText @mention regex fallback and click handler (source)", () => {
const src = fs.readFileSync(KILO_MESSAGE_PART_FILE, "utf-8")
@@ -255,13 +292,47 @@ describe("HighlightedText @mention regex fallback and click handler (source)", (
expect(src).toMatch(/segment\.text\.replace\(\/\^@\//)
})
it("escapeHtml is imported from shared util, not duplicated", () => {
expect(src).toMatch(/import.*escapeHtml.*from.*util\/escape-html/)
// Must NOT contain a local function definition
it("does not duplicate HTML escaping helpers", () => {
expect(src).not.toMatch(/function escapeHtml/)
})
})
describe("AssistantMessage visible row contract (source)", () => {
const src = fs.readFileSync(ASSISTANT_MESSAGE_FILE, "utf-8")
it("filters suppressed tools that have no visible renderer", () => {
expect(src).toContain('state.status === "completed" && !!ToolRegistry.render(tool)')
})
it("filters pending questions until their dock request exists", () => {
expect(src).toContain('part.state.status !== "pending" && part.state.status !== "running"')
expect(src).toContain('matchToolRequest(part, "question", session.questions())')
})
it("filters completed synthetic text and redaction-only reasoning", () => {
expect(src).toContain('part.type === "text" && part.synthetic && props.message.time.completed')
expect(src).toContain('.text?.replace("[REDACTED]", "").trim()')
})
it("uses the plan exit card only when plan metadata is renderable", () => {
expect(src).toContain("if (!planExitInfo(part)) return")
})
})
describe("Assistant transcript spacing contract (source)", () => {
const css = fs.readFileSync(CHAT_LAYOUT_FILE, "utf-8")
it("uses a 6px gap between virtualized assistant rows", () => {
expect(css).toMatch(/\.vscode-session-turn\[data-row="assistant"\]\s*\{\s*padding-bottom: 6px;/)
})
it("removes spacing from assistant rows without visible content", () => {
expect(css).toMatch(
/\.vscode-session-turn\[data-row="assistant"\]:has\(> \.vscode-session-turn-assistant:empty\)\s*\{\s*padding-bottom: 0;/,
)
})
})
describe("BasicTool export contract (runtime)", () => {
it("BasicTool and GenericTool are exported from basic-tool", () => {
const result = check(`
@@ -89,14 +89,16 @@ function isRenderable(part: SDKPart): boolean {
const tool = (part as SDKPart & { tool: string }).tool
const state = (part as SDKPart & { state: { status: string } }).state
if (UPSTREAM_SUPPRESSED_TOOLS.has(tool)) {
// Show todo parts only when completed (permissions are now in the dock)
return state.status === "completed"
// Show completed todo parts only when kilo-ui provides a visible renderer.
return state.status === "completed" && !!ToolRegistry.render(tool)
}
// Always render question tool parts — active ones get the inline QuestionDock
return true
}
if (part.type === "text") return !snapshotProgress(part) && !!(part as SDKPart & { text: string }).text?.trim()
if (part.type === "reasoning") return !!(part as SDKPart & { text: string }).text?.trim()
if (part.type === "reasoning") {
return !!(part as SDKPart & { text: string }).text?.replace("[REDACTED]", "").trim()
}
return !!PART_MAPPING[part.type]
}
@@ -188,7 +190,13 @@ export const AssistantMessage: Component<AssistantMessageProps> = (props) => {
const parts = createMemo(() => {
const stored = props.parts ?? data.store.part?.[props.message.id]
if (!stored) return []
return (stored as SDKPart[]).filter((part) => isRenderable(part))
return (stored as SDKPart[]).filter((part) => {
if (!isRenderable(part)) return false
if (part.type === "text" && part.synthetic && props.message.time.completed) return false
if (part.type !== "tool" || part.tool !== "question") return true
if (part.state.status !== "pending" && part.state.status !== "running") return true
return !!matchToolRequest(part, "question", session.questions())
})
})
return (
@@ -213,11 +221,8 @@ export const AssistantMessage: Component<AssistantMessageProps> = (props) => {
return part
})
const planExit = createMemo(() => {
if (part.type !== "tool") return
const tp = part as unknown as ToolPart
if (tp.tool !== "plan_exit") return
if (tp.state?.status !== "completed") return
return tp
if (!planExitInfo(part)) return
return part as unknown as ToolPart
})
return (
@@ -124,25 +124,29 @@ function BackgroundProcessTool(props: ToolProps) {
defaultOpen={expanded(props.status, props.defaultOpen)}
allowPendingToggle
>
<Show when={rows().length > 0}>
<div data-component="background-process-fields">
<For each={rows()}>
{(row) => (
<div data-slot="background-process-field">
<span data-slot="background-process-label">{row[0]}</span>
<span data-slot="background-process-value">{row[1]}</span>
<Show when={rows().length > 0 || data().output}>
<div data-component="background-process-details">
<Show when={rows().length > 0}>
<div data-component="background-process-fields">
<For each={rows()}>
{(row) => (
<div data-slot="background-process-field">
<span data-slot="background-process-label">{row[0]}</span>
<span data-slot="background-process-value">{row[1]}</span>
</div>
)}
</For>
</div>
</Show>
<Show when={data().output}>
{(value) => (
<div data-component="tool-output" data-variant="preview" data-scrollable>
<pre data-slot="background-process-output">{value()}</pre>
</div>
)}
</For>
</Show>
</div>
</Show>
<Show when={data().output}>
{(value) => (
<div data-component="tool-output" data-scrollable>
<pre data-slot="background-process-output">{value()}</pre>
</div>
)}
</Show>
</BasicTool>
)
}
@@ -18,7 +18,7 @@ import { ProviderContext } from "../context/provider"
import { flattenModels, findModel as _findModel } from "../context/provider-utils"
import { ConfigProvider, ConfigContext } from "../context/config"
import { DisplayProvider } from "../context/display"
import { DataProvider } from "@kilocode/kilo-ui/context/data"
import { DataProvider, type OpenDiffFn, type OpenFileFn } from "@kilocode/kilo-ui/context/data"
import { DiffComponentProvider } from "@kilocode/kilo-ui/context/diff"
import { CodeComponentProvider } from "@kilocode/kilo-ui/context/code"
import { FileComponentProvider } from "@kilocode/kilo-ui/context/file"
@@ -180,7 +180,6 @@ export function mockSessionValue(overrides?: {
setCurrentSessionID: noop,
sessions: () => [],
status: () => status,
submitting: () => false,
statusInfo: () => ({ type: status }),
closeReason: () => overrides?.closeReason,
statusText: () => (status === "idle" ? undefined : "Thinking…"),
@@ -188,6 +187,9 @@ export function mockSessionValue(overrides?: {
loading: () => false,
loadingOlderMessages: () => false,
hasOlderMessages: () => false,
submitting: () => false,
draftSessionID: () => undefined,
setDraftSessionID: noop,
messageMutation: () => undefined,
messages: () => [],
visibleMessages: () => [],
@@ -196,6 +198,8 @@ export function mockSessionValue(overrides?: {
allParts: () => ({}),
allStatusMap: () => ({}),
getParts: () => [],
getSessionToolParts: () => [],
getSessionToolCount: () => 0,
isErrorHidden: () => false,
hydrateParts: noop,
todos: () => [],
@@ -256,6 +260,7 @@ export function mockSessionValue(overrides?: {
deleteSession: noop,
renameSession: noop,
syncSession: noop,
exportSessionTranscript: noop,
cloudPreviewId: () => null,
selectCloudSession: noop,
}
@@ -280,6 +285,8 @@ interface StoryProvidersProps {
onConfigChange?: (config: Config) => void
onGlobalConfigChange?: (config: Config) => void
onProjectConfigChange?: (config: Config) => void
onOpenDiff?: OpenDiffFn
onOpenFile?: OpenFileFn
kiloAuth?: boolean
/** When true, renders children without the default 12px padding wrapper */
noPadding?: boolean
@@ -403,7 +410,12 @@ export const StoryProviders: ParentComponent<StoryProvidersProps> = (props) => {
<SessionContext.Provider value={session as any}>
<IndexingProvider>
<KiloEmbeddingModelsProvider>
<DataProvider data={data()} directory="/project/">
<DataProvider
data={data()}
directory="/project/"
onOpenDiff={props.onOpenDiff}
onOpenFile={props.onOpenFile}
>
<DiffComponentProvider component={Diff}>
<CodeComponentProvider component={Code}>
<FileComponentProvider component={File}>
@@ -0,0 +1,467 @@
/** @jsxImportSource solid-js */
import type { Meta, StoryObj } from "storybook-solidjs-vite"
import type {
AssistantMessage as SDKAssistantMessage,
Part as SDKPart,
ReasoningPart,
TextPart,
ToolPart,
} from "@kilocode/sdk/v2"
import { StoryProviders, defaultMockData } from "./StoryProviders"
import { AssistantMessage } from "../components/chat/AssistantMessage"
import { registerVscodeToolOverrides } from "../components/chat/VscodeToolOverrides"
import type { QuestionRequest, SuggestionRequest } from "../types/messages"
import { writeToolOpen } from "../../../../kilo-ui/src/components/tool-open-state"
registerVscodeToolOverrides()
const SID = "tool-call-lab-session"
const MID = "tool-call-lab-message"
const stamp = Date.now()
const base: SDKAssistantMessage = {
id: MID,
sessionID: SID,
role: "assistant",
parentID: "tool-call-lab-user-message",
time: { created: stamp - 9000, completed: stamp - 1000 },
modelID: "anthropic/claude-sonnet-4-6",
providerID: "kilo",
mode: "default",
agent: "default",
path: { cwd: "/project", root: "/project" },
cost: 0.0021,
tokens: { total: 742, input: 386, output: 356, reasoning: 0, cache: { read: 0, write: 0 } },
}
const hits = [
'packages/kilo-ui/src/components/message-part.tsx:1847: <div data-component="tool-output">',
'packages/kilo-ui/src/components/basic-tool.css:250: [data-component="tool-output"]',
"packages/kilo-vscode/webview-ui/src/components/chat/VscodeToolOverrides.tsx:141: background process output",
].join("\n")
const proc = [
"pid: 48122",
"status: running",
"cwd: /project",
"command: bun run --cwd packages/kilo-vscode storybook",
"last_output:",
"Storybook 9.0.18 for solid-vite started",
"Local: http://localhost:6007/",
].join("\n")
function completed(input: Record<string, unknown>, title: string, value: string): ToolPart["state"] {
return {
status: "completed",
input,
output: value,
title,
metadata: {},
time: { start: stamp - 5000, end: stamp - 4400 },
}
}
function failed(input: Record<string, unknown>, value: string): ToolPart["state"] {
return {
status: "error",
input,
error: value,
metadata: {},
time: { start: stamp - 3600, end: stamp - 3400 },
}
}
function tool(id: string, call: string, name: string, state: ToolPart["state"]): ToolPart {
return {
id,
sessionID: SID,
messageID: MID,
type: "tool",
callID: call,
tool: name,
state,
}
}
function text(id: string, value: string): TextPart {
return {
id,
sessionID: SID,
messageID: MID,
type: "text",
text: value,
}
}
function reasoning(id: string, value: string): ReasoningPart {
return {
id,
sessionID: SID,
messageID: MID,
type: "reasoning",
text: value,
time: { start: stamp - 6800, end: stamp - 6200 },
}
}
function bash(id: string, call: string, description: string, command: string, output: string): ToolPart {
return tool(id, call, "bash", completed({ description, command }, description, output))
}
const gapPatch = [
"===================================================================",
"--- packages/kilo-ui/src/components/message-part.css",
"+++ packages/kilo-ui/src/components/message-part.css",
"@@ -560,5 +560,5 @@",
' html[data-theme="kilo-vscode"] [data-component="reasoning-part"] {',
' [data-component="collapsible"].tool-collapsible {',
"- gap: 4px;",
"+ gap: 8px;",
" }",
" }",
].join("\n")
const writePatch = [
"===================================================================",
"--- packages/kilo-vscode/webview-ui/src/stories/tool-call-lab.stories.tsx",
"+++ packages/kilo-vscode/webview-ui/src/stories/tool-call-lab.stories.tsx",
"@@ -1,3 +1,4 @@",
" /** @jsxImportSource solid-js */",
' import { For } from "solid-js"',
' import type { Meta, StoryObj } from "storybook-solidjs-vite"',
'+import { AssistantMessage } from "../components/chat/AssistantMessage"',
].join("\n")
const blockQuestions: QuestionRequest[] = [
{
id: "matrix-question-request",
sessionID: SID,
questions: [
{
question: "Which visual family should this new block follow?",
header: "Block Style",
options: [
{ label: "Tool row", description: "Compact header with dark expanded output" },
{ label: "Inline card", description: "Standalone VS Code prompt-style card" },
],
},
],
tool: { messageID: MID, callID: "matrix-call-question-active" },
},
]
const blockSuggestions: SuggestionRequest[] = [
{
id: "matrix-suggestion-request",
sessionID: SID,
text: "Run a local visual review after checking this block matrix.",
actions: [
{ label: "Review UI", prompt: "/local-review-uncommitted" },
{ label: "Open Storybook", prompt: "Inspect the Tool Call Lab Block Matrix story" },
],
tool: { messageID: MID, callID: "matrix-call-suggest-active" },
},
]
const blocks: SDKPart[] = [
text("matrix-text-intro", "Block matrix rendered through the real VS Code AssistantMessage path."),
reasoning(
"matrix-reasoning-open",
"**Reasoning output**\n\nThis should use the same trigger-to-output gap and expanded width as regular tool calls.",
),
bash(
"matrix-bash",
"matrix-call-bash",
"Run visual check",
"bun run --cwd packages/kilo-vscode build-storybook",
["storybook v10.2.10", "info => Output directory: storybook-static", "success Built Storybook in 4.2s"].join("\n"),
),
tool(
"matrix-grep",
"matrix-call-grep",
"grep",
completed(
{ pattern: "tool-collapsible", include: "*.css", path: "packages/kilo-ui/src/components" },
"Find gaps",
hits,
),
),
tool(
"matrix-background-start",
"matrix-call-background-start",
"background_process",
completed(
{
action: "start",
command: "bun run --cwd packages/kilo-vscode storybook",
description: "Start Storybook",
ready: { port: 6007, pattern: "Local:", timeout: 30000 },
workdir: "/project",
},
"Start Storybook",
proc,
),
),
tool("matrix-edit", "matrix-call-edit", "edit", {
status: "completed",
input: {
filePath: "packages/kilo-ui/src/components/message-part.css",
oldString: "gap: 4px;",
newString: "gap: 8px;",
},
output: "",
title: "Edit reasoning gap",
metadata: {
filediff: {
file: "packages/kilo-ui/src/components/message-part.css",
patch: gapPatch,
additions: 1,
deletions: 1,
},
},
time: { start: stamp - 3300, end: stamp - 3100 },
}),
tool("matrix-write", "matrix-call-write", "write", {
status: "completed",
input: {
filePath: "packages/kilo-vscode/webview-ui/src/stories/tool-call-lab.stories.tsx",
content: "export const SearchPreviews = {}",
},
output: "",
title: "Write story fixture",
metadata: {
filediff: {
file: "packages/kilo-vscode/webview-ui/src/stories/tool-call-lab.stories.tsx",
patch: writePatch,
additions: 1,
deletions: 0,
},
},
time: { start: stamp - 3000, end: stamp - 2800 },
}),
tool("matrix-todos", "matrix-call-todos", "todowrite", {
status: "completed",
input: {
todos: [
{ id: "todo-1", content: "Add block matrix story", status: "completed" },
{ id: "todo-2", content: "Check spacing against reasoning output", status: "in_progress" },
{ id: "todo-3", content: "Run visual regression", status: "pending" },
],
},
output: "",
title: "Update todos",
metadata: {},
time: { start: stamp - 2300, end: stamp - 2100 },
}),
tool("matrix-patch", "matrix-call-patch", "apply_patch", {
status: "completed",
input: {},
output: "",
title: "Patch two files",
metadata: {
files: [
{
filePath: "/project/packages/kilo-ui/src/components/message-part.css",
relativePath: "packages/kilo-ui/src/components/message-part.css",
type: "update",
patch: gapPatch,
diff: gapPatch,
additions: 1,
deletions: 1,
},
{
filePath: "/project/packages/kilo-vscode/webview-ui/src/stories/tool-call-lab.stories.tsx",
relativePath: "packages/kilo-vscode/webview-ui/src/stories/tool-call-lab.stories.tsx",
type: "update",
patch: writePatch,
diff: writePatch,
additions: 1,
deletions: 0,
},
],
},
time: { start: stamp - 2700, end: stamp - 2400 },
}),
tool("matrix-question-active", "matrix-call-question-active", "question", {
status: "running",
input: { questions: blockQuestions[0].questions },
title: "Ask design question",
metadata: {},
time: { start: stamp - 2000 },
}),
tool("matrix-suggest-active", "matrix-call-suggest-active", "suggest", {
status: "running",
input: { text: blockSuggestions[0].text, actions: blockSuggestions[0].actions },
title: "Suggest follow-up",
metadata: {},
time: { start: stamp - 1900 },
}),
tool("matrix-plan-exit", "matrix-call-plan-exit", "plan_exit", {
status: "completed",
input: { path: "docs/plans/tool-call-polish.md" },
output: "",
title: "Plan ready",
metadata: { plan: "/project/docs/plans/tool-call-polish.md" },
time: { start: stamp - 1800, end: stamp - 1700 },
}),
tool("matrix-mcp", "matrix-call-mcp", "linear_search_documentation", {
status: "completed",
input: { query: "Linear attachments", page: 1 },
output:
'## Linear attachments\n\nUse uploaded asset URLs to create issue attachments.\n\n```json\n{\n "status": "ready"\n}\n```',
title: "Search docs",
metadata: {},
time: { start: stamp - 1600, end: stamp - 1400 },
}),
tool(
"matrix-tool-hint",
"matrix-call-tool-hint",
"edit",
failed({ filePath: "packages/kilo-ui/src/components/message-part.css" }, "oldString and newString are identical"),
),
tool(
"matrix-tool-error",
"matrix-call-tool-error",
"github-pr-search",
failed({ query: "tool call preview" }, "GitHub API error: 401 Unauthorized"),
),
]
const data = {
...defaultMockData,
message: { [SID]: [base] },
part: { [MID]: blocks },
}
for (const key of [
"grep:matrix-call-grep",
"edit:matrix-call-edit",
"write:matrix-call-write",
"apply_patch:matrix-call-patch",
"todowrite:matrix-call-todos",
"linear_search_documentation:matrix-call-mcp",
]) {
writeToolOpen(key, true)
}
const css = `
.tool-call-lab-search-previews {
box-sizing: border-box;
width: min(1180px, 100%);
padding: 16px;
}
.tool-call-lab-search-previews * {
box-sizing: border-box;
}
.tool-call-lab-header {
margin-bottom: 14px;
color: var(--vscode-foreground, var(--text-base));
}
.tool-call-lab-title {
margin: 0 0 4px;
font-size: 13px;
font-weight: 600;
}
.tool-call-lab-subtitle {
margin: 0;
max-width: 760px;
color: var(--vscode-descriptionForeground, var(--text-weak));
font-size: 12px;
line-height: 18px;
}
.tool-call-lab-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
gap: 16px;
align-items: start;
}
.tool-call-lab-panel {
min-width: 0;
border: 1px solid var(--vscode-panel-border, var(--border-weak-base));
background: color-mix(in srgb, var(--vscode-sideBar-background, var(--surface-base)) 96%, transparent);
}
.tool-call-lab-panel-wide {
max-width: 780px;
}
.tool-call-lab-panel-header {
display: flex;
flex-direction: column;
gap: 2px;
padding: 10px 12px;
border-bottom: 1px solid var(--vscode-panel-border, var(--border-weak-base));
}
.tool-call-lab-panel-title {
font-size: 12px;
font-weight: 600;
color: var(--vscode-foreground, var(--text-base));
}
.tool-call-lab-panel-note {
font-size: 11px;
line-height: 16px;
color: var(--vscode-descriptionForeground, var(--text-weak));
}
.tool-call-lab-stack {
padding: 12px;
}
`
const meta: Meta = {
title: "Labs/Tool Call Lab",
parameters: { layout: "padded" },
}
export default meta
type Story = StoryObj
export const SearchPreviews: Story = {
name: "Block Matrix",
render: () => (
<StoryProviders
data={data}
noPadding
onOpenDiff={() => undefined}
onOpenFile={() => undefined}
questions={blockQuestions}
sessionID={SID}
status="busy"
suggestions={blockSuggestions}
>
<style>{css}</style>
<div class="tool-call-lab-search-previews">
<div class="tool-call-lab-header">
<p class="tool-call-lab-title">Assistant block matrix</p>
<p class="tool-call-lab-subtitle">
Real VS Code AssistantMessage rendering for the block families that can appear between assistant text and
tool output: reasoning, shell, search previews, background processes, diffs, todos, active prompts, plan
exits, MCP output, hints, and errors.
</p>
</div>
<section class="tool-call-lab-panel tool-call-lab-panel-wide">
<div class="tool-call-lab-panel-header">
<span class="tool-call-lab-panel-title">Real assistant path</span>
<span class="tool-call-lab-panel-note">
Uses AssistantMessage instead of raw Part so VS Code-only cards and overrides are included.
</span>
</div>
<div class="tool-call-lab-stack">
<div class="vscode-session-turn-assistant">
<AssistantMessage message={base} parts={blocks} />
</div>
</div>
</section>
</div>
</StoryProviders>
),
}
@@ -204,6 +204,14 @@
padding: 0 4px 12px;
}
.vscode-session-turn[data-row="assistant"] {
padding-bottom: 6px;
}
.vscode-session-turn[data-row="assistant"]:has(> .vscode-session-turn-assistant:empty) {
padding-bottom: 0;
}
.vscode-session-turn-user {
width: 100%;
}
@@ -216,7 +224,7 @@
.vscode-session-turn-assistant {
display: flex;
flex-direction: column;
gap: 12px;
gap: 6px;
width: 100%;
}
@@ -13,7 +13,7 @@
align-items: baseline;
flex-wrap: wrap;
gap: 4px;
padding: 6px 0;
padding: 0;
font-family: var(--font-family-sans);
font-size: var(--kilo-font-size-13);
line-height: var(--line-height-large);
@@ -3,7 +3,7 @@
============================================ */
[data-component="question-dock"] {
margin: 6px 8px;
margin: 0 8px;
background-color: var(--surface-base);
border-radius: 8px;
overflow: hidden;
@@ -1,11 +1,3 @@
/* ============================================
Todo tool — align checkboxes with icon in header
============================================ */
[data-component="todos"] {
padding: 12px;
}
/* ============================================
Task Tool (sub-agent, v1.0.25 style)
tool-output scrollable container size override
@@ -19,13 +11,35 @@
Background Process Tool
============================================ */
[data-component="background-process-details"] {
overflow: hidden;
margin-inline: -6px;
width: calc(100% + 12px);
border-radius: var(--radius-sm);
background: var(--surface-tool-output-base);
}
[data-component="background-process-fields"] {
display: grid;
gap: 6px;
margin-bottom: 8px;
padding: 8px 12px;
}
html[data-theme="kilo-vscode"]
[data-component="tool-part-wrapper"][data-part-type="tool"]
[data-component="background-process-details"]
[data-component="tool-output"][data-variant="preview"] {
width: auto;
margin-inline: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
[data-component="background-process-fields"] + [data-component="tool-output"][data-variant="preview"] {
border-top: 1px solid color-mix(in srgb, var(--border-weak-base) 70%, transparent);
}
[data-slot="background-process-field"] {
display: grid;
grid-template-columns: minmax(72px, max-content) minmax(0, 1fr);
@@ -35,19 +49,21 @@
}
[data-slot="background-process-label"] {
color: var(--text-dimmed);
color: var(--text-weaker);
}
[data-slot="background-process-value"] {
color: var(--text-base);
font-family: var(--font-mono);
color: var(--text-weak);
font-family: var(--font-family-mono);
overflow-wrap: anywhere;
}
[data-slot="background-process-output"] {
margin: 0;
font: inherit;
font-family: var(--font-mono);
font-family: var(--font-family-mono);
font-size: var(--kilo-font-size-12);
line-height: var(--line-height-large);
white-space: pre-wrap;
overflow-wrap: anywhere;
}