perf(team): cache runtime command parsing
This commit is contained in:
parent
005742e9ad
commit
9af0a0df2b
2 changed files with 71 additions and 2 deletions
|
|
@ -45,6 +45,11 @@ export interface ResolvedTeamMemberRuntimeLiveness {
|
||||||
const SHELL_COMMAND_NAMES = new Set(['sh', 'bash', 'zsh', 'fish', 'dash', 'login', 'tmux']);
|
const SHELL_COMMAND_NAMES = new Set(['sh', 'bash', 'zsh', 'fish', 'dash', 'login', 'tmux']);
|
||||||
const SECRET_FLAG_PATTERN =
|
const SECRET_FLAG_PATTERN =
|
||||||
/(--(?:api-key|token|password|secret|authorization|auth-token)(?:=|\s+))("[^"]*"|'[^']*'|\S+)/gi;
|
/(--(?:api-key|token|password|secret|authorization|auth-token)(?:=|\s+))("[^"]*"|'[^']*'|\S+)/gi;
|
||||||
|
const MAX_CLI_ARG_CACHE_ENTRIES = 2_000;
|
||||||
|
const CLI_ARG_CACHE_KEY_SEPARATOR = '\u0000';
|
||||||
|
|
||||||
|
const cliArgValuesCache = new Map<string, string[]>();
|
||||||
|
const commandArgEqualsCache = new Map<string, boolean>();
|
||||||
|
|
||||||
function basenameCommand(command: string | undefined): string {
|
function basenameCommand(command: string | undefined): string {
|
||||||
const firstToken = command?.trim().split(/\s+/, 1)[0] ?? '';
|
const firstToken = command?.trim().split(/\s+/, 1)[0] ?? '';
|
||||||
|
|
@ -68,7 +73,37 @@ function escapeRegexLiteral(value: string): string {
|
||||||
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildCliArgCacheKey(...parts: readonly string[]): string {
|
||||||
|
return parts.join(CLI_ARG_CACHE_KEY_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCachedValue<T>(cache: Map<string, T>, key: string): T | undefined {
|
||||||
|
if (!cache.has(key)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const value = cache.get(key) as T;
|
||||||
|
cache.delete(key);
|
||||||
|
cache.set(key, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCachedValue<T>(cache: Map<string, T>, key: string, value: T): void {
|
||||||
|
if (!cache.has(key) && cache.size >= MAX_CLI_ARG_CACHE_ENTRIES) {
|
||||||
|
const oldestKey = cache.keys().next().value;
|
||||||
|
if (oldestKey !== undefined) {
|
||||||
|
cache.delete(oldestKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cache.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
export function extractCliArgValues(command: string, argName: string): string[] {
|
export function extractCliArgValues(command: string, argName: string): string[] {
|
||||||
|
const cacheKey = buildCliArgCacheKey(command, argName);
|
||||||
|
const cached = getCachedValue(cliArgValuesCache, cacheKey);
|
||||||
|
if (cached) {
|
||||||
|
return cached.slice();
|
||||||
|
}
|
||||||
|
|
||||||
const escapedArg = escapeRegexLiteral(argName);
|
const escapedArg = escapeRegexLiteral(argName);
|
||||||
const pattern = new RegExp(
|
const pattern = new RegExp(
|
||||||
`(?:^|\\s)${escapedArg}(?:=|\\s+)("([^"]*)"|'([^']*)'|([^\\s]+))`,
|
`(?:^|\\s)${escapedArg}(?:=|\\s+)("([^"]*)"|'([^']*)'|([^\\s]+))`,
|
||||||
|
|
@ -80,7 +115,8 @@ export function extractCliArgValues(command: string, argName: string): string[]
|
||||||
const value = (match[2] ?? match[3] ?? match[4] ?? '').trim();
|
const value = (match[2] ?? match[3] ?? match[4] ?? '').trim();
|
||||||
if (value) values.push(value);
|
if (value) values.push(value);
|
||||||
}
|
}
|
||||||
return values;
|
setCachedValue(cliArgValuesCache, cacheKey, values);
|
||||||
|
return values.slice();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function commandArgEquals(
|
export function commandArgEquals(
|
||||||
|
|
@ -90,7 +126,16 @@ export function commandArgEquals(
|
||||||
): boolean {
|
): boolean {
|
||||||
const normalizedExpected = expected?.trim();
|
const normalizedExpected = expected?.trim();
|
||||||
if (!normalizedExpected) return false;
|
if (!normalizedExpected) return false;
|
||||||
return extractCliArgValues(command, argName).some((value) => value === normalizedExpected);
|
const cacheKey = buildCliArgCacheKey(command, argName, normalizedExpected);
|
||||||
|
const cached = getCachedValue(commandArgEqualsCache, cacheKey);
|
||||||
|
if (cached !== undefined) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
const result = extractCliArgValues(command, argName).some(
|
||||||
|
(value) => value === normalizedExpected
|
||||||
|
);
|
||||||
|
setCachedValue(commandArgEqualsCache, cacheKey, result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectDescendants(
|
function collectDescendants(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import {
|
||||||
|
commandArgEquals,
|
||||||
|
extractCliArgValues,
|
||||||
|
} from '@main/services/team/TeamRuntimeLivenessResolver';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
describe('team runtime liveness command parsing', () => {
|
||||||
|
it('keeps cached extracted values isolated from caller mutation', () => {
|
||||||
|
const command = 'node runtime --team-name demo --agent-id agent-alice';
|
||||||
|
|
||||||
|
const firstValues = extractCliArgValues(command, '--agent-id');
|
||||||
|
firstValues.push('mutated-agent');
|
||||||
|
|
||||||
|
expect(extractCliArgValues(command, '--agent-id')).toEqual(['agent-alice']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('caches command arg equality without changing quoted value matching', () => {
|
||||||
|
const command = 'node runtime --team-name "demo team" --agent-id agent-alice';
|
||||||
|
|
||||||
|
expect(commandArgEquals(command, '--team-name', 'demo team')).toBe(true);
|
||||||
|
expect(commandArgEquals(command, '--team-name', 'other team')).toBe(false);
|
||||||
|
expect(commandArgEquals(command, '--agent-id', 'agent-alice')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue