Files
orca/src/shared/agent-tab-title.ts
T
Brennan Benson 9097ea0409 fix(naming): remove identifier-first name post-processing (#9821)
* fix(naming): remove identifier-first name post-processing

Workspace display names and tab titles were being rewritten after
generation by prompt-scanning heuristics from #8238 — a stray "#1" in
prose became a workspace named "#1 - Fix", and the rewrite ran
downstream of generation so user naming instructions couldn't override
it. Per the same principle as #9088, naming defaults stay minimal and
user overrides own the style.

- Delete work-item-reference.ts and display-name-from-work.ts (+tests).
- Auto-rename display names return to the humanized branch slug; tab
  titles return to the cleaned first prompt clause.
- Explicit create-from-work-item naming returns to action-first
  ("Review PR 1234").
- Keep #8238's URL-before-markdown strip-order bugfix in tab titles,
  with regression tests adjusted to the natural expectations.

* test(naming): pin incidental marker regression
2026-07-21 20:06:31 -07:00

112 lines
3.4 KiB
TypeScript

export const GENERATED_TAB_TITLE_MAX_LENGTH = 40
export const GENERATED_TAB_TITLE_SOURCE_SCAN_LIMIT = 512
const LEADING_FILLER_PATTERNS: RegExp[] = [
/^(?:can|could|would)\s+you(?:\s+please)?\s+/i,
/^please(?:\s+|$)/i,
/^i\s+(?:want|need)\s+(?:you\s+)?to\s+/i,
/^help\s+me(?:\s+to)?\s+/i,
/^help\s+/i,
/^let'?s\s+/i,
/^we\s+need\s+to\s+/i,
/^need\s+to\s+/i
]
function capitalizeFirstLetter(value: string): string {
return value.replace(/\p{L}/u, (letter) => letter.toLocaleUpperCase())
}
function truncateAtWordBoundary(value: string, maxLength: number): string {
if (value.length <= maxLength) {
return value
}
const rawSlice = value.slice(0, maxLength)
const sliced = rawSlice.trim()
if (sliced.length < rawSlice.length) {
return sliced
}
const lastSpace = sliced.lastIndexOf(' ')
if (lastSpace >= Math.floor(maxLength * 0.55)) {
return sliced.slice(0, lastSpace).trim()
}
return sliced
}
// Why: generated titles are derived from pasted agent prompts on renderer
// state paths; collapse display whitespace without a regex replacement pass.
function foldGeneratedTabTitleWhitespace(value: string): string {
let normalized = ''
let pendingWhitespace = false
for (let index = 0; index < value.length; index += 1) {
const code = value.charCodeAt(index)
if (isGeneratedTabTitleWhitespace(code)) {
pendingWhitespace = normalized.length > 0
continue
}
if (pendingWhitespace) {
normalized += ' '
pendingWhitespace = false
}
normalized += value.charAt(index)
}
return normalized
}
function isGeneratedTabTitleWhitespace(code: number): boolean {
return (
code === 32 ||
(code >= 9 && code <= 13) ||
code === 160 ||
code === 5760 ||
(code >= 8192 && code <= 8202) ||
code === 8232 ||
code === 8233 ||
code === 8239 ||
code === 8287 ||
code === 12288 ||
code === 65279
)
}
export function deriveGeneratedTabTitle(prompt: string): string | null {
// Why: agent prompts can be paste-sized. Generated tab titles are previews,
// so title cleanup must not scan the full prompt on the renderer state path.
const promptPreview = prompt.slice(0, GENERATED_TAB_TITLE_SOURCE_SCAN_LIMIT)
const firstClause = promptPreview
.trim()
// Strip URLs before markdown punctuation: a GitLab URL like
// `/merge_requests/42` contains `_`, and folding that to a space first would
// split the URL and leak fragments ("requests") into the title. No `\b`
// anchor: a URL wrapped in markdown emphasis (`_https://…_`) is preceded by
// a word char, where `\bhttps` would fail to match and leak the whole URL.
.replace(/https?:\/\/\S+/gi, ' ')
.replace(/[`*_~#>[\]{}()]/g, ' ')
.replace(/^(?:issue|task|bug|feature|pr)\s*(?:#?\d+)?\s*[:-]\s*/i, '')
.split(/[.!?;\n\r\u2028\u2029]/u)[0]
?.trim()
if (!firstClause) {
return null
}
let candidate = firstClause
for (let i = 0; i < 3; i += 1) {
const before = candidate
for (const pattern of LEADING_FILLER_PATTERNS) {
candidate = candidate.replace(pattern, '')
}
candidate = candidate.trim()
if (candidate === before.trim()) {
break
}
}
candidate = foldGeneratedTabTitleWhitespace(candidate.replace(/[^\p{L}\p{N}\s]/gu, ' '))
if (!candidate) {
return null
}
return truncateAtWordBoundary(capitalizeFirstLetter(candidate), GENERATED_TAB_TITLE_MAX_LENGTH)
}