fix(ci): restore dev validation gates

This commit is contained in:
777genius 2026-05-04 09:12:53 +03:00
parent b92c8c2f54
commit 92a1c2067b
33 changed files with 106 additions and 69 deletions

View file

@ -120,6 +120,7 @@ import {
import { startEventLoopLagMonitor } from './services/infrastructure/EventLoopLagMonitor'; import { startEventLoopLagMonitor } from './services/infrastructure/EventLoopLagMonitor';
import { HttpServer } from './services/infrastructure/HttpServer'; import { HttpServer } from './services/infrastructure/HttpServer';
import { clearAutoResumeService } from './services/team/AutoResumeService'; import { clearAutoResumeService } from './services/team/AutoResumeService';
import { LaunchIoGovernor } from './services/team/LaunchIoGovernor';
import { OpenCodeBridgeCommandClient } from './services/team/opencode/bridge/OpenCodeBridgeCommandClient'; import { OpenCodeBridgeCommandClient } from './services/team/opencode/bridge/OpenCodeBridgeCommandClient';
import { import {
createOpenCodeBridgeCommandLeaseStore, createOpenCodeBridgeCommandLeaseStore,
@ -136,16 +137,15 @@ import {
clearTeamControlApiState, clearTeamControlApiState,
writeTeamControlApiState, writeTeamControlApiState,
} from './services/team/TeamControlApiState'; } from './services/team/TeamControlApiState';
import { TeamInboxReader } from './services/team/TeamInboxReader';
import { getTeamDataWorkerClient } from './services/team/TeamDataWorkerClient'; import { getTeamDataWorkerClient } from './services/team/TeamDataWorkerClient';
import { getTeamFsWorkerClient } from './services/team/TeamFsWorkerClient'; import { getTeamFsWorkerClient } from './services/team/TeamFsWorkerClient';
import { TeamInboxReader } from './services/team/TeamInboxReader';
import { TeamMemberRuntimeAdvisoryService } from './services/team/TeamMemberRuntimeAdvisoryService'; import { TeamMemberRuntimeAdvisoryService } from './services/team/TeamMemberRuntimeAdvisoryService';
import { import {
createTeamReconcileDrainScheduler, createTeamReconcileDrainScheduler,
type TeamReconcileTrigger, type TeamReconcileTrigger,
} from './services/team/TeamReconcileDrainScheduler'; } from './services/team/TeamReconcileDrainScheduler';
import { TeamSentMessagesStore } from './services/team/TeamSentMessagesStore'; import { TeamSentMessagesStore } from './services/team/TeamSentMessagesStore';
import { LaunchIoGovernor } from './services/team/LaunchIoGovernor';
import { getAppIconPath } from './utils/appIcon'; import { getAppIconPath } from './utils/appIcon';
import { import {
getClaudeBasePath, getClaudeBasePath,
@ -647,7 +647,7 @@ async function runStartupJobsBounded<T>(
if (isShutdownStarted()) { if (isShutdownStarted()) {
return; return;
} }
await run(items[index]!); await run(items[index]);
} }
}); });
await Promise.allSettled(workers); await Promise.allSettled(workers);

View file

