fix(omp): acknowledge completion delivery and retire stale retries

* fix(omp): acknowledge completion delivery and retire stale retries

Co-authored-by: Tim Maximilian Lucas <7103424+timaxlucas@users.noreply.github.com>

* test(omp): exercise completion recovery over native HTTP in platform CI

* test(omp): verify rendered status clears after completion retry

---------

Co-authored-by: Tim Maximilian Lucas <7103424+timaxlucas@users.noreply.github.com>
This commit is contained in:
Neil
2026-09-19 00:17:27 -07:00
committed by GitHub
co-authored by Tim Maximilian Lucas
parent bee762bfaf
commit 097677d1da
13 changed files with 504 additions and 35 deletions
+4 -1
View File
@@ -2,8 +2,9 @@ name: Pi owner runtime verification
on:
pull_request:
paths:
- 'src/main/pi/agent-status-handler-source.ts'
- 'src/main/pi/**'
- 'tests/tools/pi-owner-runtime-smoke.mjs'
- 'tests/tools/omp-completion-runtime-smoke.mjs'
- '.github/workflows/pi-owner-runtime.yml'
workflow_dispatch:
permissions:
@@ -27,3 +28,5 @@ jobs:
run: npm install --prefix .cache/pi-owner --ignore-scripts --no-audit --no-fund @earendil-works/pi-coding-agent@0.83.0
- name: Verify real owner exit and hook delivery
run: node tests/tools/pi-owner-runtime-smoke.mjs .cache/pi-owner/node_modules/@earendil-works/pi-coding-agent
- name: Verify OMP completion over native HTTP
run: node tests/tools/omp-completion-runtime-smoke.mjs
@@ -0,0 +1,150 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createAgentStatusExtensionHarness } from './agent-status-extension-test-harness'
function events(mock: ReturnType<typeof vi.fn>): unknown[] {
return mock.mock.calls.map((call) => JSON.parse(String(call[1]?.body)).payload)
}
describe('OMP completion delivery', () => {
beforeEach(() => vi.useFakeTimers())
afterEach(() => vi.useRealTimers())
it.each(['rejection', 'HTTP failure'])(
'retries a final %s without another turn',
async (failure) => {
const harness = createAgentStatusExtensionHarness({ kind: 'omp' })
await harness.callHook('agent_start')
await vi.advanceTimersByTimeAsync(0)
if (failure === 'rejection') {
harness.fetchMock.mockRejectedValueOnce(new Error('offline'))
} else {
harness.fetchMock.mockResolvedValueOnce({ ok: false, status: 503 })
}
await harness.callHook('agent_end')
await vi.advanceTimersByTimeAsync(251)
expect(events(harness.fetchMock)).toEqual([
{ hook_event_name: 'agent_start' },
{ hook_event_name: 'agent_end' },
{ hook_event_name: 'agent_end' }
])
expect(vi.getTimerCount()).toBe(0)
}
)
it.each([22, null])('retries failed or timed-out WSL curl (exit %s)', async (curlExitCode) => {
const harness = createAgentStatusExtensionHarness({
kind: 'omp',
env: { WSL_DISTRO_NAME: 'Ubuntu' },
existsSync: (path) => path === '/mnt/c/Windows/System32/curl.exe',
curlExitCode,
fetchImpl: async () => {
throw new Error('guest unavailable')
}
})
await harness.callHook('agent_end')
await vi.advanceTimersByTimeAsync(curlExitCode === null ? 11251 : 251)
expect(harness.spawnMock).toHaveBeenCalledTimes(2)
await harness.callHook('session_shutdown')
await vi.advanceTimersByTimeAsync(12000)
expect(harness.spawnMock).toHaveBeenCalledTimes(2)
expect(vi.getTimerCount()).toBe(0)
})
it('does not acknowledge a missing WSL curl bridge', async () => {
const harness = createAgentStatusExtensionHarness({
kind: 'omp',
env: { WSL_DISTRO_NAME: 'Ubuntu' },
fetchImpl: async () => {
throw new Error('guest unavailable')
}
})
await harness.callHook('agent_end')
await vi.advanceTimersByTimeAsync(251)
expect(harness.fetchMock).toHaveBeenCalledTimes(2)
await harness.callHook('session_shutdown')
})
it('waits for WSL curl acknowledgment before draining a newer snapshot', async () => {
const harness = createAgentStatusExtensionHarness({
kind: 'omp',
env: { WSL_DISTRO_NAME: 'Ubuntu' },
existsSync: (path) => path === '/mnt/c/Windows/System32/curl.exe',
curlExitCode: null,
fetchImpl: async () => {
throw new Error('guest unavailable')
}
})
await harness.callHook('agent_end')
await vi.advanceTimersByTimeAsync(0)
await harness.callHook('agent_start')
expect(harness.fetchMock).toHaveBeenCalledTimes(1)
harness.spawnedChildren[0]?.emit('close', 0)
await vi.advanceTimersByTimeAsync(0)
expect(harness.fetchMock).toHaveBeenCalledTimes(2)
harness.spawnedChildren[1]?.emit('close', 0)
await vi.advanceTimersByTimeAsync(1000)
expect(vi.getTimerCount()).toBe(0)
})
it.each(['before_agent_start', 'agent_start', 'session_shutdown', 'session_switch'])(
'retires a failed completion at %s',
async (boundary) => {
const harness = createAgentStatusExtensionHarness({ kind: 'omp' })
harness.fetchMock.mockRejectedValueOnce(new Error('offline'))
await harness.callHook('agent_end')
await vi.advanceTimersByTimeAsync(0)
await harness.callHook(boundary, {})
await vi.advanceTimersByTimeAsync(10_000)
expect(
events(harness.fetchMock).filter((event) => JSON.stringify(event).includes('agent_end'))
).toHaveLength(1)
expect(vi.getTimerCount()).toBe(0)
}
)
it('does not schedule retries when a pending completion fails after a new start', async () => {
let rejectDelivery: ((error: Error) => void) | undefined
const harness = createAgentStatusExtensionHarness({ kind: 'omp' })
harness.fetchMock.mockImplementationOnce(
() =>
new Promise((_resolve, reject) => {
rejectDelivery = reject
})
)
await harness.callHook('agent_end')
await harness.callHook('agent_start')
rejectDelivery?.(new Error('late failure'))
await vi.advanceTimersByTimeAsync(10_000)
expect(events(harness.fetchMock)).toEqual([
{ hook_event_name: 'agent_end' },
{ hook_event_name: 'agent_start' }
])
})
it('retries a timed out completion without blocking agent handlers', async () => {
const harness = createAgentStatusExtensionHarness({ kind: 'omp' })
harness.fetchMock.mockImplementationOnce(() => new Promise(() => {}))
await harness.callHook('agent_end')
await vi.advanceTimersByTimeAsync(1251)
expect(events(harness.fetchMock)).toEqual([
{ hook_event_name: 'agent_end' },
{ hook_event_name: 'agent_end' }
])
expect(harness.fetchMock.mock.calls[0]?.[1]?.signal.aborted).toBe(true)
expect(vi.getTimerCount()).toBe(0)
})
it('bounds retries when Orca stays unreachable', async () => {
const harness = createAgentStatusExtensionHarness({
kind: 'omp',
fetchImpl: async () => {
throw new Error('offline')
}
})
await harness.callHook('agent_end')
await vi.advanceTimersByTimeAsync(30_000)
expect(harness.fetchMock).toHaveBeenCalledTimes(4)
expect(vi.getTimerCount()).toBe(0)
})
})
@@ -349,6 +349,7 @@ describe('getPiAgentStatusExtensionSource', () => {
expect(command).toBe('/mnt/c/Windows/System32/curl.exe')
expect(args).toEqual([
'-sS',
'--fail',
'--connect-timeout',
'3',
'--max-time',
+17 -21
View File
@@ -1,3 +1,4 @@
import { getPiAgentStatusPostQueueSourceLines } from './agent-status-post-queue-source'
// Why: pi has no settings.json hook surface — its extensibility is the
// in-process TypeScript extension API (pi.on('agent_start'), 'tool_call',
// etc.). To get pi panes into the unified agent-hooks pipeline alongside
@@ -100,9 +101,8 @@ export function getPiAgentStatusExtensionSource(kind: PiAgentKind = 'pi'): strin
'// critical path, and the latest-only pending slot prevents a stalled',
'// Orca receiver from building an unbounded queue of obsolete snapshots.',
'const HOOK_POST_TIMEOUT_MS = 1000',
'let activePost = false',
...getPiAgentStatusPostQueueSourceLines(),
...(kind === 'pi' ? ['let piUiPromptDepth = 0', 'let piTurnInFlight = false'] : []),
'let pendingPost: { hookEventName: string; extra: Record<string, unknown>; metadata: Record<string, unknown>; ompRuntime: boolean } | null = null',
...sessionMetadataSourceLines,
'',
'// Why: re-reading the endpoint file on every event is cheap (small file,',
@@ -163,31 +163,26 @@ export function getPiAgentStatusExtensionSource(kind: PiAgentKind = 'pi'): strin
'',
'function post(hookEventName: string, extra: Record<string, unknown> = {}): void {',
' const ompRuntime = isOmpRuntime()',
' cancelPostRetry()',
' const metadata = getPostSessionMetadata(ompRuntime)',
'// Model changes must not erase an unacknowledged completion in the latest-only slot.',
" const previousCompletion = latestPost?.hookEventName === 'agent_end' && !latestPost.delivered && latestPost.metadata.session_id === metadata.session_id",
' pendingPost = {',
' hookEventName,',
' revision: ++postRevision,',
' attempts: 0,',
' delivered: false,',
" hookEventName: ompRuntime && hookEventName === 'model_select' && previousCompletion ? 'agent_end' : hookEventName,",
// Why: every coalesced snapshot must retain an open modal, not just its start event.
kind === 'pi'
? ' extra: { ...extra, ...(!ompRuntime && piUiPromptDepth > 0 ? { ui_prompt_active: true } : {}) },'
: ' extra,',
' metadata: getPostSessionMetadata(ompRuntime),',
' metadata,',
' ompRuntime,',
' }',
' latestPost = pendingPost',
' drainPosts()',
'}',
'',
'function drainPosts(): void {',
' if (activePost || !pendingPost) return',
' const next = pendingPost',
' pendingPost = null',
' activePost = true',
' void postOnce(next.hookEventName, next.extra, next.metadata, next.ompRuntime)',
' .catch(() => {})',
' .finally(() => {',
' activePost = false',
' drainPosts()',
' })',
'}',
'',
'async function postOnce(',
' hookEventName: string,',
' extra: Record<string, unknown>,',
@@ -217,7 +212,7 @@ export function getPiAgentStatusExtensionSource(kind: PiAgentKind = 'pi'): strin
" if (typeof timeout.unref === 'function') timeout.unref()",
' })',
' try {',
' await Promise.race([',
' const response = await Promise.race([',
' fetch(url, {',
" method: 'POST',",
' headers: {',
@@ -229,11 +224,12 @@ export function getPiAgentStatusExtensionSource(kind: PiAgentKind = 'pi'): strin
' }),',
' timeoutPromise,',
' ])',
' } catch {',
" if (!response.ok) throw new Error('Orca hook HTTP ' + response.status)",
' } catch (error) {',
' // Why: status reporting must never fail the pi run just because Orca',
' // is unavailable or the loopback request failed (e.g. Orca restart).',
' if (!isWslRuntime()) return',
' postViaWindowsCurl(body, ompRuntime)',
' if (!isWslRuntime()) throw error',
' await postViaWindowsCurl(body, ompRuntime)',
' } finally {',
' if (timeout) clearTimeout(timeout)',
' }',
@@ -1,3 +1,4 @@
import { EventEmitter } from 'node:events'
import { runInNewContext } from 'node:vm'
// TypeScript 7 is a native CLI; transpile tests still need the legacy JavaScript API.
import ts from 'typescript-api'
@@ -6,7 +7,10 @@ import { vi } from 'vitest'
import { getPiAgentStatusExtensionSource } from './agent-status-extension-source'
export type HookContext = {
hasUI?: boolean
ui?: { setEditorText?: (text: string) => void }
isIdle?: () => boolean
model?: { provider?: unknown; id?: unknown } | null
sessionManager?: {
getSessionId?: () => unknown
getSessionFile?: () => unknown
@@ -16,6 +20,8 @@ export type HookContext = {
export type HookHandler = (event?: unknown, context?: HookContext) => Promise<void> | void
type FakeCurlChild = {
kill: ReturnType<typeof vi.fn>
emit: (event: string, ...args: unknown[]) => boolean
on: ReturnType<typeof vi.fn>
stdin: {
on: ReturnType<typeof vi.fn>
@@ -66,6 +72,7 @@ export function createAgentStatusExtensionHarness(args: {
existsSync?: (path: string) => boolean
readFileSync?: (path: string, encoding: string) => string
statSync?: (path: string) => { mtimeMs: number; size: number; ino: number }
curlExitCode?: number | null
fetchImpl?: (...params: Parameters<typeof fetch>) => Promise<unknown>
}): AgentStatusExtensionHarness {
const fetchMock = vi.fn(
@@ -77,14 +84,20 @@ export function createAgentStatusExtensionHarness(args: {
const spawnedChildren: FakeCurlChild[] = []
const spawnMock = vi.fn(() => {
const emitter = new EventEmitter()
const child: FakeCurlChild = {
on: vi.fn(),
emit: emitter.emit.bind(emitter),
kill: vi.fn(() => emitter.emit('close', null)),
on: vi.fn(emitter.on.bind(emitter)),
stdin: {
on: vi.fn(),
end: vi.fn()
}
}
spawnedChildren.push(child)
if (args.curlExitCode !== null) {
void Promise.resolve().then(() => emitter.emit('close', args.curlExitCode ?? 0))
}
return child
})
@@ -114,6 +114,20 @@ export function getPiAgentStatusHandlerSourceLines(kind: PiAgentKind): string[]
' const selfPid = String(process.pid)',
' if (ownerPid && ownerPid !== selfPid && isStatusOwnerAlive(ownerPid)) return',
` process.env.${ownerEnv} = selfPid`,
' resetPostQueue()',
...(kind !== 'pi'
? [" pi.on('session_shutdown', () => { resetPostQueue(); clearPendingAgentEndCheck() })"]
: []),
...(kind !== 'prime-agent'
? [
" pi.on('session_switch', (_event, ctx) => {",
' if (!isOmpRuntime()) return',
' resetPostQueue()',
' clearPendingAgentEndCheck()',
' updateRuntimeOmpSessionMetadata(ctx)',
' })'
]
: []),
...sessionStartHandler,
` pi.on('before_agent_start', (event${ctxParam}) => {`,
...captureSessionMetadata,
@@ -0,0 +1,52 @@
export function getPiAgentStatusPostQueueSourceLines(): string[] {
return [
'type HookPost = { hookEventName: string; extra: Record<string, unknown>; metadata: Record<string, unknown>; ompRuntime: boolean; revision: number; attempts: number; delivered: boolean }',
'let activePost = false',
'let pendingPost: HookPost | null = null',
'let latestPost: HookPost | null = null',
'// A newer snapshot or session boundary retires every older retry.',
'let postRevision = 0',
'let retryTimer: ReturnType<typeof setTimeout> | null = null',
'',
'function cancelPostRetry(): void {',
' if (retryTimer !== null) clearTimeout(retryTimer)',
' retryTimer = null',
'}',
'',
'function resetPostQueue(): void {',
' cancelPostRetry()',
' postRevision++',
' pendingPost = null',
' latestPost = null',
'}',
'',
'function drainPosts(): void {',
' if (activePost || !pendingPost) return',
' const next = pendingPost',
' pendingPost = null',
' activePost = true',
' void postOnce(next.hookEventName, next.extra, next.metadata, next.ompRuntime)',
' .then(() => { next.delivered = true })',
' .catch(() => {',
' if (!next.ompRuntime || next.revision !== postRevision) return',
' if (next.attempts >= 3) {',
" console.warn('[orca-pi-status] hook delivery failed after retries:', next.hookEventName)",
' return',
' }',
' const delay = 250 * 2 ** next.attempts++',
' retryTimer = setTimeout(() => {',
' retryTimer = null',
' if (next.revision !== postRevision) return',
' pendingPost = next',
' drainPosts()',
' }, delay)',
" if (typeof retryTimer.unref === 'function') retryTimer.unref()",
' })',
' .finally(() => {',
' activePost = false',
' drainPosts()',
' })',
'}',
''
]
}
@@ -32,6 +32,8 @@ export function getPiAgentStatusUiPromptHandlerSourceLines(kind: PiAgentKind): s
' })',
'',
" pi.on('session_shutdown', () => {",
' resetPostQueue()',
' clearPendingAgentEndCheck()',
' if (isOmpRuntime()) return',
' // Why: pi tears an open dialog down through resetExtensionUI without resolving its',
' // promise, so a replaced session never emits the matching ui_prompt_end and the wait',
+18 -10
View File
@@ -43,20 +43,20 @@ export function getPiAgentStatusWslCurlSourceLines(): string[] {
'}',
'',
'// Why: WSL loopback is not the Windows loopback, so use curl.exe on the host.',
'function postViaWindowsCurl(body: string, ompRuntime: boolean): void {',
'async function postViaWindowsCurl(body: string, ompRuntime: boolean): Promise<void> {',
' const curlPath = resolveWindowsCurlPath()',
' const windowsPort = process.env.ORCA_AGENT_HOOK_PORT',
' const windowsToken = process.env.ORCA_AGENT_HOOK_TOKEN',
' if (!curlPath || !windowsPort || !windowsToken) return',
" if (!curlPath || !windowsPort || !windowsToken) throw new Error('Orca WSL hook bridge unavailable')",
' // Why: a stale guest endpoint must fall back to current host coordinates.',
' const windowsUrl = `http://127.0.0.1:${windowsPort}${resolveHookPath(ompRuntime)}`',
' try {',
' await new Promise<void>((resolve, reject) => {',
" const { spawn } = require('child_process')",
' const child = spawn(',
' curlPath,',
' [',
" '-sS',",
' // Why: detached delivery may take seconds under loaded WSL interop.',
" '-sS', '--fail',",
' // WSL interop needs a bounded, acknowledged delivery window.',
" '--connect-timeout', '3',",
" '--max-time', '10',",
" '--noproxy', '127.0.0.1',",
@@ -69,12 +69,20 @@ export function getPiAgentStatusWslCurlSourceLines(): string[] {
' ],',
" { stdio: ['pipe', 'ignore', 'ignore'] }",
' )',
" child.on('error', () => {})",
" child.stdin.on('error', () => {})",
' const timer = setTimeout(() => {',
' try { child.kill() } catch {}',
" reject(new Error('Orca WSL hook delivery timed out'))",
' }, 11000)',
" if (typeof timer.unref === 'function') timer.unref()",
' const fail = (error: Error): void => { clearTimeout(timer); reject(error) }',
" child.on('error', fail)",
" child.stdin.on('error', fail)",
" child.on('close', (code: number | null) => {",
' clearTimeout(timer)',
" if (code === 0) resolve(); else reject(new Error('Orca WSL hook exit ' + code))",
' })',
' child.stdin.end(body)',
' } catch {',
' // Why: status delivery must not surface inside the agent TUI.',
' }',
' })',
'}',
''
]
@@ -228,6 +228,21 @@ describe('getPiTitlebarExtensionSource', () => {
expect(transferTitles[0]).toMatch(BRAILLE_RE)
})
it.each([{ kind: 'omp' as const }, { processTitle: 'omp' }])(
'stops final OMP completion without waiting for isIdle: %j',
async (options) => {
const isIdle = vi.fn(() => false)
const harness = createHarness({ ...options, isIdle })
await harness.callHook('agent_start')
await harness.callHook('agent_end', { willContinue: false })
const completedTitle = harness.lastTitle()
expect(completedTitle).not.toMatch(BRAILLE_RE)
await vi.advanceTimersByTimeAsync(1000)
expect(harness.lastTitle()).toBe(completedTitle)
expect(isIdle).not.toHaveBeenCalled()
}
)
it('keeps spinning across a non-terminal OMP agent_end', async () => {
const harness = createHarness()
+2 -2
View File
@@ -255,7 +255,7 @@ export function getPiTitlebarExtensionSource(kind: PiAgentKind = 'pi'): string {
' resetPromptState()',
' })',
'',
' // Why: modern Pi/OMP emit agent_end mid-run and only settle later, so settlement is the',
' // Why: modern Pi emits agent_end mid-run and only settle later, so settlement is the',
' // authoritative completion boundary. Legacy runtimes never emit it, so agent_end stays.',
" on('agent_settled', async (_event, ctx) => {",
' stopAnimation(ctx)',
@@ -266,7 +266,7 @@ export function getPiTitlebarExtensionSource(kind: PiAgentKind = 'pi'): string {
' clearPendingAgentEndCheck()',
' return',
' }',
" if (!ctx || typeof ctx.isIdle !== 'function') {",
` if (${kind === 'omp' ? 'true' : kind === 'pi' ? 'isOmpRuntime()' : 'false'} || !ctx || typeof ctx.isIdle !== 'function') {`,
' stopAnimation(ctx)',
' return',
' }',
@@ -0,0 +1,107 @@
import { once } from 'node:events'
import { createServer } from 'node:http'
import { createRequire } from 'node:module'
import { join } from 'node:path'
import { runInNewContext } from 'node:vm'
import { transform } from 'esbuild'
import { getPiAgentStatusExtensionSource } from '../../src/main/pi/agent-status-extension-source'
import { test, expect } from './helpers/orca-app'
import { readHookEndpoint } from './helpers/agent-hook-endpoint'
import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store'
import { waitForActivePaneHookDescriptor, waitForActiveTerminalManager } from './helpers/terminal'
test('OMP completion retry clears the rendered working indicator', async ({
orcaPage,
electronApp
}, testInfo) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
await waitForActiveTerminalManager(orcaPage, 30_000)
const endpoint = await readHookEndpoint(electronApp)
const { paneKey, worktreeId } = await waitForActivePaneHookDescriptor(orcaPage)
let completions = 0
const proxy = createServer(async (request, response) => {
let body = ''
for await (const chunk of request) {
body += chunk
}
if (JSON.parse(body).payload.hook_event_name === 'agent_end' && ++completions === 1) {
response.writeHead(503).end()
return
}
const forwarded = await fetch(`http://127.0.0.1:${endpoint.port}/hook/omp`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Orca-Agent-Hook-Token': endpoint.token },
body
})
response.writeHead(forwarded.status).end()
})
proxy.listen(0, '127.0.0.1')
await once(proxy, 'listening')
try {
const address = proxy.address()
if (!address || typeof address === 'string') {
throw new Error('Expected TCP listener')
}
type Handler = (event: Record<string, unknown>, ctx: { isIdle: () => boolean }) => void
const handlers = new Map<string, Handler>()
const module: {
exports: { default?: (api: { on: (name: string, fn: Handler) => void }) => void }
} = { exports: {} }
const { code } = await transform(getPiAgentStatusExtensionSource('omp'), {
loader: 'ts',
format: 'cjs'
})
runInNewContext(code, {
module,
exports: module.exports,
require: createRequire(join(process.cwd(), 'package.json')),
process: {
pid: process.pid,
argv: [],
title: 'omp',
env: {
ORCA_PANE_KEY: paneKey,
ORCA_TAB_ID: paneKey.split(':')[0],
ORCA_WORKTREE_ID: worktreeId,
ORCA_AGENT_HOOK_PORT: String(address.port),
ORCA_AGENT_HOOK_TOKEN: endpoint.token,
ORCA_AGENT_HOOK_ENV: endpoint.env,
ORCA_AGENT_HOOK_VERSION: endpoint.version
}
},
fetch,
AbortController,
Buffer,
console,
setTimeout,
clearTimeout
})
expect(module.exports.default).toBeDefined()
module.exports.default?.({ on: (name, fn) => handlers.set(name, fn) })
const working = orcaPage.locator('[aria-label="Working"]')
handlers.get('before_agent_start')?.(
{ prompt: 'OMP completion recovery' },
{ isIdle: () => false }
)
handlers.get('agent_start')?.({}, { isIdle: () => false })
await expect(working.first()).toBeVisible()
await orcaPage.screenshot({ path: testInfo.outputPath('before-working.png') })
handlers.get('agent_end')?.({ willContinue: false }, { isIdle: () => false })
await expect.poll(() => completions).toBe(2)
await expect(working).toHaveCount(0)
await expect
.poll(() =>
orcaPage.evaluate(
(key) => window.__store?.getState().agentStatusByPaneKey[key]?.state,
paneKey
)
)
.toBe('done')
await orcaPage.screenshot({ path: testInfo.outputPath('after-completed.png') })
} finally {
proxy.closeAllConnections()
proxy.close()
}
})
@@ -0,0 +1,108 @@
import assert from 'node:assert/strict'
import { once } from 'node:events'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { createServer } from 'node:http'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { setTimeout as delay } from 'node:timers/promises'
import { build, transform } from 'esbuild'
const scratch = await mkdtemp(join(tmpdir(), 'orca-omp-completion-'))
const received = []
let rejectCompletion = true
const server = createServer(async (request, response) => {
let body = ''
for await (const chunk of request) {
body += chunk
}
const event = JSON.parse(body).payload.hook_event_name
received.push(event)
const reject = event === 'agent_end' && rejectCompletion
if (reject) {
rejectCompletion = false
}
response.writeHead(reject ? 503 : 204)
response.end()
})
async function waitForRequests(count) {
const deadline = Date.now() + 5000
while (received.length < count && Date.now() < deadline) {
await delay(10)
}
assert.equal(received.length, count, 'Hook requests did not arrive before the deadline')
}
try {
const bundle = join(scratch, 'source.cjs')
await build({
entryPoints: ['src/main/pi/agent-status-extension-source.ts'],
bundle: true,
platform: 'node',
format: 'cjs',
outfile: bundle
})
const require = createRequire(import.meta.url)
const { getPiAgentStatusExtensionSource } = require(bundle)
const generated = await transform(getPiAgentStatusExtensionSource('omp'), {
loader: 'ts',
format: 'cjs',
target: 'node24'
})
const extension = join(scratch, 'extension.cjs')
await writeFile(extension, generated.code)
server.listen(0, '127.0.0.1')
await once(server, 'listening')
Object.assign(process.env, {
ORCA_BACKGROUND_LAUNCH: '1',
ORCA_PANE_KEY: 'completion-proof',
ORCA_TAB_ID: 'proof-tab',
ORCA_AGENT_HOOK_PORT: String(server.address().port),
ORCA_AGENT_HOOK_TOKEN: 'isolated-proof-token',
ORCA_AGENT_HOOK_ENDPOINT: '',
ORCA_PI_STATUS_OWNED: '',
WSL_DISTRO_NAME: ''
})
const handlers = new Map()
require(extension).default({ on: (event, handler) => handlers.set(event, handler) })
const emit = async (event) => {
assert.ok(handlers.has(event), `Missing lifecycle handler: ${event}`)
await handlers.get(event)({}, { isIdle: () => false })
}
await emit('agent_start')
await waitForRequests(1)
await emit('agent_end')
await waitForRequests(3)
assert.deepEqual(received, ['agent_start', 'agent_end', 'agent_end'])
const recovered = [...received]
for (const boundary of ['agent_start', 'session_switch', 'session_shutdown']) {
received.length = 0
rejectCompletion = true
await emit('agent_start')
await waitForRequests(1)
await emit('agent_end')
await waitForRequests(2)
await emit(boundary)
await delay(600)
assert.equal(
received.filter((event) => event === 'agent_end').length,
1,
`An obsolete completion retried after ${boundary}`
)
}
console.log(
JSON.stringify({
platform: process.platform,
node: process.version,
recovered,
cancelledAt: ['agent_start', 'session_switch', 'session_shutdown'],
scope: 'Generated OMP extension, real native HTTP 503, synthetic lifecycle callbacks'
})
)
} finally {
server.closeAllConnections()
server.close()
await rm(scratch, { recursive: true, force: true })
}