Add benchmark results and tests for LSP graph builder and staged search

- Introduced a new benchmark results file for performance comparison on 2026-02-09.
- Added a test for LspGraphBuilder to ensure it does not expand nodes at maximum depth.
- Created a test for the staged search pipeline to validate fallback behavior when stage 1 returns empty results.
This commit is contained in:
catlog22
2026-02-09 21:43:13 +08:00
parent 4344e79e68
commit 362f354f1c
25 changed files with 2613 additions and 51 deletions

View File

@@ -14,10 +14,52 @@
import type { RouteContext } from './types.js';
import { getCliSessionManager } from '../services/cli-session-manager.js';
import path from 'path';
import { getCliSessionPolicy } from '../services/cli-session-policy.js';
import { RateLimiter } from '../services/rate-limiter.js';
import { appendCliSessionAudit } from '../services/cli-session-audit.js';
import { describeShareAuthFailure, getCliSessionShareManager } from '../services/cli-session-share.js';
function clientKey(req: RouteContext['req']): string {
const addr = req.socket?.remoteAddress ?? 'unknown';
const ua = Array.isArray(req.headers['user-agent']) ? req.headers['user-agent'][0] : req.headers['user-agent'];
return `${addr}|${ua ?? ''}`;
}
function clientInfo(req: RouteContext['req']): { ip?: string; userAgent?: string } {
const ip = req.socket?.remoteAddress ?? undefined;
const userAgent = Array.isArray(req.headers['user-agent']) ? req.headers['user-agent'][0] : req.headers['user-agent'];
return { ip: ip || undefined, userAgent: userAgent || undefined };
}
function resolveProjectRoot(ctx: RouteContext): string {
const forced = (ctx.req as any).__cliSessionShareProjectRoot;
if (typeof forced === 'string' && forced.trim()) return path.resolve(forced);
const raw = ctx.url.searchParams.get('path');
if (raw && raw.trim()) return path.resolve(raw);
return path.resolve(ctx.initialPath || process.cwd());
}
function validateWorkingDir(projectRoot: string, workingDir: string, allowOutside: boolean): string | null {
const resolved = path.resolve(workingDir);
if (allowOutside) return null;
const rel = path.relative(projectRoot, resolved);
const isInside = rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
return isInside ? null : `workingDir must be within project: ${projectRoot}`;
}
const policy = getCliSessionPolicy();
const createLimiter = new RateLimiter({ limit: policy.rateLimit.createPerMinute, windowMs: 60_000 });
const executeLimiter = new RateLimiter({ limit: policy.rateLimit.executePerMinute, windowMs: 60_000 });
const resizeLimiter = new RateLimiter({ limit: policy.rateLimit.resizePerMinute, windowMs: 60_000 });
const sendBytesLimiter = new RateLimiter({ limit: policy.rateLimit.sendBytesPerMinute, windowMs: 60_000 });
const shareManager = getCliSessionShareManager();
export async function handleCliSessionsRoutes(ctx: RouteContext): Promise<boolean> {
const { pathname, req, res, handlePostRequest, initialPath } = ctx;
const manager = getCliSessionManager(process.cwd());
const projectRoot = resolveProjectRoot(ctx);
const manager = getCliSessionManager(projectRoot);
// GET /api/cli-sessions
if (pathname === '/api/cli-sessions' && req.method === 'GET') {
@@ -29,6 +71,15 @@ export async function handleCliSessionsRoutes(ctx: RouteContext): Promise<boolea
// POST /api/cli-sessions
if (pathname === '/api/cli-sessions' && req.method === 'POST') {
handlePostRequest(req, res, async (body: unknown) => {
const rate = createLimiter.consume(clientKey(req), 1);
if (!rate.ok) {
return { error: 'Rate limited', status: 429 };
}
if (policy.maxSessions > 0 && manager.listSessions().length >= policy.maxSessions) {
return { error: `Too many sessions (max ${policy.maxSessions})`, status: 429 };
}
const {
workingDir,
cols,
@@ -39,16 +90,41 @@ export async function handleCliSessionsRoutes(ctx: RouteContext): Promise<boolea
resumeKey
} = (body || {}) as any;
if (tool && typeof tool === 'string') {
const normalizedTool = tool.trim();
if (!policy.allowedTools.includes(normalizedTool)) {
return { error: `Tool not allowed: ${normalizedTool}`, status: 400 };
}
}
const desiredWorkingDir = workingDir || initialPath;
if (typeof desiredWorkingDir !== 'string' || !desiredWorkingDir.trim()) {
return { error: 'workingDir is required', status: 400 };
}
const wdError = validateWorkingDir(projectRoot, desiredWorkingDir, policy.allowWorkingDirOutsideProject);
if (wdError) return { error: wdError, status: 400 };
const session = manager.createSession({
workingDir: workingDir || initialPath,
workingDir: desiredWorkingDir,
cols: typeof cols === 'number' ? cols : undefined,
rows: typeof rows === 'number' ? rows : undefined,
preferredShell: preferredShell === 'pwsh' ? 'pwsh' : 'bash',
tool,
tool: typeof tool === 'string' ? tool.trim() : undefined,
model,
resumeKey
});
appendCliSessionAudit({
type: 'session_created',
timestamp: new Date().toISOString(),
projectRoot,
sessionKey: session.sessionKey,
tool: session.tool,
resumeKey: session.resumeKey,
workingDir: session.workingDir,
...clientInfo(req),
});
return { success: true, session };
});
return true;
@@ -58,6 +134,17 @@ export async function handleCliSessionsRoutes(ctx: RouteContext): Promise<boolea
const bufferMatch = pathname.match(/^\/api\/cli-sessions\/([^/]+)\/buffer$/);
if (bufferMatch && req.method === 'GET') {
const sessionKey = decodeURIComponent(bufferMatch[1]);
const shareToken = ctx.url.searchParams.get('shareToken');
if (shareToken) {
const validated = shareManager.validateToken(shareToken, sessionKey);
if (!validated || (validated.mode !== 'read' && validated.mode !== 'write')) {
res.writeHead(403, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: describeShareAuthFailure().error }));
return true;
}
}
const session = manager.getSession(sessionKey);
if (!session) {
res.writeHead(404, { 'Content-Type': 'application/json' });
@@ -69,6 +156,59 @@ export async function handleCliSessionsRoutes(ctx: RouteContext): Promise<boolea
return true;
}
// GET /api/cli-sessions/:sessionKey/stream (SSE)
const streamMatch = pathname.match(/^\/api\/cli-sessions\/([^/]+)\/stream$/);
if (streamMatch && req.method === 'GET') {
const sessionKey = decodeURIComponent(streamMatch[1]);
const shareToken = ctx.url.searchParams.get('shareToken');
if (!shareToken) {
res.writeHead(403, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'shareToken is required' }));
return true;
}
const validated = shareManager.validateToken(shareToken, sessionKey);
if (!validated || (validated.mode !== 'read' && validated.mode !== 'write')) {
res.writeHead(403, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: describeShareAuthFailure().error }));
return true;
}
const session = manager.getSession(sessionKey);
if (!session) {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Session not found' }));
return true;
}
res.writeHead(200, {
'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
});
const includeBuffer = ctx.url.searchParams.get('includeBuffer') !== '0';
if (includeBuffer) {
const buffer = manager.getBuffer(sessionKey);
res.write(`event: buffer\ndata: ${JSON.stringify({ sessionKey, buffer })}\n\n`);
}
const unsubscribe = manager.onOutput((event) => {
if (event.sessionKey !== sessionKey) return;
res.write(`event: output\ndata: ${JSON.stringify(event)}\n\n`);
});
req.on('close', () => {
unsubscribe();
try {
res.end();
} catch {
// ignore
}
});
return true;
}
// POST /api/cli-sessions/:sessionKey/send
const sendMatch = pathname.match(/^\/api\/cli-sessions\/([^/]+)\/send$/);
if (sendMatch && req.method === 'POST') {
@@ -78,17 +218,69 @@ export async function handleCliSessionsRoutes(ctx: RouteContext): Promise<boolea
if (typeof text !== 'string') {
return { error: 'text is required', status: 400 };
}
const cost = Buffer.byteLength(text, 'utf8');
const rate = sendBytesLimiter.consume(clientKey(req), cost);
if (!rate.ok) {
return { error: 'Rate limited', status: 429 };
}
manager.sendText(sessionKey, text, appendNewline !== false);
appendCliSessionAudit({
type: 'session_send',
timestamp: new Date().toISOString(),
projectRoot,
sessionKey,
...clientInfo(req),
details: { bytes: cost, appendNewline: appendNewline !== false },
});
return { success: true };
});
return true;
}
// POST /api/cli-sessions/:sessionKey/share
const shareMatch = pathname.match(/^\/api\/cli-sessions\/([^/]+)\/share$/);
if (shareMatch && req.method === 'POST') {
const sessionKey = decodeURIComponent(shareMatch[1]);
handlePostRequest(req, res, async (body: unknown) => {
const { mode, ttlMs } = (body || {}) as any;
const session = manager.getSession(sessionKey);
if (!session) return { error: 'Session not found', status: 404 };
const shareMode = mode === 'write' ? 'write' : 'read';
const safeTtlMs = typeof ttlMs === 'number' ? Math.min(Math.max(60_000, ttlMs), 7 * 24 * 60 * 60_000) : undefined;
const token = shareManager.createToken({
sessionKey,
projectRoot,
mode: shareMode,
ttlMs: safeTtlMs,
});
appendCliSessionAudit({
type: 'session_share_created',
timestamp: new Date().toISOString(),
projectRoot,
sessionKey,
...clientInfo(req),
details: { shareMode, expiresAt: token.expiresAt },
});
return { success: true, shareToken: token.token, expiresAt: token.expiresAt, mode: token.mode };
});
return true;
}
// POST /api/cli-sessions/:sessionKey/execute
const executeMatch = pathname.match(/^\/api\/cli-sessions\/([^/]+)\/execute$/);
if (executeMatch && req.method === 'POST') {
const sessionKey = decodeURIComponent(executeMatch[1]);
handlePostRequest(req, res, async (body: unknown) => {
const rate = executeLimiter.consume(clientKey(req), 1);
if (!rate.ok) {
return { error: 'Rate limited', status: 429 };
}
const {
tool,
prompt,
@@ -106,9 +298,18 @@ export async function handleCliSessionsRoutes(ctx: RouteContext): Promise<boolea
if (!prompt || typeof prompt !== 'string') {
return { error: 'prompt is required', status: 400 };
}
const normalizedTool = tool.trim();
if (!policy.allowedTools.includes(normalizedTool)) {
return { error: `Tool not allowed: ${normalizedTool}`, status: 400 };
}
if (workingDir && typeof workingDir === 'string') {
const wdError = validateWorkingDir(projectRoot, workingDir, policy.allowWorkingDirOutsideProject);
if (wdError) return { error: wdError, status: 400 };
}
const result = manager.execute(sessionKey, {
tool,
tool: normalizedTool,
prompt,
mode,
model,
@@ -118,6 +319,18 @@ export async function handleCliSessionsRoutes(ctx: RouteContext): Promise<boolea
resumeStrategy: resumeStrategy === 'promptConcat' ? 'promptConcat' : 'nativeResume'
});
appendCliSessionAudit({
type: 'session_execute',
timestamp: new Date().toISOString(),
projectRoot,
sessionKey,
tool: normalizedTool,
resumeKey: typeof resumeKey === 'string' ? resumeKey : undefined,
workingDir: typeof workingDir === 'string' ? workingDir : undefined,
...clientInfo(req),
details: { executionId: result.executionId, mode, category, resumeStrategy },
});
return { success: true, ...result };
});
return true;
@@ -128,11 +341,23 @@ export async function handleCliSessionsRoutes(ctx: RouteContext): Promise<boolea
if (resizeMatch && req.method === 'POST') {
const sessionKey = decodeURIComponent(resizeMatch[1]);
handlePostRequest(req, res, async (body: unknown) => {
const rate = resizeLimiter.consume(clientKey(req), 1);
if (!rate.ok) {
return { error: 'Rate limited', status: 429 };
}
const { cols, rows } = (body || {}) as any;
if (typeof cols !== 'number' || typeof rows !== 'number') {
return { error: 'cols and rows are required', status: 400 };
}
manager.resize(sessionKey, cols, rows);
appendCliSessionAudit({
type: 'session_resize',
timestamp: new Date().toISOString(),
projectRoot,
sessionKey,
...clientInfo(req),
details: { cols, rows },
});
return { success: true };
});
return true;
@@ -143,6 +368,13 @@ export async function handleCliSessionsRoutes(ctx: RouteContext): Promise<boolea
if (closeMatch && req.method === 'POST') {
const sessionKey = decodeURIComponent(closeMatch[1]);
manager.close(sessionKey);
appendCliSessionAudit({
type: 'session_closed',
timestamp: new Date().toISOString(),
projectRoot,
sessionKey,
...clientInfo(req),
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true }));
return true;
@@ -150,4 +382,3 @@ export async function handleCliSessionsRoutes(ctx: RouteContext): Promise<boolea
return false;
}