@ -125,6 +125,10 @@ import {
initializeAutoResumeService, initializeAutoResumeService,
planRateLimitAutoResume, planRateLimitAutoResume,
} from '../services/team/AutoResumeService'; } from '../services/team/AutoResumeService';
import {
cloneLaunchIoGovernorPayload,
type LaunchIoGovernor,
} from '../services/team/LaunchIoGovernor';
import { import {
buildReplaceMembersDiff, buildReplaceMembersDiff,
buildReplaceMembersSummaryMessage, buildReplaceMembersSummaryMessage,
@ -136,10 +140,6 @@ import { TeamMetaStore } from '../services/team/TeamMetaStore';
import { buildAddMemberSpawnMessage } from '../services/team/TeamProvisioningService'; import { buildAddMemberSpawnMessage } from '../services/team/TeamProvisioningService';
import { TeamTaskAttachmentStore } from '../services/team/TeamTaskAttachmentStore'; import { TeamTaskAttachmentStore } from '../services/team/TeamTaskAttachmentStore';
import { TeamWorktreeGitService } from '../services/team/TeamWorktreeGitService'; import { TeamWorktreeGitService } from '../services/team/TeamWorktreeGitService';
import {
cloneLaunchIoGovernorPayload,
type LaunchIoGovernor,
} from '../services/team/LaunchIoGovernor';
import { import {
validateFromField, validateFromField,
@ -983,7 +983,6 @@ async function handleGetData(
} else { } else {
noteHeavyTeamDataWorkerFallback('teams:getData'); noteHeavyTeamDataWorkerFallback('teams:getData');
data = await getTeamDataService().getTeamData(tn); data = await getTeamDataService().getTeamData(tn);
dataSource = 'main-unavailable';
} }
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error); const message = error instanceof Error ? error.message : String(error);

View file

@ -23,7 +23,7 @@ import { stripAgentBlocks } from '@shared/constants/agentBlocks';
import { getMemberColorByName, MEMBER_COLOR_HUE } from '@shared/constants/memberColors'; import { getMemberColorByName, MEMBER_COLOR_HUE } from '@shared/constants/memberColors';
import { isLeadMember } from '@shared/utils/leadDetection'; import { isLeadMember } from '@shared/utils/leadDetection';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import { Notification as ElectronNotification, nativeImage } from 'electron'; import { nativeImage, Notification as ElectronNotification } from 'electron';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
import * as fsp from 'fs/promises'; import * as fsp from 'fs/promises';
@ -279,7 +279,7 @@ function truncateNotificationText(value: string, maxLength: number): string {
} }
function extractTaskRef(summary: string): string | null { function extractTaskRef(summary: string): string | null {
const match = summary.match(/#([A-Za-z0-9][A-Za-z0-9-]*)/); const match = /#([A-Za-z0-9][A-Za-z0-9-]*)/.exec(summary);
return match ? `#${match[1]}` : null; return match ? `#${match[1]}` : null;
} }

View file

@ -1,6 +1,6 @@
import { randomUUID } from 'crypto';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { randomUUID } from 'crypto';
import { mergeJsonSettingsObjects, parseJsonSettingsObject } from './cliSettingsArgs'; import { mergeJsonSettingsObjects, parseJsonSettingsObject } from './cliSettingsArgs';
@ -24,7 +24,8 @@ export function splitSettingsJsonArgs(args: string[]): SplitSettingsJsonArgsResu
const settingsFragments: TeamRuntimeSettingsJson[] = []; const settingsFragments: TeamRuntimeSettingsJson[] = [];
const passthroughArgs: string[] = []; const passthroughArgs: string[] = [];
for (let index = 0; index < args.length; index += 1) { let index = 0;
while (index < args.length) {
const arg = args[index]; const arg = args[index];
if (arg === '--settings') { if (arg === '--settings') {
const value = args[index + 1]; const value = args[index + 1];
@ -32,11 +33,11 @@ export function splitSettingsJsonArgs(args: string[]): SplitSettingsJsonArgsResu
const parsed = parseJsonSettingsObject(value); const parsed = parseJsonSettingsObject(value);
if (parsed) { if (parsed) {
settingsFragments.push(parsed); settingsFragments.push(parsed);
index += 1; index += 2;
continue; continue;
} }
passthroughArgs.push(arg, value); passthroughArgs.push(arg, value);
index += 1; index += 2;
continue; continue;
} }
} }
@ -52,6 +53,7 @@ export function splitSettingsJsonArgs(args: string[]): SplitSettingsJsonArgsResu
} }
passthroughArgs.push(arg); passthroughArgs.push(arg);
index += 1;
} }
return { settingsFragments, passthroughArgs }; return { settingsFragments, passthroughArgs };
@ -119,7 +121,7 @@ async function writeSettingsFile(
export async function materializeTeamRuntimeSettingsBundle(input: { export async function materializeTeamRuntimeSettingsBundle(input: {
teamName: string; teamName: string;
providerId: TeamProviderId; providerId: TeamProviderId;
baseSettings?: Array<TeamRuntimeSettingsJson | null | undefined>; baseSettings?: (TeamRuntimeSettingsJson | null | undefined)[];
anthropicHelper?: AnthropicTeamApiKeyHelperMaterial | null; anthropicHelper?: AnthropicTeamApiKeyHelperMaterial | null;
}): Promise<TeamRuntimeSettingsBundle | null> { }): Promise<TeamRuntimeSettingsBundle | null> {
const fragments = [...(input.baseSettings ?? [])].filter( const fragments = [...(input.baseSettings ?? [])].filter(

View file

@ -198,12 +198,8 @@ export class TeamFsWorkerClient {
)}` )}`
); );
this.failWorker(worker, timeoutError); this.failWorker(worker, timeoutError);
try { // Terminate and recreate on next call - worker may be stuck in native IO.
// Terminate and recreate on next call - worker may be stuck in native IO. void worker.terminate().catch(() => undefined);
worker.terminate().catch(() => undefined);
} catch {
// ignore
}
reject(timeoutError); reject(timeoutError);
}, WORKER_CALL_TIMEOUT_MS); }, WORKER_CALL_TIMEOUT_MS);

