mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
* 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
112 lines
3.4 KiB
TypeScript
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)
|
|
}
|