From 1073a51039011dc80d87c1a86f342e4c860a902a Mon Sep 17 00:00:00 2001 From: iliya Date: Sat, 14 Mar 2026 17:54:49 +0200 Subject: [PATCH] refactor: update context handling in team provisioning and UI components - Replaced references to extended context with limit context in TeamProvisioningService and related components, ensuring consistent terminology. - Updated LaunchTeamDialog and CreateTeamDialog to reflect the new limit context functionality, enhancing user clarity. - Removed the deprecated ExtendedContextCheckbox component and integrated its functionality into the new LimitContextCheckbox. - Adjusted teamSlice to accommodate changes in launch parameters, streamlining the context management process. --- .../services/team/TeamMemberLogsFinder.ts | 84 ++++++++++++++++++- .../services/team/TeamProvisioningService.ts | 16 +++- .../components/team/TeamDetailView.tsx | 4 +- .../team/dialogs/CreateTeamDialog.tsx | 2 +- .../team/dialogs/LaunchTeamDialog.tsx | 32 +++---- ...tCheckbox.tsx => LimitContextCheckbox.tsx} | 0 src/renderer/store/slices/teamSlice.ts | 28 +++---- 7 files changed, 126 insertions(+), 40 deletions(-) rename src/renderer/components/team/dialogs/{ExtendedContextCheckbox.tsx => LimitContextCheckbox.tsx} (100%) diff --git a/src/main/services/team/TeamMemberLogsFinder.ts b/src/main/services/team/TeamMemberLogsFinder.ts index a01401be..313136b9 100644 --- a/src/main/services/team/TeamMemberLogsFinder.ts +++ b/src/main/services/team/TeamMemberLogsFinder.ts @@ -145,8 +145,17 @@ export class TeamMemberLogsFinder { since?: string; } ): Promise { + const t0 = performance.now(); + const discovery = await this.discoverProjectSessions(teamName); - if (!discovery) return []; + const tDiscovery = performance.now(); + + if (!discovery) { + logger.info( + `[perf] findLogsForTask(${taskId}) discovery=null ${(tDiscovery - t0).toFixed(0)}ms` + ); + return []; + } const sinceMs = this.deriveSinceMs(options); const { projectDir, projectId, config, sessionIds, knownMembers } = discovery; @@ -171,6 +180,12 @@ export class TeamMemberLogsFinder { // file missing or unreadable } } + const tLead = performance.now(); + + let totalFiles = 0; + let mentionHits = 0; + let cacheHits = 0; + const cacheSnapshotBefore = this.fileMentionsCache.size; for (const sessionId of sessionIds) { const subagentsDir = path.join(projectDir, sessionId, 'subagents'); @@ -183,9 +198,15 @@ export class TeamMemberLogsFinder { for (const file of files) { if (!file.startsWith('agent-') || !file.endsWith('.jsonl')) continue; if (file.startsWith('agent-acompact')) continue; + totalFiles++; const filePath = path.join(subagentsDir, file); - if (!(await this.fileMentionsTaskIdCached(filePath, teamName, taskId, false, sinceMs))) + const cacheSizeBefore = this.fileMentionsCache.size; + if (!(await this.fileMentionsTaskIdCached(filePath, teamName, taskId, false, sinceMs))) { + if (this.fileMentionsCache.size === cacheSizeBefore) cacheHits++; continue; + } + if (this.fileMentionsCache.size === cacheSizeBefore) cacheHits++; + mentionHits++; const attribution = await this.attributeSubagent(filePath, knownMembers); if (!attribution) continue; const summary = await this.parseSubagentSummary( @@ -199,6 +220,7 @@ export class TeamMemberLogsFinder { if (summary) results.push(summary); } } + const tScan = performance.now(); const normalizedOwner = typeof options?.owner === 'string' ? options.owner.trim() : options?.owner; @@ -281,10 +303,25 @@ export class TeamMemberLogsFinder { } } } + const tOwner = performance.now(); - return results.sort( + const sorted = results.sort( (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime() ); + const tTotal = performance.now(); + + logger.info( + `[perf] findLogsForTask(${taskId}@${teamName}) ` + + `total=${(tTotal - t0).toFixed(0)}ms | ` + + `discovery=${(tDiscovery - t0).toFixed(0)}ms | ` + + `lead=${(tLead - tDiscovery).toFixed(0)}ms | ` + + `scan=${(tScan - tLead).toFixed(0)}ms (${totalFiles} files, ${mentionHits} hits, ${cacheHits} cache) | ` + + `owner=${(tOwner - tScan).toFixed(0)}ms | ` + + `sessions=${sessionIds.length} | cache=${cacheSnapshotBefore}→${this.fileMentionsCache.size} | ` + + `results=${sorted.length}` + ); + + return sorted; } /** @@ -301,8 +338,17 @@ export class TeamMemberLogsFinder { since?: string; } ): Promise<{ filePath: string; memberName: string }[]> { + const t0 = performance.now(); + const discovery = await this.discoverProjectSessions(teamName); - if (!discovery) return []; + const tDiscovery = performance.now(); + + if (!discovery) { + logger.info( + `[perf] findLogFileRefsForTask(${taskId}) discovery=null ${(tDiscovery - t0).toFixed(0)}ms` + ); + return []; + } const sinceMs = this.deriveSinceMs(options); const { projectDir, config, sessionIds, knownMembers } = discovery; @@ -330,6 +376,10 @@ export class TeamMemberLogsFinder { // file missing or unreadable } } + const tLead = performance.now(); + + let totalFiles = 0; + let mentionHits = 0; for (const sessionId of sessionIds) { const subagentsDir = path.join(projectDir, sessionId, 'subagents'); @@ -342,11 +392,13 @@ export class TeamMemberLogsFinder { for (const file of files) { if (!file.startsWith('agent-') || !file.endsWith('.jsonl')) continue; if (file.startsWith('agent-acompact')) continue; + totalFiles++; const filePath = path.join(subagentsDir, file); if (!(await this.fileMentionsTaskIdCached(filePath, teamName, taskId, false, sinceMs))) { continue; } + mentionHits++; const attribution = await this.attributeSubagent(filePath, knownMembers); if (!attribution) continue; @@ -357,6 +409,7 @@ export class TeamMemberLogsFinder { ); } } + const tScan = performance.now(); const normalizedOwner = typeof options?.owner === 'string' ? options.owner.trim() : options?.owner; @@ -432,8 +485,21 @@ export class TeamMemberLogsFinder { ); } } + const tOwner = performance.now(); const sortedRefs = [...refs].sort((a, b) => b.sortTime - a.sortTime); + const tTotal = performance.now(); + + logger.info( + `[perf] findLogFileRefsForTask(${taskId}@${teamName}) ` + + `total=${(tTotal - t0).toFixed(0)}ms | ` + + `discovery=${(tDiscovery - t0).toFixed(0)}ms | ` + + `lead=${(tLead - tDiscovery).toFixed(0)}ms | ` + + `scan=${(tScan - tLead).toFixed(0)}ms (${totalFiles} files, ${mentionHits} hits) | ` + + `owner=${(tOwner - tScan).toFixed(0)}ms | ` + + `sessions=${sessionIds.length} | results=${sortedRefs.length}` + ); + return sortedRefs.map(({ filePath, memberName }) => ({ filePath, memberName })); } @@ -1067,6 +1133,16 @@ export class TeamMemberLogsFinder { } catch { // Skip malformed lines } + + // Early exit: reliable signal found and description extracted — no need to scan further. + // Only process_team and routing_sender trigger this; teammate_id is unreliable (identifies + // the message sender, not the agent) so we keep scanning for better signals. + if ( + description && + signals.some((s) => s.source === 'process_team' || s.source === 'routing_sender') + ) { + break; + } } if (signals.length === 0) return null; diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 35beae1b..0d92932b 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -2779,7 +2779,10 @@ export class TeamProvisioningService { try { child = spawnCli(claudePath, spawnArgs, { cwd: request.cwd, - env: { ...shellEnv }, + env: { + ...shellEnv, + ...(request.limitContext ? { CLAUDE_CODE_DISABLE_1M_CONTEXT: '1' } : {}), + }, stdio: ['pipe', 'pipe', 'pipe'], }); } catch (error) { @@ -2797,7 +2800,10 @@ export class TeamProvisioningService { claudePath, args: spawnArgs, cwd: request.cwd, - env: { ...shellEnv }, + env: { + ...shellEnv, + ...(request.limitContext ? { CLAUDE_CODE_DISABLE_1M_CONTEXT: '1' } : {}), + }, prompt, }; @@ -3182,6 +3188,7 @@ export class TeamProvisioningService { cwd: request.cwd, env: { ...shellEnv, + ...(request.limitContext ? { CLAUDE_CODE_DISABLE_1M_CONTEXT: '1' } : {}), }, stdio: ['pipe', 'pipe', 'pipe'], }); @@ -3202,7 +3209,10 @@ export class TeamProvisioningService { claudePath, args: launchArgs, cwd: request.cwd, - env: { ...shellEnv }, + env: { + ...shellEnv, + ...(request.limitContext ? { CLAUDE_CODE_DISABLE_1M_CONTEXT: '1' } : {}), + }, prompt, }; diff --git a/src/renderer/components/team/TeamDetailView.tsx b/src/renderer/components/team/TeamDetailView.tsx index 93c45d93..3dca3c4d 100644 --- a/src/renderer/components/team/TeamDetailView.tsx +++ b/src/renderer/components/team/TeamDetailView.tsx @@ -1175,8 +1175,8 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele const effortLabel = launchParams.effort ? launchParams.effort.charAt(0).toUpperCase() + launchParams.effort.slice(1) : ''; - const extLabel = launchParams.extendedContext ? '1M' : ''; - const parts = [modelLabel, effortLabel, extLabel].filter(Boolean).join(' '); + const limitLabel = launchParams.limitContext ? '200K' : ''; + const parts = [modelLabel, effortLabel, limitLabel].filter(Boolean).join(' '); return ( {parts} diff --git a/src/renderer/components/team/dialogs/CreateTeamDialog.tsx b/src/renderer/components/team/dialogs/CreateTeamDialog.tsx index f27db6e7..358c25d5 100644 --- a/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/CreateTeamDialog.tsx @@ -36,7 +36,7 @@ import { AlertTriangle, CheckCircle2, Info, Loader2, X } from 'lucide-react'; import { AdvancedCliSection } from './AdvancedCliSection'; import { EffortLevelSelector } from './EffortLevelSelector'; -import { LimitContextCheckbox } from './ExtendedContextCheckbox'; +import { LimitContextCheckbox } from './LimitContextCheckbox'; import { OptionalSettingsSection } from './OptionalSettingsSection'; import { ProjectPathSelector } from './ProjectPathSelector'; import { SkipPermissionsCheckbox } from './SkipPermissionsCheckbox'; diff --git a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx index 8ca227eb..963f27e8 100644 --- a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { api } from '@renderer/api'; -import { ExtendedContextCheckbox } from '@renderer/components/team/dialogs/ExtendedContextCheckbox'; +import { LimitContextCheckbox } from '@renderer/components/team/dialogs/LimitContextCheckbox'; import { SkipPermissionsCheckbox } from '@renderer/components/team/dialogs/SkipPermissionsCheckbox'; import { Button } from '@renderer/components/ui/button'; import { Checkbox } from '@renderer/components/ui/checkbox'; @@ -170,8 +170,8 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen // Launch-only state // --------------------------------------------------------------------------- - const [extendedContext, setExtendedContextRaw] = useState( - () => localStorage.getItem('team:lastExtendedContext') === 'true' + const [limitContext, setLimitContextRaw] = useState( + () => localStorage.getItem('team:lastLimitContext') === 'true' ); const [clearContext, setClearContext] = useState(false); const [conflictDismissed, setConflictDismissed] = useState(false); @@ -235,9 +235,9 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen localStorage.setItem('team:lastSelectedModel', value); }; - const setExtendedContext = (value: boolean): void => { - setExtendedContextRaw(value); - localStorage.setItem('team:lastExtendedContext', String(value)); + const setLimitContext = (value: boolean): void => { + setLimitContextRaw(value); + localStorage.setItem('team:lastLimitContext', String(value)); }; const setSkipPermissions = (value: boolean): void => { @@ -512,12 +512,12 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen args.push('--verbose', '--setting-sources', 'user,project,local'); args.push('--mcp-config', '', '--disallowedTools', 'TeamDelete,TodoWrite'); if (skipPermissions) args.push('--dangerously-skip-permissions'); - const model = computeEffectiveTeamModel(selectedModel, extendedContext); + const model = computeEffectiveTeamModel(selectedModel); if (model) args.push('--model', model); if (selectedEffort) args.push('--effort', selectedEffort); if (!clearContext) args.push('--resume', ''); return args; - }, [isLaunch, skipPermissions, selectedModel, extendedContext, selectedEffort, clearContext]); + }, [isLaunch, skipPermissions, selectedModel, selectedEffort, clearContext]); const launchOptionalSummary = useMemo(() => { if (!isLaunch) return []; @@ -526,7 +526,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen if (promptDraft.value.trim()) summary.push('Lead prompt'); if (selectedModel) summary.push(`Model: ${selectedModel}`); if (selectedEffort) summary.push(`Effort: ${selectedEffort}`); - if (extendedContext) summary.push('Extended context'); + if (limitContext) summary.push('Limited to 200K context'); if (skipPermissions) summary.push('Auto-approve tools'); if (clearContext) summary.push('Fresh session'); if (worktreeEnabled && worktreeName.trim()) summary.push(`Worktree: ${worktreeName.trim()}`); @@ -537,7 +537,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen promptDraft.value, selectedModel, selectedEffort, - extendedContext, + limitContext, skipPermissions, clearContext, worktreeEnabled, @@ -590,9 +590,10 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen teamName: effectiveTeamName, cwd: effectiveCwd, prompt: promptDraft.value.trim() || undefined, - model: computeEffectiveTeamModel(selectedModel, extendedContext), + model: computeEffectiveTeamModel(selectedModel), effort: (selectedEffort as EffortLevel) || undefined, clearContext: clearContext || undefined, + limitContext: limitContext || undefined, skipPermissions, worktree: worktreeEnabled && worktreeName.trim() ? worktreeName.trim() : undefined, extraCliArgs: customArgs.trim() || undefined, @@ -953,11 +954,10 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen onValueChange={setSelectedEffort} id="dialog-effort" /> - = (set, } const response = await unwrapIpc('team:create', () => api.teams.createTeam(request)); - // Persist per-team launch params (model, effort, extended context) - const { model: baseModel, extendedContext } = parseModelString(request.model); + // Persist per-team launch params (model, effort, limit context) + const { model: baseModel } = parseModelString(request.model); const params: TeamLaunchParams = { model: baseModel || 'default', effort: request.effort, - extendedContext, + limitContext: request.limitContext, }; saveLaunchParams(request.teamName, params); set((state) => ({ @@ -1558,12 +1558,12 @@ export const createTeamSlice: StateCreator = (set, try { const response = await unwrapIpc('team:launch', () => api.teams.launchTeam(request)); - // Persist per-team launch params (model, effort, extended context) - const { model: baseModel, extendedContext } = parseModelString(request.model); + // Persist per-team launch params (model, effort, limit context) + const { model: baseModel } = parseModelString(request.model); const params: TeamLaunchParams = { model: baseModel || 'default', effort: request.effort, - extendedContext, + limitContext: request.limitContext, }; saveLaunchParams(request.teamName, params); set((state) => ({