View file

@ -13,13 +13,13 @@ import {
BOARD_TASK_CHANGES_DIRNAME, BOARD_TASK_CHANGES_DIRNAME,
BOARD_TASK_LOG_FRESHNESS_DIRNAME, BOARD_TASK_LOG_FRESHNESS_DIRNAME,
BOARD_TASK_LOG_FRESHNESS_FILE_SUFFIX, BOARD_TASK_LOG_FRESHNESS_FILE_SUFFIX,
MAX_PENDING_UNKNOWN_ROOT_REFRESH_ATTEMPTS,
MAX_PENDING_UNKNOWN_ROOT_SESSIONS,
PENDING_UNKNOWN_ROOT_SESSION_TTL_MS,
classifyLogSourceWatcherEvent, classifyLogSourceWatcherEvent,
getRelativeLogSourceParts, getRelativeLogSourceParts,
isAgentTranscriptFileName, isAgentTranscriptFileName,
MAX_PENDING_UNKNOWN_ROOT_REFRESH_ATTEMPTS,
MAX_PENDING_UNKNOWN_ROOT_SESSIONS,
normalizeLogSourceSessionId, normalizeLogSourceSessionId,
PENDING_UNKNOWN_ROOT_SESSION_TTL_MS,
} from './teamLogSourceWatchScope'; } from './teamLogSourceWatchScope';
import type { TeamLogSourceLiveContext, TeamMemberLogsFinder } from './TeamMemberLogsFinder'; import type { TeamLogSourceLiveContext, TeamMemberLogsFinder } from './TeamMemberLogsFinder';
@ -420,18 +420,21 @@ export class TeamLogSourceTracker {
}); });
if (action.kind === 'task-freshness') { if (action.kind === 'task-freshness') {
this.handleTaskFreshnessSignalChange( if (
teamName, !this.handleTaskFreshnessSignalChange(
current.projectDir, teamName,
changedPath, current.projectDir,
BOARD_TASK_LOG_FRESHNESS_DIRNAME changedPath,
) || BOARD_TASK_LOG_FRESHNESS_DIRNAME
)
) {
this.handleTaskFreshnessSignalChange( this.handleTaskFreshnessSignalChange(
teamName, teamName,
current.projectDir, current.projectDir,
changedPath, changedPath,
BOARD_TASK_CHANGE_FRESHNESS_DIRNAME BOARD_TASK_CHANGE_FRESHNESS_DIRNAME
); );
}
return; return;
} }

View file

