fix(websearch): close review gaps in managed runtime

This commit is contained in:
Tam Nhu Tran committed 2026-03-30 23:02:20 -04:00
1 parent de7171d810
commit cae5b710b8
15 files changed
+299 -60

No files matched your search

+3 -2
View File
@@ -1039,9 +1039,10 @@ export async function execClaudeWithCLIProxy(
const traceEnv = createWebSearchTraceContext({
launcher: 'cliproxy.executor',
args: launchArgs,
profile: provider,
profileType: 'settings',
profile: cfg.profileName || provider,
profileType: 'cliproxy',
settingsPath,
claudeConfigDir: inheritedClaudeConfigDir,
});
const tracedEnv = { ...env, ...traceEnv };
if (needsShell) {
+35 -2
View File
@@ -18,12 +18,15 @@ import { buildExecutionResult } from './executor/result-aggregator';
import { getCcsDir, getModelDisplayName } from '../utils/config-manager';
import { getProfileLookupCandidates } from '../utils/profile-compat';
import { getClaudeLaunchEnvOverrides, stripClaudeCodeEnv } from '../utils/shell-executor';
import { resolveProfileContinuityInheritance } from '../auth/profile-continuity-inheritance';
import {
appendThirdPartyWebSearchToolArgs,
appendWebSearchTrace,
createWebSearchTraceContext,
ensureWebSearchMcpOrThrow,
getWebSearchHookEnv,
readWebSearchTraceRecords,
syncWebSearchMcpToConfigDir,
} from '../utils/websearch-manager';
// Re-export types for consumers
@@ -87,6 +90,23 @@ export class HeadlessExecutor {
);
}
const continuityInheritance = await resolveProfileContinuityInheritance({
profileName: profile,
profileType: 'settings',
target: 'claude',
});
const inheritedClaudeConfigDir = continuityInheritance.claudeConfigDir;
if (continuityInheritance.sourceAccount && process.env.CCS_DEBUG) {
console.error(
info(
`Continuity inheritance active: profile "${profile}" -> account "${continuityInheritance.sourceAccount}"`
)
);
}
ensureWebSearchMcpOrThrow();
syncWebSearchMcpToConfigDir(inheritedClaudeConfigDir);
// Smart slash command detection and preservation
const processedPrompt = this._processSlashCommand(enhancedPrompt);
@@ -132,7 +152,7 @@ export class HeadlessExecutor {
args.push('--allowedTools', ...toolRestrictions.allowedTools);
}
if (toolRestrictions.disallowedTools.length > 0) {
args.push('--disallowedTools', ...toolRestrictions.disallowedTools);
args.push('--disallowedTools', toolRestrictions.disallowedTools.join(','));
}
// Claude Code CLI passthrough flags (explicit, validated)
@@ -178,6 +198,7 @@ export class HeadlessExecutor {
profile,
profileType: 'settings',
settingsPath,
claudeConfigDir: inheritedClaudeConfigDir,
});
if (process.env.CCS_DEBUG) {
@@ -195,6 +216,7 @@ export class HeadlessExecutor {
resumeSession,
sessionId,
sessionMgr,
claudeConfigDir: inheritedClaudeConfigDir,
traceEnv,
});
}
@@ -212,10 +234,20 @@ export class HeadlessExecutor {
resumeSession: boolean;
sessionId: string | null;
sessionMgr: SessionManager;
claudeConfigDir?: string;
traceEnv?: Record<string, string>;
}
): Promise<ExecutionResult> {
const { cwd, profile, timeout, resumeSession, sessionId, sessionMgr, traceEnv = {} } = ctx;
const {
cwd,
profile,
timeout,
resumeSession,
sessionId,
sessionMgr,
claudeConfigDir,
traceEnv = {},
} = ctx;
return new Promise((resolve, reject) => {
const startTime = Date.now();
@@ -234,6 +266,7 @@ export class HeadlessExecutor {
...getClaudeLaunchEnvOverrides(),
...getWebSearchHookEnv(),
...traceEnv,
...(claudeConfigDir ? { CLAUDE_CONFIG_DIR: claudeConfigDir } : {}),
CCS_PROFILE_TYPE: 'settings',
});
+11 -2
View File
@@ -14,6 +14,8 @@ import { DEFAULT_ACCOUNT_CONTEXT_MODE } from '../auth/account-context';
import type { AccountContextPolicy } from '../auth/account-context';
import { getCcsDir, getCcsHome } from '../utils/config-manager';
const MANAGED_MCP_SERVER_NAMES = new Set(['ccs-websearch']);
/** Options for instance creation */
export interface InstanceOptions {
/** Skip shared symlinks (commands, skills, agents, settings.json) */
@@ -235,13 +237,20 @@ class InstanceManager {
}
}
// Merge: global MCP servers as base, instance-specific overrides on top
// Merge: global MCP servers as base, instance-specific overrides on top,
// except for CCS-managed entries which must stay aligned with the global runtime.
const rawExistingMcp = instanceContent.mcpServers;
const existingMcp =
rawExistingMcp && typeof rawExistingMcp === 'object' && !Array.isArray(rawExistingMcp)
? (rawExistingMcp as Record<string, unknown>)
: {};
instanceContent.mcpServers = { ...mcpServers, ...existingMcp };
const mergedMcpServers = { ...mcpServers, ...existingMcp };
for (const managedName of MANAGED_MCP_SERVER_NAMES) {
if (managedName in mcpServers) {
mergedMcpServers[managedName] = mcpServers[managedName];
}
}
instanceContent.mcpServers = mergedMcpServers;
fs.writeFileSync(instanceClaudeJson, JSON.stringify(instanceContent, null, 2), {
encoding: 'utf8',
+4 -2
View File
@@ -7,6 +7,7 @@
*/
import { getWebSearchConfig } from '../../config/unified-config-loader';
import { resolveAllowedWebSearchTraceFile } from './trace';
/**
* Get environment variables for WebSearch hook configuration.
@@ -22,8 +23,9 @@ export function getWebSearchHookEnv(): Record<string, string> {
if (process.env.CCS_WEBSEARCH_TRACE === '1' || process.env.CCS_DEBUG === '1') {
env.CCS_WEBSEARCH_TRACE = '1';
}
if (process.env.CCS_WEBSEARCH_TRACE_FILE) {
env.CCS_WEBSEARCH_TRACE_FILE = process.env.CCS_WEBSEARCH_TRACE_FILE;
const traceFileOverride = resolveAllowedWebSearchTraceFile(process.env);
if (traceFileOverride) {
env.CCS_WEBSEARCH_TRACE_FILE = traceFileOverride;
}
// Skip hook entirely if disabled
+13 -1
View File
@@ -194,7 +194,19 @@ export function installWebSearchMcpServer(): boolean {
try {
fs.copyFileSync(sourcePath, tempPath);
fs.chmodSync(tempPath, 0o755);
fs.renameSync(tempPath, serverPath);
try {
fs.renameSync(tempPath, serverPath);
} catch (renameError) {
const errorCode = (renameError as NodeJS.ErrnoException).code;
if (errorCode !== 'EEXIST' && errorCode !== 'EPERM') {
throw renameError;
}
if (!hasMatchingContents(sourcePath, serverPath)) {
fs.copyFileSync(tempPath, serverPath);
fs.chmodSync(serverPath, 0o755);
}
}
appendWebSearchTrace('websearch_mcp_install_ready', { serverPath });
return true;
} catch (error) {
+26 -15
View File
@@ -7,11 +7,11 @@
*/
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { getCcsDir } from '../config-manager';
const TRACE_FILE_NAME = 'websearch-trace.jsonl';
const SAFE_SYSTEM_TRACE_PREFIXES = ['/tmp/', '/var/log/'];
const NATIVE_WEBSEARCH_TOOL = 'WebSearch';
const DISALLOWED_TOOLS_FLAG = '--disallowedTools';
const APPEND_SYSTEM_PROMPT_FLAG = '--append-system-prompt';
@@ -75,26 +75,36 @@ function hasExactFlagValue(args: string[], flag: string, expectedValue: string):
return false;
}
function getTraceFilePath(env: NodeJS.ProcessEnv): string {
function normalizeSafePrefix(inputPath: string): string {
return `${path.resolve(inputPath)}${path.sep}`;
}
function getSafeTracePrefixes(): string[] {
return [
normalizeSafePrefix(path.join(getCcsDir(), 'logs')),
normalizeSafePrefix(os.tmpdir()),
normalizeSafePrefix('/var/log'),
];
}
export function resolveAllowedWebSearchTraceFile(
env: NodeJS.ProcessEnv = process.env
): string | null {
const configured = env.CCS_WEBSEARCH_TRACE_FILE?.trim();
if (!configured) {
return path.join(getCcsDir(), 'logs', TRACE_FILE_NAME);
return null;
}
const resolved = path.resolve(configured);
const home = env.HOME || env.USERPROFILE || '';
const normalizedHome = home ? `${path.resolve(home)}${path.sep}` : '';
const normalizedCcsDir = `${path.resolve(getCcsDir())}${path.sep}`;
if (
resolved.startsWith(normalizedCcsDir) ||
(normalizedHome && resolved.startsWith(normalizedHome)) ||
SAFE_SYSTEM_TRACE_PREFIXES.some((prefix) => resolved.startsWith(prefix))
) {
if (getSafeTracePrefixes().some((prefix) => resolved.startsWith(prefix))) {
return resolved;
}
return path.join(getCcsDir(), 'logs', TRACE_FILE_NAME);
return null;
}
function getTraceFilePath(env: NodeJS.ProcessEnv): string {
return resolveAllowedWebSearchTraceFile(env) ?? path.join(getCcsDir(), 'logs', TRACE_FILE_NAME);
}
export function isWebSearchTraceEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
@@ -197,8 +207,9 @@ export function createWebSearchTraceContext(params: {
CCS_WEBSEARCH_TRACE_LAUNCHER: params.launcher,
};
if (env.CCS_WEBSEARCH_TRACE_FILE) {
traceEnv.CCS_WEBSEARCH_TRACE_FILE = env.CCS_WEBSEARCH_TRACE_FILE;
const traceFileOverride = resolveAllowedWebSearchTraceFile(env);
if (traceFileOverride) {
traceEnv.CCS_WEBSEARCH_TRACE_FILE = traceFileOverride;
}
appendWebSearchTrace(