From ca774091d79cf6e257e41506fbef7232b0497f11 Mon Sep 17 00:00:00 2001 From: Borys Papevis <36397618+boris-papevis@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:17:16 -0700 Subject: [PATCH] fix(agents): recognize Pi bundled npm entrypoint Carry the focused patch from boris-papevis/orca PR #25040 onto current main, with independent npm runtime and regression verification. --- .../agent-foreground-process-pi.test.ts | 108 +++++++++--------- .../agent-node-entrypoint-identities.ts | 2 +- src/shared/agent-process-recognition.test.ts | 38 +++++- 3 files changed, 89 insertions(+), 59 deletions(-) diff --git a/src/main/providers/agent-foreground-process-pi.test.ts b/src/main/providers/agent-foreground-process-pi.test.ts index 691ee63e748..7ae8ec3f3fc 100644 --- a/src/main/providers/agent-foreground-process-pi.test.ts +++ b/src/main/providers/agent-foreground-process-pi.test.ts @@ -33,60 +33,64 @@ describe('Pi Windows foreground recognition', () => { } }) - it('recognizes the npm entrypoint within the active ConPTY', async () => { - const rows = [ - { - pid: 100, - ppid: 99, - name: 'bash.exe', - commandLine: '"C:\\Program Files\\Git\\usr\\bin\\bash.exe"' - }, - { - pid: 101, - ppid: 100, - name: 'node.exe', - commandLine: - 'node.exe C:\\Users\\dev\\AppData\\Roaming\\npm\\node_modules\\@earendil-works\\pi-coding-agent\\dist\\cli.js' - } - ] - getAllProcessesMock.mockImplementation((cb: (snapshot: unknown) => void) => { - cb(withSelf(rows)) - }) - const readWindowsConsoleAttachedProcessIds = vi.fn(async () => new Set([100, 101])) - - await expect( - resolveAgentForegroundProcessWithAvailability(100, 'node.exe', { - fresh: true, - readWindowsConsoleAttachedProcessIds + it.each(['cli.js', String.raw`bundle\cli.js`])( + 'recognizes the npm entrypoint dist/%s within the active ConPTY', + async (entrypoint) => { + const rows = [ + { + pid: 100, + ppid: 99, + name: 'bash.exe', + commandLine: '"C:\\Program Files\\Git\\usr\\bin\\bash.exe"' + }, + { + pid: 101, + ppid: 100, + name: 'node.exe', + commandLine: `node.exe C:\\Users\\dev\\AppData\\Roaming\\npm\\node_modules\\@earendil-works\\pi-coding-agent\\dist\\${entrypoint}` + } + ] + getAllProcessesMock.mockImplementation((cb: (snapshot: unknown) => void) => { + cb(withSelf(rows)) }) - ).resolves.toEqual({ available: true, processName: 'pi', processId: 101 }) - expect(readWindowsConsoleAttachedProcessIds).toHaveBeenCalledTimes(1) - }) + const readWindowsConsoleAttachedProcessIds = vi.fn(async () => new Set([100, 101])) - it('anchors a collapsed omp name to the omp pid, not the embedded pi leaf', async () => { - // Pi restarts under a live OMP; an anchor on pi's pid would read that as - // OMP's exit and fire a false "agent done" when the next snapshot degrades. - const rows = [ - { pid: 100, ppid: 99, name: 'powershell.exe', commandLine: 'powershell.exe' }, - { pid: 101, ppid: 100, name: 'omp.exe', commandLine: 'omp' }, - { - pid: 102, - ppid: 101, - name: 'node.exe', - commandLine: - 'node.exe C:\\npm\\node_modules\\@earendil-works\\pi-coding-agent\\dist\\cli.js' - } - ] - getAllProcessesMock.mockImplementation((cb: (snapshot: unknown) => void) => { - cb(withSelf(rows)) - }) - const readWindowsConsoleAttachedProcessIds = vi.fn(async () => new Set([100, 101, 102])) + await expect( + resolveAgentForegroundProcessWithAvailability(100, 'node.exe', { + fresh: true, + readWindowsConsoleAttachedProcessIds + }) + ).resolves.toEqual({ available: true, processName: 'pi', processId: 101 }) + expect(readWindowsConsoleAttachedProcessIds).toHaveBeenCalledTimes(1) + } + ) - await expect( - resolveAgentForegroundProcessWithAvailability(100, 'powershell.exe', { - fresh: true, - readWindowsConsoleAttachedProcessIds + it.each(['cli.js', String.raw`bundle\cli.js`])( + 'anchors a collapsed omp name to the omp pid, not the embedded pi leaf at dist/%s', + async (entrypoint) => { + // Pi restarts under a live OMP; an anchor on pi's pid would read that as + // OMP's exit and fire a false "agent done" when the next snapshot degrades. + const rows = [ + { pid: 100, ppid: 99, name: 'powershell.exe', commandLine: 'powershell.exe' }, + { pid: 101, ppid: 100, name: 'omp.exe', commandLine: 'omp' }, + { + pid: 102, + ppid: 101, + name: 'node.exe', + commandLine: `node.exe C:\\npm\\node_modules\\@earendil-works\\pi-coding-agent\\dist\\${entrypoint}` + } + ] + getAllProcessesMock.mockImplementation((cb: (snapshot: unknown) => void) => { + cb(withSelf(rows)) }) - ).resolves.toEqual({ available: true, processName: 'omp', processId: 101 }) - }) + const readWindowsConsoleAttachedProcessIds = vi.fn(async () => new Set([100, 101, 102])) + + await expect( + resolveAgentForegroundProcessWithAvailability(100, 'powershell.exe', { + fresh: true, + readWindowsConsoleAttachedProcessIds + }) + ).resolves.toEqual({ available: true, processName: 'omp', processId: 101 }) + } + ) }) diff --git a/src/shared/agent-node-entrypoint-identities.ts b/src/shared/agent-node-entrypoint-identities.ts index fb4327c076f..efc52cef2b3 100644 --- a/src/shared/agent-node-entrypoint-identities.ts +++ b/src/shared/agent-node-entrypoint-identities.ts @@ -17,7 +17,7 @@ export const EXACT_NODE_ENTRYPOINT_IDENTITIES: readonly { // Why: Pi's npm shim launches a generic cli.js; only the exact package path is authoritative. { pattern: - /(?:^|\/)node_modules\/@(?:earendil-works|mariozechner)\/pi-coding-agent\/dist\/cli\.js$/, + /(?:^|\/)node_modules\/@(?:earendil-works|mariozechner)\/pi-coding-agent\/dist\/(?:bundle\/)?cli\.js$/, agent: 'pi', processName: 'pi' }, diff --git a/src/shared/agent-process-recognition.test.ts b/src/shared/agent-process-recognition.test.ts index 3c275cb2d69..acb96e173c5 100644 --- a/src/shared/agent-process-recognition.test.ts +++ b/src/shared/agent-process-recognition.test.ts @@ -405,12 +405,38 @@ describe('agent process recognition', () => { ).toEqual({ agent: 'gemini', processName: 'gemini' }) }) - it.each(['earendil-works', 'mariozechner'])('recognizes the @%s Pi npm entrypoint', (scope) => { - expect( - recognizeAgentProcessFromCommandLine( - String.raw`node.exe C:\Users\dev\AppData\Roaming\npm\node_modules\@${scope}\pi-coding-agent\dist\cli.js` - ) - ).toEqual({ agent: 'pi', processName: 'pi' }) + it.each([ + ['earendil-works', 'cli.js'], + ['earendil-works', 'bundle/cli.js'], + ['mariozechner', 'cli.js'], + ['mariozechner', 'bundle/cli.js'] + ])('recognizes the @%s Pi npm entrypoint at dist/%s', (scope, entrypoint) => { + const windowsEntrypoint = entrypoint.replace('/', '\\') + const windowsPath = `C:\\Users\\Dev User\\AppData\\Roaming\\npm\\node_modules\\@${scope}\\pi-coding-agent\\dist\\${windowsEntrypoint}` + for (const command of [ + `node /usr/local/lib/node_modules/@${scope}/pi-coding-agent/dist/${entrypoint}`, + `node.exe ${windowsPath.replace('Dev User', 'dev')}`, + String.raw`"C:\Program Files\nodejs\node.exe" "${windowsPath}"` + ]) { + expect(recognizeAgentProcessFromCommandLine(command)).toEqual({ + agent: 'pi', + processName: 'pi' + }) + } + }) + + it.each([ + 'node /tmp/dist/bundle/cli.js', + 'node /tmp/node_modules/@other/pi-coding-agent/dist/bundle/cli.js', + 'node /tmp/node_modules/@earendil-works/not-pi-coding-agent/dist/bundle/cli.js', + 'node /tmp/notnode_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js', + 'node /tmp/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js.extra', + 'node /tmp/node_modules/@earendil-works/pi-coding-agent/dist/bundle/nested/cli.js', + 'node /tmp/server.js /tmp/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js', + 'node --require /tmp/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js /tmp/server.js', + 'node --eval "console.log(1)" /tmp/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js' + ])('does not recognize a Pi entrypoint lookalike or argument: %s', (command) => { + expect(recognizeAgentProcessFromCommandLine(command)).toBeNull() }) it('recognizes Prime Agent by its binary and npm entrypoint', () => {