@ -11,15 +11,15 @@ import {
lineHasAgentTeamsTaskBoundaryToolName, lineHasAgentTeamsTaskBoundaryToolName,
} from './agentTeamsToolNames'; } from './agentTeamsToolNames';
import { import {
readBootstrapLaunchSnapshot,
choosePreferredLaunchSnapshot, choosePreferredLaunchSnapshot,
readBootstrapLaunchSnapshot,
} from './TeamBootstrapStateReader'; } from './TeamBootstrapStateReader';
import { TeamConfigReader } from './TeamConfigReader'; import { TeamConfigReader } from './TeamConfigReader';
import { TeamInboxReader } from './TeamInboxReader'; import { TeamInboxReader } from './TeamInboxReader';
import { TeamLaunchStateStore } from './TeamLaunchStateStore'; import { TeamLaunchStateStore } from './TeamLaunchStateStore';
import { buildTeamLogWatchSessionIds, extractRuntimeSessionIds } from './teamLogSourceWatchScope';
import { TeamMembersMetaStore } from './TeamMembersMetaStore'; import { TeamMembersMetaStore } from './TeamMembersMetaStore';
import { TeamTranscriptProjectResolver } from './TeamTranscriptProjectResolver'; import { TeamTranscriptProjectResolver } from './TeamTranscriptProjectResolver';
import { buildTeamLogWatchSessionIds, extractRuntimeSessionIds } from './teamLogSourceWatchScope';
import type { import type {
MemberLogSummary, MemberLogSummary,

View file

@ -109,20 +109,20 @@ import * as path from 'path';
import pidusage from 'pidusage'; import pidusage from 'pidusage';
import * as readline from 'readline'; import * as readline from 'readline';
import { mergeJsonSettingsArgs, parseJsonSettingsObject } from '../runtime/cliSettingsArgs';
import { import {
ANTHROPIC_HELPER_MODE_COMPETING_AUTH_ENV_KEYS, ANTHROPIC_HELPER_MODE_COMPETING_AUTH_ENV_KEYS,
type AnthropicTeamApiKeyHelperMaterial,
CLAUDE_TEAM_ANTHROPIC_API_KEY_HELPER_SETTINGS_PATH_ENV, CLAUDE_TEAM_ANTHROPIC_API_KEY_HELPER_SETTINGS_PATH_ENV,
CLAUDE_TEAM_ANTHROPIC_AUTH_MODE_API_KEY_HELPER, CLAUDE_TEAM_ANTHROPIC_AUTH_MODE_API_KEY_HELPER,
CLAUDE_TEAM_ANTHROPIC_AUTH_MODE_ENV, CLAUDE_TEAM_ANTHROPIC_AUTH_MODE_ENV,
DISABLE_ANTHROPIC_TEAM_API_KEY_HELPER_ENV,
cleanupAnthropicTeamApiKeyHelperForTeam, cleanupAnthropicTeamApiKeyHelperForTeam,
cleanupAnthropicTeamApiKeyHelperMaterial, cleanupAnthropicTeamApiKeyHelperMaterial,
cleanupStaleAnthropicTeamApiKeyHelpers, cleanupStaleAnthropicTeamApiKeyHelpers,
DISABLE_ANTHROPIC_TEAM_API_KEY_HELPER_ENV,
materializeAnthropicTeamApiKeyHelper, materializeAnthropicTeamApiKeyHelper,
verifyAnthropicTeamApiKeyHelperMaterial, verifyAnthropicTeamApiKeyHelperMaterial,
type AnthropicTeamApiKeyHelperMaterial,
} from '../runtime/anthropicTeamApiKeyHelper'; } from '../runtime/anthropicTeamApiKeyHelper';
import { mergeJsonSettingsArgs, parseJsonSettingsObject } from '../runtime/cliSettingsArgs';
import { import {
type GeminiRuntimeAuthState, type GeminiRuntimeAuthState,
resolveGeminiRuntimeAuth, resolveGeminiRuntimeAuth,
@ -359,9 +359,9 @@ import type {
LeadContextUsage, LeadContextUsage,
MemberLaunchState, MemberLaunchState,
MemberSpawnLivenessSource, MemberSpawnLivenessSource,
MemberSpawnStatusesSnapshot,
MemberSpawnStatus, MemberSpawnStatus,
MemberSpawnStatusEntry, MemberSpawnStatusEntry,
MemberSpawnStatusesSnapshot,
PersistedTeamLaunchMemberState, PersistedTeamLaunchMemberState,
PersistedTeamLaunchPhase, PersistedTeamLaunchPhase,
PersistedTeamLaunchSnapshot, PersistedTeamLaunchSnapshot,
@ -1100,22 +1100,26 @@ function filterOutSettingsPathArgs(
return [...args]; return [...args];
} }
const filtered: string[] = []; const filtered: string[] = [];
for (let index = 0; index < args.length; index += 1) { let index = 0;
while (index < args.length) {
const arg = args[index]; const arg = args[index];
if (arg === '--settings' && args[index + 1] === settingsPath) { if (arg === '--settings' && args[index + 1] === settingsPath) {
index += 1; index += 2;
continue; continue;
} }
if (arg === `--settings=${settingsPath}`) { if (arg === `--settings=${settingsPath}`) {
index += 1;
continue; continue;
} }
filtered.push(arg); filtered.push(arg);
index += 1;
} }
return filtered; return filtered;
} }
function hasPathBasedSettingsArgs(args: string[]): boolean { function hasPathBasedSettingsArgs(args: string[]): boolean {
for (let index = 0; index < args.length; index += 1) { let index = 0;
while (index < args.length) {
const arg = args[index]; const arg = args[index];
if (arg === '--settings') { if (arg === '--settings') {
const value = args[index + 1]; const value = args[index + 1];
@ -1123,17 +1127,20 @@ function hasPathBasedSettingsArgs(args: string[]): boolean {
if (!parseJsonSettingsObject(value)) { if (!parseJsonSettingsObject(value)) {
return true; return true;
} }
index += 1; index += 2;
continue;
} }
if (typeof value !== 'string') { if (typeof value !== 'string') {
return true; return true;
} }
index += 1;
continue; continue;
} }
const prefix = '--settings='; const prefix = '--settings=';
if (arg.startsWith(prefix) && !parseJsonSettingsObject(arg.slice(prefix.length))) { if (arg.startsWith(prefix) && !parseJsonSettingsObject(arg.slice(prefix.length))) {
return true; return true;
} }
index += 1;
} }
return false; return false;
} }
@ -4989,6 +4996,10 @@ export class TeamProvisioningService {
this.transcriptProjectResolver = new TeamTranscriptProjectResolver({ this.transcriptProjectResolver = new TeamTranscriptProjectResolver({
getConfig: (teamName) => this.configReader.getConfigSnapshot(teamName), getConfig: (teamName) => this.configReader.getConfigSnapshot(teamName),
}); });
this.scheduleStaleAnthropicTeamApiKeyHelperCleanup();
}
private scheduleStaleAnthropicTeamApiKeyHelperCleanup(): void {
void cleanupStaleAnthropicTeamApiKeyHelpers({ void cleanupStaleAnthropicTeamApiKeyHelpers({
baseClaudeDir: getClaudeBasePath(), baseClaudeDir: getClaudeBasePath(),
maxAgeMs: 14 * 24 * 60 * 60 * 1000, maxAgeMs: 14 * 24 * 60 * 60 * 1000,
@ -11090,8 +11101,7 @@ export class TeamProvisioningService {
const existingRequest = this.memberSpawnStatusesInFlightByTeam.get(teamName); const existingRequest = this.memberSpawnStatusesInFlightByTeam.get(teamName);
if ( if (
existingRequest && existingRequest?.generationAtStart === generationAtStart &&
existingRequest.generationAtStart === generationAtStart &&
existingRequest.runIdAtStart === run.runId existingRequest.runIdAtStart === run.runId
) { ) {
const snapshot = await existingRequest.promise; const snapshot = await existingRequest.promise;
@ -11201,8 +11211,7 @@ export class TeamProvisioningService {
const generationAtStart = this.getRuntimeSnapshotCacheGeneration(teamName); const generationAtStart = this.getRuntimeSnapshotCacheGeneration(teamName);
const existingRequest = this.agentRuntimeSnapshotInFlightByTeam.get(teamName); const existingRequest = this.agentRuntimeSnapshotInFlightByTeam.get(teamName);
if ( if (
existingRequest && existingRequest?.generationAtStart === generationAtStart &&
existingRequest.generationAtStart === generationAtStart &&
existingRequest.runIdAtStart === runId existingRequest.runIdAtStart === runId
) { ) {
return existingRequest.promise; return existingRequest.promise;
@ -18732,8 +18741,7 @@ export class TeamProvisioningService {
const generationAtStart = this.getRuntimeSnapshotCacheGeneration(teamName); const generationAtStart = this.getRuntimeSnapshotCacheGeneration(teamName);
const existingRequest = this.liveTeamAgentRuntimeMetadataInFlightByTeam.get(teamName); const existingRequest = this.liveTeamAgentRuntimeMetadataInFlightByTeam.get(teamName);
if ( if (
existingRequest && existingRequest?.generationAtStart === generationAtStart &&
existingRequest.generationAtStart === generationAtStart &&
existingRequest.runIdAtStart === runId existingRequest.runIdAtStart === runId
) { ) {
return this.cloneLiveTeamAgentRuntimeMetadata(await existingRequest.promise); return this.cloneLiveTeamAgentRuntimeMetadata(await existingRequest.promise);

View file

@ -3,10 +3,10 @@ import { createHash, randomUUID } from 'crypto';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import type { FileLockOptions } from '../../fileLock';
import { VersionedJsonStore, VersionedJsonStoreError } from './VersionedJsonStore'; import { VersionedJsonStore, VersionedJsonStoreError } from './VersionedJsonStore';
import type { FileLockOptions } from '../../fileLock';
export const OPENCODE_RUNTIME_STORE_MANIFEST_SCHEMA_VERSION = 1; export const OPENCODE_RUNTIME_STORE_MANIFEST_SCHEMA_VERSION = 1;
export const OPENCODE_RUNTIME_STORE_RECEIPT_SCHEMA_VERSION = 1; export const OPENCODE_RUNTIME_STORE_RECEIPT_SCHEMA_VERSION = 1;

View file

@ -148,8 +148,8 @@ interface TeamSummaryCacheEntry {
} }
type CachedTaskReadResult = type CachedTaskReadResult =
| { task: Record<string, unknown>; skipReason?: undefined } | { task: Record<string, unknown>; skipReason?: never }
| { task?: undefined; skipReason: string }; | { task?: never; skipReason: string };
interface TaskFileCacheEntry { interface TaskFileCacheEntry {
fingerprint: string; fingerprint: string;

View file

@ -216,3 +216,5 @@ export const BaseItem = memo(
); );
} }
); );
BaseItem.displayName = 'BaseItem';

View file

@ -282,3 +282,5 @@ export const ExecutionTrace: React.FC<ExecutionTraceProps> = React.memo(
); );
} }
); );
ExecutionTrace.displayName = 'ExecutionTrace';

View file

@ -236,3 +236,5 @@ export const LinkedToolItem = memo(
); );
} }
); );
LinkedToolItem.displayName = 'LinkedToolItem';

View file

@ -215,3 +215,5 @@ export const MetricsPill = memo(
); );
} }
); );
MetricsPill.displayName = 'MetricsPill';

