fix(prompt): resolve repeated root mentions without sentinels (#864)

I removed the temporary root markers from prompt preparation. Each `@./`
or `@.` reference now resolves directly, so repeated references no
longer send `___ROOT___` to the model.

I added regression tests for repeated references, literal marker text,
pasted content and display metadata, shell input and command arguments.
All three new tests fail before the fix; all 21 targeted tests and UI
typechecking pass afterward.

I also built the Tauri resources and passed the packaged-resource
checks. I did not replace the installed app or restart the shared
daemon.

Fixes #863
This commit is contained in:
Pascal André 2026-10-06 19:34:41 +02:00 • committed by GitHub
commit 1abeba7804
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 40 additions and 6 deletions

View file

@ -15,6 +15,19 @@ describe("resolvePromptDelivery", () => {
})
describe("preparePromptSubmission", () => {
it("resolves repeated root mentions in history, shell input and command arguments", () => {
const text = "@./a @./b @. @."
const expected = "./a ./b ./ ./"
const message = preparePromptSubmission({ mode: "message", text, attachments: [] })
assert.equal(message.historyEntry, expected)
assert.equal(message.submitPrompt, text)
assert.equal(preparePromptSubmission({ mode: "shell", text, attachments: [] }).submitPrompt, expected)
const command = preparePromptSubmission({ mode: "slash", text, attachments: [], commandToken: "/review", commandArgs: text })
assert.equal(command.resolvedCommandArgs, expected)
assert.equal(command.submitPrompt, `/review ${expected}`)
})
it("keeps placeholder-backed pasted text intact for message submission while resolving history text", () => {
const attachment = createTextAttachment("alpha\nbeta\ngamma\ndelta", "pasted #1 (4 lines)", "paste-1.txt")

View file

@ -1,10 +1,34 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"
import { createTextAttachment } from "../types/attachment"
import { createFileAttachment, createTextAttachment } from "../types/attachment"
import { preparePromptDisplayText, resolvePastedPlaceholders, splitPromptDisplaySections } from "./prompt-display-metadata"
describe("preparePromptDisplayText", () => {
it("resolves every root mention without changing parent paths or literal marker text", () => {
const prompt = "@./a @./b @. @. @.. @../c ___ROOT___ ___ROOT_NOSLASH___"
const expected = "./a ./b ./ ./ @.. @../c ___ROOT___ ___ROOT_NOSLASH___"
const attachments = [createFileAttachment("./a", "a"), createTextAttachment(".", "path:.", ".")]
assert.equal(resolvePastedPlaceholders(prompt, attachments), expected)
assert.equal(preparePromptDisplayText(prompt, attachments).promptToSend, expected)
})
it("preserves pasted text and display sections alongside repeated root mentions", () => {
const pasted = "@./verbatim ___ROOT___ ___ROOT_NOSLASH___"
const attachment = createTextAttachment(pasted, "pasted #1", "paste-1.txt")
const prompt = "@./a @./b\n[pasted #1]\n@. @."
const prepared = preparePromptDisplayText(prompt, [attachment])
assert.equal(prepared.promptToSend, `./a ./b\n${pasted}\n./ ./`)
assert.equal(resolvePastedPlaceholders(prompt, [attachment]), prepared.promptToSend)
assert.deepEqual(splitPromptDisplaySections(prepared.promptToSend, prepared.displayMetadata), [
{ kind: "inline", text: "./a ./b\n" },
{ kind: "pasted", text: pasted },
{ kind: "inline", text: "\n./ ./" },
])
})
it("keeps pasted text fully visible to the model while storing display metadata", () => {
const attachment = createTextAttachment("line 1\nline 2\nline 3\nline 4", "pasted #1 (4 lines)", "paste-1.txt")

View file

@ -67,8 +67,8 @@ function resolvePathMentions(prompt: string, attachments: Attachment[] = []): st
let result = prompt
result = result.replace(/@(\.\/)/g, "___ROOT___")
result = result.replace(/@(\.)(?!\.)/g, "___ROOT_NOSLASH___")
result = result.replace(/@(\.\/)/g, "./")
result = result.replace(/@(\.)(?!\.)/g, "./")
const allPaths = new Set<string>()
for (const path of fileAttachments) {
@ -85,9 +85,6 @@ function resolvePathMentions(prompt: string, attachments: Attachment[] = []): st
result = result.replace(`@${withoutPrefix}/`, `${withPrefix}/`)
}
result = result.replace("___ROOT___", "./")
result = result.replace("___ROOT_NOSLASH___", "./")
return result
}