From e499a7bbdc73a18c996a4450eb5f2231aff891e5 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:56:11 -0700 Subject: [PATCH] fix: harden dispatch status prompt compaction --- src/shared/agent-status-types.test.ts | 32 +++++++++++++++++ src/shared/orca-dispatch-status-prompt.ts | 43 +++++++++++++++++++++-- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/shared/agent-status-types.test.ts b/src/shared/agent-status-types.test.ts index 3f9f204ba88..34817f20324 100644 --- a/src/shared/agent-status-types.test.ts +++ b/src/shared/agent-status-types.test.ts @@ -118,6 +118,38 @@ Fix dispatch fallback preview for normalized status prompts` expect(result!.prompt).not.toContain('heartbeat') }) + it('ignores task-marker text inside base-drift commit subjects', () => { + const result = normalizeAgentStatusPayload({ + state: 'working', + // Why: use CRLF to cover Windows hook payloads while proving repository + // commit text cannot impersonate Orca's standalone task separator. + prompt: [ + 'You are working inside Orca, a multi-agent IDE. You are a dispatched worker.', + 'Your task ID is: task_drift_marker', + '', + '--- BASE DRIFT ---', + ' - docs: explain === TASK === marker parsing', + '---', + '', + '=== TASK ===', + 'Fix the actual dispatch fallback preview' + ].join('\r\n') + }) + + expect(result!.prompt).toContain('=== TASK === Fix the actual dispatch fallback preview') + expect(result!.prompt).not.toContain('marker parsing') + }) + + it('keeps dispatch detection bounded for oversized whitespace prompts', () => { + const trimStartSpy = vi.spyOn(String.prototype, 'trimStart') + const prompt = ' '.repeat(1_000_000) + + expect(normalizeAgentStatusPayload({ state: 'working', prompt })!.prompt).toBe('') + expect( + trimStartSpy.mock.contexts.some((context) => String(context).length === prompt.length) + ).toBe(false) + }) + it('defaults missing prompt to empty string', () => { const result = parseAgentStatusPayload('{"state":"done"}') expect(result!.prompt).toBe('') diff --git a/src/shared/orca-dispatch-status-prompt.ts b/src/shared/orca-dispatch-status-prompt.ts index 69fe1adf332..42b5a2a3e57 100644 --- a/src/shared/orca-dispatch-status-prompt.ts +++ b/src/shared/orca-dispatch-status-prompt.ts @@ -14,7 +14,17 @@ const ORCA_DISPATCH_STATUS_TASK_ID_MARKER = 'Your task ID is:' const ORCA_DISPATCH_STATUS_SOURCE_SCAN_LIMIT = 24_576 export function isOrcaDispatchStatusPrompt(value: string): boolean { - return value.trimStart().startsWith(ORCA_DISPATCH_STATUS_PREAMBLE_PREFIX) + // Why: status payloads cross a trust boundary. Keep dispatch detection + // bounded too, or leading whitespace can bypass the normalizer's scan cap. + const scanEnd = Math.min(value.length, ORCA_DISPATCH_STATUS_SOURCE_SCAN_LIMIT) + let start = 0 + while (start < scanEnd && isEcmaTrimWhitespace(value.charCodeAt(start))) { + start++ + } + return ( + start + ORCA_DISPATCH_STATUS_PREAMBLE_PREFIX.length <= scanEnd && + value.startsWith(ORCA_DISPATCH_STATUS_PREAMBLE_PREFIX, start) + ) } /** @@ -50,7 +60,7 @@ export function compactDispatchPromptForStatus( } let taskBody = '' - const taskMarkerIndex = scan.indexOf(ORCA_DISPATCH_STATUS_TASK_MARKER) + const taskMarkerIndex = findTaskMarkerIndex(scan) if (taskMarkerIndex !== -1) { const body = scan.slice(taskMarkerIndex + ORCA_DISPATCH_STATUS_TASK_MARKER.length) for (const line of body.split(/\r?\n/)) { @@ -74,6 +84,35 @@ export function compactDispatchPromptForStatus( return normalizeSingleLine(compact, maxLength) } +function findTaskMarkerIndex(value: string): number { + // Why: base-drift commit subjects are repository-controlled and may mention + // the marker. Raw preambles must use the standalone line emitted by Orca. + let searchFrom = 0 + while (searchFrom < value.length) { + const markerIndex = value.indexOf(ORCA_DISPATCH_STATUS_TASK_MARKER, searchFrom) + if (markerIndex === -1) { + break + } + const markerEnd = markerIndex + ORCA_DISPATCH_STATUS_TASK_MARKER.length + const startsLine = markerIndex === 0 || isLineBreak(value.charCodeAt(markerIndex - 1)) + const endsLine = markerEnd === value.length || isLineBreak(value.charCodeAt(markerEnd)) + if (startsLine && endsLine) { + return markerIndex + } + searchFrom = markerEnd + } + + // Already-normalized dispatch previews are single-line and intentionally + // carry the marker inline; normalization must stay idempotent across hops. + return value.includes('\n') || value.includes('\r') + ? -1 + : value.indexOf(ORCA_DISPATCH_STATUS_TASK_MARKER) +} + +function isLineBreak(code: number): boolean { + return code === 10 || code === 13 +} + function isEcmaTrimWhitespace(code: number): boolean { return ( code === 0x20 ||