mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 06:17:27 +00:00
fix(websearch): close review gaps in managed runtime
This commit is contained in:
1 parent
de7171d810
commit
cae5b710b8
15 files changed
+299
-60
No files matched your search
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user