mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-03 20:13:02 +00:00
fix(websearch): honor disabled steering per launch
This commit is contained in:
1 parent
09757b418e
commit
abc3002e89
18 files changed
+165
-76
No files matched your search
@@ -15,6 +15,7 @@ import {
|
||||
appendThirdPartyWebSearchToolArgs,
|
||||
createWebSearchTraceContext,
|
||||
} from '../../utils/websearch-manager';
|
||||
import type { WebSearchLaunchState } from '../../utils/websearch-manager';
|
||||
import { appendThirdPartyImageAnalysisToolArgs } from '../../utils/image-analysis';
|
||||
import { appendBrowserToolArgs } from '../../utils/browser';
|
||||
import { getDefaultAccount } from '../accounts/account-manager';
|
||||
@@ -51,6 +52,8 @@ export interface ClaudeLaunchContext {
|
||||
sessionId: string | undefined;
|
||||
/** Whether image analysis MCP is ready */
|
||||
imageAnalysisMcpReady: boolean;
|
||||
/** WebSearch state captured when launch provisioning ran */
|
||||
webSearchLaunch: WebSearchLaunchState;
|
||||
/** Browser runtime environment variables (undefined if browser not active) */
|
||||
browserRuntimeEnv: NodeJS.ProcessEnv | undefined;
|
||||
/** Inherited Claude config dir for continuity */
|
||||
@@ -82,6 +85,7 @@ export async function launchClaude(context: ClaudeLaunchContext): Promise<ChildP
|
||||
skipLocalAuth,
|
||||
sessionId,
|
||||
imageAnalysisMcpReady,
|
||||
webSearchLaunch,
|
||||
browserRuntimeEnv,
|
||||
inheritedClaudeConfigDir,
|
||||
codexReasoningProxy,
|
||||
@@ -124,8 +128,12 @@ export async function launchClaude(context: ClaudeLaunchContext): Promise<ChildP
|
||||
? appendBrowserToolArgs(imageAnalysisArgs)
|
||||
: imageAnalysisArgs;
|
||||
const launchArgs = isSubcommand
|
||||
? appendThirdPartyWebSearchToolArgs(browserArgs)
|
||||
: ['--settings', settingsPath, ...appendThirdPartyWebSearchToolArgs(browserArgs)];
|
||||
? appendThirdPartyWebSearchToolArgs(browserArgs, webSearchLaunch.enabled)
|
||||
: [
|
||||
'--settings',
|
||||
settingsPath,
|
||||
...appendThirdPartyWebSearchToolArgs(browserArgs, webSearchLaunch.enabled),
|
||||
];
|
||||
|
||||
// Inject web search trace context into env
|
||||
const traceEnv = createWebSearchTraceContext({
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
import { applyExtendedContextConfig } from '../config/extended-context-config';
|
||||
import { CLIProxyProvider } from '../types';
|
||||
import { CompositeTierConfig } from '../../config/unified-config-types';
|
||||
import { getWebSearchHookEnv } from '../../utils/websearch-manager';
|
||||
import {
|
||||
applyImageAnalysisRuntimeOverrides,
|
||||
getImageAnalysisHookEnv,
|
||||
@@ -78,6 +77,8 @@ export interface ProxyChainConfig {
|
||||
claudeConfigDir?: string;
|
||||
/** Execution-aware image analysis env prepared by the caller */
|
||||
imageAnalysisEnv?: Record<string, string>;
|
||||
/** WebSearch hook env captured from the launch configuration snapshot. */
|
||||
webSearchEnv?: Record<string, string>;
|
||||
/** Optional browser runtime env for Claude browser MCP reuse. */
|
||||
browserRuntimeEnv?: Record<string, string>;
|
||||
}
|
||||
@@ -246,6 +247,7 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
|
||||
compositeDefaultTier,
|
||||
claudeConfigDir,
|
||||
imageAnalysisEnv: resolvedImageAnalysisEnv,
|
||||
webSearchEnv = {},
|
||||
browserRuntimeEnv,
|
||||
} = config;
|
||||
|
||||
@@ -386,7 +388,6 @@ export function buildClaudeEnvironment(config: ProxyChainConfig): Record<string,
|
||||
};
|
||||
|
||||
// Add hook environment variables
|
||||
const webSearchEnv = getWebSearchHookEnv();
|
||||
const imageAnalysisEnv = resolvedImageAnalysisEnv ?? getImageAnalysisHookEnv(provider);
|
||||
|
||||
// Merge all environment variables (filter undefined values)
|
||||
|
||||
@@ -52,7 +52,7 @@ import {
|
||||
resolveCliproxyImageAnalysisEnv,
|
||||
} from './env-resolver';
|
||||
import { checkOrJoinProxy, registerProxySession } from './session-bridge';
|
||||
import { getWebSearchHookEnv } from '../../utils/websearch-manager';
|
||||
import { resolveWebSearchLaunchState } from '../../utils/websearch-manager';
|
||||
import {
|
||||
handleLogout,
|
||||
handleImport,
|
||||
@@ -276,10 +276,9 @@ export async function execClaudeWithCLIProxy(
|
||||
await handleImport(authCtx);
|
||||
|
||||
// Setup first-class CCS WebSearch runtime for non-strict user launches.
|
||||
const shouldDisplayWebSearchStatus = ensureWebSearchMcpForLaunch();
|
||||
if (shouldDisplayWebSearchStatus) {
|
||||
displayWebSearchStatus();
|
||||
}
|
||||
const webSearchLaunch = resolveWebSearchLaunchState();
|
||||
ensureWebSearchMcpForLaunch(webSearchLaunch.config);
|
||||
displayWebSearchStatus(webSearchLaunch.config);
|
||||
|
||||
// 3. Ensure OAuth completed (if provider requires it)
|
||||
const remoteAuthToken = proxyConfig.authToken?.trim();
|
||||
@@ -483,6 +482,7 @@ export async function execClaudeWithCLIProxy(
|
||||
compositeDefaultTier: cfg.compositeDefaultTier,
|
||||
claudeConfigDir: inheritedClaudeConfigDir,
|
||||
imageAnalysisEnv,
|
||||
webSearchEnv: webSearchLaunch.hookEnv,
|
||||
});
|
||||
|
||||
// 9b. Build env-dependent proxy chain (tool-sanitization + codex-reasoning)
|
||||
@@ -527,6 +527,7 @@ export async function execClaudeWithCLIProxy(
|
||||
compositeDefaultTier: cfg.compositeDefaultTier,
|
||||
claudeConfigDir: inheritedClaudeConfigDir,
|
||||
imageAnalysisEnv,
|
||||
webSearchEnv: webSearchLaunch.hookEnv,
|
||||
browserRuntimeEnv,
|
||||
});
|
||||
|
||||
@@ -542,7 +543,7 @@ export async function execClaudeWithCLIProxy(
|
||||
);
|
||||
}
|
||||
|
||||
const webSearchEnv = getWebSearchHookEnv();
|
||||
const webSearchEnv = webSearchLaunch.hookEnv;
|
||||
if (process.env.CCS_DEBUG) {
|
||||
logger.info('browser-env-keys', 'CCS_BROWSER_* keys in environment', {
|
||||
keys: Object.keys(env)
|
||||
@@ -580,6 +581,7 @@ export async function execClaudeWithCLIProxy(
|
||||
skipLocalAuth,
|
||||
sessionId,
|
||||
imageAnalysisMcpReady,
|
||||
webSearchLaunch,
|
||||
browserRuntimeEnv,
|
||||
inheritedClaudeConfigDir,
|
||||
codexReasoningProxy,
|
||||
|
||||
@@ -18,11 +18,11 @@ import { normalizeCopilotConfigWithWarnings } from './copilot-model-normalizer';
|
||||
import { CopilotStatus } from './types';
|
||||
import { fail, info, ok, warn } from '../utils/ui';
|
||||
import {
|
||||
getWebSearchHookEnv,
|
||||
appendThirdPartyWebSearchToolArgs,
|
||||
createWebSearchTraceContext,
|
||||
syncWebSearchMcpToConfigDir,
|
||||
} from '../utils/websearch-manager';
|
||||
import type { WebSearchLaunchState } from '../utils/websearch-manager';
|
||||
import {
|
||||
appendThirdPartyImageAnalysisToolArgs,
|
||||
ensureImageAnalysisMcpOrThrow,
|
||||
@@ -71,6 +71,17 @@ export async function getCopilotStatus(config: CopilotConfig): Promise<CopilotSt
|
||||
};
|
||||
}
|
||||
|
||||
export function buildCopilotClaudeLaunchArgs(
|
||||
claudeArgs: string[],
|
||||
imageAnalysisMcpReady: boolean,
|
||||
webSearchEnabled: boolean
|
||||
): string[] {
|
||||
const imageAnalysisArgs = imageAnalysisMcpReady
|
||||
? appendThirdPartyImageAnalysisToolArgs(claudeArgs)
|
||||
: claudeArgs;
|
||||
return appendThirdPartyWebSearchToolArgs(imageAnalysisArgs, webSearchEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate environment variables for Claude Code to use copilot-api.
|
||||
* Uses model mapping for opus/sonnet/haiku tiers if configured.
|
||||
@@ -185,7 +196,8 @@ export async function executeCopilotProfile(
|
||||
config: CopilotConfig,
|
||||
claudeArgs: string[],
|
||||
claudeConfigDir?: string,
|
||||
claudeCliPath: string = 'claude'
|
||||
claudeCliPath: string = 'claude',
|
||||
webSearchLaunch?: WebSearchLaunchState
|
||||
): Promise<number> {
|
||||
const { config: normalizedConfig, warnings } = normalizeCopilotConfigWithWarnings(config);
|
||||
|
||||
@@ -276,7 +288,7 @@ export async function executeCopilotProfile(
|
||||
syncImageAnalysisMcpToConfigDir(claudeConfigDir);
|
||||
|
||||
// Merge with current environment (global env first, copilot overrides, then hook env vars)
|
||||
const webSearchEnv = getWebSearchHookEnv();
|
||||
const webSearchEnv = webSearchLaunch?.hookEnv ?? {};
|
||||
const imageAnalysisResolution = await resolveCopilotImageAnalysisEnv();
|
||||
const imageAnalysisProvisioningFailed =
|
||||
!imageAnalysisMcpReady && imageAnalysisResolution.env.CCS_IMAGE_ANALYSIS_ENABLED === '1';
|
||||
@@ -305,10 +317,11 @@ export async function executeCopilotProfile(
|
||||
// Spawn Claude CLI
|
||||
const spawnStartedAt = Date.now();
|
||||
return new Promise((resolve) => {
|
||||
const imageAnalysisArgs = imageAnalysisMcpReady
|
||||
? appendThirdPartyImageAnalysisToolArgs(claudeArgs)
|
||||
: claudeArgs;
|
||||
const launchArgs = appendThirdPartyWebSearchToolArgs(imageAnalysisArgs);
|
||||
const launchArgs = buildCopilotClaudeLaunchArgs(
|
||||
claudeArgs,
|
||||
imageAnalysisMcpReady,
|
||||
webSearchLaunch?.enabled ?? true
|
||||
);
|
||||
const traceEnv = createWebSearchTraceContext({
|
||||
launcher: 'copilot.executor',
|
||||
args: launchArgs,
|
||||
|
||||
@@ -9,9 +9,9 @@ import { fail, info, ok } from '../utils/ui';
|
||||
import {
|
||||
appendThirdPartyWebSearchToolArgs,
|
||||
createWebSearchTraceContext,
|
||||
getWebSearchHookEnv,
|
||||
syncWebSearchMcpToConfigDir,
|
||||
} from '../utils/websearch-manager';
|
||||
import type { WebSearchLaunchState } from '../utils/websearch-manager';
|
||||
import { getImageAnalysisHookEnv, resolveImageAnalysisRuntimeStatus } from '../utils/hooks';
|
||||
import { stripClaudeCodeEnv } from '../utils/shell-executor';
|
||||
import { checkAuthStatus } from './cursor-auth';
|
||||
@@ -106,11 +106,19 @@ export async function resolveCursorImageAnalysisEnv(
|
||||
return { env, warning: null };
|
||||
}
|
||||
|
||||
export function buildCursorClaudeLaunchArgs(
|
||||
claudeArgs: string[],
|
||||
webSearchEnabled: boolean
|
||||
): string[] {
|
||||
return appendThirdPartyWebSearchToolArgs(claudeArgs, webSearchEnabled);
|
||||
}
|
||||
|
||||
export async function executeCursorProfile(
|
||||
config: CursorConfig,
|
||||
claudeArgs: string[],
|
||||
claudeConfigDir?: string,
|
||||
claudeCliPath = 'claude'
|
||||
claudeCliPath = 'claude',
|
||||
webSearchLaunch?: WebSearchLaunchState
|
||||
): Promise<number> {
|
||||
if (!config.enabled) {
|
||||
process.stderr.write(fail('Cursor integration is not enabled.') + '\n');
|
||||
@@ -163,7 +171,7 @@ export async function executeCursorProfile(
|
||||
const cursorEnv = generateCursorEnv(config, daemonToken, claudeConfigDir);
|
||||
const globalEnvConfig = getGlobalEnvConfig();
|
||||
const globalEnv = globalEnvConfig.enabled ? globalEnvConfig.env : {};
|
||||
const webSearchEnv = getWebSearchHookEnv();
|
||||
const webSearchEnv = webSearchLaunch?.hookEnv ?? {};
|
||||
const { env: imageAnalysisEnv, warning: imageAnalysisWarning } =
|
||||
await resolveCursorImageAnalysisEnv();
|
||||
const env = stripClaudeCodeEnv({
|
||||
@@ -184,7 +192,7 @@ export async function executeCursorProfile(
|
||||
syncWebSearchMcpToConfigDir(claudeConfigDir);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const launchArgs = appendThirdPartyWebSearchToolArgs(claudeArgs);
|
||||
const launchArgs = buildCursorClaudeLaunchArgs(claudeArgs, webSearchLaunch?.enabled ?? true);
|
||||
const traceEnv = createWebSearchTraceContext({
|
||||
launcher: 'cursor.executor',
|
||||
args: launchArgs,
|
||||
|
||||
@@ -55,8 +55,8 @@ import {
|
||||
appendWebSearchTrace,
|
||||
createWebSearchTraceContext,
|
||||
ensureWebSearchMcpForLaunch,
|
||||
getWebSearchHookEnv,
|
||||
readWebSearchTraceRecords,
|
||||
resolveWebSearchLaunchState,
|
||||
syncWebSearchMcpToConfigDir,
|
||||
} from '../utils/websearch-manager';
|
||||
import { getCcsDir, getGlobalEnvConfig, loadSettings } from '../config/config-loader-facade';
|
||||
@@ -159,7 +159,8 @@ export class HeadlessExecutor {
|
||||
);
|
||||
}
|
||||
|
||||
ensureWebSearchMcpForLaunch();
|
||||
const webSearchLaunch = resolveWebSearchLaunchState();
|
||||
ensureWebSearchMcpForLaunch(webSearchLaunch.config);
|
||||
const imageAnalysisMcpReady = ensureImageAnalysisMcpOrThrow();
|
||||
syncWebSearchMcpToConfigDir(inheritedClaudeConfigDir);
|
||||
syncImageAnalysisMcpToConfigDir(inheritedClaudeConfigDir);
|
||||
@@ -257,6 +258,7 @@ export class HeadlessExecutor {
|
||||
|
||||
let runtimeEnvVars: NodeJS.ProcessEnv = {
|
||||
...stripAnthropicRoutingEnv({ ...globalEnv, ...settingsEnv }, settingsEnv),
|
||||
...webSearchLaunch.hookEnv,
|
||||
...(inheritedClaudeConfigDir ? { CLAUDE_CONFIG_DIR: inheritedClaudeConfigDir } : {}),
|
||||
CCS_PROFILE_TYPE: 'settings',
|
||||
CCS_STRIP_INHERITED_ANTHROPIC_ENV: '1',
|
||||
@@ -380,7 +382,10 @@ export class HeadlessExecutor {
|
||||
const imageAnalysisArgs = imageAnalysisMcpReady
|
||||
? appendThirdPartyImageAnalysisToolArgs(args)
|
||||
: args;
|
||||
const launchArgs = appendThirdPartyWebSearchToolArgs(imageAnalysisArgs);
|
||||
const launchArgs = appendThirdPartyWebSearchToolArgs(
|
||||
imageAnalysisArgs,
|
||||
webSearchLaunch.enabled
|
||||
);
|
||||
const traceEnv = createWebSearchTraceContext({
|
||||
launcher: 'delegation.headless-executor',
|
||||
args: launchArgs,
|
||||
@@ -467,7 +472,6 @@ export class HeadlessExecutor {
|
||||
const cleanEnv = stripClaudeCodeEnv({
|
||||
...stripAnthropicRoutingEnv(process.env),
|
||||
...getClaudeLaunchEnvOverrides(),
|
||||
...getWebSearchHookEnv(),
|
||||
...runtimeEnvVars,
|
||||
...imageAnalysisEnv,
|
||||
...traceEnv,
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
*/
|
||||
|
||||
import { fail, info } from '../../utils/ui';
|
||||
import { ensureWebSearchMcpForLaunch } from '../../utils/websearch-manager';
|
||||
import {
|
||||
ensureWebSearchMcpForLaunch,
|
||||
resolveWebSearchLaunchState,
|
||||
} from '../../utils/websearch-manager';
|
||||
import { ensureImageAnalysisMcpOrThrow } from '../../utils/image-analysis';
|
||||
import {
|
||||
ensureProfileHooks as ensureImageAnalyzerHooks,
|
||||
@@ -23,7 +26,8 @@ export async function runCopilotFlow(ctx: ProfileDispatchContext): Promise<void>
|
||||
resolveProfileContinuityInheritance,
|
||||
} = ctx;
|
||||
|
||||
ensureWebSearchMcpForLaunch();
|
||||
const webSearchLaunch = resolveWebSearchLaunchState();
|
||||
ensureWebSearchMcpForLaunch(webSearchLaunch.config);
|
||||
const imageAnalysisMcpReady = ensureImageAnalysisMcpOrThrow();
|
||||
if (resolvedTarget === 'claude') {
|
||||
if (imageAnalysisMcpReady) {
|
||||
@@ -60,7 +64,8 @@ export async function runCopilotFlow(ctx: ProfileDispatchContext): Promise<void>
|
||||
copilotConfig,
|
||||
remainingArgs,
|
||||
continuityInheritance.claudeConfigDir,
|
||||
claudeCli
|
||||
claudeCli,
|
||||
webSearchLaunch
|
||||
);
|
||||
process.exit(exitCode);
|
||||
}
|
||||
@@ -5,7 +5,10 @@
|
||||
*/
|
||||
|
||||
import { fail, info } from '../../utils/ui';
|
||||
import { ensureWebSearchMcpForLaunch } from '../../utils/websearch-manager';
|
||||
import {
|
||||
ensureWebSearchMcpForLaunch,
|
||||
resolveWebSearchLaunchState,
|
||||
} from '../../utils/websearch-manager';
|
||||
import { ensureProfileHooks as ensureImageAnalyzerHooks } from '../../utils/hooks/image-analyzer-profile-hook-injector';
|
||||
import { installImageAnalyzerHook } from '../../utils/hooks';
|
||||
import type { ProfileDispatchContext } from '../dispatcher-context';
|
||||
@@ -19,7 +22,8 @@ export async function runCursorFlow(ctx: ProfileDispatchContext): Promise<void>
|
||||
resolveProfileContinuityInheritance,
|
||||
} = ctx;
|
||||
|
||||
ensureWebSearchMcpForLaunch();
|
||||
const webSearchLaunch = resolveWebSearchLaunchState();
|
||||
ensureWebSearchMcpForLaunch(webSearchLaunch.config);
|
||||
installImageAnalyzerHook();
|
||||
ensureImageAnalyzerHooks({
|
||||
profileName: profileInfo.name,
|
||||
@@ -48,7 +52,8 @@ export async function runCursorFlow(ctx: ProfileDispatchContext): Promise<void>
|
||||
cursorConfig,
|
||||
remainingArgs,
|
||||
continuityInheritance.claudeConfigDir,
|
||||
claudeCli
|
||||
claudeCli,
|
||||
webSearchLaunch
|
||||
);
|
||||
process.exit(exitCode);
|
||||
}
|
||||
@@ -16,10 +16,10 @@ import {
|
||||
import {
|
||||
ensureWebSearchMcpForLaunch,
|
||||
displayWebSearchStatus,
|
||||
getWebSearchHookEnv,
|
||||
syncWebSearchMcpToConfigDir,
|
||||
appendThirdPartyWebSearchToolArgs,
|
||||
createWebSearchTraceContext,
|
||||
resolveWebSearchLaunchState,
|
||||
} from '../../utils/websearch-manager';
|
||||
import {
|
||||
ensureImageAnalysisMcpOrThrow,
|
||||
@@ -94,18 +94,16 @@ export async function runSettingsFlow(ctx: ProfileDispatchContext): Promise<void
|
||||
if (browserAttachRuntime?.warning) {
|
||||
process.stderr.write(`${warn(browserAttachRuntime.warning)}\n`);
|
||||
}
|
||||
let shouldDisplayWebSearchStatus = true;
|
||||
const webSearchLaunch = resolveWebSearchLaunchState();
|
||||
if (resolvedTarget === 'claude') {
|
||||
shouldDisplayWebSearchStatus = ensureWebSearchMcpForLaunch();
|
||||
ensureWebSearchMcpForLaunch(webSearchLaunch.config);
|
||||
if (browserRuntimeEnv) {
|
||||
ensureBrowserMcpOrThrow();
|
||||
}
|
||||
}
|
||||
|
||||
// Display WebSearch status (single line, equilibrium UX)
|
||||
if (shouldDisplayWebSearchStatus) {
|
||||
displayWebSearchStatus();
|
||||
}
|
||||
displayWebSearchStatus(webSearchLaunch.config);
|
||||
|
||||
const continuityInheritance =
|
||||
resolvedTarget === 'claude'
|
||||
@@ -275,7 +273,7 @@ export async function runSettingsFlow(ctx: ProfileDispatchContext): Promise<void
|
||||
targetRemainingArgs,
|
||||
});
|
||||
|
||||
const webSearchEnv = getWebSearchHookEnv();
|
||||
const webSearchEnv = webSearchLaunch.hookEnv;
|
||||
|
||||
// Get global env vars (DISABLE_TELEMETRY, etc.) for third-party profiles
|
||||
const globalEnvConfig = getGlobalEnvConfig();
|
||||
@@ -395,11 +393,11 @@ export async function runSettingsFlow(ctx: ProfileDispatchContext): Promise<void
|
||||
? stripClaudeSubcommandSessionArgs(browserArgs)
|
||||
: browserArgs;
|
||||
const launchArgs = isSubcommand
|
||||
? appendThirdPartyWebSearchToolArgs(subcommandArgs)
|
||||
? appendThirdPartyWebSearchToolArgs(subcommandArgs, webSearchLaunch.enabled)
|
||||
: [
|
||||
'--settings',
|
||||
launchSettings.settingsPath,
|
||||
...appendThirdPartyWebSearchToolArgs(browserArgs),
|
||||
...appendThirdPartyWebSearchToolArgs(browserArgs, webSearchLaunch.enabled),
|
||||
];
|
||||
const traceEnv = createWebSearchTraceContext({
|
||||
launcher: 'ccs.settings-profile.proxy',
|
||||
@@ -418,8 +416,12 @@ export async function runSettingsFlow(ctx: ProfileDispatchContext): Promise<void
|
||||
const isSubcommand = isClaudeSubcommandInvocation(browserArgs);
|
||||
const subcommandArgs = isSubcommand ? stripClaudeSubcommandSessionArgs(browserArgs) : browserArgs;
|
||||
const launchArgs = isSubcommand
|
||||
? appendThirdPartyWebSearchToolArgs(subcommandArgs)
|
||||
: ['--settings', expandedSettingsPath, ...appendThirdPartyWebSearchToolArgs(browserArgs)];
|
||||
? appendThirdPartyWebSearchToolArgs(subcommandArgs, webSearchLaunch.enabled)
|
||||
: [
|
||||
'--settings',
|
||||
expandedSettingsPath,
|
||||
...appendThirdPartyWebSearchToolArgs(browserArgs, webSearchLaunch.enabled),
|
||||
];
|
||||
const traceEnv = createWebSearchTraceContext({
|
||||
launcher: 'ccs.settings-profile',
|
||||
args: launchArgs,
|
||||
|
||||
@@ -73,6 +73,8 @@ export {
|
||||
|
||||
// Re-export Claude launch arg helpers
|
||||
export { appendThirdPartyWebSearchToolArgs } from './websearch/claude-tool-args';
|
||||
export { resolveWebSearchLaunchState } from './websearch/launch-state';
|
||||
export type { WebSearchConfigSnapshot, WebSearchLaunchState } from './websearch/launch-state';
|
||||
|
||||
// Re-export trace helpers
|
||||
export {
|
||||
|
||||
@@ -135,9 +135,14 @@ function ensureWebSearchSteeringPrompt(args: string[]): string[] {
|
||||
return [...optionArgs, ...steeringArgs, ...trailingArgs];
|
||||
}
|
||||
|
||||
export function appendThirdPartyWebSearchToolArgs(args: string[]): string[] {
|
||||
export function appendThirdPartyWebSearchToolArgs(
|
||||
args: string[],
|
||||
webSearchEnabled = true
|
||||
): string[] {
|
||||
// Claude subcommands (agents, doctor, mcp, ...) reject top-level session flags
|
||||
// like `--append-system-prompt` and `--disallowedTools`. Issue #1218.
|
||||
if (isClaudeSubcommandInvocation(args)) return args;
|
||||
return ensureWebSearchSteeringPrompt(ensureDisallowedNativeWebSearchTool(args));
|
||||
|
||||
const disallowedArgs = ensureDisallowedNativeWebSearchTool(args);
|
||||
return webSearchEnabled ? ensureWebSearchSteeringPrompt(disallowedArgs) : disallowedArgs;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import { getCcsHooksDir } from '../config-manager';
|
||||
import { getClaudeSettingsPath } from '../claude-config-path';
|
||||
import { isCcsWebSearchHook, deduplicateCcsHooks } from './hook-utils';
|
||||
import { getWebSearchConfig } from '../../config/config-loader-facade';
|
||||
import type { WebSearchConfigSnapshot } from './launch-state';
|
||||
|
||||
// Hook file name
|
||||
const WEBSEARCH_HOOK = 'websearch-transformer.cjs';
|
||||
@@ -35,9 +36,10 @@ export function getHookPath(): string {
|
||||
* Get WebSearch hook configuration for settings.json
|
||||
* Timeout is computed from max provider timeout in config.yaml + buffer
|
||||
*/
|
||||
export function getWebSearchHookConfig(): Record<string, unknown> {
|
||||
export function getWebSearchHookConfig(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
): Record<string, unknown> {
|
||||
const hookPath = getHookPath();
|
||||
const wsConfig = getWebSearchConfig();
|
||||
|
||||
// Compute max timeout from enabled providers
|
||||
const timeouts: number[] = [];
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { normalizeSearxngBaseUrl } from './types';
|
||||
import { resolveAllowedWebSearchTraceFile } from './trace';
|
||||
import { getWebSearchConfig } from '../../config/config-loader-facade';
|
||||
import type { WebSearchConfigSnapshot } from './launch-state';
|
||||
|
||||
/**
|
||||
* Get environment variables for WebSearch hook configuration.
|
||||
@@ -17,8 +18,9 @@ import { getWebSearchConfig } from '../../config/config-loader-facade';
|
||||
*
|
||||
* @returns Record of environment variables to set before spawning Claude
|
||||
*/
|
||||
export function getWebSearchHookEnv(): Record<string, string> {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
export function getWebSearchHookEnv(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
): Record<string, string> {
|
||||
const env: Record<string, string> = {
|
||||
CCS_WEBSEARCH_ENABLED: '0',
|
||||
CCS_WEBSEARCH_SKIP: '0',
|
||||
|
||||
@@ -13,6 +13,7 @@ import { info, warn } from '../ui';
|
||||
import { getCcsDir, getCcsHooksDir } from '../config-manager';
|
||||
import { getHookPath } from './hook-config';
|
||||
import { getWebSearchConfig } from '../../config/config-loader-facade';
|
||||
import type { WebSearchConfigSnapshot } from './launch-state';
|
||||
|
||||
// Re-export from hook-config for backward compatibility
|
||||
export { getHookPath, getWebSearchHookConfig } from './hook-config';
|
||||
@@ -76,10 +77,10 @@ export function hasWebSearchHook(): boolean {
|
||||
*
|
||||
* @returns true if hook installed successfully
|
||||
*/
|
||||
export function installWebSearchHook(): boolean {
|
||||
export function installWebSearchHook(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
): boolean {
|
||||
try {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
|
||||
// Skip if disabled
|
||||
if (!wsConfig.enabled) {
|
||||
if (process.env.CCS_DEBUG) {
|
||||
|
||||
@@ -71,6 +71,8 @@ export {
|
||||
|
||||
// Claude launch args
|
||||
export { appendThirdPartyWebSearchToolArgs } from './claude-tool-args';
|
||||
export { resolveWebSearchLaunchState } from './launch-state';
|
||||
export type { WebSearchConfigSnapshot, WebSearchLaunchState } from './launch-state';
|
||||
|
||||
// Trace helpers
|
||||
export {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { getWebSearchConfig } from '../../config/config-loader-facade';
|
||||
import { getWebSearchHookEnv } from './hook-env';
|
||||
|
||||
export type WebSearchConfigSnapshot = ReturnType<typeof getWebSearchConfig>;
|
||||
|
||||
export interface WebSearchLaunchState {
|
||||
config: WebSearchConfigSnapshot;
|
||||
enabled: boolean;
|
||||
hookEnv: Record<string, string>;
|
||||
}
|
||||
|
||||
export function resolveWebSearchLaunchState(): WebSearchLaunchState {
|
||||
const config = getWebSearchConfig();
|
||||
return {
|
||||
config,
|
||||
enabled: config.enabled,
|
||||
hookEnv: getWebSearchHookEnv(config),
|
||||
};
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
isClaudeUserConfigLockUnavailableError as isLockUnavailableError,
|
||||
withClaudeUserConfigLock,
|
||||
} from '../claude-user-config-lock';
|
||||
import type { WebSearchConfigSnapshot } from './launch-state';
|
||||
|
||||
const WEBSEARCH_MCP_SERVER = 'ccs-websearch-server.cjs';
|
||||
const WEBSEARCH_MCP_SERVER_NAME = 'ccs-websearch';
|
||||
@@ -189,14 +190,15 @@ function removeManagedServerConfig(configPath: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
export function installWebSearchMcpServer(): boolean {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
export function installWebSearchMcpServer(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
): boolean {
|
||||
if (!wsConfig.enabled) {
|
||||
appendWebSearchTrace('websearch_mcp_install_skipped', { reason: 'disabled' });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!installWebSearchHook()) {
|
||||
if (!installWebSearchHook(wsConfig)) {
|
||||
appendWebSearchTrace('websearch_mcp_install_failed', { reason: 'hook_unavailable' });
|
||||
if (process.env.CCS_DEBUG) {
|
||||
process.stderr.write(
|
||||
@@ -267,8 +269,9 @@ export function installWebSearchMcpServer(): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
export function ensureWebSearchMcpConfig(): boolean {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
export function ensureWebSearchMcpConfig(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
): boolean {
|
||||
if (!wsConfig.enabled) {
|
||||
appendWebSearchTrace('websearch_mcp_config_skipped', { reason: 'disabled' });
|
||||
return false;
|
||||
@@ -366,15 +369,16 @@ export function ensureWebSearchMcpConfig(): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
export function ensureWebSearchMcp(): boolean {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
export function ensureWebSearchMcp(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
): boolean {
|
||||
if (!wsConfig.enabled) {
|
||||
appendWebSearchTrace('websearch_mcp_ensure_skipped', { reason: 'disabled' });
|
||||
return false;
|
||||
}
|
||||
|
||||
const installed = installWebSearchMcpServer();
|
||||
const configured = installed && ensureWebSearchMcpConfig();
|
||||
const installed = installWebSearchMcpServer(wsConfig);
|
||||
const configured = installed && ensureWebSearchMcpConfig(wsConfig);
|
||||
appendWebSearchTrace('websearch_mcp_ensure_result', { installed, configured });
|
||||
return installed && configured;
|
||||
}
|
||||
@@ -428,13 +432,14 @@ export function uninstallWebSearchMcp(): boolean {
|
||||
return removedConfig || removedServer;
|
||||
}
|
||||
|
||||
export function ensureWebSearchMcpOrThrow(): void {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
export function ensureWebSearchMcpOrThrow(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
): void {
|
||||
if (!wsConfig.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ensureWebSearchMcp()) {
|
||||
if (!ensureWebSearchMcp(wsConfig)) {
|
||||
throw new Error('WebSearch is enabled, but CCS could not prepare the local WebSearch tool.');
|
||||
}
|
||||
}
|
||||
@@ -447,7 +452,6 @@ export function ensureWebSearchMcpOrThrow(): void {
|
||||
* native WebSearch and inject fallback steering while the constrained MCP
|
||||
* search path is unavailable.
|
||||
*/
|
||||
export function ensureWebSearchMcpForLaunch(): boolean {
|
||||
ensureWebSearchMcpOrThrow();
|
||||
return true;
|
||||
export function ensureWebSearchMcpForLaunch(wsConfig: WebSearchConfigSnapshot): void {
|
||||
ensureWebSearchMcpOrThrow(wsConfig);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { getOpenCodeCliStatus } from './opencode-cli';
|
||||
import { getWebSearchApiKeyStates } from './provider-secrets';
|
||||
import { normalizeSearxngBaseUrl, type WebSearchCliInfo, type WebSearchStatus } from './types';
|
||||
import { getWebSearchConfig } from '../../config/config-loader-facade';
|
||||
import type { WebSearchConfigSnapshot } from './launch-state';
|
||||
|
||||
const PROVIDER_STATE_FILE = 'websearch-provider-state.json';
|
||||
|
||||
@@ -112,8 +113,7 @@ function applyCooldownStatus(
|
||||
};
|
||||
}
|
||||
|
||||
function getLegacyProviderStatuses(): WebSearchCliInfo[] {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
function getLegacyProviderStatuses(wsConfig: WebSearchConfigSnapshot): WebSearchCliInfo[] {
|
||||
const agyStatus = getAgyCliStatus();
|
||||
const geminiStatus = getGeminiCliStatus();
|
||||
const grokStatus = getGrokCliStatus();
|
||||
@@ -197,8 +197,9 @@ function getLegacyProviderStatuses(): WebSearchCliInfo[] {
|
||||
/**
|
||||
* Get all WebSearch providers with their current status.
|
||||
*/
|
||||
export function getWebSearchCliProviders(): WebSearchCliInfo[] {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
export function getWebSearchCliProviders(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
): WebSearchCliInfo[] {
|
||||
const apiKeyStates = getWebSearchApiKeyStates();
|
||||
const cooldowns = readProviderCooldowns();
|
||||
const providers: WebSearchCliInfo[] = [
|
||||
@@ -283,7 +284,7 @@ export function getWebSearchCliProviders(): WebSearchCliInfo[] {
|
||||
},
|
||||
];
|
||||
|
||||
return [...providers, ...getLegacyProviderStatuses()].map((provider) =>
|
||||
return [...providers, ...getLegacyProviderStatuses(wsConfig)].map((provider) =>
|
||||
applyCooldownStatus(provider, cooldowns)
|
||||
);
|
||||
}
|
||||
@@ -355,17 +356,20 @@ export function buildWebSearchReadiness(
|
||||
/**
|
||||
* Get WebSearch readiness status for display.
|
||||
*/
|
||||
export function getWebSearchReadiness(): WebSearchStatus {
|
||||
const wsConfig = getWebSearchConfig();
|
||||
const providers = getWebSearchCliProviders();
|
||||
export function getWebSearchReadiness(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
): WebSearchStatus {
|
||||
const providers = getWebSearchCliProviders(wsConfig);
|
||||
return buildWebSearchReadiness(wsConfig.enabled, providers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display WebSearch status (single line, equilibrium UX).
|
||||
*/
|
||||
export function displayWebSearchStatus(): void {
|
||||
const status = getWebSearchReadiness();
|
||||
export function displayWebSearchStatus(
|
||||
wsConfig: WebSearchConfigSnapshot = getWebSearchConfig()
|
||||
): void {
|
||||
const status = getWebSearchReadiness(wsConfig);
|
||||
|
||||
switch (status.readiness) {
|
||||
case 'ready':
|
||||
|
||||
Reference in new issue
Block a user