Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("terminal context architecture", () => {
it("keeps webview terminal attachment logic outside PromptInput", () => {
const prompt = src("webview-ui/src/components/chat/PromptInput.tsx")
const hook = src("webview-ui/src/hooks/useTerminalContext.ts")
const util = src("webview-ui/src/hooks/terminal-context-utils.ts")
const util = src("webview-ui/src/hooks/context-mention-utils.ts")

expect(prompt).toContain("useTerminalContext")
expect(prompt).toContain("resolveAttachment(message, id, readTerminalContext(props.terminalContext))")
Expand Down
42 changes: 23 additions & 19 deletions packages/kilo-vscode/tests/unit/terminal-context-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import { describe, expect, it } from "bun:test"
import {
buildTerminalAttachment,
findTerminalMention,
hasTerminalMention,
} from "../../webview-ui/src/hooks/terminal-context-utils"
import { buildGitChangesAttachment as git } from "../../webview-ui/src/hooks/git-changes-context-utils"
import { buildTerminalAttachment as terminal } from "../../webview-ui/src/hooks/terminal-context-utils"

describe("terminal context utils", () => {
it("detects standalone terminal mentions", () => {
expect(hasTerminalMention("see @terminal output")).toBe(true)
expect(hasTerminalMention("see foo@terminal output")).toBe(false)
expect(hasTerminalMention("see @terminal-output")).toBe(false)
})
describe.each([
["terminal", terminal, "terminal-output.txt"],
["git-changes", git, "git-changes.txt"],
])("%s context utils", (token, build, filename) => {
const value = `@${token}`

it("returns mention source range", () => {
expect(findTerminalMention("hello @terminal")!).toEqual({ value: "@terminal", start: 6, end: 15 })
it("rejects missing mentions and false prefixes or suffixes", () => {
for (const text of ["plain text", `foo${value}`, `${value}-output`, `${value}.`]) {
expect(build(text, "content")).toBeUndefined()
}
})

it("builds a text attachment with source metadata", () => {
const attachment = buildTerminalAttachment("check @terminal", "npm failed")!
expect(attachment.mime).toBe("text/plain")
expect(attachment.filename).toBe("terminal-output.txt")
expect(attachment.url).toBe("data:text/plain;charset=utf-8,npm%20failed")
expect(attachment.source?.text).toEqual({ value: "@terminal", start: 6, end: 15 })
it("preserves whitespace boundaries, first spans, repeated calls and encoded content", () => {
for (const prefix of ["", "hello ", "\r\n\t", "\u00a0"]) {
const mention = { value, start: prefix.length, end: prefix.length + value.length }
const expected = {
mime: "text/plain",
filename,
url: "data:text/plain;charset=utf-8,%C3%A9%20%25%26%23%3F%0D%0A",
source: { type: "file", path: filename, text: mention },
}
expect(build(`${prefix}${value} ${value}`, "é %&#?\r\n")).toEqual(expected)
expect(build(`${prefix}${value}`, "é %&#?\r\n")).toEqual(expected)
}
})
})
37 changes: 37 additions & 0 deletions packages/kilo-vscode/webview-ui/src/hooks/context-mention-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { FileAttachment } from "../types/messages"

export type Mention = {
value: string
start: number
end: number
}

export function find(text: string, pattern: RegExp, token: string): Mention | undefined {
pattern.lastIndex = 0
const match = pattern.exec(text)
if (!match) return undefined

const prefix = match[1] ?? ""
const start = match.index + prefix.length
const value = `@${token}`
return { value, start, end: start + value.length }
}

export function attachment(
mention: Mention | undefined,
content: string,
filename: string,
): FileAttachment | undefined {
if (!mention) return undefined

return {
mime: "text/plain",
url: `data:text/plain;charset=utf-8,${encodeURIComponent(content)}`,
filename,
source: {
type: "file",
path: filename,
text: mention,
},
}
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,20 @@
import type { FileAttachment } from "../types/messages"
import { attachment, find, type Mention } from "./context-mention-utils"

export const GIT_CHANGES_MENTION = "git-changes"
export const GIT_CHANGES_FILENAME = "git-changes.txt"
export const GIT_CHANGES_PATTERN = /(^|\s)@git-changes(?=\s|$)/g

export type GitChangesMention = {
value: string
start: number
end: number
}
export type GitChangesMention = Mention

export function findGitChangesMention(text: string): GitChangesMention | undefined {
GIT_CHANGES_PATTERN.lastIndex = 0
const match = GIT_CHANGES_PATTERN.exec(text)
if (!match) return undefined

const prefix = match[1] ?? ""
const start = match.index + prefix.length
const value = `@${GIT_CHANGES_MENTION}`
return { value, start, end: start + value.length }
return find(text, GIT_CHANGES_PATTERN, GIT_CHANGES_MENTION)
}

export function hasGitChangesMention(text: string): boolean {
return findGitChangesMention(text) !== undefined
}

export function buildGitChangesAttachment(text: string, content: string): FileAttachment | undefined {
const mention = findGitChangesMention(text)
if (!mention) return undefined

return {
mime: "text/plain",
url: `data:text/plain;charset=utf-8,${encodeURIComponent(content)}`,
filename: GIT_CHANGES_FILENAME,
source: {
type: "file",
path: GIT_CHANGES_FILENAME,
text: mention,
},
}
return attachment(findGitChangesMention(text), content, GIT_CHANGES_FILENAME)
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,20 @@
import type { FileAttachment } from "../types/messages"
import { attachment, find, type Mention } from "./context-mention-utils"

export const TERMINAL_MENTION = "terminal"
export const TERMINAL_FILENAME = "terminal-output.txt"
export const TERMINAL_PATTERN = /(^|\s)@terminal(?=\s|$)/g

export type TerminalMention = {
value: string
start: number
end: number
}
export type TerminalMention = Mention

export function findTerminalMention(text: string): TerminalMention | undefined {
TERMINAL_PATTERN.lastIndex = 0
const match = TERMINAL_PATTERN.exec(text)
if (!match) return undefined

const prefix = match[1] ?? ""
const start = match.index + prefix.length
const value = `@${TERMINAL_MENTION}`
return { value, start, end: start + value.length }
return find(text, TERMINAL_PATTERN, TERMINAL_MENTION)
}

export function hasTerminalMention(text: string): boolean {
return findTerminalMention(text) !== undefined
}

export function buildTerminalAttachment(text: string, content: string): FileAttachment | undefined {
const mention = findTerminalMention(text)
if (!mention) return undefined

return {
mime: "text/plain",
url: `data:text/plain;charset=utf-8,${encodeURIComponent(content)}`,
filename: TERMINAL_FILENAME,
source: {
type: "file",
path: TERMINAL_FILENAME,
text: mention,
},
}
return attachment(findTerminalMention(text), content, TERMINAL_FILENAME)
}
Loading