feat(tui): use canonical prompt attachments
This commit is contained in:
parent
91f1815732
commit
c13f06c30c
24 changed files with 611 additions and 466 deletions
|
|
@ -5,9 +5,9 @@ import { describe, expect, test } from "bun:test"
|
|||
//
|
||||
// Before the fix, two concurrent `submit()` calls (e.g. a double-pressed
|
||||
// Enter, or the input's native onSubmit racing another dispatch) each
|
||||
// passed the `if (!store.prompt.input) return false` guard, each
|
||||
// passed the `if (!store.prompt.text) return false` guard, each
|
||||
// `await sdk.client.session.create(...)`, and each only captured
|
||||
// `inputText = store.prompt.input` AFTER that await. The first invocation
|
||||
// `inputText = store.prompt.text` AFTER that await. The first invocation
|
||||
// finished, sent the prompt, and cleared the store; the second invocation,
|
||||
// now past its await, read the cleared store and sent an empty prompt to a
|
||||
// second freshly-created session - leaving an orphaned session with the
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { isDuplicateEntry, MAX_HISTORY_ENTRIES, parsePromptHistory, type PromptInfo } from "../../src/prompt/history"
|
||||
|
||||
const entry = (input: string, parts: PromptInfo["parts"] = []): PromptInfo => ({ input, parts })
|
||||
const entry = (text: string, files: PromptInfo["files"] = []): PromptInfo => ({
|
||||
text,
|
||||
files,
|
||||
agents: [],
|
||||
pasted: [],
|
||||
})
|
||||
|
||||
describe("prompt history", () => {
|
||||
test("recovers valid JSONL entries around corruption", () => {
|
||||
|
|
@ -11,13 +16,17 @@ describe("prompt history", () => {
|
|||
])
|
||||
})
|
||||
|
||||
test("ignores the legacy parts shape", () => {
|
||||
expect(parsePromptHistory(JSON.stringify({ input: "old", parts: [] }))).toEqual([])
|
||||
})
|
||||
|
||||
test("retains only the newest entries", () => {
|
||||
const input = Array.from({ length: MAX_HISTORY_ENTRIES + 5 }, (_, index) =>
|
||||
JSON.stringify(entry(String(index))),
|
||||
).join("\n")
|
||||
const result = parsePromptHistory(input)
|
||||
expect(result).toHaveLength(MAX_HISTORY_ENTRIES)
|
||||
expect(result[0]?.input).toBe("5")
|
||||
expect(result[0]?.text).toBe("5")
|
||||
})
|
||||
|
||||
test("dedupes only identical consecutive entries", () => {
|
||||
|
|
@ -27,13 +36,10 @@ describe("prompt history", () => {
|
|||
expect(isDuplicateEntry({ ...entry("ls"), mode: "normal" }, { ...entry("ls"), mode: "shell" })).toBe(false)
|
||||
})
|
||||
|
||||
test("does not dedupe entries with different parts", () => {
|
||||
const a = entry("describe this", [
|
||||
{ type: "file", mime: "image/png", filename: "a.png", url: "data:image/png;base64,AAA" },
|
||||
])
|
||||
const b = entry("describe this", [
|
||||
{ type: "file", mime: "image/png", filename: "b.png", url: "data:image/png;base64,BBB" },
|
||||
])
|
||||
test("does not dedupe entries with different attachments", () => {
|
||||
const a = entry("describe this", [{ name: "a.png", uri: "data:image/png;base64,AAA" }])
|
||||
const b = entry("describe this", [{ name: "b.png", uri: "data:image/png;base64,BBB" }])
|
||||
expect(isDuplicateEntry(a, b)).toBe(false)
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import { MAX_STASH_ENTRIES, parsePromptStash } from "../../src/prompt/stash"
|
|||
|
||||
test("stash JSONL skips corruption and retains newest entries", () => {
|
||||
const entries = Array.from({ length: MAX_STASH_ENTRIES + 2 }, (_, index) =>
|
||||
JSON.stringify({ input: String(index), parts: [], timestamp: index }),
|
||||
JSON.stringify({ prompt: { text: String(index), files: [], agents: [], pasted: [] }, timestamp: index }),
|
||||
)
|
||||
entries.splice(2, 0, "broken")
|
||||
const result = parsePromptStash(entries.join("\n"))
|
||||
expect(result).toHaveLength(MAX_STASH_ENTRIES)
|
||||
expect(result[0]?.input).toBe("2")
|
||||
expect(result[0]?.prompt.text).toBe("2")
|
||||
})
|
||||
|
||||
test("frecency JSONL skips corruption, keeps latest path state, and limits entries", () => {
|
||||
|
|
|
|||
|
|
@ -1,26 +1,7 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { expandTrackedPastedText, stripPromptPartIDs } from "../../src/prompt/part"
|
||||
import { expandTrackedPastedText } from "../../src/prompt/part"
|
||||
|
||||
describe("prompt part", () => {
|
||||
test("strips persisted IDs from reused parts", () => {
|
||||
expect(
|
||||
stripPromptPartIDs({
|
||||
id: "prt_old",
|
||||
sessionID: "ses_old",
|
||||
messageID: "msg_old",
|
||||
type: "file" as const,
|
||||
mime: "image/png",
|
||||
filename: "tiny.png",
|
||||
url: "data:image/png;base64,abc",
|
||||
}),
|
||||
).toEqual({
|
||||
type: "file",
|
||||
mime: "image/png",
|
||||
filename: "tiny.png",
|
||||
url: "data:image/png;base64,abc",
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves wide characters around pasted text", () => {
|
||||
const marker = "[Pasted ~3 lines]"
|
||||
const prefix = "你好你好\n"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue