mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 06:17:09 +00:00
feat(websearch): finish managed third-party rollout
- steer third-party launches toward the managed WebSearch MCP tool - add opt-in trace diagnostics across launch, MCP, provider, and headless paths - extend docs and regression coverage for the first-class runtime Refs #862
This commit is contained in:
@@ -70,6 +70,14 @@ export {
|
||||
// Re-export Claude launch arg helpers
|
||||
export { appendThirdPartyWebSearchToolArgs } from './websearch/claude-tool-args';
|
||||
|
||||
// Re-export trace helpers
|
||||
export {
|
||||
appendWebSearchTrace,
|
||||
createWebSearchTraceContext,
|
||||
isWebSearchTraceEnabled,
|
||||
readWebSearchTraceRecords,
|
||||
} from './websearch/trace';
|
||||
|
||||
// Re-export status and readiness functions
|
||||
export {
|
||||
getWebSearchCliProviders,
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
const NATIVE_WEBSEARCH_TOOL = 'WebSearch';
|
||||
const DISALLOWED_TOOLS_FLAG = '--disallowedTools';
|
||||
const APPEND_SYSTEM_PROMPT_FLAG = '--append-system-prompt';
|
||||
const THIRD_PARTY_WEBSEARCH_STEERING_PROMPT =
|
||||
'For web lookup or current-information requests, prefer the CCS MCP tool WebSearch instead of Bash/curl/http fetches. If the user explicitly wants shell commands, or WebSearch is unavailable or fails, you may fall back to Bash/network tools.';
|
||||
|
||||
function parseToolValue(rawValue: string): string[] {
|
||||
return rawValue
|
||||
@@ -20,19 +23,34 @@ function mergeToolValues(rawValues: string[], toolName: string): string {
|
||||
return merged.join(',');
|
||||
}
|
||||
|
||||
function splitArgsAtTerminator(args: string[]): { optionArgs: string[]; trailingArgs: string[] } {
|
||||
const terminatorIndex = args.indexOf('--');
|
||||
if (terminatorIndex === -1) {
|
||||
return { optionArgs: args, trailingArgs: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
optionArgs: args.slice(0, terminatorIndex),
|
||||
trailingArgs: args.slice(terminatorIndex),
|
||||
};
|
||||
}
|
||||
|
||||
function getImmediateFlagValue(args: string[], index: number): string | null {
|
||||
const value = args[index + 1];
|
||||
if (value === undefined || value === '--' || value.startsWith('--')) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function hasToolInFlag(args: string[], flag: string, toolName: string): boolean {
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
|
||||
if (arg === flag) {
|
||||
for (let cursor = index + 1; cursor < args.length; cursor += 1) {
|
||||
const value = args[cursor];
|
||||
if (value.startsWith('--')) {
|
||||
break;
|
||||
}
|
||||
if (parseToolValue(value).includes(toolName)) {
|
||||
return true;
|
||||
}
|
||||
const value = getImmediateFlagValue(args, index);
|
||||
if (value && parseToolValue(value).includes(toolName)) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -50,39 +68,86 @@ function hasToolInFlag(args: string[], flag: string, toolName: string): boolean
|
||||
return false;
|
||||
}
|
||||
|
||||
export function appendThirdPartyWebSearchToolArgs(args: string[]): string[] {
|
||||
if (hasToolInFlag(args, DISALLOWED_TOOLS_FLAG, NATIVE_WEBSEARCH_TOOL)) {
|
||||
return args;
|
||||
}
|
||||
|
||||
function hasExactFlagValue(args: string[], flag: string, expectedValue: string): boolean {
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
|
||||
if (arg === DISALLOWED_TOOLS_FLAG) {
|
||||
let cursor = index + 1;
|
||||
const rawValues: string[] = [];
|
||||
|
||||
while (cursor < args.length && !args[cursor].startsWith('--')) {
|
||||
rawValues.push(args[cursor]);
|
||||
cursor += 1;
|
||||
if (arg === flag) {
|
||||
const value = getImmediateFlagValue(args, index);
|
||||
if (value === expectedValue) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === `${flag}=${expectedValue}`) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (arg.startsWith(`${flag}=`) && arg.slice(flag.length + 1) === expectedValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function ensureDisallowedNativeWebSearchTool(args: string[]): string[] {
|
||||
const { optionArgs, trailingArgs } = splitArgsAtTerminator(args);
|
||||
|
||||
if (hasToolInFlag(optionArgs, DISALLOWED_TOOLS_FLAG, NATIVE_WEBSEARCH_TOOL)) {
|
||||
return args;
|
||||
}
|
||||
|
||||
for (let index = 0; index < optionArgs.length; index += 1) {
|
||||
const arg = optionArgs[index];
|
||||
|
||||
if (arg === DISALLOWED_TOOLS_FLAG) {
|
||||
const currentValue = getImmediateFlagValue(optionArgs, index);
|
||||
const mergedValue = mergeToolValues(
|
||||
currentValue ? [currentValue] : [],
|
||||
NATIVE_WEBSEARCH_TOOL
|
||||
);
|
||||
|
||||
return [
|
||||
...args.slice(0, index + 1),
|
||||
mergeToolValues(rawValues, NATIVE_WEBSEARCH_TOOL),
|
||||
...args.slice(cursor),
|
||||
...optionArgs.slice(0, index + 1),
|
||||
mergedValue,
|
||||
...optionArgs.slice(currentValue === null ? index + 1 : index + 2),
|
||||
...trailingArgs,
|
||||
];
|
||||
}
|
||||
|
||||
if (arg.startsWith(`${DISALLOWED_TOOLS_FLAG}=`)) {
|
||||
const rawValue = arg.slice(DISALLOWED_TOOLS_FLAG.length + 1);
|
||||
return [
|
||||
...args.slice(0, index),
|
||||
...optionArgs.slice(0, index),
|
||||
`${DISALLOWED_TOOLS_FLAG}=${mergeToolValues([rawValue], NATIVE_WEBSEARCH_TOOL)}`,
|
||||
...args.slice(index + 1),
|
||||
...optionArgs.slice(index + 1),
|
||||
...trailingArgs,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [...args, DISALLOWED_TOOLS_FLAG, NATIVE_WEBSEARCH_TOOL];
|
||||
return [...optionArgs, DISALLOWED_TOOLS_FLAG, NATIVE_WEBSEARCH_TOOL, ...trailingArgs];
|
||||
}
|
||||
|
||||
function ensureWebSearchSteeringPrompt(args: string[]): string[] {
|
||||
const { optionArgs, trailingArgs } = splitArgsAtTerminator(args);
|
||||
|
||||
if (
|
||||
hasExactFlagValue(optionArgs, APPEND_SYSTEM_PROMPT_FLAG, THIRD_PARTY_WEBSEARCH_STEERING_PROMPT)
|
||||
) {
|
||||
return args;
|
||||
}
|
||||
|
||||
return [
|
||||
...optionArgs,
|
||||
APPEND_SYSTEM_PROMPT_FLAG,
|
||||
THIRD_PARTY_WEBSEARCH_STEERING_PROMPT,
|
||||
...trailingArgs,
|
||||
];
|
||||
}
|
||||
|
||||
export function appendThirdPartyWebSearchToolArgs(args: string[]): string[] {
|
||||
return ensureWebSearchSteeringPrompt(ensureDisallowedNativeWebSearchTool(args));
|
||||
}
|
||||
|
||||
@@ -19,6 +19,13 @@ export function getWebSearchHookEnv(): Record<string, string> {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
const env: 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;
|
||||
}
|
||||
|
||||
// Skip hook entirely if disabled
|
||||
if (!wsConfig.enabled) {
|
||||
env.CCS_WEBSEARCH_SKIP = '1';
|
||||
|
||||
@@ -67,6 +67,14 @@ export {
|
||||
// Claude launch args
|
||||
export { appendThirdPartyWebSearchToolArgs } from './claude-tool-args';
|
||||
|
||||
// Trace helpers
|
||||
export {
|
||||
appendWebSearchTrace,
|
||||
createWebSearchTraceContext,
|
||||
isWebSearchTraceEnabled,
|
||||
readWebSearchTraceRecords,
|
||||
} from './trace';
|
||||
|
||||
// Status and Readiness
|
||||
export {
|
||||
getWebSearchCliProviders,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { getClaudeUserConfigPath } from '../claude-config-path';
|
||||
import { info, warn } from '../ui';
|
||||
import { InstanceManager } from '../../management/instance-manager';
|
||||
import { installWebSearchHook } from './hook-installer';
|
||||
import { appendWebSearchTrace } from './trace';
|
||||
|
||||
const WEBSEARCH_MCP_SERVER = 'ccs-websearch-server.cjs';
|
||||
const WEBSEARCH_MCP_SERVER_NAME = 'ccs-websearch';
|
||||
@@ -154,10 +155,12 @@ function removeManagedServerConfig(configPath: string): boolean {
|
||||
export function installWebSearchMcpServer(): boolean {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
if (!wsConfig.enabled) {
|
||||
appendWebSearchTrace('websearch_mcp_install_skipped', { reason: 'disabled' });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!installWebSearchHook()) {
|
||||
appendWebSearchTrace('websearch_mcp_install_failed', { reason: 'hook_unavailable' });
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.error(
|
||||
warn('WebSearch MCP server install skipped because hook runtime is unavailable')
|
||||
@@ -168,6 +171,7 @@ export function installWebSearchMcpServer(): boolean {
|
||||
|
||||
const sourcePath = resolveBundledServerSourcePath();
|
||||
if (!sourcePath) {
|
||||
appendWebSearchTrace('websearch_mcp_install_failed', { reason: 'source_missing' });
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.error(warn(`WebSearch MCP server source not found: ${WEBSEARCH_MCP_SERVER}`));
|
||||
}
|
||||
@@ -181,6 +185,7 @@ export function installWebSearchMcpServer(): boolean {
|
||||
|
||||
const serverPath = getWebSearchMcpServerPath();
|
||||
if (hasMatchingContents(sourcePath, serverPath)) {
|
||||
appendWebSearchTrace('websearch_mcp_install_ready', { serverPath });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -190,8 +195,13 @@ export function installWebSearchMcpServer(): boolean {
|
||||
fs.copyFileSync(sourcePath, tempPath);
|
||||
fs.chmodSync(tempPath, 0o755);
|
||||
fs.renameSync(tempPath, serverPath);
|
||||
appendWebSearchTrace('websearch_mcp_install_ready', { serverPath });
|
||||
return true;
|
||||
} catch (error) {
|
||||
appendWebSearchTrace('websearch_mcp_install_failed', {
|
||||
reason: 'copy_failed',
|
||||
error: (error as Error).message,
|
||||
});
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.error(warn(`Failed to install WebSearch MCP server: ${(error as Error).message}`));
|
||||
}
|
||||
@@ -206,6 +216,7 @@ export function installWebSearchMcpServer(): boolean {
|
||||
export function ensureWebSearchMcpConfig(): boolean {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
if (!wsConfig.enabled) {
|
||||
appendWebSearchTrace('websearch_mcp_config_skipped', { reason: 'disabled' });
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -214,6 +225,7 @@ export function ensureWebSearchMcpConfig(): boolean {
|
||||
const config = readClaudeUserConfig(claudeUserConfigPath);
|
||||
|
||||
if (config === null) {
|
||||
appendWebSearchTrace('websearch_mcp_config_failed', { reason: 'malformed_user_config' });
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.error(warn('Malformed ~/.claude.json prevents WebSearch MCP provisioning'));
|
||||
}
|
||||
@@ -239,6 +251,7 @@ export function ensureWebSearchMcpConfig(): boolean {
|
||||
currentConfig !== null &&
|
||||
JSON.stringify(currentConfig) === JSON.stringify(desiredServerConfig)
|
||||
) {
|
||||
appendWebSearchTrace('websearch_mcp_config_ready', { configPath: claudeUserConfigPath });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -252,11 +265,17 @@ export function ensureWebSearchMcpConfig(): boolean {
|
||||
|
||||
try {
|
||||
writeClaudeUserConfig(claudeUserConfigPath, nextConfig);
|
||||
appendWebSearchTrace('websearch_mcp_config_ready', { configPath: claudeUserConfigPath });
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.error(info(`Ensured WebSearch MCP config in ${claudeUserConfigPath}`));
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
appendWebSearchTrace('websearch_mcp_config_failed', {
|
||||
reason: 'write_failed',
|
||||
configPath: claudeUserConfigPath,
|
||||
error: (error as Error).message,
|
||||
});
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.error(warn(`Failed to update ~/.claude.json: ${(error as Error).message}`));
|
||||
}
|
||||
@@ -267,18 +286,25 @@ export function ensureWebSearchMcpConfig(): boolean {
|
||||
export function ensureWebSearchMcp(): boolean {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
if (!wsConfig.enabled) {
|
||||
appendWebSearchTrace('websearch_mcp_ensure_skipped', { reason: 'disabled' });
|
||||
return false;
|
||||
}
|
||||
|
||||
return installWebSearchMcpServer() && ensureWebSearchMcpConfig();
|
||||
const installed = installWebSearchMcpServer();
|
||||
const configured = installed && ensureWebSearchMcpConfig();
|
||||
appendWebSearchTrace('websearch_mcp_ensure_result', { installed, configured });
|
||||
return installed && configured;
|
||||
}
|
||||
|
||||
export function syncWebSearchMcpToConfigDir(claudeConfigDir: string | undefined): boolean {
|
||||
if (!claudeConfigDir) {
|
||||
appendWebSearchTrace('websearch_mcp_sync_skipped', { reason: 'missing_config_dir' });
|
||||
return false;
|
||||
}
|
||||
|
||||
return new InstanceManager().syncMcpServers(claudeConfigDir);
|
||||
const synced = new InstanceManager().syncMcpServers(claudeConfigDir);
|
||||
appendWebSearchTrace('websearch_mcp_sync_result', { claudeConfigDir, synced });
|
||||
return synced;
|
||||
}
|
||||
|
||||
export function uninstallWebSearchMcpServer(): boolean {
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* Best-effort WebSearch trace helpers.
|
||||
*
|
||||
* Writes opt-in JSONL trace records to ~/.ccs/logs/websearch-trace.jsonl so
|
||||
* CCS can explain launch intent, MCP exposure, provider selection, and likely
|
||||
* bypass scenarios without polluting Claude/MCP stdout.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
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';
|
||||
const THIRD_PARTY_WEBSEARCH_STEERING_PROMPT =
|
||||
'For web lookup or current-information requests, prefer the CCS MCP tool WebSearch instead of Bash/curl/http fetches. If the user explicitly wants shell commands, or WebSearch is unavailable or fails, you may fall back to Bash/network tools.';
|
||||
|
||||
function parseToolValue(rawValue: string): string[] {
|
||||
return rawValue
|
||||
.split(',')
|
||||
.map((value) => value.trim())
|
||||
.filter((value) => value.length > 0);
|
||||
}
|
||||
|
||||
function getImmediateFlagValue(args: string[], index: number): string | null {
|
||||
const value = args[index + 1];
|
||||
if (value === undefined || value === '--' || value.startsWith('--')) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function hasToolInFlag(args: string[], flag: string, toolName: string): boolean {
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
|
||||
if (arg === flag) {
|
||||
const value = getImmediateFlagValue(args, index);
|
||||
if (value && parseToolValue(value).includes(toolName)) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith(`${flag}=`)) {
|
||||
const rawValue = arg.slice(flag.length + 1);
|
||||
if (parseToolValue(rawValue).includes(toolName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasExactFlagValue(args: string[], flag: string, expectedValue: string): boolean {
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
|
||||
if (arg === flag) {
|
||||
if (getImmediateFlagValue(args, index) === expectedValue) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === `${flag}=${expectedValue}`) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getTraceFilePath(env: NodeJS.ProcessEnv): string {
|
||||
const configured = env.CCS_WEBSEARCH_TRACE_FILE?.trim();
|
||||
if (!configured) {
|
||||
return path.join(getCcsDir(), 'logs', TRACE_FILE_NAME);
|
||||
}
|
||||
|
||||
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))
|
||||
) {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
return path.join(getCcsDir(), 'logs', TRACE_FILE_NAME);
|
||||
}
|
||||
|
||||
export function isWebSearchTraceEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
return env.CCS_WEBSEARCH_TRACE === '1' || env.CCS_DEBUG === '1';
|
||||
}
|
||||
|
||||
export function appendWebSearchTrace(
|
||||
event: string,
|
||||
payload: Record<string, unknown> = {},
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): void {
|
||||
if (!isWebSearchTraceEnabled(env)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const traceFilePath = getTraceFilePath(env);
|
||||
fs.mkdirSync(path.dirname(traceFilePath), { recursive: true });
|
||||
fs.appendFileSync(
|
||||
traceFilePath,
|
||||
JSON.stringify({
|
||||
at: new Date().toISOString(),
|
||||
event,
|
||||
launchId: env.CCS_WEBSEARCH_TRACE_LAUNCH_ID || null,
|
||||
launcher: env.CCS_WEBSEARCH_TRACE_LAUNCHER || null,
|
||||
profileType: env.CCS_PROFILE_TYPE || null,
|
||||
pid: process.pid,
|
||||
...payload,
|
||||
}) + '\n',
|
||||
'utf8'
|
||||
);
|
||||
} catch {
|
||||
// Tracing must never affect launch behavior.
|
||||
}
|
||||
}
|
||||
|
||||
export function readWebSearchTraceRecords(
|
||||
launchId: string,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): Array<Record<string, unknown>> {
|
||||
if (!launchId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const traceFilePath = getTraceFilePath(env);
|
||||
if (!fs.existsSync(traceFilePath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return fs
|
||||
.readFileSync(traceFilePath, 'utf8')
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
.map((line) => JSON.parse(line) as Record<string, unknown>)
|
||||
.filter((record) => record.launchId === launchId);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function buildLaunchId(): string {
|
||||
const random = Math.random().toString(36).slice(2, 10);
|
||||
return `websearch-${Date.now()}-${process.pid}-${random}`;
|
||||
}
|
||||
|
||||
function summarizeLaunchArgs(args: string[]): Record<string, unknown> {
|
||||
return {
|
||||
argCount: args.length,
|
||||
hasSettingsFlag: args.includes('--settings'),
|
||||
nativeWebSearchDisallowed: hasToolInFlag(args, DISALLOWED_TOOLS_FLAG, NATIVE_WEBSEARCH_TOOL),
|
||||
steeringPromptApplied: hasExactFlagValue(
|
||||
args,
|
||||
APPEND_SYSTEM_PROMPT_FLAG,
|
||||
THIRD_PARTY_WEBSEARCH_STEERING_PROMPT
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function createWebSearchTraceContext(params: {
|
||||
launcher: string;
|
||||
args: string[];
|
||||
cwd?: string;
|
||||
profile?: string;
|
||||
profileType?: string;
|
||||
settingsPath?: string;
|
||||
claudeConfigDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Record<string, string> {
|
||||
const env = params.env ?? process.env;
|
||||
if (!isWebSearchTraceEnabled(env)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const launchId = buildLaunchId();
|
||||
const traceEnv: Record<string, string> = {
|
||||
CCS_WEBSEARCH_TRACE: '1',
|
||||
CCS_WEBSEARCH_TRACE_LAUNCH_ID: launchId,
|
||||
CCS_WEBSEARCH_TRACE_LAUNCHER: params.launcher,
|
||||
};
|
||||
|
||||
if (env.CCS_WEBSEARCH_TRACE_FILE) {
|
||||
traceEnv.CCS_WEBSEARCH_TRACE_FILE = env.CCS_WEBSEARCH_TRACE_FILE;
|
||||
}
|
||||
|
||||
appendWebSearchTrace(
|
||||
'ccs_websearch_launch',
|
||||
{
|
||||
launcher: params.launcher,
|
||||
profile: params.profile || null,
|
||||
profileType: params.profileType || null,
|
||||
cwd: params.cwd || null,
|
||||
settingsPath: params.settingsPath || null,
|
||||
claudeConfigDir: params.claudeConfigDir || null,
|
||||
...summarizeLaunchArgs(params.args),
|
||||
},
|
||||
{
|
||||
...env,
|
||||
...traceEnv,
|
||||
CCS_PROFILE_TYPE: params.profileType || env.CCS_PROFILE_TYPE || '',
|
||||
}
|
||||
);
|
||||
|
||||
return traceEnv;
|
||||
}
|
||||
Reference in New Issue
Block a user