View file

@ -81,3 +81,5 @@ export const SlashItem = memo(
); );
} }
); );
SlashItem.displayName = 'SlashItem';

View file

@ -598,3 +598,5 @@ export const SubagentItem: React.FC<SubagentItemProps> = React.memo(
); );
} }
); );
SubagentItem.displayName = 'SubagentItem';

View file

@ -264,3 +264,5 @@ export const TeammateMessageItem = memo(
); );
} }
); );
TeammateMessageItem.displayName = 'TeammateMessageItem';

View file

@ -82,3 +82,5 @@ export const TextItem: React.FC<TextItemProps> = React.memo(
); );
} }
); );
TextItem.displayName = 'TextItem';

View file

@ -82,3 +82,5 @@ export const ThinkingItem: React.FC<ThinkingItemProps> = React.memo(
); );
} }
); );
ThinkingItem.displayName = 'ThinkingItem';

View file

@ -59,3 +59,5 @@ export const CollapsibleOutputSection = memo(
); );
} }
); );
CollapsibleOutputSection.displayName = 'CollapsibleOutputSection';

View file

@ -1112,3 +1112,5 @@ export const DateGroupedSessions = memo((): React.JSX.Element => {
</div> </div>
); );
}); });
DateGroupedSessions.displayName = 'DateGroupedSessions';

