mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-30 17:14:40 +08:00
fix(vscode): render heredoc approvals as plain text (#12304)
* fix(vscode): render heredoc approvals as plain text * test: handle heredoc approvals across platforms * chore: update kilo-vscode visual regression baselines --------- Co-authored-by: kilo-maintainer[bot] <kilo-maintainer[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Display here-document content as plain text in terminal approval prompts.
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:31a447c3602b2959fde3da003baa05901d4d68e935f245c63d7080a574ef7b53
|
||||
size 22366
|
||||
oid sha256:e7905a190dff49bc7e23af1773ea93ac79cc71659336469745561355cba5f413
|
||||
size 22602
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Icon } from "@kilocode/kilo-ui/icon"
|
||||
import { Tooltip } from "@kilocode/kilo-ui/tooltip"
|
||||
import { useLanguage } from "../../context/language"
|
||||
|
||||
export const PermissionCommand: Component<{ command: string }> = (props) => {
|
||||
export const PermissionCommand: Component<{ command: string; plain?: boolean }> = (props) => {
|
||||
const language = useLanguage()
|
||||
const [copied, setCopied] = createSignal(false)
|
||||
const state = { signal: { aborted: false } }
|
||||
@@ -23,10 +23,11 @@ export const PermissionCommand: Component<{ command: string }> = (props) => {
|
||||
|
||||
const pre = document.createElement("pre")
|
||||
const code = document.createElement("code")
|
||||
code.dataset.lang = "shellscript"
|
||||
if (!props.plain) code.dataset.lang = "shellscript"
|
||||
code.textContent = command
|
||||
pre.append(code)
|
||||
ref.replaceChildren(pre)
|
||||
if (props.plain) return
|
||||
|
||||
const signal = { aborted: false }
|
||||
state.signal = signal
|
||||
|
||||
@@ -269,7 +269,9 @@ export const PermissionDock: Component<{
|
||||
}
|
||||
>
|
||||
<Show when={cmdDescription()}>{(desc) => <div data-slot="permission-hint">{desc()}</div>}</Show>
|
||||
<Show when={command()}>{(cmd) => <PermissionCommand command={cmd()} />}</Show>
|
||||
<Show when={command()}>
|
||||
{(cmd) => <PermissionCommand command={cmd()} plain={props.request.args.heredoc === true} />}
|
||||
</Show>
|
||||
|
||||
{(() => {
|
||||
const desc = description()
|
||||
|
||||
@@ -1199,6 +1199,7 @@ print(f"Entries with audio_file set: {found_audio}")
|
||||
print(f"Missing audio_file: {len(expected) - found_audio}")
|
||||
EOF`,
|
||||
rules: ["python3 *"],
|
||||
heredoc: true,
|
||||
},
|
||||
tool: { messageID: ASST_MSG_ID, callID: "call-heredoc-001" },
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ export interface PermissionRequest {
|
||||
filediff?: PermissionFileDiff
|
||||
files?: PermissionPatchFile[]
|
||||
description?: string
|
||||
heredoc?: boolean
|
||||
}
|
||||
message?: string
|
||||
tool?: { messageID: string; callID: string }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { ShellID } from "@/tool/shell/id"
|
||||
import type { Node } from "web-tree-sitter"
|
||||
|
||||
export function heredocs(root: Node, kind: ShellID.Kind) {
|
||||
if (kind !== "bash") return {}
|
||||
return root.descendantsOfType("heredoc_redirect").length > 0 ? { heredoc: true } : {}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import * as Truncate from "./truncate"
|
||||
import { Plugin } from "@/plugin"
|
||||
import { normalizeUrls } from "@/kilocode/util/url" // kilocode_change
|
||||
import { CommandTimeout } from "@/kilocode/command-timeout" // kilocode_change
|
||||
import { heredocs } from "@/kilocode/tool/shell-heredoc" // kilocode_change
|
||||
import { ChildProcess } from "effect/unstable/process"
|
||||
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
|
||||
import { ShellPrompt, type Parameters } from "./shell/prompt"
|
||||
@@ -282,6 +283,7 @@ const ask = Effect.fn("ShellTool.ask")(function* (
|
||||
ctx: Tool.Context,
|
||||
scan: Scan,
|
||||
command: string,
|
||||
metadata: ReturnType<typeof heredocs>, // kilocode_change
|
||||
description?: string, // kilocode_change
|
||||
) {
|
||||
// kilocode_change
|
||||
@@ -302,6 +304,7 @@ const ask = Effect.fn("ShellTool.ask")(function* (
|
||||
directories,
|
||||
patterns: globs,
|
||||
...(scan.access === "read" ? { access: "read" as const } : {}),
|
||||
...metadata,
|
||||
},
|
||||
// kilocode_change end
|
||||
})
|
||||
@@ -312,7 +315,7 @@ const ask = Effect.fn("ShellTool.ask")(function* (
|
||||
permission: ShellID.ToolID,
|
||||
patterns: Array.from(scan.patterns),
|
||||
always: Array.from(scan.always),
|
||||
metadata: { command: normalizeUrls(command), ...(description ? { description } : {}) }, // kilocode_change
|
||||
metadata: { command: normalizeUrls(command), ...(description ? { description } : {}), ...metadata }, // kilocode_change
|
||||
})
|
||||
})
|
||||
|
||||
@@ -411,11 +414,12 @@ export const ShellPermission = Effect.gen(function* () {
|
||||
Effect.gen(function* () {
|
||||
const tree = yield* Effect.acquireRelease(parse(input.command, ps), (tree) => Effect.sync(() => tree.delete()))
|
||||
const scan = yield* collect(tree.rootNode, input.cwd, ps, input.shell, instance)
|
||||
const metadata = heredocs(tree.rootNode, ShellID.toKind(Shell.name(input.shell))) // kilocode_change
|
||||
if (!containsPath(input.cwd, instance)) {
|
||||
scan.dirs.add(input.cwd)
|
||||
scan.access = "unknown"
|
||||
}
|
||||
yield* ask(ctx, scan, input.command, input.description)
|
||||
yield* ask(ctx, scan, input.command, metadata, input.description) // kilocode_change
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -65,4 +65,42 @@ describe("bash permission metadata.command", () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test.skipIf(process.platform === "win32").each([
|
||||
["single quoted", "cat << 'EOF'\n$HOME\nEOF"],
|
||||
["double quoted", 'cat << "EOF"\n$HOME\nEOF'],
|
||||
["escaped", "cat << \\EOF\n$HOME\nEOF"],
|
||||
["unquoted", "cat << EOF\n$HOME\nEOF"],
|
||||
] as const)("marks %s heredocs", async (_, command) => {
|
||||
await using tmp = await tmpdir()
|
||||
await provideTestInstance({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const bash = await runtime.runPromise(ShellTool.pipe(Effect.flatMap((info) => info.init())))
|
||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||
await Effect.runPromise(bash.execute({ command }, capture(requests)))
|
||||
|
||||
const req = requests.find((item) => item.permission === "bash")
|
||||
expect(req?.metadata.heredoc).toBe(true)
|
||||
expect(req?.metadata.command).toBe(command)
|
||||
expect(req?.patterns).toEqual([command])
|
||||
expect(req?.always).toEqual(["cat *"])
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("omits heredoc metadata for ordinary commands", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await provideTestInstance({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const bash = await runtime.runPromise(ShellTool.pipe(Effect.flatMap((info) => info.init())))
|
||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||
await Effect.runPromise(bash.execute({ command: "echo hello" }, capture(requests)))
|
||||
|
||||
const req = requests.find((item) => item.permission === "bash")
|
||||
expect(req?.metadata.heredoc).toBeUndefined()
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user