* 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>
143 lines
5.0 KiB
JavaScript
143 lines
5.0 KiB
JavaScript
import { describe, expect, it, afterEach } from 'vitest';
|
|
import express from 'express';
|
|
import request from 'supertest';
|
|
|
|
import { registerTtsRoutes } from './routes.js';
|
|
import { normalizeCustomOpenAIBaseURL } from './base-url.js';
|
|
|
|
const createApp = (sayTTSCapability = null) => {
|
|
const app = express();
|
|
app.use(express.json());
|
|
registerTtsRoutes(app, {
|
|
resolveZenModel: async () => 'gpt-5-nano',
|
|
sayTTSCapability,
|
|
});
|
|
return app;
|
|
};
|
|
|
|
describe('tts routes', () => {
|
|
it('waits for the authoritative macOS say capability', async () => {
|
|
let resolveCapability;
|
|
const capability = new Promise((resolve) => {
|
|
resolveCapability = resolve;
|
|
});
|
|
const pending = request(createApp(capability)).get('/api/tts/say/status');
|
|
|
|
resolveCapability({ available: true, voices: [{ name: 'Samantha', locale: 'en_US' }] });
|
|
|
|
const response = await pending;
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual({
|
|
available: true,
|
|
voices: [{ name: 'Samantha', locale: 'en_US' }],
|
|
});
|
|
});
|
|
|
|
it('returns local note fallback while model summarization is retired', async () => {
|
|
const response = await request(createApp())
|
|
.post('/api/text/summarize')
|
|
.send({
|
|
text: 'First sentence. Second sentence with the useful insight.',
|
|
threshold: 0,
|
|
maxLength: 100,
|
|
mode: 'note',
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toMatchObject({
|
|
summary: 'First sentence.',
|
|
summarized: false,
|
|
reason: 'Model summarization provider unavailable',
|
|
});
|
|
});
|
|
|
|
it('keeps notification fallback behavior without calling zen', async () => {
|
|
const response = await request(createApp())
|
|
.post('/api/text/summarize')
|
|
.send({
|
|
text: 'Notification text that should fall back cleanly.',
|
|
threshold: 0,
|
|
maxLength: 100,
|
|
mode: 'notification',
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toMatchObject({
|
|
summary: 'Notification text that should fall back cleanly.',
|
|
summarized: false,
|
|
reason: 'Model summarization provider unavailable',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('normalizeCustomOpenAIBaseURL', () => {
|
|
const originalRuntime = process.env.OPENCHAMBER_RUNTIME;
|
|
const originalAllowRemote = process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS;
|
|
|
|
afterEach(() => {
|
|
// Restore env vars after each test
|
|
if (originalRuntime === undefined) {
|
|
delete process.env.OPENCHAMBER_RUNTIME;
|
|
} else {
|
|
process.env.OPENCHAMBER_RUNTIME = originalRuntime;
|
|
}
|
|
if (originalAllowRemote === undefined) {
|
|
delete process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS;
|
|
} else {
|
|
process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS = originalAllowRemote;
|
|
}
|
|
});
|
|
|
|
it('rejects remote URLs when OPENCHAMBER_RUNTIME is not set (web)', () => {
|
|
delete process.env.OPENCHAMBER_RUNTIME;
|
|
delete process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS;
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('https://my-tts-server.example.com/v1');
|
|
expect(result.error).toMatch(/Remote custom server URLs are disabled/);
|
|
expect(result.value).toBeUndefined();
|
|
});
|
|
|
|
it('allows remote URLs when OPENCHAMBER_RUNTIME is desktop', () => {
|
|
process.env.OPENCHAMBER_RUNTIME = 'desktop';
|
|
delete process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS;
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('https://my-tts-server.example.com/v1');
|
|
expect(result.error).toBeUndefined();
|
|
expect(result.value).toBe('https://my-tts-server.example.com/v1');
|
|
});
|
|
|
|
it('allows remote URLs when OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS is true', () => {
|
|
delete process.env.OPENCHAMBER_RUNTIME;
|
|
process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS = 'true';
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('https://my-tts-server.example.com/v1');
|
|
expect(result.error).toBeUndefined();
|
|
expect(result.value).toBe('https://my-tts-server.example.com/v1');
|
|
});
|
|
|
|
it('allows localhost URLs regardless of runtime', () => {
|
|
delete process.env.OPENCHAMBER_RUNTIME;
|
|
delete process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS;
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('http://localhost:8880/v1');
|
|
expect(result.error).toBeUndefined();
|
|
expect(result.value).toBe('http://localhost:8880/v1');
|
|
});
|
|
|
|
it('strips query strings and trailing slashes', () => {
|
|
process.env.OPENCHAMBER_RUNTIME = 'desktop';
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('https://my-server.com/v1/?key=123');
|
|
expect(result.value).toBe('https://my-server.com/v1');
|
|
});
|
|
|
|
it('denies remote URLs on desktop when env var is explicitly false', () => {
|
|
process.env.OPENCHAMBER_RUNTIME = 'desktop';
|
|
process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS = 'false';
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('https://my-tts-server.example.com/v1');
|
|
expect(result.error).toMatch(/Remote custom server URLs are disabled/);
|
|
expect(result.value).toBeUndefined();
|
|
});
|
|
});
|