View file

@ -117,3 +117,5 @@ export const MemberBadge = memo(
); );
} }
); );
MemberBadge.displayName = 'MemberBadge';

View file

@ -128,12 +128,12 @@ import {
} from './sidebar/teamSidebarUiState'; } from './sidebar/teamSidebarUiState';
import { ClaudeLogsSection } from './ClaudeLogsSection'; import { ClaudeLogsSection } from './ClaudeLogsSection';
import { CollapsibleTeamSection } from './CollapsibleTeamSection'; import { CollapsibleTeamSection } from './CollapsibleTeamSection';
import { ProcessesSection } from './ProcessesSection';
import { getLaunchJoinMilestonesFromMembers, getLaunchJoinState } from './provisioningSteps';
import { TeamProvisioningBanner } from './TeamProvisioningBanner';
import { deriveLeadContextButtonLabel } from './leadContextLoadGuards'; import { deriveLeadContextButtonLabel } from './leadContextLoadGuards';
import { LeadSessionDetailGate } from './LeadSessionDetailGate'; import { LeadSessionDetailGate } from './LeadSessionDetailGate';
import { LiveRuntimeStatusBridge } from './LiveRuntimeStatusBridge'; import { LiveRuntimeStatusBridge } from './LiveRuntimeStatusBridge';
import { ProcessesSection } from './ProcessesSection';
import { getLaunchJoinMilestonesFromMembers, getLaunchJoinState } from './provisioningSteps';
import { TeamProvisioningBanner } from './TeamProvisioningBanner';
import { loadTeamSessionMetadata } from './teamSessionFetchGuards'; import { loadTeamSessionMetadata } from './teamSessionFetchGuards';
import { TeamSessionsSection } from './TeamSessionsSection'; import { TeamSessionsSection } from './TeamSessionsSection';
@ -1847,7 +1847,7 @@ export const TeamDetailView = memo(function TeamDetailView({
const pendingTeamSectionFocus = useStore((s) => s.pendingTeamSectionFocus); const pendingTeamSectionFocus = useStore((s) => s.pendingTeamSectionFocus);
const clearTeamSectionFocus = useStore((s) => s.clearTeamSectionFocus); const clearTeamSectionFocus = useStore((s) => s.clearTeamSectionFocus);
useEffect(() => { useEffect(() => {
if (!pendingTeamSectionFocus || pendingTeamSectionFocus.teamName !== teamName) return; if (pendingTeamSectionFocus?.teamName !== teamName) return;
const sectionId = const sectionId =
pendingTeamSectionFocus.section === 'members' pendingTeamSectionFocus.section === 'members'

View file

@ -79,3 +79,5 @@ export const ReplyQuoteBlock = memo(
); );
} }
); );
ReplyQuoteBlock.displayName = 'ReplyQuoteBlock';

View file

@ -458,5 +458,7 @@ export const KanbanGridLayout = memo(
} }
); );
KanbanGridLayout.displayName = 'KanbanGridLayout';
export { SKELETON_HIDE_DELAY_MS, SKELETON_HIDE_DELAY_MS_ON_MODE_SWITCH }; export { SKELETON_HIDE_DELAY_MS, SKELETON_HIDE_DELAY_MS_ON_MODE_SWITCH };
/* eslint-enable tailwindcss/no-custom-classname -- stable class hooks remain scoped to this file. */ /* eslint-enable tailwindcss/no-custom-classname -- stable class hooks remain scoped to this file. */

