perf: optimize session loading and desktop startup (#2545)

* perf: optimize session loading and startup

* fix(chat): stabilize history prepend virtualization

* perf: unblock first session open from startup network contention

Opening the first session after app start waited seconds for its message
fetch. Three independent contributors, each measured via CDP network
capture and Chromium net-log against the packaged desktop app:

- The active-session watchdog fired an uncapped per-directory status poll
  and child-session discovery burst at startup, and other subsystems
  (git checks, global session pages, command/skill discovery) fanned out
  alongside it, saturating the browser's ~6 HTTP/1.1 sockets per origin.
  Add a shared background-network gate (concurrency 3) and route the
  watchdog, poll-shaped git reads (also priority: low), global session
  pages, command/skill loads, and the background update check through it.

- The packaged renderer is cross-origin to the loopback backend, so every
  API call needs a CORS preflight; a few slow OpenCode-proxied requests
  held the whole pool while preflights and interactive traffic queued
  behind them. Lift Chromium's per-host connection cap for loopback via
  ignore-connections-limit in the Electron shell.

- OpenCode initializes each directory lazily on its first request, so the
  first click paid that cost interactively. Warm the last-used directory
  and the three most recently opened projects right after OpenCode
  readiness, sequentially and best-effort, overlapping UI startup.

Validation: new background-network tests, lifecycle warmup test, focused
store/sync tests, UI type-check and lint, dead-code report, node --check
plus electron type-check/lint, and CDP first-open measurements on the
packaged app (message fetch socket queue 5.4s -> 0.03s).

* fix(ui): keep interactive git reads out of background queue

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
𝖎𝖚𝖑𝖎𝖎𝖆
2026-07-31 12:51:15 +03:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent 09f0c64839
commit aae889b904
41 changed files with 1690 additions and 203 deletions
@@ -10,7 +10,7 @@ This module provides OpenCode server integration utilities for the web server ru
- `packages/web/server/lib/opencode/cli-options.js`: CLI/environment option parsing for server startup arguments.
- `packages/web/server/lib/opencode/cli-entry-runtime.js`: CLI entrypoint runtime that detects direct execution, parses CLI options, and starts server bootstrap.
- `packages/web/server/lib/opencode/routes.js`: OpenCode/provider settings and auth-related route registration.
- `packages/web/server/lib/opencode/lifecycle.js`: OpenCode process lifecycle runtime (startup, restart, readiness, health monitoring).
- `packages/web/server/lib/opencode/lifecycle.js`: OpenCode process lifecycle runtime (startup, restart, readiness, health monitoring). After readiness it warms the most recently used directories (`getWarmupDirectories` dep, sequential and best-effort) because OpenCode initializes each directory lazily on first request and that cost would otherwise be paid by the user's first interactive session open.
- `packages/web/server/lib/opencode/env-runtime.js`: OpenCode CLI/binary resolution and shell environment runtime.
- `packages/web/server/lib/opencode/env-config.js`: OpenCode-related environment variable parsing and validation (host/port/hostname).
- `packages/web/server/lib/opencode/hmr-state-runtime.js`: HMR-persistent runtime state initialization, auth-state bootstrap, and HMR sync helpers.
@@ -29,6 +29,7 @@ This module provides OpenCode server integration utilities for the web server ru
- `packages/web/server/lib/opencode/upgrade-capability.js`: authoritative upgrade ownership policy for the active OpenCode runtime. Bundled, external, and unresolved runtimes fail closed; only managed non-bundled runtimes delegate upgrades to OpenCode.
- `packages/web/server/lib/opencode/tunnel-wiring-runtime.js`: tunnel service/routes composition runtime and active-port wiring for main server startup.
- `packages/web/server/lib/opencode/startup-pipeline-runtime.js`: server startup tail orchestration runtime for terminal/proxy/static/start-listen flow.
- `packages/web/server/lib/opencode/startup-performance.js`: opt-in startup phase diagnostics with fixed labels and numeric metadata allowlists.
- `packages/web/server/lib/agent-tool/runtime.js`: managed OpenCode custom-tool materialization, environment injection, loopback authentication, and fixed CLI action dispatch.
- `packages/web/server/lib/system-prompt/runtime.js`: opt-in managed OpenCode system-prompt optimizer materialization and plugin injection.
- `packages/web/server/lib/opencode/server-utils-runtime.js`: shared server runtime utilities for OpenCode proxy wiring, OpenCode port/readiness helpers, and snapshot fetchers.
@@ -121,6 +122,10 @@ runtime. PATH and `OPENCODE_SERVER_PASSWORD` remain lifecycle-owned and cannot
be replaced by injected values. External OpenCode processes receive no
OpenChamber tool injection.
Set `OPENCHAMBER_STARTUP_PERF=1` to emit bounded startup phase records for server listen, managed OpenCode preparation/readiness, and proxy readiness holds. Every OpenCode bootstrap emits one terminal `opencode.bootstrap.ready` or `opencode.bootstrap.error` event, including reused and external server paths. Records contain controlled phase/outcome/route labels and timing values only; they never contain request URLs, runtime keys, directories, session IDs, credentials, or content.
macOS `say` voice enumeration starts concurrently with server composition. The server listener and managed OpenCode startup do not wait for it; `/api/tts/say/status` awaits the same authoritative capability promise when queried before enumeration completes.
Transport-triggered health checks share the periodic monitor's failure accounting interval. Rapid WS reconnect callbacks therefore cannot exhaust the managed-process restart threshold using one cached unhealthy result; an exited managed process still restarts immediately.
## Public exports (env-runtime.js)
+102 -3
View File
@@ -1,6 +1,7 @@
import { spawn, spawnSync } from 'node:child_process';
import net from 'node:net';
import { registerManagedProcess, unregisterManagedProcess, reapOrphanedProcesses } from './managed-process-registry.js';
import { recordStartupPerformance } from './startup-performance.js';
const parsePositiveInt = (value, fallback) => {
const parsed = Number.parseInt(String(value ?? ''), 10);
@@ -15,6 +16,10 @@ const HEALTH_CHECK_MAX_CONSECUTIVE_FAILURES = parsePositiveInt(
const HEALTH_CHECK_INTERVAL_OVERRIDE_MS = parsePositiveInt(process.env.OPENCHAMBER_OPENCODE_HEALTH_INTERVAL_MS, 0);
const HEALTH_CHECK_RESULT_CACHE_MS = parsePositiveInt(process.env.OPENCHAMBER_OPENCODE_HEALTH_CACHE_MS, 750);
const OPENCODE_HEALTH_PATH = '/global/health';
// Last-used directory plus the three most recently opened projects — deeper
// tails are unlikely to be the user's first click and just add background work.
const WARMUP_DIRECTORY_LIMIT = 4;
const WARMUP_REQUEST_TIMEOUT_MS = 30000;
export const createOpenCodeLifecycleRuntime = (deps) => {
const {
@@ -40,6 +45,8 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
getManagedOpenCodeShellEnvSnapshot,
getManagedOpenCodeEnv = async () => ({}),
getActiveSessionCount = () => 0,
reapManagedOrphanedProcesses = reapOrphanedProcesses,
getWarmupDirectories = async () => [],
now = Date.now,
} = deps;
@@ -466,7 +473,10 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const startOpenCodeOnce = async () => {
const startOpenCodeOnce = async (attempt) => {
const attemptStartedAt = performance.now();
let phaseStartedAt = attemptStartedAt;
recordStartupPerformance('opencode.attempt.start', { attempt });
const desiredPort = env.ENV_CONFIGURED_OPENCODE_PORT ?? 0;
const spawnPort = await resolveManagedOpenCodePort(desiredPort, env.ENV_CONFIGURED_OPENCODE_HOSTNAME);
console.log(
@@ -477,6 +487,12 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
await applyOpencodeBinaryFromSettings({ strict: true });
ensureOpencodeCliEnv();
recordStartupPerformance('opencode.binary.ready', {
attempt,
durationMs: performance.now() - phaseStartedAt,
totalDurationMs: performance.now() - attemptStartedAt,
});
phaseStartedAt = performance.now();
const openCodePassword = await ensureLocalOpenCodeServerPassword({ rotateManaged: true });
let envPath = process.env.PATH;
if (typeof buildManagedOpenCodePath === 'function') {
@@ -488,6 +504,12 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
? getManagedOpenCodeShellEnvSnapshot() || {}
: {};
const managedOpenCodeEnv = await getManagedOpenCodeEnv();
recordStartupPerformance('opencode.environment.ready', {
attempt,
durationMs: performance.now() - phaseStartedAt,
totalDurationMs: performance.now() - attemptStartedAt,
});
phaseStartedAt = performance.now();
try {
const serverInstance = await createManagedOpenCodeServerProcess({
@@ -508,6 +530,12 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
if (!serverInstance || !serverInstance.url) {
throw new Error('OpenCode server started but URL is missing');
}
recordStartupPerformance('opencode.process.ready', {
attempt,
durationMs: performance.now() - phaseStartedAt,
totalDurationMs: performance.now() - attemptStartedAt,
});
phaseStartedAt = performance.now();
const url = new URL(serverInstance.url);
const port = parseInt(url.port, 10);
@@ -521,6 +549,13 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
state.lastOpenCodeError = null;
state.openCodeNotReadySince = 0;
recordStartupPerformance('opencode.health.ready', {
attempt,
durationMs: performance.now() - phaseStartedAt,
totalDurationMs: performance.now() - attemptStartedAt,
outcome: 'ready',
});
return serverInstance;
}
@@ -534,6 +569,11 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
state.lastOpenCodeError = message;
state.openCodePort = null;
syncToHmrState();
recordStartupPerformance('opencode.attempt.error', {
attempt,
totalDurationMs: performance.now() - attemptStartedAt,
outcome: 'error',
});
console.error(`Failed to start OpenCode: ${message}`);
throw error;
}
@@ -543,7 +583,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
let lastError = null;
for (let attempt = 1; attempt <= START_OPEN_CODE_MAX_ATTEMPTS; attempt += 1) {
try {
return await startOpenCodeOnce();
return await startOpenCodeOnce(attempt);
} catch (error) {
lastError = error;
if (error?.code === 'OPENCODE_BINARY_INVALID') {
@@ -792,12 +832,20 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
};
const bootstrapOpenCodeAtStartup = async () => {
const bootstrapStartedAt = performance.now();
let bootstrapError = null;
recordStartupPerformance('opencode.bootstrap.start');
try {
// Before doing anything, reap any OpenCode process WE spawned in a prior
// run that was orphaned by a crash/hard-exit. Verified + scoped to our own
// pids, so it never touches a live instance's or the user's own server.
try {
const { reaped } = await reapOrphanedProcesses({ log: (msg) => console.log(msg) });
const orphanReapStartedAt = performance.now();
const { reaped } = await reapManagedOrphanedProcesses({ log: (msg) => console.log(msg) });
recordStartupPerformance('opencode.orphan-reap.ready', {
durationMs: performance.now() - orphanReapStartedAt,
totalDurationMs: performance.now() - bootstrapStartedAt,
});
if (reaped > 0) console.log(`[lifecycle] startup reaped ${reaped} orphaned OpenCode process(es)`);
} catch (error) {
console.warn('[lifecycle] orphan reap failed:', error?.message ?? error);
@@ -851,13 +899,64 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
try {
await waitForOpenCodeReady();
} catch (error) {
bootstrapError = error;
console.error(`OpenCode readiness check failed: ${error.message}`);
}
} catch (error) {
bootstrapError = error;
console.error(`Failed to start OpenCode: ${error.message}`);
console.log('Continuing without OpenCode integration...');
state.lastOpenCodeError = error.message;
}
recordStartupPerformance(
bootstrapError ? 'opencode.bootstrap.error' : 'opencode.bootstrap.ready',
{
totalDurationMs: performance.now() - bootstrapStartedAt,
outcome: bootstrapError ? 'error' : 'ready',
},
);
if (!bootstrapError) {
void warmOpenCodeDirectories();
}
};
// OpenCode initializes each project directory lazily on its first
// directory-scoped request, and that initialization takes seconds on large
// session stores. Without warming, the user's first session open pays it
// interactively (the chat waits on the message fetch until the directory
// finishes initializing). Warm the most recently used directories right
// after readiness so the work overlaps UI startup instead. Sequential and
// best-effort: a failed or slow directory never blocks the others for long,
// and a restart invalidates the pass via the port/readiness guard.
const warmOpenCodeDirectories = async () => {
let directories = [];
try {
directories = await getWarmupDirectories();
} catch {
return;
}
if (!Array.isArray(directories) || directories.length === 0) return;
const warmedPort = state.openCodePort;
for (const directory of directories.slice(0, WARMUP_DIRECTORY_LIMIT)) {
if (typeof directory !== 'string' || !directory) continue;
if (!state.isOpenCodeReady || state.openCodePort !== warmedPort) return;
let timeout = null;
try {
const controller = new AbortController();
timeout = setTimeout(() => controller.abort(), WARMUP_REQUEST_TIMEOUT_MS);
const url = `${buildOpenCodeUrl('/session/status', '')}?directory=${encodeURIComponent(directory)}`;
await fetch(url, {
method: 'GET',
headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
signal: controller.signal,
});
} catch {
// Best-effort — the directory stays lazy and the UI's own request warms it.
} finally {
if (timeout) clearTimeout(timeout);
}
}
};
/**
@@ -2,11 +2,15 @@ import { EventEmitter } from 'node:events';
import { afterEach, describe, expect, it, vi } from 'vitest';
const spawnMock = vi.fn();
const recordStartupPerformanceMock = vi.fn();
vi.mock('node:child_process', () => ({
spawn: spawnMock,
spawnSync: vi.fn(),
}));
vi.mock('./startup-performance.js', () => ({
recordStartupPerformance: recordStartupPerformanceMock,
}));
const { createOpenCodeLifecycleRuntime } = await import('./lifecycle.js');
@@ -16,6 +20,7 @@ const originalFetch = globalThis.fetch;
afterEach(() => {
spawnMock.mockReset();
recordStartupPerformanceMock.mockReset();
globalThis.fetch = originalFetch;
if (typeof originalOpencodeBinary === 'string') {
process.env.OPENCODE_BINARY = originalOpencodeBinary;
@@ -108,6 +113,92 @@ const createRuntime = (overrides = {}, stateOverrides = {}) => {
};
describe('OpenCode lifecycle', () => {
it('records an authoritative ready terminal event for external startup', async () => {
globalThis.fetch = vi.fn(async () => ({
ok: true,
json: async () => ({ healthy: true }),
}));
const runtime = createRuntime({
env: {
ENV_CONFIGURED_OPENCODE_PORT: 45678,
ENV_CONFIGURED_OPENCODE_HOST: null,
ENV_EFFECTIVE_PORT: 45678,
ENV_CONFIGURED_OPENCODE_HOSTNAME: '127.0.0.1',
ENV_SKIP_OPENCODE_START: true,
},
reapManagedOrphanedProcesses: vi.fn(async () => ({ reaped: 0 })),
});
await runtime.bootstrapOpenCodeAtStartup();
expect(recordStartupPerformanceMock).toHaveBeenCalledWith('opencode.bootstrap.ready', {
totalDurationMs: expect.any(Number),
outcome: 'ready',
});
expect(recordStartupPerformanceMock).not.toHaveBeenCalledWith(
'opencode.bootstrap.error',
expect.anything(),
);
const terminalEvents = recordStartupPerformanceMock.mock.calls.filter(([phase]) => (
phase === 'opencode.bootstrap.ready' || phase === 'opencode.bootstrap.error'
));
expect(terminalEvents).toHaveLength(1);
});
it('warms recently used directories after a successful bootstrap', async () => {
const fetchMock = vi.fn(async () => ({
ok: true,
json: async () => ({ healthy: true }),
}));
globalThis.fetch = fetchMock;
const runtime = createRuntime({
env: {
ENV_CONFIGURED_OPENCODE_PORT: 45678,
ENV_CONFIGURED_OPENCODE_HOST: null,
ENV_EFFECTIVE_PORT: 45678,
ENV_CONFIGURED_OPENCODE_HOSTNAME: '127.0.0.1',
ENV_SKIP_OPENCODE_START: true,
},
reapManagedOrphanedProcesses: vi.fn(async () => ({ reaped: 0 })),
getWarmupDirectories: vi.fn(async () => ['/tmp/worktree-a', '/tmp/project-b']),
});
await runtime.bootstrapOpenCodeAtStartup();
await new Promise((resolve) => setTimeout(resolve, 0));
const warmupUrls = fetchMock.mock.calls
.map(([url]) => String(url))
.filter((url) => url.includes('/session/status'));
expect(warmupUrls).toEqual([
'http://127.0.0.1:45678/session/status?directory=%2Ftmp%2Fworktree-a',
'http://127.0.0.1:45678/session/status?directory=%2Ftmp%2Fproject-b',
]);
});
it('records an authoritative error terminal event when bootstrap fails', async () => {
const runtime = createRuntime({
syncFromHmrState: vi.fn(() => {
throw new Error('bootstrap failed');
}),
reapManagedOrphanedProcesses: vi.fn(async () => ({ reaped: 0 })),
});
await runtime.bootstrapOpenCodeAtStartup();
expect(recordStartupPerformanceMock).toHaveBeenCalledWith('opencode.bootstrap.error', {
totalDurationMs: expect.any(Number),
outcome: 'error',
});
expect(recordStartupPerformanceMock).not.toHaveBeenCalledWith(
'opencode.bootstrap.ready',
expect.anything(),
);
const terminalEvents = recordStartupPerformanceMock.mock.calls.filter(([phase]) => (
phase === 'opencode.bootstrap.ready' || phase === 'opencode.bootstrap.error'
));
expect(terminalEvents).toHaveLength(1);
});
it('does not count rapid transport-triggered checks as independent health failures', async () => {
const close = vi.fn(async () => {});
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
+27 -1
View File
@@ -7,6 +7,7 @@ import {
} from '../../proxy-headers.js';
import { createRealpathCache } from '../path-realpath-cache.js';
import { DEFAULT_UPSTREAM_STALL_TIMEOUT_MS } from '../event-stream/upstream-reader.js';
import { recordStartupPerformance } from './startup-performance.js';
const DEFAULT_SSE_HEARTBEAT_INTERVAL_MS = 20_000;
@@ -598,6 +599,12 @@ export const registerOpenCodeProxy = (app, deps) => {
!runtimeState.openCodePort
);
};
const classifyReadinessRoute = (requestPath) => {
if (/^\/session\/[^/]+\/message(?:\/|$)/.test(requestPath)) return 'session-messages';
if (requestPath === '/session' || requestPath.startsWith('/session/')) return 'session';
if (requestPath === '/event' || requestPath === '/global/event') return 'events';
return 'other';
};
app.use('/api', async (req, res, next) => {
if (
@@ -617,16 +624,35 @@ export const registerOpenCodeProxy = (app, deps) => {
return next();
}
const holdStartedAt = performance.now();
const routeClass = classifyReadinessRoute(req.path);
const deadline = Date.now() + Math.min(OPEN_CODE_READY_GRACE_MS, READINESS_HOLD_MAX_MS);
while (Date.now() < deadline) {
// Client gave up (closed/aborted) — stop holding.
if (res.writableEnded || req.aborted) return;
if (res.writableEnded || req.aborted) {
recordStartupPerformance('proxy.readiness-hold', {
durationMs: performance.now() - holdStartedAt,
outcome: 'aborted',
routeClass,
});
return;
}
await sleep(READINESS_HOLD_POLL_MS);
if (!isStillWaiting(getRuntime())) {
recordStartupPerformance('proxy.readiness-hold', {
durationMs: performance.now() - holdStartedAt,
outcome: 'ready',
routeClass,
});
return next();
}
}
recordStartupPerformance('proxy.readiness-hold', {
durationMs: performance.now() - holdStartedAt,
outcome: 'timeout',
routeClass,
});
if (!res.headersSent) {
res.status(503).json({
error: 'OpenCode is restarting',
@@ -0,0 +1,44 @@
const ENABLED_VALUES = new Set(['1', 'true']);
const ALLOWED_PHASES = new Set([
'web.pipeline.start',
'web.listener.ready',
'opencode.bootstrap.start',
'opencode.bootstrap.ready',
'opencode.bootstrap.error',
'opencode.orphan-reap.ready',
'opencode.attempt.start',
'opencode.binary.ready',
'opencode.environment.ready',
'opencode.process.ready',
'opencode.health.ready',
'opencode.attempt.error',
'proxy.readiness-hold',
]);
const ALLOWED_OUTCOMES = new Set(['ready', 'timeout', 'aborted', 'error']);
const ALLOWED_ROUTE_CLASSES = new Set(['session-messages', 'session', 'events', 'other']);
const finiteNonNegative = (value) => Number.isFinite(value) && value >= 0 ? value : undefined;
const nonNegativeInteger = (value) => Number.isInteger(value) && value >= 0 ? value : undefined;
const isStartupPerformanceEnabled = () => (
ENABLED_VALUES.has(String(process.env.OPENCHAMBER_STARTUP_PERF ?? '').toLowerCase())
);
export const recordStartupPerformance = (phase, details = {}) => {
if (!isStartupPerformanceEnabled() || !ALLOWED_PHASES.has(phase)) return;
const event = {
phase,
at: Date.now(),
};
const durationMs = finiteNonNegative(details.durationMs);
const totalDurationMs = finiteNonNegative(details.totalDurationMs);
const attempt = nonNegativeInteger(details.attempt);
if (durationMs !== undefined) event.durationMs = durationMs;
if (totalDurationMs !== undefined) event.totalDurationMs = totalDurationMs;
if (attempt !== undefined) event.attempt = attempt;
if (ALLOWED_OUTCOMES.has(details.outcome)) event.outcome = details.outcome;
if (ALLOWED_ROUTE_CLASSES.has(details.routeClass)) event.routeClass = details.routeClass;
console.info('[startup-performance]', event);
};
@@ -0,0 +1,73 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { recordStartupPerformance } from './startup-performance.js';
describe('startup performance diagnostics', () => {
const previousValue = process.env.OPENCHAMBER_STARTUP_PERF;
afterEach(() => {
if (previousValue === undefined) delete process.env.OPENCHAMBER_STARTUP_PERF;
else process.env.OPENCHAMBER_STARTUP_PERF = previousValue;
vi.restoreAllMocks();
});
it('is disabled by default', () => {
delete process.env.OPENCHAMBER_STARTUP_PERF;
const info = vi.spyOn(console, 'info').mockImplementation(() => {});
recordStartupPerformance('opencode.health.ready', { durationMs: 5 });
expect(info).not.toHaveBeenCalled();
});
it('records only approved labels and numeric metadata', () => {
process.env.OPENCHAMBER_STARTUP_PERF = '1';
const info = vi.spyOn(console, 'info').mockImplementation(() => {});
recordStartupPerformance('proxy.readiness-hold', {
durationMs: 75,
totalDurationMs: 100,
attempt: 1,
outcome: 'ready',
routeClass: 'session-messages',
sessionID: 'secret-session',
directory: '/secret/directory',
token: 'secret-token',
});
expect(info).toHaveBeenCalledOnce();
const event = info.mock.calls[0][1];
expect(event).toMatchObject({
phase: 'proxy.readiness-hold',
durationMs: 75,
totalDurationMs: 100,
attempt: 1,
outcome: 'ready',
routeClass: 'session-messages',
});
expect(Number.isFinite(event.at)).toBe(true);
expect(JSON.stringify(event)).not.toContain('secret');
});
it('rejects unknown phases and invalid field values', () => {
process.env.OPENCHAMBER_STARTUP_PERF = 'true';
const info = vi.spyOn(console, 'info').mockImplementation(() => {});
recordStartupPerformance('secret.phase', { durationMs: 1 });
recordStartupPerformance('opencode.bootstrap.error', {
durationMs: -1,
attempt: 1.5,
outcome: 'secret-outcome',
routeClass: 'secret-route',
});
expect(info).toHaveBeenCalledOnce();
expect(info.mock.calls[0][1]).toEqual(expect.objectContaining({
phase: 'opencode.bootstrap.error',
}));
expect(info.mock.calls[0][1]).not.toHaveProperty('durationMs');
expect(info.mock.calls[0][1]).not.toHaveProperty('attempt');
expect(info.mock.calls[0][1]).not.toHaveProperty('outcome');
expect(info.mock.calls[0][1]).not.toHaveProperty('routeClass');
});
});
@@ -1,3 +1,5 @@
import { recordStartupPerformance } from './startup-performance.js';
export const createStartupPipelineRuntime = (dependencies) => {
const {
createTerminalRuntime,
@@ -7,6 +9,8 @@ export const createStartupPipelineRuntime = (dependencies) => {
} = dependencies;
const run = async (options) => {
const pipelineStartedAt = performance.now();
recordStartupPerformance('web.pipeline.start');
const {
app,
server,
@@ -129,6 +133,9 @@ export const createStartupPipelineRuntime = (dependencies) => {
startupTunnelRequest,
onTunnelReady,
});
recordStartupPerformance('web.listener.ready', {
durationMs: performance.now() - pipelineStartedAt,
});
tunnelRuntimeContext.setActivePort(startupResult.activePort);
scheduleOpenCodeApiDetection();
void bootstrapOpenCodeAtStartup();