refactor: streamline logging and context handling in team services
- Replaced logger.info calls with console.log in TeamMemberLogsFinder for performance logging. - Simplified environment variable handling in TeamProvisioningService by removing unnecessary context settings. - Updated stopTeam method to accept a signal parameter for improved process termination control. - Enhanced model handling in team dialogs to support new context management logic, ensuring consistent behavior across components. - Adjusted teamSlice to reflect changes in model parsing and context handling, improving overall state management.
This commit is contained in:
parent
1073a51039
commit
e6b7a6fe53
11 changed files with 70 additions and 74 deletions
|
|
@ -151,7 +151,7 @@ export class TeamMemberLogsFinder {
|
|||
const tDiscovery = performance.now();
|
||||
|
||||
if (!discovery) {
|
||||
logger.info(
|
||||
console.log(
|
||||
`[perf] findLogsForTask(${taskId}) discovery=null ${(tDiscovery - t0).toFixed(0)}ms`
|
||||
);
|
||||
return [];
|
||||
|
|
@ -310,7 +310,7 @@ export class TeamMemberLogsFinder {
|
|||
);
|
||||
const tTotal = performance.now();
|
||||
|
||||
logger.info(
|
||||
console.log(
|
||||
`[perf] findLogsForTask(${taskId}@${teamName}) ` +
|
||||
`total=${(tTotal - t0).toFixed(0)}ms | ` +
|
||||
`discovery=${(tDiscovery - t0).toFixed(0)}ms | ` +
|
||||
|
|
@ -344,7 +344,7 @@ export class TeamMemberLogsFinder {
|
|||
const tDiscovery = performance.now();
|
||||
|
||||
if (!discovery) {
|
||||
logger.info(
|
||||
console.log(
|
||||
`[perf] findLogFileRefsForTask(${taskId}) discovery=null ${(tDiscovery - t0).toFixed(0)}ms`
|
||||
);
|
||||
return [];
|
||||
|
|
@ -490,7 +490,7 @@ export class TeamMemberLogsFinder {
|
|||
const sortedRefs = [...refs].sort((a, b) => b.sortTime - a.sortTime);
|
||||
const tTotal = performance.now();
|
||||
|
||||
logger.info(
|
||||
console.log(
|
||||
`[perf] findLogFileRefsForTask(${taskId}@${teamName}) ` +
|
||||
`total=${(tTotal - t0).toFixed(0)}ms | ` +
|
||||
`discovery=${(tDiscovery - t0).toFixed(0)}ms | ` +
|
||||
|
|
|
|||
|
|
@ -2779,10 +2779,7 @@ export class TeamProvisioningService {
|
|||
try {
|
||||
child = spawnCli(claudePath, spawnArgs, {
|
||||
cwd: request.cwd,
|
||||
env: {
|
||||
...shellEnv,
|
||||
...(request.limitContext ? { CLAUDE_CODE_DISABLE_1M_CONTEXT: '1' } : {}),
|
||||
},
|
||||
env: { ...shellEnv },
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
@ -2800,10 +2797,7 @@ export class TeamProvisioningService {
|
|||
claudePath,
|
||||
args: spawnArgs,
|
||||
cwd: request.cwd,
|
||||
env: {
|
||||
...shellEnv,
|
||||
...(request.limitContext ? { CLAUDE_CODE_DISABLE_1M_CONTEXT: '1' } : {}),
|
||||
},
|
||||
env: { ...shellEnv },
|
||||
prompt,
|
||||
};
|
||||
|
||||
|
|
@ -3186,10 +3180,7 @@ export class TeamProvisioningService {
|
|||
try {
|
||||
child = spawnCli(claudePath, launchArgs, {
|
||||
cwd: request.cwd,
|
||||
env: {
|
||||
...shellEnv,
|
||||
...(request.limitContext ? { CLAUDE_CODE_DISABLE_1M_CONTEXT: '1' } : {}),
|
||||
},
|
||||
env: { ...shellEnv },
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
@ -3209,10 +3200,7 @@ export class TeamProvisioningService {
|
|||
claudePath,
|
||||
args: launchArgs,
|
||||
cwd: request.cwd,
|
||||
env: {
|
||||
...shellEnv,
|
||||
...(request.limitContext ? { CLAUDE_CODE_DISABLE_1M_CONTEXT: '1' } : {}),
|
||||
},
|
||||
env: { ...shellEnv },
|
||||
prompt,
|
||||
};
|
||||
|
||||
|
|
@ -4360,7 +4348,7 @@ export class TeamProvisioningService {
|
|||
/**
|
||||
* Stop the running process for a team. No-op if team is not running.
|
||||
*/
|
||||
stopTeam(teamName: string): void {
|
||||
stopTeam(teamName: string, signal?: NodeJS.Signals): void {
|
||||
const runId = this.getTrackedRunId(teamName);
|
||||
if (!runId) {
|
||||
return;
|
||||
|
|
@ -4378,25 +4366,25 @@ export class TeamProvisioningService {
|
|||
run.cancelRequested = true;
|
||||
// Note: do NOT call stdin.end() before kill — EOF triggers CLI's graceful
|
||||
// shutdown which deletes team files (config.json, inboxes/, tasks/).
|
||||
// SIGTERM alone kills the process before cleanup runs, preserving files.
|
||||
killProcessTree(run.child);
|
||||
// SIGTERM/SIGKILL kills the process before cleanup runs, preserving files.
|
||||
killProcessTree(run.child, signal);
|
||||
const progress = updateProgress(run, 'disconnected', 'Team stopped by user');
|
||||
run.onProgress(progress);
|
||||
this.cleanupRun(run);
|
||||
logger.info(`[${teamName}] Process stopped by user`);
|
||||
logger.info(`[${teamName}] Process stopped (signal=${signal ?? 'SIGTERM'})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all running team processes. Called during app shutdown to kill
|
||||
* processes via SIGTERM before the OS closes stdin (which would trigger
|
||||
* CLI's graceful cleanup and delete team files).
|
||||
* Stop all running team processes. Called during app shutdown.
|
||||
* Uses SIGKILL (uncatchable) to guarantee the process dies instantly
|
||||
* without any cleanup — prevents CLI from deleting team files on exit.
|
||||
*/
|
||||
stopAllTeams(): void {
|
||||
const alive = this.getAliveTeams();
|
||||
if (alive.length === 0) return;
|
||||
logger.info(`Stopping all team processes on shutdown: ${alive.join(', ')}`);
|
||||
logger.info(`Killing all team processes on shutdown (SIGKILL): ${alive.join(', ')}`);
|
||||
for (const teamName of alive) {
|
||||
this.stopTeam(teamName);
|
||||
this.stopTeam(teamName, 'SIGKILL');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1168,7 +1168,7 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
|||
const MODEL_LABELS: Record<string, string> = {
|
||||
default: 'Default',
|
||||
opus: 'Opus 4.6',
|
||||
sonnet: 'Sonnet 4.5',
|
||||
sonnet: 'Sonnet 4.6',
|
||||
haiku: 'Haiku 4.5',
|
||||
};
|
||||
const modelLabel = MODEL_LABELS[launchParams.model] ?? launchParams.model;
|
||||
|
|
|
|||
|
|
@ -546,7 +546,10 @@ export const CreateTeamDialog = ({
|
|||
[memberColorMap, members, soloTeam]
|
||||
);
|
||||
|
||||
const effectiveModel = useMemo(() => computeEffectiveTeamModel(selectedModel), [selectedModel]);
|
||||
const effectiveModel = useMemo(
|
||||
() => computeEffectiveTeamModel(selectedModel, limitContext),
|
||||
[selectedModel, limitContext]
|
||||
);
|
||||
|
||||
const sanitizedTeamName = sanitizeTeamName(teamName.trim());
|
||||
|
||||
|
|
@ -561,7 +564,6 @@ export const CreateTeamDialog = ({
|
|||
model: effectiveModel,
|
||||
effort: (selectedEffort as EffortLevel) || undefined,
|
||||
skipPermissions,
|
||||
limitContext: limitContext || undefined,
|
||||
worktree: worktreeEnabled && worktreeName.trim() ? worktreeName.trim() : undefined,
|
||||
extraCliArgs: customArgs.trim() || undefined,
|
||||
}),
|
||||
|
|
@ -576,7 +578,6 @@ export const CreateTeamDialog = ({
|
|||
effectiveModel,
|
||||
selectedEffort,
|
||||
skipPermissions,
|
||||
limitContext,
|
||||
worktreeEnabled,
|
||||
worktreeName,
|
||||
customArgs,
|
||||
|
|
@ -985,6 +986,7 @@ export const CreateTeamDialog = ({
|
|||
id="create-limit-context"
|
||||
checked={limitContext}
|
||||
onCheckedChange={setLimitContext}
|
||||
disabled={selectedModel === 'haiku'}
|
||||
/>
|
||||
<SkipPermissionsCheckbox
|
||||
id="create-skip-permissions"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
const model = computeEffectiveTeamModel(selectedModel, limitContext);
|
||||
if (model) args.push('--model', model);
|
||||
if (selectedEffort) args.push('--effort', selectedEffort);
|
||||
if (!clearContext) args.push('--resume', '<previous>');
|
||||
return args;
|
||||
}, [isLaunch, skipPermissions, selectedModel, selectedEffort, clearContext]);
|
||||
}, [isLaunch, skipPermissions, selectedModel, limitContext, selectedEffort, clearContext]);
|
||||
|
||||
const launchOptionalSummary = useMemo(() => {
|
||||
if (!isLaunch) return [];
|
||||
|
|
@ -590,10 +590,9 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
|
|||
teamName: effectiveTeamName,
|
||||
cwd: effectiveCwd,
|
||||
prompt: promptDraft.value.trim() || undefined,
|
||||
model: computeEffectiveTeamModel(selectedModel),
|
||||
model: computeEffectiveTeamModel(selectedModel, limitContext),
|
||||
effort: (selectedEffort as EffortLevel) || undefined,
|
||||
clearContext: clearContext || undefined,
|
||||
limitContext: limitContext || undefined,
|
||||
skipPermissions,
|
||||
worktree: worktreeEnabled && worktreeName.trim() ? worktreeName.trim() : undefined,
|
||||
extraCliArgs: customArgs.trim() || undefined,
|
||||
|
|
@ -958,6 +957,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
|
|||
id="launch-limit-context"
|
||||
checked={limitContext}
|
||||
onCheckedChange={setLimitContext}
|
||||
disabled={selectedModel === 'haiku'}
|
||||
/>
|
||||
<SkipPermissionsCheckbox
|
||||
id="dialog-skip-permissions"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export const LimitContextCheckbox: React.FC<LimitContextCheckboxProps> = ({
|
|||
}`}
|
||||
>
|
||||
Limit context to 200K tokens
|
||||
{disabled && <span className="text-[10px] italic">(always 200K for this model)</span>}
|
||||
</Label>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -78,16 +78,24 @@ const ACTIVE_PROVIDER = PROVIDERS[0];
|
|||
const MODEL_OPTIONS = [
|
||||
{ value: '', label: 'Default' },
|
||||
{ value: 'opus', label: 'Opus 4.6' },
|
||||
{ value: 'sonnet', label: 'Sonnet 4.5' },
|
||||
{ value: 'sonnet', label: 'Sonnet 4.6' },
|
||||
{ value: 'haiku', label: 'Haiku 4.5' },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Returns the effective model string for team provisioning.
|
||||
* Simply maps empty selection to undefined.
|
||||
* Computes the effective model string for team provisioning.
|
||||
* By default adds [1m] suffix for 1M context (Opus/Sonnet).
|
||||
* When limitContext=true, returns base model without [1m] (200K context).
|
||||
* Haiku does not support 1M — always returned as-is.
|
||||
*/
|
||||
export function computeEffectiveTeamModel(selectedModel: string): string | undefined {
|
||||
return selectedModel || undefined;
|
||||
export function computeEffectiveTeamModel(
|
||||
selectedModel: string,
|
||||
limitContext: boolean
|
||||
): string | undefined {
|
||||
const base = selectedModel || undefined;
|
||||
if (limitContext) return base;
|
||||
if (base === 'haiku') return base;
|
||||
return base ? `${base}[1m]` : 'sonnet[1m]';
|
||||
}
|
||||
|
||||
export interface TeamModelSelectorProps {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ interface KanbanColumnProps {
|
|||
className?: string;
|
||||
headerClassName?: string;
|
||||
bodyClassName?: string;
|
||||
headerDragClassName?: string;
|
||||
headerAccessory?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
|
@ -23,6 +24,7 @@ export const KanbanColumn = ({
|
|||
className,
|
||||
headerClassName,
|
||||
bodyClassName,
|
||||
headerDragClassName,
|
||||
headerAccessory,
|
||||
children,
|
||||
}: KanbanColumnProps): React.JSX.Element => {
|
||||
|
|
@ -35,20 +37,27 @@ export const KanbanColumn = ({
|
|||
)}
|
||||
style={bodyBg ? { backgroundColor: bodyBg } : undefined}
|
||||
>
|
||||
{count > 0 && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="absolute -right-2 -top-2 z-10 min-w-5 px-1.5 py-0 text-[10px] font-medium leading-5"
|
||||
>
|
||||
{count}
|
||||
</Badge>
|
||||
)}
|
||||
<header
|
||||
className={cn('border-b border-[var(--color-border)] px-3 py-2 pr-14', headerClassName)}
|
||||
className={cn(
|
||||
'border-b border-[var(--color-border)] px-3 py-2',
|
||||
headerClassName,
|
||||
headerDragClassName
|
||||
)}
|
||||
style={headerBg ? { backgroundColor: headerBg } : undefined}
|
||||
>
|
||||
<h4 className="flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text)]">
|
||||
{icon}
|
||||
{title}
|
||||
</h4>
|
||||
<div className="absolute right-2 top-2 z-10 flex items-center gap-2">
|
||||
{headerAccessory}
|
||||
<Badge variant="secondary" className="px-2 py-0.5 text-[10px] font-normal">
|
||||
{count}
|
||||
</Badge>
|
||||
</div>
|
||||
{headerAccessory && <div className="absolute right-2 top-2 z-10">{headerAccessory}</div>}
|
||||
</header>
|
||||
<div className={cn('flex max-h-[480px] flex-col gap-1.5 overflow-auto p-2', bodyClassName)}>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import ReactGridLayout, { WidthProvider } from 'react-grid-layout/legacy';
|
|||
|
||||
import { usePersistedGridLayout } from '@renderer/hooks/usePersistedGridLayout';
|
||||
import { browserGridLayoutRepository } from '@renderer/services/layout-system/BrowserGridLayoutRepository';
|
||||
import { GripVertical } from 'lucide-react';
|
||||
|
||||
import { KanbanColumn } from './KanbanColumn';
|
||||
|
||||
|
|
@ -213,15 +212,7 @@ const LoadedKanbanGridLayout = ({
|
|||
className="flex h-full min-h-0 flex-col"
|
||||
headerClassName="shrink-0"
|
||||
bodyClassName="kanban-grid-no-drag min-h-0 max-h-none flex-1"
|
||||
headerAccessory={
|
||||
<button
|
||||
type="button"
|
||||
className="kanban-grid-drag-handle inline-flex cursor-grab items-center justify-center rounded-sm p-1 text-[var(--color-text-muted)] transition-colors hover:text-[var(--color-text)] active:cursor-grabbing"
|
||||
aria-label={`Drag ${column.title} column`}
|
||||
>
|
||||
<GripVertical size={14} />
|
||||
</button>
|
||||
}
|
||||
headerDragClassName="kanban-grid-drag-handle cursor-grab active:cursor-grabbing"
|
||||
>
|
||||
{column.content}
|
||||
</KanbanColumn>
|
||||
|
|
|
|||
|
|
@ -542,15 +542,16 @@ function saveLaunchParams(teamName: string, params: TeamLaunchParams): void {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse raw model string — simply returns the base model name.
|
||||
* The [1m] suffix is no longer used; context limiting is handled via env var.
|
||||
* Parse raw model string from TeamCreateRequest/TeamLaunchRequest.
|
||||
* E.g. 'opus[1m]' → { model: 'opus', limitContext: false }
|
||||
* 'sonnet' → { model: 'sonnet', limitContext: true }
|
||||
* Models without [1m] suffix mean context is limited to 200K.
|
||||
*/
|
||||
function parseModelString(raw?: string): { model?: string } {
|
||||
if (!raw) return {};
|
||||
// Strip legacy [1m] suffix if present in saved data
|
||||
function parseModelString(raw?: string): { model?: string; limitContext: boolean } {
|
||||
if (!raw) return { limitContext: true };
|
||||
const match = raw.match(/^(\w+)\[1m\]$/);
|
||||
if (match) return { model: match[1] };
|
||||
return { model: raw };
|
||||
if (match) return { model: match[1], limitContext: false };
|
||||
return { model: raw, limitContext: true };
|
||||
}
|
||||
|
||||
function loadToolApprovalSettings(): ToolApprovalSettings {
|
||||
|
|
@ -1422,11 +1423,11 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
const response = await unwrapIpc('team:create', () => api.teams.createTeam(request));
|
||||
|
||||
// Persist per-team launch params (model, effort, limit context)
|
||||
const { model: baseModel } = parseModelString(request.model);
|
||||
const { model: baseModel, limitContext } = parseModelString(request.model);
|
||||
const params: TeamLaunchParams = {
|
||||
model: baseModel || 'default',
|
||||
effort: request.effort,
|
||||
limitContext: request.limitContext,
|
||||
limitContext,
|
||||
};
|
||||
saveLaunchParams(request.teamName, params);
|
||||
set((state) => ({
|
||||
|
|
@ -1559,11 +1560,11 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
const response = await unwrapIpc('team:launch', () => api.teams.launchTeam(request));
|
||||
|
||||
// Persist per-team launch params (model, effort, limit context)
|
||||
const { model: baseModel } = parseModelString(request.model);
|
||||
const { model: baseModel, limitContext } = parseModelString(request.model);
|
||||
const params: TeamLaunchParams = {
|
||||
model: baseModel || 'default',
|
||||
effort: request.effort,
|
||||
limitContext: request.limitContext,
|
||||
limitContext,
|
||||
};
|
||||
saveLaunchParams(request.teamName, params);
|
||||
set((state) => ({
|
||||
|
|
|
|||
|
|
@ -401,8 +401,6 @@ export interface TeamLaunchRequest {
|
|||
effort?: EffortLevel;
|
||||
/** When true, skip --resume and start a fresh session (clears context memory). */
|
||||
clearContext?: boolean;
|
||||
/** When true, set CLAUDE_CODE_DISABLE_1M_CONTEXT=1 to limit context to 200K tokens. */
|
||||
limitContext?: boolean;
|
||||
/** When false, run WITHOUT --dangerously-skip-permissions (manual tool approval). Default: true. */
|
||||
skipPermissions?: boolean;
|
||||
/** Worktree name — CLI: --worktree <name>. */
|
||||
|
|
@ -525,8 +523,6 @@ export interface TeamCreateRequest {
|
|||
prompt?: string;
|
||||
model?: string;
|
||||
effort?: EffortLevel;
|
||||
/** When true, set CLAUDE_CODE_DISABLE_1M_CONTEXT=1 to limit context to 200K tokens. */
|
||||
limitContext?: boolean;
|
||||
/** When false, run WITHOUT --dangerously-skip-permissions (manual tool approval). Default: true. */
|
||||
skipPermissions?: boolean;
|
||||
/** Worktree name — CLI: --worktree <name>. */
|
||||
|
|
|
|||
Loading…
Reference in a new issue