diff --git a/src/main/amp/hook-service.test.ts b/src/main/amp/hook-service.test.ts index 9c83074d9..25c6939a1 100644 --- a/src/main/amp/hook-service.test.ts +++ b/src/main/amp/hook-service.test.ts @@ -50,10 +50,13 @@ describe('AmpHookService', () => { expect(source).toContain("amp.on('tool.result'") expect(source).toContain("amp.on('agent.end'") expect(source).toContain('return { action: "allow" }') - expect(source).toContain('let postQueue = Promise.resolve()') + expect(source).toContain('const MAX_PENDING_POSTS = 50') + expect(source).toContain('let postQueue: QueuedPost[] = []') expect(source).toContain('function enqueuePost') + expect(source).toContain('postQueue.shift()') expect(source).toContain('enqueuePost("tool.call"') expect(source).not.toContain('await post("tool.call"') + expect(source).not.toContain('postQueue = postQueue.then') expect(source).toContain('process.env.ORCA_PANE_KEY') expect(source).toContain('process.env.ORCA_AGENT_HOOK_ENDPOINT') }) diff --git a/src/main/amp/hook-service.ts b/src/main/amp/hook-service.ts index 93b0e066f..ad56b4641 100644 --- a/src/main/amp/hook-service.ts +++ b/src/main/amp/hook-service.ts @@ -247,11 +247,35 @@ function getAmpPluginSource(): string { ' }', '}', '', - 'let postQueue = Promise.resolve()', + 'const MAX_PENDING_POSTS = 50', + 'type QueuedPost = { hookEventName: string; payload: Record }', + 'let postQueue: QueuedPost[] = []', + 'let postDraining = false', + '', + 'async function drainPostQueue(): Promise {', + ' if (postDraining) return', + ' postDraining = true', + ' try {', + ' while (postQueue.length > 0) {', + ' const next = postQueue.shift()', + ' if (!next) continue', + ' await post(next.hookEventName, next.payload)', + ' }', + ' } finally {', + ' postDraining = false', + ' if (postQueue.length > 0) {', + ' void drainPostQueue()', + ' }', + ' }', + '}', 'function enqueuePost(hookEventName: string, payload: Record): void {', - ' // Why: keep hook callbacks non-blocking while preserving Amp event order.', - ' postQueue = postQueue.then(() => post(hookEventName, payload), () => post(hookEventName, payload))', - ' void postQueue.catch(() => {})', + ' // Why: keep hook callbacks non-blocking without retaining unbounded', + ' // payload closures when Orca is down and each POST waits for timeout.', + ' if (postQueue.length >= MAX_PENDING_POSTS) {', + ' postQueue.shift()', + ' }', + ' postQueue.push({ hookEventName, payload })', + ' void drainPostQueue()', '}', '', 'export default function (amp: PluginAPI) {',