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;
}

View File

@@ -53,6 +53,7 @@ import { randomBytes } from 'crypto';
// Import health check service
import { getHealthCheckService } from './services/health-check-service.js';
import { getCliSessionShareManager } from './services/cli-session-share.js';
// Import status check functions for warmup
import { checkSemanticStatus, checkVenvStatus } from '../tools/codex-lens.js';
@@ -465,6 +466,7 @@ export async function startServer(options: ServerOptions = {}): Promise<http.Ser
const secretKey = tokenManager.getSecretKey();
tokenManager.getOrCreateAuthToken();
const unauthenticatedPaths = new Set<string>(['/api/auth/token', '/api/csrf-token', '/api/hook', '/api/test/ask-question', '/api/a2ui/answer']);
const cliSessionShareManager = getCliSessionShareManager();
const server = http.createServer(async (req, res) => {
const url = new URL(req.url ?? '/', `http://localhost:${serverPort}`);
@@ -521,8 +523,24 @@ export async function startServer(options: ServerOptions = {}): Promise<http.Ser
// Authentication middleware for all API routes
if (pathname.startsWith('/api/')) {
const ok = authMiddleware({ pathname, req, res, tokenManager, secretKey, unauthenticatedPaths });
if (!ok) return;
let shareBypass = false;
const shareToken = url.searchParams.get('shareToken');
if (shareToken) {
const match = pathname.match(/^\/api\/cli-sessions\/([^/]+)\/(buffer|stream)$/);
if (match?.[1]) {
const sessionKey = decodeURIComponent(match[1]);
const validated = cliSessionShareManager.validateToken(shareToken, sessionKey);
if (validated && (validated.mode === 'read' || validated.mode === 'write')) {
(req as any).__cliSessionShareProjectRoot = validated.projectRoot;
shareBypass = true;
}
}
}
if (!shareBypass) {
const ok = authMiddleware({ pathname, req, res, tokenManager, secretKey, unauthenticatedPaths });
if (!ok) return;
}
}
// CSRF validation middleware for state-changing API routes

View File

@@ -0,0 +1,39 @@
import { existsSync, mkdirSync, appendFileSync } from 'fs';
import path from 'path';
export type CliSessionAuditEventType =
| 'session_created'
| 'session_closed'
| 'session_send'
| 'session_execute'
| 'session_resize'
| 'session_share_created'
| 'session_idle_reaped';
export interface CliSessionAuditEvent {
type: CliSessionAuditEventType;
timestamp: string;
projectRoot: string;
sessionKey?: string;
tool?: string;
resumeKey?: string;
workingDir?: string;
ip?: string;
userAgent?: string;
details?: Record<string, unknown>;
}
function auditFilePath(projectRoot: string): string {
return path.join(projectRoot, '.workflow', 'audit', 'cli-sessions.jsonl');
}
export function appendCliSessionAudit(event: CliSessionAuditEvent): void {
try {
const filePath = auditFilePath(event.projectRoot);
const dir = path.dirname(filePath);
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
appendFileSync(filePath, JSON.stringify(event) + '\n', { encoding: 'utf8' });
} catch {
// Best-effort: never fail API requests due to audit write errors.
}
}

View File

@@ -12,6 +12,7 @@ import {
type CliSessionResumeStrategy
} from './cli-session-command-builder.js';
import { getCliSessionPolicy } from './cli-session-policy.js';
import { appendCliSessionAudit } from './cli-session-audit.js';
export interface CliSession {
sessionKey: string;
@@ -147,10 +148,29 @@ export class CliSessionManager {
private projectRoot: string;
private emitter = new EventEmitter();
private maxBufferBytes: number;
private idleTimeoutMs: number;
private reaperTimer: NodeJS.Timeout | null = null;
constructor(projectRoot: string) {
this.projectRoot = projectRoot;
this.maxBufferBytes = getCliSessionPolicy().maxBufferBytes;
const policy = getCliSessionPolicy();
this.maxBufferBytes = policy.maxBufferBytes;
this.idleTimeoutMs = policy.idleTimeoutMs;
if (this.idleTimeoutMs > 0) {
this.reaperTimer = setInterval(() => {
const reaped = this.closeIdleSessions(this.idleTimeoutMs);
for (const sessionKey of reaped) {
appendCliSessionAudit({
type: 'session_idle_reaped',
timestamp: nowIso(),
projectRoot: this.projectRoot,
sessionKey,
});
}
}, 60_000);
this.reaperTimer.unref?.();
}
}
listSessions(): CliSession[] {
@@ -354,14 +374,14 @@ export class CliSessionManager {
return () => this.emitter.off('output', handler);
}
closeIdleSessions(idleTimeoutMs: number): number {
if (idleTimeoutMs <= 0) return 0;
closeIdleSessions(idleTimeoutMs: number): string[] {
if (idleTimeoutMs <= 0) return [];
const now = Date.now();
let closed = 0;
const closed: string[] = [];
for (const s of this.sessions.values()) {
if (now - s.lastActivityAt >= idleTimeoutMs) {
this.close(s.sessionKey);
closed += 1;
closed.push(s.sessionKey);
}
}
return closed;

View File

@@ -0,0 +1,83 @@
import { randomBytes } from 'crypto';
export type CliSessionShareMode = 'read' | 'write';
export interface CliSessionShareTokenRecord {
token: string;
sessionKey: string;
projectRoot: string;
mode: CliSessionShareMode;
expiresAt: string;
}
interface InternalTokenRecord extends CliSessionShareTokenRecord {
expiresAtMs: number;
}
function createTokenValue(): string {
// 32 bytes => 43 chars base64url (approx), safe for URLs.
return randomBytes(32).toString('base64url');
}
export class CliSessionShareManager {
private tokens = new Map<string, InternalTokenRecord>();
createToken(input: {
sessionKey: string;
projectRoot: string;
mode: CliSessionShareMode;
ttlMs?: number;
}): CliSessionShareTokenRecord {
const ttlMs = typeof input.ttlMs === 'number' ? Math.max(1_000, input.ttlMs) : 24 * 60 * 60_000;
const expiresAtMs = Date.now() + ttlMs;
const record: InternalTokenRecord = {
token: createTokenValue(),
sessionKey: input.sessionKey,
projectRoot: input.projectRoot,
mode: input.mode,
expiresAt: new Date(expiresAtMs).toISOString(),
expiresAtMs,
};
this.tokens.set(record.token, record);
return record;
}
validateToken(token: string, sessionKey: string): CliSessionShareTokenRecord | null {
const record = this.tokens.get(token);
if (!record) return null;
if (record.sessionKey !== sessionKey) return null;
if (Date.now() >= record.expiresAtMs) {
this.tokens.delete(token);
return null;
}
const { expiresAtMs: _expiresAtMs, ...publicRecord } = record;
return publicRecord;
}
revokeToken(token: string): boolean {
return this.tokens.delete(token);
}
cleanupExpired(): number {
const now = Date.now();
let removed = 0;
for (const [token, record] of this.tokens) {
if (now >= record.expiresAtMs) {
this.tokens.delete(token);
removed += 1;
}
}
return removed;
}
}
let singleton: CliSessionShareManager | null = null;
export function getCliSessionShareManager(): CliSessionShareManager {
if (!singleton) singleton = new CliSessionShareManager();
return singleton;
}
export function describeShareAuthFailure(): { error: string; status: number } {
return { error: 'Invalid or expired share token', status: 403 };
}

View File

@@ -255,7 +255,7 @@ export class NodeRunner {
};
}
const manager = getCliSessionManager(process.cwd());
const manager = getCliSessionManager(this.context.workingDir || process.cwd());
const routed = manager.execute(targetSessionKey, {
tool,
prompt: instruction,