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.
This commit is contained in:
parent
da4d98ec2b
commit
1073a51039
7 changed files with 126 additions and 40 deletions
|
|
@ -145,8 +145,17 @@ export class TeamMemberLogsFinder {
|
|||
since?: string;
|
||||
}
|
||||
): Promise<MemberLogSummary[]> {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-[var(--color-surface-raised)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-text-secondary)]">
|
||||
{parts}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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', '<auto>', '--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', '<previous>');
|
||||
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"
|
||||
/>
|
||||
<ExtendedContextCheckbox
|
||||
id="launch-extended-context"
|
||||
checked={extendedContext}
|
||||
onCheckedChange={setExtendedContext}
|
||||
disabled={selectedModel === 'haiku'}
|
||||
<LimitContextCheckbox
|
||||
id="launch-limit-context"
|
||||
checked={limitContext}
|
||||
onCheckedChange={setLimitContext}
|
||||
/>
|
||||
<SkipPermissionsCheckbox
|
||||
id="dialog-skip-permissions"
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ export interface GlobalTaskDetailState {
|
|||
export interface TeamLaunchParams {
|
||||
model?: string; // 'opus' | 'sonnet' | 'haiku'
|
||||
effort?: EffortLevel;
|
||||
extendedContext?: boolean;
|
||||
limitContext?: boolean;
|
||||
}
|
||||
|
||||
export interface TeamSlice {
|
||||
|
|
@ -542,15 +542,15 @@ function saveLaunchParams(teamName: string, params: TeamLaunchParams): void {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse raw model string from TeamLaunchRequest back into base model + extended context flag.
|
||||
* E.g. 'opus[1m]' → { model: 'opus', extendedContext: true }
|
||||
* 'sonnet' → { model: 'sonnet', extendedContext: false }
|
||||
* Parse raw model string — simply returns the base model name.
|
||||
* The [1m] suffix is no longer used; context limiting is handled via env var.
|
||||
*/
|
||||
function parseModelString(raw?: string): { model?: string; extendedContext: boolean } {
|
||||
if (!raw) return { extendedContext: false };
|
||||
function parseModelString(raw?: string): { model?: string } {
|
||||
if (!raw) return {};
|
||||
// Strip legacy [1m] suffix if present in saved data
|
||||
const match = raw.match(/^(\w+)\[1m\]$/);
|
||||
if (match) return { model: match[1], extendedContext: true };
|
||||
return { model: raw, extendedContext: false };
|
||||
if (match) return { model: match[1] };
|
||||
return { model: raw };
|
||||
}
|
||||
|
||||
function loadToolApprovalSettings(): ToolApprovalSettings {
|
||||
|
|
@ -1421,12 +1421,12 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (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<AppState, [], [], TeamSlice> = (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) => ({
|
||||
|
|
|
|||
Loading…
Reference in a new issue