feat: add API settings page for managing providers, endpoints, cache, model pools, and CLI settings

feat: implement semantic install dialog for CodexLens with GPU mode selection
feat: add radio group component for GPU mode selection
feat: update navigation and localization for API settings and semantic install
This commit is contained in:
catlog22
2026-02-02 11:16:19 +08:00
parent e4b627bc76
commit a54246a46f
10 changed files with 2648 additions and 14 deletions

1
ccw/src/tools/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.ace-tool/

View File

@@ -748,6 +748,11 @@ async function executeCliTool(
conversationId = `${Date.now()}-${tool}`;
}
// Generate transaction ID for concurrent session disambiguation
// This will be injected into the prompt for exact session matching during resume
const transactionId = generateTransactionId(conversationId);
debugLog('TX_ID', `Generated transaction ID: ${transactionId}`, { conversationId });
// Determine resume strategy (native vs prompt-concat vs hybrid)
let resumeDecision: ResumeDecision | null = null;
let nativeResumeConfig: NativeResumeConfig | undefined;
@@ -811,6 +816,11 @@ async function executeCliTool(
}
}
// Inject transaction ID at the start of the final prompt for session tracking
// This enables exact session matching during parallel execution scenarios
finalPrompt = injectTransactionId(finalPrompt, transactionId);
debugLog('TX_ID', `Injected transaction ID into prompt`, { transactionId, promptLength: finalPrompt.length });
// Check tool availability
const toolStatus = await checkToolAvailability(tool);
if (!toolStatus.available) {
@@ -1207,11 +1217,11 @@ async function executeCliTool(
}
// Track native session after execution (awaited to prevent process hang)
// Pass prompt for precise matching in parallel execution scenarios
// Pass prompt and transactionId for precise matching in parallel execution scenarios
try {
const nativeSession = await trackNewSession(tool, new Date(startTime), workingDir, prompt);
const nativeSession = await trackNewSession(tool, new Date(startTime), workingDir, prompt, transactionId);
if (nativeSession) {
// Save native session mapping
// Save native session mapping with transaction ID
try {
store.saveNativeSessionMapping({
ccw_id: conversationId,
@@ -1219,6 +1229,7 @@ async function executeCliTool(
native_session_id: nativeSession.sessionId,
native_session_path: nativeSession.filePath,
project_hash: nativeSession.projectHash,
transaction_id: transactionId,
created_at: new Date().toISOString()
});
} catch (err) {

View File

@@ -1010,6 +1010,7 @@ export class CliHistoryStore {
native_session_id: row.native_session_id,
native_session_path: row.native_session_path,
project_hash: row.project_hash,
transaction_id: row.transaction_id,
created_at: row.created_at
};
}
@@ -1033,6 +1034,7 @@ export class CliHistoryStore {
native_session_id: row.native_session_id,
native_session_path: row.native_session_path,
project_hash: row.project_hash,
transaction_id: row.transaction_id,
created_at: row.created_at
};
}

View File

@@ -73,34 +73,50 @@ abstract class SessionDiscoverer {
* @param beforeTimestamp - Filter sessions created after this time
* @param workingDir - Project working directory
* @param prompt - Optional prompt content for precise matching (fallback)
* @param transactionId - Optional transaction ID for exact matching (highest priority)
*/
async trackNewSession(
beforeTimestamp: Date,
workingDir: string,
prompt?: string
prompt?: string,
transactionId?: string
): Promise<NativeSession | null> {
const sessions = this.getSessions({
workingDir,
afterTimestamp: beforeTimestamp,
limit: 10 // Get more candidates for prompt matching
limit: 10 // Get more candidates for matching
});
if (sessions.length === 0) return null;
// If only one session or no prompt provided, return the latest
if (sessions.length === 1 || !prompt) {
// Priority 1: Match by transaction ID (exact match, highest confidence)
if (transactionId) {
const matched = this.matchSessionByTransactionId(transactionId, sessions);
if (matched) {
return matched;
}
// Transaction ID provided but no match - fall through to other methods
}
// If only one session, return it
if (sessions.length === 1) {
return sessions[0];
}
// Try to match by prompt content (fallback for parallel execution)
const matched = this.matchSessionByPrompt(sessions, prompt);
// Priority 2: Match by prompt content (fallback for parallel execution)
if (prompt) {
const matched = this.matchSessionByPrompt(sessions, prompt);
if (matched) {
return matched;
}
}
// Warn if multiple sessions and no prompt match found (low confidence)
if (!matched && sessions.length > 1) {
// Warn if multiple sessions and no match found (low confidence)
if (sessions.length > 1) {
console.warn(`[ccw] Session tracking: multiple candidates found (${sessions.length}), using latest session`);
}
return matched || sessions[0]; // Fallback to latest if no match
return sessions[0]; // Fallback to latest if no match
}
/**
@@ -125,6 +141,33 @@ abstract class SessionDiscoverer {
return null;
}
/**
* Match session by transaction ID
* Extracts transaction ID from session's first user message and compares
* @param txId - Transaction ID to match (format: ccw-tx-${conversationId}-${uniquePart})
* @param sessions - Candidate sessions to search
* @returns Matching session or null
*/
matchSessionByTransactionId(txId: string, sessions: NativeSession[]): NativeSession | null {
if (!txId) return null;
for (const session of sessions) {
try {
const userMessage = this.extractFirstUserMessage(session.filePath);
if (userMessage) {
// Extract transaction ID from user message
const match = userMessage.match(/\[CCW-TX-ID:\s+([^\]]+)\]/);
if (match && match[1] === txId) {
return session;
}
}
} catch {
// Skip sessions that can't be read
}
}
return null;
}
/**
* Extract first user message from session file
* Override in subclass for tool-specific format
@@ -956,16 +999,18 @@ export function findNativeSessionById(
* @param beforeTimestamp - Filter sessions created after this time
* @param workingDir - Project working directory
* @param prompt - Optional prompt for precise matching in parallel execution
* @param transactionId - Optional transaction ID for exact session matching
*/
export async function trackNewSession(
tool: string,
beforeTimestamp: Date,
workingDir: string,
prompt?: string
prompt?: string,
transactionId?: string
): Promise<NativeSession | null> {
const discoverer = discoverers[tool];
if (!discoverer) return null;
return discoverer.trackNewSession(beforeTimestamp, workingDir, prompt);
return discoverer.trackNewSession(beforeTimestamp, workingDir, prompt, transactionId);
}
/**