View file

@ -58,3 +58,5 @@ export const CurrentTaskIndicator = memo(
); );
} }
); );
CurrentTaskIndicator.displayName = 'CurrentTaskIndicator';

View file

@ -27,3 +27,5 @@ export const MemberPresenceDot = memo(
); );
} }
); );
MemberPresenceDot.displayName = 'MemberPresenceDot';

View file

@ -36,11 +36,11 @@ export interface TeamRuntimeDisplayRow {
actionsAllowed: false; actionsAllowed: false;
} }
type SpawnDegradation = { interface SpawnDegradation {
reason: string; reason: string;
diagnostic?: string; diagnostic?: string;
diagnosticSeverity: TeamAgentRuntimeDiagnosticSeverity; diagnosticSeverity: TeamAgentRuntimeDiagnosticSeverity;
}; }
const ACTIVE_SPAWN_STATUSES = new Set(['waiting', 'spawning']); const ACTIVE_SPAWN_STATUSES = new Set(['waiting', 'spawning']);

View file

@ -48,6 +48,8 @@ import {
isTeamDataRefreshPending, isTeamDataRefreshPending,
selectTeamDataForName, selectTeamDataForName,
} from './slices/teamSlice'; } from './slices/teamSlice';
import { createUISlice } from './slices/uiSlice';
import { createUpdateSlice } from './slices/updateSlice';
import { import {
decideProcessFanoutDryRun, decideProcessFanoutDryRun,
decideProcessFanoutMode, decideProcessFanoutMode,
@ -58,8 +60,6 @@ import {
noteTeamRefreshFanout, noteTeamRefreshFanout,
type TeamRefreshFanoutOperation, type TeamRefreshFanoutOperation,
} from './teamRefreshFanoutDiagnostics'; } from './teamRefreshFanoutDiagnostics';
import { createUISlice } from './slices/uiSlice';
import { createUpdateSlice } from './slices/updateSlice';
import type { DetectedError } from '../types/data'; import type { DetectedError } from '../types/data';
import type { AppState } from './types'; import type { AppState } from './types';

View file

@ -60,7 +60,7 @@ export interface TeamRefreshFanoutStructuredCount {
phase: TeamRefreshFanoutPhase; phase: TeamRefreshFanoutPhase;
} }
export interface TeamRefreshFanoutSummaryRow extends TeamRefreshFanoutStructuredCount {} export type TeamRefreshFanoutSummaryRow = TeamRefreshFanoutStructuredCount;
export interface TeamRefreshFanoutSummary { export interface TeamRefreshFanoutSummary {
generatedAt: number; generatedAt: number;
@ -92,7 +92,7 @@ function createEmptyBucket(): TeamRefreshFanoutBucket {
function ensureTeamBucket(teamName: string): TeamRefreshFanoutBucket { function ensureTeamBucket(teamName: string): TeamRefreshFanoutBucket {
if (!buckets.has(teamName) && buckets.size >= MAX_TEAM_REFRESH_DIAGNOSTIC_TEAMS) { if (!buckets.has(teamName) && buckets.size >= MAX_TEAM_REFRESH_DIAGNOSTIC_TEAMS) {
const oldestKey = buckets.keys().next().value as string | undefined; const oldestKey = buckets.keys().next().value;
if (oldestKey) { if (oldestKey) {
buckets.delete(oldestKey); buckets.delete(oldestKey);
} }

View file

@ -1,5 +1,5 @@
import type { EnhancedChunk } from '@main/types';
import type { NotificationTarget, TeamEventType } from './notifications'; import type { NotificationTarget, TeamEventType } from './notifications';
import type { EnhancedChunk } from '@main/types';
export interface TeamMember { export interface TeamMember {
name: string; name: string;

View file

@ -235,10 +235,7 @@ liveDescribe('Mixed provider team launch live e2e', () => {
await waitUntilWithDiagnostics(async () => { await waitUntilWithDiagnostics(async () => {
const status = await harness!.svc.getMemberSpawnStatuses(teamName!); const status = await harness!.svc.getMemberSpawnStatuses(teamName!);
if ( if (status.teamLaunchState === 'partial_failure') {
status.teamLaunchState === 'failed' ||
status.teamLaunchState === 'partial_failure'
) {
throw new Error(await formatMixedLaunchDiagnostics(harness!, teamName!, progressEvents)); throw new Error(await formatMixedLaunchDiagnostics(harness!, teamName!, progressEvents));
} }
for (const memberName of ['alice', 'cody', 'oscar'] as const) { for (const memberName of ['alice', 'cody', 'oscar'] as const) {
@ -268,8 +265,8 @@ liveDescribe('Mixed provider team launch live e2e', () => {
const laneIndex = await readOpenCodeRuntimeLaneIndex(getTeamsBasePath(), teamName); const laneIndex = await readOpenCodeRuntimeLaneIndex(getTeamsBasePath(), teamName);
expect( expect(
Object.values(laneIndex.lanes).some( Object.entries(laneIndex.lanes).some(
(lane) => lane.state === 'active' && lane.memberName === 'oscar' ([laneId, lane]) => lane.state === 'active' && laneId === 'secondary:opencode:oscar'
) )
).toBe(true); ).toBe(true);
}, },

View file

@ -25,7 +25,7 @@ vi.mock('@main/services/team/ClaudeBinaryResolver', () => ({
vi.mock('@main/utils/childProcess', () => ({ vi.mock('@main/utils/childProcess', () => ({
execCli: vi.fn(async (_binaryPath: string | null, args: string[]) => { execCli: vi.fn(async (_binaryPath: string | null, args: string[]) => {
if (args[0] === 'model') { if (args.includes('model') && args.includes('list')) {
return { return {
stdout: JSON.stringify({ stdout: JSON.stringify({
schemaVersion: 1, schemaVersion: 1,
@ -54,7 +54,7 @@ vi.mock('@main/utils/childProcess', () => ({
stderr: '', stderr: '',
}; };
} }
if (args[0] === 'runtime') { if (args.includes('runtime') && args.includes('status')) {
return { return {
stdout: JSON.stringify({ stdout: JSON.stringify({
providers: { providers: {