e4a335a8e9
- runtime-fallback: guard session.error with sessionRetryInFlight to prevent double-advance during active retry; expand session.stop abort to include sessionAwaitingFallbackResult; remove premature pendingFallbackModel clearing from auto-retry finally block - hashline-edit: add HASHLINE_LEGACY_REF_PATTERN for backward-compatible LINE:HEX dual-parse in parseLineRef and normalizeLineRef - tmux-subagent: defer session on null queryWindowState; unconditionally re-queue deferred session on spawn failure (not just close+spawn) - ultrawork-db: wrap new Database(dbPath) in try/catch to handle corrupted DB - event: add try/catch guards around model-fallback logic in message.updated, session.status, and session.error handlers
93 lines
2.4 KiB
TypeScript
93 lines
2.4 KiB
TypeScript
import { computeLineHash } from "./hash-computation"
|
|
import { HASHLINE_REF_PATTERN, HASHLINE_LEGACY_REF_PATTERN } from "./constants"
|
|
|
|
export interface LineRef {
|
|
line: number
|
|
hash: string
|
|
}
|
|
|
|
const LINE_REF_EXTRACT_PATTERN = /([0-9]+#[ZPMQVRWSNKTXJBYH]{2}|[0-9]+:[0-9a-fA-F]{2,})/
|
|
|
|
function normalizeLineRef(ref: string): string {
|
|
const trimmed = ref.trim()
|
|
if (HASHLINE_REF_PATTERN.test(trimmed)) {
|
|
return trimmed
|
|
}
|
|
if (HASHLINE_LEGACY_REF_PATTERN.test(trimmed)) {
|
|
return trimmed
|
|
}
|
|
|
|
const extracted = trimmed.match(LINE_REF_EXTRACT_PATTERN)
|
|
if (extracted) {
|
|
return extracted[1]
|
|
}
|
|
|
|
return trimmed
|
|
}
|
|
|
|
export function parseLineRef(ref: string): LineRef {
|
|
const normalized = normalizeLineRef(ref)
|
|
const match = normalized.match(HASHLINE_REF_PATTERN)
|
|
if (match) {
|
|
return {
|
|
line: Number.parseInt(match[1], 10),
|
|
hash: match[2],
|
|
}
|
|
}
|
|
const legacyMatch = normalized.match(HASHLINE_LEGACY_REF_PATTERN)
|
|
if (legacyMatch) {
|
|
return {
|
|
line: Number.parseInt(legacyMatch[1], 10),
|
|
hash: legacyMatch[2],
|
|
}
|
|
}
|
|
throw new Error(
|
|
`Invalid line reference format: "${ref}". Expected format: "LINE#ID" (e.g., "42#VK")`
|
|
)
|
|
}
|
|
|
|
export function validateLineRef(lines: string[], ref: string): void {
|
|
const { line, hash } = parseLineRef(ref)
|
|
|
|
if (line < 1 || line > lines.length) {
|
|
throw new Error(
|
|
`Line number ${line} out of bounds. File has ${lines.length} lines.`
|
|
)
|
|
}
|
|
|
|
const content = lines[line - 1]
|
|
const currentHash = computeLineHash(line, content)
|
|
|
|
if (currentHash !== hash) {
|
|
throw new Error(
|
|
`Hash mismatch at line ${line}. Expected hash: ${hash}, current hash: ${currentHash}. ` +
|
|
`Line content may have changed. Current content: "${content}"`
|
|
)
|
|
}
|
|
}
|
|
|
|
export function validateLineRefs(lines: string[], refs: string[]): void {
|
|
const mismatches: string[] = []
|
|
|
|
for (const ref of refs) {
|
|
const { line, hash } = parseLineRef(ref)
|
|
|
|
if (line < 1 || line > lines.length) {
|
|
mismatches.push(`Line number ${line} out of bounds (file has ${lines.length} lines)`)
|
|
continue
|
|
}
|
|
|
|
const content = lines[line - 1]
|
|
const currentHash = computeLineHash(line, content)
|
|
if (currentHash !== hash) {
|
|
mismatches.push(
|
|
`line ${line}: expected ${hash}, current ${currentHash} (${line}#${currentHash}) content: "${content}"`
|
|
)
|
|
}
|
|
}
|
|
|
|
if (mismatches.length > 0) {
|
|
throw new Error(`Hash mismatches:\n- ${mismatches.join("\n- ")}`)
|
|
}
|
|
}
|