fix(ui): avoid stripping non-path @mentions

This commit is contained in:
Shantur Rathore
2026-02-14 08:18:35 +00:00
parent e4e10cc630
commit 4e0f064c3a
2 changed files with 78 additions and 24 deletions

View File

@@ -1,7 +1,7 @@
import { createSignal, type Accessor, type Setter } from "solid-js"
import type { Command as SDKCommand } from "@opencode-ai/sdk/v2"
import type { Agent } from "../../types/session"
import { createAgentAttachment, createFileAttachment, createTextAttachment } from "../../types/attachment"
import { createAgentAttachment, createFileAttachment } from "../../types/attachment"
import { addAttachment, getAttachments, removeAttachment } from "../../stores/attachments"
import type { PickerMode } from "./types"
import type { PickerSelectAction } from "../unified-picker"
@@ -213,18 +213,6 @@ export function usePromptPicker(options: PromptPickerOptions): PromptPickerContr
return trimmed.length > 0 ? trimmed : "."
})()
const addPathOnlyAttachment = (value: string) => {
const display = `path: ${value}`
const filename = value
const existing = getAttachments(options.instanceId(), options.sessionId())
const alreadyAttached = existing.some(
(att) => att.source.type === "text" && att.source.value === value && att.display === display,
)
if (!alreadyAttached) {
addAttachment(options.instanceId(), options.sessionId(), createTextAttachment(value, display, filename))
}
}
if (isFolder) {
if (action === "tab") {
// TAB on directory: autocomplete directory name and show its contents.

View File

@@ -5,21 +5,87 @@ export function resolvePastedPlaceholders(prompt: string, attachments: Attachmen
return prompt
}
// First, strip @ from file/directory paths that don't have file attachments
// This handles SHIFT+ENTER case where @path should become path
const fileAttachments = new Set(
// First, strip `@` from path-like mentions that do NOT have a backing file attachment.
// This is intended for SHIFT+ENTER selection where we keep `@path` in the textarea for
// easy deletion, but send `path` to the API.
//
// IMPORTANT: avoid rewriting plain `@mentions` or email addresses.
const fileAttachmentPaths = new Set(
attachments
.filter((a) => a.source.type === "file" && "path" in a.source)
.map((a) => (a.source as { path: string }).path),
.filter((a) => a.source.type === "file")
.map((a) => a.source.path),
)
let result = prompt.replace(/@([^\s@]+)/g, (match, path) => {
// If this path has a file attachment, keep the @ (attachment is sent separately)
if (fileAttachments.has(path) || fileAttachments.has(path.replace(/\/$/, ""))) {
return match
const isPathLike = (value: string) => {
if (!value) return false
if (value.includes("/") || value.includes("\\")) return true
if (value.startsWith("./") || value.startsWith("../")) return true
if (value.startsWith("~")) return true
if (value.endsWith("/")) return true
// Root-level files (no `/`) still commonly have an extension.
const ext = value.split(".").pop()?.toLowerCase()
if (!ext || ext === value.toLowerCase()) return false
// Keep this list intentionally small and code-focused to avoid matching domains like `example.com`.
const allowedExts = new Set([
"ts",
"tsx",
"js",
"jsx",
"mjs",
"cjs",
"json",
"md",
"txt",
"yml",
"yaml",
"toml",
"css",
"html",
"htm",
"svg",
"png",
"jpg",
"jpeg",
"gif",
"pdf",
"rs",
"go",
"py",
"java",
"kt",
"swift",
"sh",
"bash",
"zsh",
"sql",
"lock",
])
return allowedExts.has(ext)
}
const stripTrailingPunctuation = (value: string) => {
const match = value.match(/^(.*?)([)\]}.,!?:;]+)?$/)
if (!match) return { core: value, trailing: "" }
return { core: match[1] ?? value, trailing: match[2] ?? "" }
}
let result = prompt.replace(/(^|[\s([{"'`])@([^\s@]+)/g, (full, prefix, rawToken) => {
const { core, trailing } = stripTrailingPunctuation(String(rawToken))
if (!core) return full
// If this path has a file attachment, keep the `@` (attachment is sent separately).
if (fileAttachmentPaths.has(core) || fileAttachmentPaths.has(core.replace(/\/$/, ""))) {
return `${prefix}@${core}${trailing}`
}
// Otherwise (SHIFT+ENTER case), strip the @
return path
// Only strip for path-like tokens; leave plain `@mentions` intact.
if (!isPathLike(core)) {
return `${prefix}@${core}${trailing}`
}
return `${prefix}${core}${trailing}`
})
// Then, resolve [pasted #N] placeholders