From d3e1494674f32cc43039727df33a2d71ec3ca450 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:21:34 -0700 Subject: [PATCH] test: bound memory used by the runtime Electron audit (#26049) --- .../check-runtime-electron-ratchet.mjs | 15 ++++- .../check-runtime-electron-ratchet.test.mjs | 66 +++++++++++++++---- .../structured-agent-session-host.ts | 5 +- 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/config/scripts/check-runtime-electron-ratchet.mjs b/config/scripts/check-runtime-electron-ratchet.mjs index 71a8e3a8aef..8c20cb8e8c2 100644 --- a/config/scripts/check-runtime-electron-ratchet.mjs +++ b/config/scripts/check-runtime-electron-ratchet.mjs @@ -113,11 +113,20 @@ const externalNativeAddons = { // Why `plugins`: lets a test add an Electron import to a real file in memory, never on disk. export async function collectElectronImporters(entryPoints, { plugins = [] } = {}) { const result = await build({ - entryPoints, + // Export every entry through one bundle so shared dependencies are emitted once. + stdin: { + contents: entryPoints + .map( + (entry, index) => + `export * as entry_${index} from ${JSON.stringify(path.resolve(ROOT, entry))}` + ) + .join('\n'), + resolveDir: ROOT, + loader: 'ts', + sourcefile: 'runtime-electron-ratchet-entry.ts' + }, bundle: true, write: false, - // Why outdir with write:false: esbuild refuses multiple entry points without one, - // even though nothing is emitted — the metafile is all this reads. outdir: path.join(ROOT, 'runtime-electron-ratchet-metafile-only'), platform: 'node', target: 'node20', diff --git a/config/scripts/check-runtime-electron-ratchet.test.mjs b/config/scripts/check-runtime-electron-ratchet.test.mjs index 2e46362390e..9f9e81f0f40 100644 --- a/config/scripts/check-runtime-electron-ratchet.test.mjs +++ b/config/scripts/check-runtime-electron-ratchet.test.mjs @@ -94,21 +94,61 @@ describe('structured chat coverage', () => { } ) - it('finds Electron through a package imported by each unwired future lane', async () => { + it.each([true, false])( + 'finds Electron through an unwired lane package (sideEffects=%s)', + async (sideEffects) => { + const root = fixture({ + ...requiredLanes, + 'src/main/acp/adapter.ts': "import 'acp-desktop-package'", + 'src/main/provider-process/worker.ts': "import 'provider-desktop-package'", + 'node_modules/acp-desktop-package/package.json': JSON.stringify({ + main: 'index.js', + sideEffects + }), + 'node_modules/acp-desktop-package/index.js': "require('electron')", + 'node_modules/provider-desktop-package/package.json': JSON.stringify({ + main: 'index.js', + sideEffects + }), + 'node_modules/provider-desktop-package/index.js': "require('electron')" + }) + const current = await collectElectronImporters(collectStructuredChatEntryPoints(root)) + expect(current.map((file) => file.split('/node_modules/').pop())).toEqual([ + 'acp-desktop-package/index.js', + 'provider-desktop-package/index.js' + ]) + } + ) + + it('bounds shared dependency output without dropping any entry point', async () => { + const entries = Array.from({ length: 40 }, (_, index) => `entry-${index}.ts`) const root = fixture({ - ...requiredLanes, - 'src/main/acp/adapter.ts': "import 'acp-desktop-package'", - 'src/main/provider-process/worker.ts': "import 'provider-desktop-package'", - 'node_modules/acp-desktop-package/package.json': '{"main":"index.js"}', - 'node_modules/acp-desktop-package/index.js': "require('electron')", - 'node_modules/provider-desktop-package/package.json': '{"main":"index.js"}', - 'node_modules/provider-desktop-package/index.js': "require('electron')" + 'shared.ts': `export const payload = ${JSON.stringify('x'.repeat(64 * 1024))}`, + ...Object.fromEntries( + entries.map((file) => [file, "import 'electron'; export { payload } from './shared'"]) + ) }) - const current = await collectElectronImporters(collectStructuredChatEntryPoints(root)) - expect(current.map((file) => file.split('/node_modules/').pop())).toEqual([ - 'acp-desktop-package/index.js', - 'provider-desktop-package/index.js' - ]) + let emittedBytes = 0 + const current = await collectElectronImporters( + entries.map((file) => path.join(root, file)), + { + plugins: [ + { + name: 'measure-audit-output', + setup(builder) { + builder.onEnd((result) => { + emittedBytes = result.outputFiles.reduce( + (bytes, file) => bytes + file.contents.byteLength, + 0 + ) + }) + } + } + ] + } + ) + expect(current.map((file) => path.basename(file)).sort()).toEqual(entries.sort()) + expect(emittedBytes).toBeLessThan(128 * 1024) }) }) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-host.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-host.ts index d223862c150..c71d182a086 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-host.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-host.ts @@ -50,8 +50,7 @@ import { type StructuredAgentSessionRestartResume } from './structured-agent-session-restart-resume-host' import { structuredAgentSessionRestartResumeSurfaces } from './structured-agent-session-restart-resume-wiring' -import { createStructuredAgentSessionConversationDelivery } from './structured-agent-session-host-delivery' -import type * as conversation from './structured-agent-session-host-delivery' +import * as conversation from './structured-agent-session-host-delivery' import { structuredAgentSessionConversationFence } from './structured-agent-session-provider-child' import { wireStructuredAgentSessionQueuedMessages } from './structured-agent-session-queued-wiring' import * as sessionLogger from './structured-agent-session-logger' @@ -117,7 +116,7 @@ export class StructuredAgentSessionHost { ...(deps.probeOwners ? { probeMany: deps.probeOwners } : {}), now: () => this.now() }) - this.conversationDelivery = createStructuredAgentSessionConversationDelivery({ + this.conversationDelivery = conversation.createStructuredAgentSessionConversationDelivery({ deps, sessions: this.sessions, serialize: (sessionId, task) => this.serialize(sessionId, task),