diff --git a/mcp-server/test/tools.test.ts b/mcp-server/test/tools.test.ts index fdae26fd..26a16407 100644 --- a/mcp-server/test/tools.test.ts +++ b/mcp-server/test/tools.test.ts @@ -1353,6 +1353,7 @@ describe('agent-teams-mcp tools', () => { members: [ { name: 'lead', role: 'team-lead' }, { name: 'alice', role: 'developer' }, + { name: 'bob', role: 'reviewer' }, ], }); diff --git a/package.json b/package.json index c8ec837c..ffb89ac9 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "opencode:prove-team-provisioning": "node ./scripts/prove-opencode-team-provisioning.mjs", "team:prove-launch-matrix": "pnpm exec vitest run --maxWorkers 1 --minWorkers 1 test/main/services/team/TeamAgentLaunchMatrix.safe-e2e.test.ts", "prebuild": "tsx scripts/fetch-pricing-data.ts && pnpm --filter agent-teams-controller build && pnpm --filter agent-teams-mcp build", - "build": "electron-vite build", + "build": "node --max-old-space-size=8192 ./node_modules/electron-vite/bin/electron-vite.js build", "dist": "electron-builder --mac --win --linux", "dist:mac": "electron-builder --mac", "dist:mac:arm64": "electron-builder --mac --arm64", diff --git a/src/features/runtime-provider-management/contracts/api.ts b/src/features/runtime-provider-management/contracts/api.ts index e25f8917..eea72627 100644 --- a/src/features/runtime-provider-management/contracts/api.ts +++ b/src/features/runtime-provider-management/contracts/api.ts @@ -4,11 +4,11 @@ import type { RuntimeProviderManagementDirectoryResponse, RuntimeProviderManagementForgetInput, RuntimeProviderManagementLoadDirectoryInput, + RuntimeProviderManagementLoadModelsInput, RuntimeProviderManagementLoadSetupFormInput, RuntimeProviderManagementLoadViewInput, - RuntimeProviderManagementLoadModelsInput, - RuntimeProviderManagementModelTestResponse, RuntimeProviderManagementModelsResponse, + RuntimeProviderManagementModelTestResponse, RuntimeProviderManagementProviderResponse, RuntimeProviderManagementSetDefaultModelInput, RuntimeProviderManagementSetupFormResponse, diff --git a/src/features/runtime-provider-management/core/application/runtimeProviderManagementUseCases.ts b/src/features/runtime-provider-management/core/application/runtimeProviderManagementUseCases.ts index bad1c703..6eddf5ec 100644 --- a/src/features/runtime-provider-management/core/application/runtimeProviderManagementUseCases.ts +++ b/src/features/runtime-provider-management/core/application/runtimeProviderManagementUseCases.ts @@ -5,11 +5,11 @@ import type { RuntimeProviderManagementDirectoryResponse, RuntimeProviderManagementForgetInput, RuntimeProviderManagementLoadDirectoryInput, - RuntimeProviderManagementLoadSetupFormInput, RuntimeProviderManagementLoadModelsInput, + RuntimeProviderManagementLoadSetupFormInput, RuntimeProviderManagementLoadViewInput, - RuntimeProviderManagementModelTestResponse, RuntimeProviderManagementModelsResponse, + RuntimeProviderManagementModelTestResponse, RuntimeProviderManagementProviderResponse, RuntimeProviderManagementSetDefaultModelInput, RuntimeProviderManagementSetupFormResponse, diff --git a/src/features/runtime-provider-management/main/adapters/input/registerRuntimeProviderManagementIpc.ts b/src/features/runtime-provider-management/main/adapters/input/registerRuntimeProviderManagementIpc.ts index bc52cc00..0dbd5713 100644 --- a/src/features/runtime-provider-management/main/adapters/input/registerRuntimeProviderManagementIpc.ts +++ b/src/features/runtime-provider-management/main/adapters/input/registerRuntimeProviderManagementIpc.ts @@ -18,11 +18,11 @@ import type { RuntimeProviderManagementDirectoryResponse, RuntimeProviderManagementForgetInput, RuntimeProviderManagementLoadDirectoryInput, - RuntimeProviderManagementLoadSetupFormInput, RuntimeProviderManagementLoadModelsInput, + RuntimeProviderManagementLoadSetupFormInput, RuntimeProviderManagementLoadViewInput, - RuntimeProviderManagementModelTestResponse, RuntimeProviderManagementModelsResponse, + RuntimeProviderManagementModelTestResponse, RuntimeProviderManagementProviderResponse, RuntimeProviderManagementSetDefaultModelInput, RuntimeProviderManagementSetupFormResponse, diff --git a/src/features/runtime-provider-management/main/composition/createRuntimeProviderManagementFeature.ts b/src/features/runtime-provider-management/main/composition/createRuntimeProviderManagementFeature.ts index 02eda5a0..050ef2b3 100644 --- a/src/features/runtime-provider-management/main/composition/createRuntimeProviderManagementFeature.ts +++ b/src/features/runtime-provider-management/main/composition/createRuntimeProviderManagementFeature.ts @@ -8,11 +8,11 @@ import type { RuntimeProviderManagementDirectoryResponse, RuntimeProviderManagementForgetInput, RuntimeProviderManagementLoadDirectoryInput, - RuntimeProviderManagementLoadSetupFormInput, RuntimeProviderManagementLoadModelsInput, + RuntimeProviderManagementLoadSetupFormInput, RuntimeProviderManagementLoadViewInput, - RuntimeProviderManagementModelTestResponse, RuntimeProviderManagementModelsResponse, + RuntimeProviderManagementModelTestResponse, RuntimeProviderManagementProviderResponse, RuntimeProviderManagementSetDefaultModelInput, RuntimeProviderManagementSetupFormResponse, diff --git a/src/features/runtime-provider-management/main/infrastructure/AgentTeamsRuntimeProviderManagementCliClient.ts b/src/features/runtime-provider-management/main/infrastructure/AgentTeamsRuntimeProviderManagementCliClient.ts index 175f5989..85f0a39d 100644 --- a/src/features/runtime-provider-management/main/infrastructure/AgentTeamsRuntimeProviderManagementCliClient.ts +++ b/src/features/runtime-provider-management/main/infrastructure/AgentTeamsRuntimeProviderManagementCliClient.ts @@ -11,8 +11,8 @@ import type { RuntimeProviderManagementErrorDto, RuntimeProviderManagementForgetInput, RuntimeProviderManagementLoadDirectoryInput, - RuntimeProviderManagementLoadSetupFormInput, RuntimeProviderManagementLoadModelsInput, + RuntimeProviderManagementLoadSetupFormInput, RuntimeProviderManagementLoadViewInput, RuntimeProviderManagementModelsResponse, RuntimeProviderManagementModelTestResponse, diff --git a/src/features/runtime-provider-management/preload/createRuntimeProviderManagementBridge.ts b/src/features/runtime-provider-management/preload/createRuntimeProviderManagementBridge.ts index 9bc0c4ac..a8a6ec9b 100644 --- a/src/features/runtime-provider-management/preload/createRuntimeProviderManagementBridge.ts +++ b/src/features/runtime-provider-management/preload/createRuntimeProviderManagementBridge.ts @@ -17,11 +17,11 @@ import type { RuntimeProviderManagementDirectoryResponse, RuntimeProviderManagementForgetInput, RuntimeProviderManagementLoadDirectoryInput, - RuntimeProviderManagementLoadSetupFormInput, RuntimeProviderManagementLoadModelsInput, + RuntimeProviderManagementLoadSetupFormInput, RuntimeProviderManagementLoadViewInput, - RuntimeProviderManagementModelTestResponse, RuntimeProviderManagementModelsResponse, + RuntimeProviderManagementModelTestResponse, RuntimeProviderManagementProviderResponse, RuntimeProviderManagementSetDefaultModelInput, RuntimeProviderManagementSetupFormResponse, diff --git a/src/features/runtime-provider-management/renderer/ui/RuntimeProviderManagementPanelView.tsx b/src/features/runtime-provider-management/renderer/ui/RuntimeProviderManagementPanelView.tsx index 7f1bf6a6..2f094e24 100644 --- a/src/features/runtime-provider-management/renderer/ui/RuntimeProviderManagementPanelView.tsx +++ b/src/features/runtime-provider-management/renderer/ui/RuntimeProviderManagementPanelView.tsx @@ -77,7 +77,7 @@ interface ProviderRowProps { readonly actions: RuntimeProviderManagementActions; } -const DIRECTORY_FILTERS: Array<{ id: RuntimeProviderDirectoryFilterDto; label: string }> = [ +const DIRECTORY_FILTERS: { id: RuntimeProviderDirectoryFilterDto; label: string }[] = [ { id: 'all', label: 'All' }, { id: 'connectable', label: 'Connectable' }, { id: 'connected', label: 'Connected' }, diff --git a/src/features/runtime-provider-management/renderer/ui/providerBrandIcons.tsx b/src/features/runtime-provider-management/renderer/ui/providerBrandIcons.tsx index 25f6678e..3bbda2e2 100644 --- a/src/features/runtime-provider-management/renderer/ui/providerBrandIcons.tsx +++ b/src/features/runtime-provider-management/renderer/ui/providerBrandIcons.tsx @@ -2,10 +2,10 @@ import { useEffect, useState } from 'react'; import type { CSSProperties, JSX } from 'react'; -type ProviderBrand = { +interface ProviderBrand { providerId: string; displayName: string; -}; +} interface SvgPath { d: string; diff --git a/src/main/ipc/teams.ts b/src/main/ipc/teams.ts index a73dd279..fc122faa 100644 --- a/src/main/ipc/teams.ts +++ b/src/main/ipc/teams.ts @@ -11,8 +11,8 @@ import { TEAM_ALIVE_LIST, TEAM_CANCEL_PROVISIONING, TEAM_CREATE, - TEAM_CREATE_INITIAL_GIT_COMMIT, TEAM_CREATE_CONFIG, + TEAM_CREATE_INITIAL_GIT_COMMIT, TEAM_CREATE_TASK, TEAM_DELETE_DRAFT, TEAM_DELETE_TASK_ATTACHMENT, @@ -209,8 +209,8 @@ import type { TeamTask, TeamTaskStatus, TeamUpdateConfigRequest, - TeamWorktreeGitStatus, TeamViewSnapshot, + TeamWorktreeGitStatus, ToolApprovalFileContent, ToolApprovalSettings, UpdateKanbanPatch, diff --git a/src/main/services/analysis/SubagentDetailBuilder.ts b/src/main/services/analysis/SubagentDetailBuilder.ts index 93b6c192..c6fc54d9 100644 --- a/src/main/services/analysis/SubagentDetailBuilder.ts +++ b/src/main/services/analysis/SubagentDetailBuilder.ts @@ -18,9 +18,10 @@ import { countTokens } from '@main/utils/tokenizer'; import { createLogger } from '@shared/utils/logger'; import * as path from 'path'; -import { buildSemanticStepGroups } from './SemanticStepGrouper'; import { resolveProjectStorageDir } from '../discovery/projectStorageDir'; +import { buildSemanticStepGroups } from './SemanticStepGrouper'; + import type { SubagentResolver } from '../discovery/SubagentResolver'; import type { FileSystemProvider } from '../infrastructure/FileSystemProvider'; import type { SessionParser } from '../parsing/SessionParser'; diff --git a/src/main/services/discovery/SessionSearcher.ts b/src/main/services/discovery/SessionSearcher.ts index 4bb72868..b51cf9d0 100644 --- a/src/main/services/discovery/SessionSearcher.ts +++ b/src/main/services/discovery/SessionSearcher.ts @@ -19,9 +19,9 @@ import * as path from 'path'; import { startMainSpan } from '../../sentry'; +import { resolveProjectStorageDir } from './projectStorageDir'; import { SearchTextCache } from './SearchTextCache'; import { extractSearchableEntries } from './SearchTextExtractor'; -import { resolveProjectStorageDir } from './projectStorageDir'; import { subprojectRegistry } from './SubprojectRegistry'; import type { SearchableEntry } from './SearchTextExtractor'; diff --git a/src/main/services/infrastructure/codexAppServer/__tests__/CodexBinaryResolver.test.ts b/src/main/services/infrastructure/codexAppServer/__tests__/CodexBinaryResolver.test.ts index 7ac04e11..863bfbfd 100644 --- a/src/main/services/infrastructure/codexAppServer/__tests__/CodexBinaryResolver.test.ts +++ b/src/main/services/infrastructure/codexAppServer/__tests__/CodexBinaryResolver.test.ts @@ -1,10 +1,11 @@ // @vitest-environment node import { constants as fsConstants } from 'node:fs'; -import type { PathLike } from 'node:fs'; import path from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { PathLike } from 'node:fs'; + const accessMock = vi.fn<(filePath: PathLike, mode?: number) => Promise>(); vi.mock('node:fs/promises', () => ({ diff --git a/src/main/services/team/ChangeExtractorService.ts b/src/main/services/team/ChangeExtractorService.ts index 6e7a7d86..fe46e6b0 100644 --- a/src/main/services/team/ChangeExtractorService.ts +++ b/src/main/services/team/ChangeExtractorService.ts @@ -1,5 +1,5 @@ -import { getTasksBasePath, getTeamsBasePath } from '@main/utils/pathDecoder'; import { appendOpenCodeTaskChangeDiag } from '@main/utils/openCodeTaskChangeDiagLog'; +import { getTasksBasePath, getTeamsBasePath } from '@main/utils/pathDecoder'; import { createLogger } from '@shared/utils/logger'; import { resolveTaskChangePresenceFromResult } from '@shared/utils/taskChangePresence'; import { @@ -9,19 +9,18 @@ import { } from '@shared/utils/taskChangeState'; import { createHash } from 'crypto'; import { existsSync } from 'fs'; -import { mkdtemp, readFile, readdir, rm, stat, writeFile } from 'fs/promises'; +import { mkdtemp, readdir, readFile, rm, stat, writeFile } from 'fs/promises'; import * as os from 'os'; import * as path from 'path'; import { JsonTaskChangeSummaryCacheRepository } from './cache/JsonTaskChangeSummaryCacheRepository'; -import { TeamMetaStore } from './TeamMetaStore'; -import { TaskChangeComputer } from './TaskChangeComputer'; -import { TaskChangeLedgerReader } from './TaskChangeLedgerReader'; import { getOpenCodeLaneScopedRuntimeFilePath, getOpenCodeTeamRuntimeDirectory, readOpenCodeRuntimeLaneIndex, } from './opencode/store/OpenCodeRuntimeManifestEvidenceReader'; +import { TaskChangeComputer } from './TaskChangeComputer'; +import { TaskChangeLedgerReader } from './TaskChangeLedgerReader'; import { buildTaskChangePresenceDescriptor, computeTaskChangePresenceProjectFingerprint, @@ -34,14 +33,15 @@ import { type TaskChangeTaskMeta, } from './taskChangeWorkerTypes'; import { TeamConfigReader } from './TeamConfigReader'; +import { TeamMetaStore } from './TeamMetaStore'; import type { TaskChangePresenceRepository } from './cache/TaskChangePresenceRepository'; +import type { OpenCodeLedgerBackfillPort } from './opencode/bridge/OpenCodeReadinessBridge'; +import type { OpenCodePromptDeliveryLedgerRecord } from './opencode/delivery/OpenCodePromptDeliveryLedger'; import type { TaskBoundaryParser } from './TaskBoundaryParser'; import type { TaskChangeWorkerClient } from './TaskChangeWorkerClient'; import type { TeamLogSourceTracker } from './TeamLogSourceTracker'; import type { TeamMemberLogsFinder } from './TeamMemberLogsFinder'; -import type { OpenCodeLedgerBackfillPort } from './opencode/bridge/OpenCodeReadinessBridge'; -import type { OpenCodePromptDeliveryLedgerRecord } from './opencode/delivery/OpenCodePromptDeliveryLedger'; import type { AgentChangeSet, ChangeStats, TaskChangeSetV2 } from '@shared/types'; const logger = createLogger('Service:ChangeExtractorService'); @@ -678,7 +678,7 @@ export class ChangeExtractorService { teamName: string, taskId: string ): Promise< - Array<{ + { memberName: string; laneId?: string; runtimeSessionId: string | null; @@ -687,8 +687,8 @@ export class ChangeExtractorService { observedAssistantMessageId: string | null; prePromptCursor: string | null; postPromptCursor: string | null; - taskRefs: Array<{ taskId: string; displayId: string; teamName: string }>; - }> + taskRefs: { taskId: string; displayId: string; teamName: string }[]; + }[] > { const teamsBasePath = getTeamsBasePath(); const laneIds = new Set(['primary']); @@ -704,7 +704,7 @@ export class ChangeExtractorService { laneIds.add(laneId); } - const records: Array<{ + const records: { memberName: string; laneId?: string; runtimeSessionId: string | null; @@ -713,8 +713,8 @@ export class ChangeExtractorService { observedAssistantMessageId: string | null; prePromptCursor: string | null; postPromptCursor: string | null; - taskRefs: Array<{ taskId: string; displayId: string; teamName: string }>; - }> = []; + taskRefs: { taskId: string; displayId: string; teamName: string }[]; + }[] = []; for (const laneId of laneIds) { const filePath = getOpenCodeLaneScopedRuntimeFilePath({ diff --git a/src/main/services/team/TeamMemberWorktreeManager.ts b/src/main/services/team/TeamMemberWorktreeManager.ts index 0637382d..94b613e0 100644 --- a/src/main/services/team/TeamMemberWorktreeManager.ts +++ b/src/main/services/team/TeamMemberWorktreeManager.ts @@ -1,6 +1,6 @@ import { getAppDataPath, getClaudeBasePath } from '@main/utils/pathDecoder'; -import { createHash } from 'crypto'; import { execFile } from 'child_process'; +import { createHash } from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 0225c311..cbfac5cb 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -3959,10 +3959,10 @@ export class TeamProvisioningService { Promise >(); private readonly openCodePromptDeliveryWatchdogTimers = new Map(); - private readonly openCodePromptDeliveryWatchdogQueue: Array<{ + private readonly openCodePromptDeliveryWatchdogQueue: { teamName: string; run: () => Promise; - }> = []; + }[] = []; private openCodePromptDeliveryWatchdogInFlight = 0; private openCodePromptDeliveryWatchdogDisabledLogged = false; private readonly openCodePromptDeliveryWatchdogInFlightByTeam = new Map(); @@ -13625,7 +13625,7 @@ export class TeamProvisioningService { (config) => config?.members?.find((member) => isLeadMember(member))?.name?.trim() || null ) .catch(() => null); - if (leadName && inboxName.trim().toLowerCase() === leadName.toLowerCase()) { + if (inboxName.trim().toLowerCase() === leadName?.toLowerCase()) { if (await this.isOpenCodeRuntimeRecipient(teamName, inboxName)) { const diagnostic = 'opencode_lead_runtime_session_missing: OpenCode lead inbox relay is unsupported in v1; leaving inbox unread for durable retry/diagnostics.'; @@ -18154,7 +18154,7 @@ export class TeamProvisioningService { ? this.runs.get(this.getTrackedRunId(teamName)!)?.child?.pid : undefined; const pids = new Set(); - const rows: Array<{ pid: number; command: string }> = []; + const rows: { pid: number; command: string }[] = []; if (process.platform === 'win32') { try { diff --git a/src/main/services/team/opencode/bridge/OpenCodeBridgeCommandContract.ts b/src/main/services/team/opencode/bridge/OpenCodeBridgeCommandContract.ts index 67db3b43..21660989 100644 --- a/src/main/services/team/opencode/bridge/OpenCodeBridgeCommandContract.ts +++ b/src/main/services/team/opencode/bridge/OpenCodeBridgeCommandContract.ts @@ -131,7 +131,7 @@ export interface OpenCodeCleanupHostsCommandBody { export interface OpenCodeCleanupHostsCommandData { cleaned: number; remaining: number; - hosts: Array<{ + hosts: { hostKey: string; projectPath: string; pid: number; @@ -146,7 +146,7 @@ export interface OpenCodeCleanupHostsCommandData { | 'failed'; reason: string; leaseCount: number; - }>; + }[]; diagnostics: string[]; } @@ -270,7 +270,7 @@ export interface OpenCodeBackfillTaskLedgerCommandData { importedEvents: number; skippedEvents: number; outcome: OpenCodeBackfillTaskLedgerOutcome; - notices: Array<{ severity: 'warning'; message: string; code: string }>; + notices: { severity: 'warning'; message: string; code: string }[]; diagnostics: string[]; } diff --git a/src/main/services/team/opencode/bridge/OpenCodeReadinessBridge.ts b/src/main/services/team/opencode/bridge/OpenCodeReadinessBridge.ts index 5704dd95..ad4dff69 100644 --- a/src/main/services/team/opencode/bridge/OpenCodeReadinessBridge.ts +++ b/src/main/services/team/opencode/bridge/OpenCodeReadinessBridge.ts @@ -4,13 +4,13 @@ import type { OpenCodeTeamLaunchReadinessState, } from '../readiness/OpenCodeTeamLaunchReadiness'; import type { + OpenCodeBackfillTaskLedgerCommandBody, + OpenCodeBackfillTaskLedgerCommandData, OpenCodeBridgeCommandName, OpenCodeBridgeDiagnosticEvent, OpenCodeBridgeFailureKind, OpenCodeBridgeResult, OpenCodeBridgeRuntimeSnapshot, - OpenCodeBackfillTaskLedgerCommandBody, - OpenCodeBackfillTaskLedgerCommandData, OpenCodeCleanupHostsCommandBody, OpenCodeCleanupHostsCommandData, OpenCodeLaunchTeamCommandBody, diff --git a/src/main/services/team/opencode/delivery/OpenCodePromptDeliveryLedger.ts b/src/main/services/team/opencode/delivery/OpenCodePromptDeliveryLedger.ts index 0cb284d0..b87140d0 100644 --- a/src/main/services/team/opencode/delivery/OpenCodePromptDeliveryLedger.ts +++ b/src/main/services/team/opencode/delivery/OpenCodePromptDeliveryLedger.ts @@ -1,12 +1,12 @@ import { stableHash } from '../bridge/OpenCodeBridgeCommandContract'; import { VersionedJsonStore, VersionedJsonStoreError } from '../store/VersionedJsonStore'; -import type { AgentActionMode, TaskRef } from '@shared/types/team'; import type { OpenCodeDeliveryResponseObservation, OpenCodeDeliveryResponseState, OpenCodeDeliveryVisibleReplyCorrelation, } from '../bridge/OpenCodeBridgeCommandContract'; +import type { AgentActionMode, TaskRef } from '@shared/types/team'; export const OPENCODE_PROMPT_DELIVERY_LEDGER_SCHEMA_VERSION = 1; export const OPENCODE_PROMPT_DELIVERY_RESPONDED_RETENTION_MS = 7 * 24 * 60 * 60 * 1000; @@ -603,7 +603,7 @@ export function hashOpenCodePromptDeliveryPayload(input: { replyRecipient: string; actionMode?: AgentActionMode | null; taskRefs?: TaskRef[]; - attachments?: Array<{ id?: string; filename?: string; mimeType?: string; size?: number }>; + attachments?: { id?: string; filename?: string; mimeType?: string; size?: number }[]; source?: string; }): string { return `sha256:${stableHash({ diff --git a/src/main/services/team/opencode/delivery/OpenCodePromptDeliveryWatchdog.ts b/src/main/services/team/opencode/delivery/OpenCodePromptDeliveryWatchdog.ts index 1f9f7073..7247620b 100644 --- a/src/main/services/team/opencode/delivery/OpenCodePromptDeliveryWatchdog.ts +++ b/src/main/services/team/opencode/delivery/OpenCodePromptDeliveryWatchdog.ts @@ -1,6 +1,6 @@ -import type { AgentActionMode, InboxMessage, TaskRef } from '@shared/types/team'; import type { OpenCodeDeliveryResponseState } from '../bridge/OpenCodeBridgeCommandContract'; import type { OpenCodePromptDeliveryStatus } from './OpenCodePromptDeliveryLedger'; +import type { AgentActionMode, InboxMessage, TaskRef } from '@shared/types/team'; export const OPENCODE_PROMPT_DELIVERY_OBSERVE_DELAY_MS = 3_000; export const OPENCODE_PROMPT_DELIVERY_RETRY_DELAY_MS = 15_000; diff --git a/src/main/services/team/stallMonitor/OpenCodeTaskStallEvidenceSource.ts b/src/main/services/team/stallMonitor/OpenCodeTaskStallEvidenceSource.ts index 04cedf35..f2a29fca 100644 --- a/src/main/services/team/stallMonitor/OpenCodeTaskStallEvidenceSource.ts +++ b/src/main/services/team/stallMonitor/OpenCodeTaskStallEvidenceSource.ts @@ -1,6 +1,6 @@ +import { ClaudeMultimodelBridgeService } from '../../runtime/ClaudeMultimodelBridgeService'; import { canonicalizeAgentTeamsToolName } from '../agentTeamsToolNames'; import { ClaudeBinaryResolver } from '../ClaudeBinaryResolver'; -import { ClaudeMultimodelBridgeService } from '../../runtime/ClaudeMultimodelBridgeService'; import type { OpenCodeRuntimeTranscriptLogMessage, diff --git a/src/main/services/team/stallMonitor/TaskProgressSignalClassifier.ts b/src/main/services/team/stallMonitor/TaskProgressSignalClassifier.ts index 71bbbc80..9a531026 100644 --- a/src/main/services/team/stallMonitor/TaskProgressSignalClassifier.ts +++ b/src/main/services/team/stallMonitor/TaskProgressSignalClassifier.ts @@ -15,28 +15,185 @@ export interface TaskProgressTouchClassification { reason: string; } -const CONCRETE_FILE_OR_PATH_RE = - /(?:^|\s)(?:\.{1,2}\/|~\/|\/|\w[\w.-]*\/)[\w./\s-]+|\b[\w.-]+\.(?:[cm]?[tj]sx?|json|md|css|scss|py|go|rs|java|kt|swift|ya?ml|toml|lock|sh|sql)\b/i; -const TASK_OR_ISSUE_REF_RE = /#[a-f0-9]{6,}|\btask-[\w-]+/i; -const TEST_OR_BUILD_RESULT_RE = - /\b(?:test(?:s|ed|ing)?|vitest|jest|playwright|pnpm|npm|bun|build|typecheck|lint|passed|failed|green|red|error|exception|stack trace)\b|тест|сборк|линт|ошибк|упал|прош[её]л/i; -const SUBSTANTIVE_WORK_RE = - /\b(?:implemented|fixed|added|updated|changed|removed|found|verified|confirmed|completed|created|refactored|patched|root cause|next step)\b|исправ|добав|обнов|измен|удал|наш[её]л|подтверд|готово|сделал|сделана|причин|следующ/i; -const BLOCKER_OR_CLARIFICATION_RE = - /\?|(?:^|\b)(?:blocked|blocker|cannot|can't|need|needs|waiting|clarification|question|permission|access denied|not enough context)\b|не могу|не получается|нужн|жду|блок|уточн|вопрос|нет доступа|недостаточно контекст/i; -const WEAK_START_ONLY_RE = - /^(?:я\s+)?(?:начинаю(?:\s+работу)?|начну|приступаю(?:\s+к\s+работе)?|беру\s+в\s+работу|проверю|сейчас\s+проверю|посмотрю|разберусь|готов(?:а)?\s+приступить|готов(?:а)?\s+к\s+работе|will\s+start|starting\s+work|starting|taking\s+this|i(?:'|’)?ll\s+start|i\s+will\s+start|i\s+am\s+starting|i(?:'|’)?ll\s+check|i\s+will\s+check|checking\s+now|on\s+it)(?:[.!…\s]*)$/i; +const FILE_EXTENSIONS = [ + '.ts', + '.tsx', + '.js', + '.jsx', + '.cts', + '.mts', + '.ctsx', + '.mtsx', + '.json', + '.md', + '.css', + '.scss', + '.py', + '.go', + '.rs', + '.java', + '.kt', + '.swift', + '.yaml', + '.yml', + '.toml', + '.lock', + '.sh', + '.sql', +] as const; + +const TEST_OR_BUILD_KEYWORDS = [ + 'test', + 'tests', + 'tested', + 'testing', + 'vitest', + 'jest', + 'playwright', + 'pnpm', + 'npm', + 'bun', + 'build', + 'typecheck', + 'lint', + 'passed', + 'failed', + 'green', + 'red', + 'error', + 'exception', + 'stack trace', + 'тест', + 'сборк', + 'линт', + 'ошибк', + 'упал', + 'прошел', + 'прошёл', +] as const; + +const SUBSTANTIVE_WORK_KEYWORDS = [ + 'implemented', + 'fixed', + 'added', + 'updated', + 'changed', + 'removed', + 'found', + 'verified', + 'confirmed', + 'completed', + 'created', + 'refactored', + 'patched', + 'root cause', + 'next step', + 'исправ', + 'добав', + 'обнов', + 'измен', + 'удал', + 'нашел', + 'нашёл', + 'подтверд', + 'готово', + 'сделал', + 'сделана', + 'причин', + 'следующ', +] as const; + +const BLOCKER_OR_CLARIFICATION_KEYWORDS = [ + 'blocked', + 'blocker', + 'cannot', + "can't", + 'need', + 'needs', + 'waiting', + 'clarification', + 'question', + 'permission', + 'access denied', + 'not enough context', + 'не могу', + 'не получается', + 'нужн', + 'жду', + 'блок', + 'уточн', + 'вопрос', + 'нет доступа', + 'недостаточно контекст', +] as const; + +const WEAK_START_ONLY_PHRASES = [ + 'начинаю', + 'начинаю работу', + 'начну', + 'приступаю', + 'приступаю к работе', + 'беру в работу', + 'проверю', + 'сейчас проверю', + 'посмотрю', + 'разберусь', + 'готов приступить', + 'готова приступить', + 'готов к работе', + 'готова к работе', + 'will start', + 'starting work', + 'starting', + 'taking this', + "i'll start", + 'i’ll start', + 'i will start', + 'i am starting', + "i'll check", + 'i’ll check', + 'i will check', + 'checking now', + 'on it', +] as const; function normalizeCommentText(text: string): string { return stripAgentBlocks(text).replace(/\s+/g, ' ').trim(); } +function includesAnyKeyword(text: string, keywords: readonly string[]): boolean { + return keywords.some((keyword) => text.includes(keyword)); +} + +function containsTaskOrIssueRef(text: string): boolean { + return text.includes('task-') || /#[a-f0-9]{6,}/i.test(text); +} + +function containsConcreteFileOrPath(text: string): boolean { + const parts = text.split(/\s+/); + return ( + parts.some( + (part) => part.startsWith('./') || part.startsWith('../') || part.startsWith('~/') + ) || + parts.some((part) => part.includes('/') && /[a-z0-9_]/i.test(part)) || + FILE_EXTENSIONS.some((extension) => text.includes(extension)) + ); +} + +function isWeakStartOnly(text: string): boolean { + const normalized = text + .replace(/[.!…\s]+$/g, '') + .replace(/^я\s+/, '') + .trim(); + return WEAK_START_ONLY_PHRASES.includes(normalized as (typeof WEAK_START_ONLY_PHRASES)[number]); +} + function isConcreteProgress(text: string): boolean { return ( - CONCRETE_FILE_OR_PATH_RE.test(text) || - TASK_OR_ISSUE_REF_RE.test(text) || - TEST_OR_BUILD_RESULT_RE.test(text) || - SUBSTANTIVE_WORK_RE.test(text) + containsConcreteFileOrPath(text) || + containsTaskOrIssueRef(text) || + includesAnyKeyword(text, TEST_OR_BUILD_KEYWORDS) || + includesAnyKeyword(text, SUBSTANTIVE_WORK_KEYWORDS) ); } @@ -46,18 +203,20 @@ function classifyTaskCommentText(text: string): TaskProgressTouchClassification return { signal: 'unknown', reason: 'comment_text_empty' }; } - if (BLOCKER_OR_CLARIFICATION_RE.test(normalized)) { + const lowerText = normalized.toLowerCase(); + + if (lowerText.includes('?') || includesAnyKeyword(lowerText, BLOCKER_OR_CLARIFICATION_KEYWORDS)) { return { signal: 'blocker_or_clarification', reason: 'comment_mentions_blocker_or_clarification', }; } - if (isConcreteProgress(normalized)) { + if (isConcreteProgress(lowerText)) { return { signal: 'strong_progress', reason: 'comment_contains_concrete_progress' }; } - if (normalized.length <= 120 && WEAK_START_ONLY_RE.test(normalized)) { + if (lowerText.length <= 120 && isWeakStartOnly(lowerText)) { return { signal: 'weak_start_only', reason: 'comment_is_start_only' }; } diff --git a/src/main/services/team/stallMonitor/TeamTaskStallNotifier.ts b/src/main/services/team/stallMonitor/TeamTaskStallNotifier.ts index af942b99..e5b3adb5 100644 --- a/src/main/services/team/stallMonitor/TeamTaskStallNotifier.ts +++ b/src/main/services/team/stallMonitor/TeamTaskStallNotifier.ts @@ -1,8 +1,9 @@ -import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity'; import { createLogger } from '@shared/utils/logger'; +import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity'; import { TeamInboxReader } from '../TeamInboxReader'; import { TeamInboxWriter } from '../TeamInboxWriter'; + import type { TeamDataService } from '../TeamDataService'; import type { TeamProvisioningService } from '../TeamProvisioningService'; import type { TaskStallAlert } from './TeamTaskStallTypes'; diff --git a/src/main/services/team/stallMonitor/TeamTaskStallPolicy.ts b/src/main/services/team/stallMonitor/TeamTaskStallPolicy.ts index 9c9e2868..fe202870 100644 --- a/src/main/services/team/stallMonitor/TeamTaskStallPolicy.ts +++ b/src/main/services/team/stallMonitor/TeamTaskStallPolicy.ts @@ -1,5 +1,7 @@ -import type { BoardTaskActivityRecord } from '../taskLogs/activity/BoardTaskActivityRecord'; +import { getOpenCodeWeakStartStallThresholdMs } from './featureGates'; import { classifyTaskProgressTouch, type TaskProgressSignal } from './TaskProgressSignalClassifier'; + +import type { BoardTaskActivityRecord } from '../taskLogs/activity/BoardTaskActivityRecord'; import type { ReviewTaskContext, TaskStallBranch, @@ -9,7 +11,6 @@ import type { TeamTaskStallSnapshot, WorkTaskContext, } from './TeamTaskStallTypes'; -import { getOpenCodeWeakStartStallThresholdMs } from './featureGates'; import type { TaskHistoryEvent, TaskWorkInterval, TeamTask } from '@shared/types'; const WORK_TOUCH_TOOLS = new Set(['task_start', 'task_add_comment', 'task_set_status']); diff --git a/src/main/services/team/stallMonitor/TeamTaskStallSnapshotSource.ts b/src/main/services/team/stallMonitor/TeamTaskStallSnapshotSource.ts index e447fb0b..336c6df3 100644 --- a/src/main/services/team/stallMonitor/TeamTaskStallSnapshotSource.ts +++ b/src/main/services/team/stallMonitor/TeamTaskStallSnapshotSource.ts @@ -1,20 +1,21 @@ +import { + inferTeamProviderIdFromModel, + normalizeOptionalTeamProviderId, +} from '@shared/utils/teamProvider'; + import { BoardTaskActivityTranscriptReader } from '../taskLogs/activity/BoardTaskActivityTranscriptReader'; import { isBoardTaskActivityReadEnabled } from '../taskLogs/activity/featureGates'; import { TeamTranscriptSourceLocator } from '../taskLogs/discovery/TeamTranscriptSourceLocator'; import { isBoardTaskExactLogsReadEnabled } from '../taskLogs/exact/featureGates'; import { TeamKanbanManager } from '../TeamKanbanManager'; -import { TeamTaskReader } from '../TeamTaskReader'; import { TeamMembersMetaStore } from '../TeamMembersMetaStore'; +import { TeamTaskReader } from '../TeamTaskReader'; import { BoardTaskActivityBatchIndexer } from './BoardTaskActivityBatchIndexer'; import { OpenCodeTaskStallEvidenceSource } from './OpenCodeTaskStallEvidenceSource'; import { buildResolvedReviewerIndex } from './reviewerResolution'; import { TeamTaskLogFreshnessReader } from './TeamTaskLogFreshnessReader'; import { TeamTaskStallExactRowReader } from './TeamTaskStallExactRowReader'; -import { - inferTeamProviderIdFromModel, - normalizeOptionalTeamProviderId, -} from '@shared/utils/teamProvider'; import type { BoardTaskActivityRecord } from '../taskLogs/activity/BoardTaskActivityRecord'; import type { TeamTaskStallExactRow, TeamTaskStallSnapshot } from './TeamTaskStallTypes'; diff --git a/src/main/services/team/taskLogs/activity/BoardTaskActivityTranscriptReader.ts b/src/main/services/team/taskLogs/activity/BoardTaskActivityTranscriptReader.ts index 64e92093..02e7b1a0 100644 --- a/src/main/services/team/taskLogs/activity/BoardTaskActivityTranscriptReader.ts +++ b/src/main/services/team/taskLogs/activity/BoardTaskActivityTranscriptReader.ts @@ -45,7 +45,7 @@ async function mapLimit( if (currentIndex >= items.length) { return; } - results[currentIndex] = await fn(items[currentIndex]!); + results[currentIndex] = await fn(items[currentIndex]); } }); await Promise.all(workers); diff --git a/src/main/services/team/taskLogs/discovery/TeamTranscriptSourceLocator.ts b/src/main/services/team/taskLogs/discovery/TeamTranscriptSourceLocator.ts index 4cdddef0..e133b615 100644 --- a/src/main/services/team/taskLogs/discovery/TeamTranscriptSourceLocator.ts +++ b/src/main/services/team/taskLogs/discovery/TeamTranscriptSourceLocator.ts @@ -1,8 +1,7 @@ +import { createLogger } from '@shared/utils/logger'; import * as fs from 'fs/promises'; import * as path from 'path'; -import { createLogger } from '@shared/utils/logger'; - import { TeamTranscriptProjectResolver } from '../../TeamTranscriptProjectResolver'; import type { TeamConfig } from '@shared/types'; @@ -35,7 +34,7 @@ async function mapLimit( if (currentIndex >= items.length) { return; } - results[currentIndex] = await fn(items[currentIndex]!); + results[currentIndex] = await fn(items[currentIndex]); } }); await Promise.all(workers); diff --git a/src/main/services/team/taskLogs/exact/BoardTaskExactLogStrictParser.ts b/src/main/services/team/taskLogs/exact/BoardTaskExactLogStrictParser.ts index fd822753..1eed2e49 100644 --- a/src/main/services/team/taskLogs/exact/BoardTaskExactLogStrictParser.ts +++ b/src/main/services/team/taskLogs/exact/BoardTaskExactLogStrictParser.ts @@ -40,7 +40,7 @@ async function mapLimit( if (currentIndex >= items.length) { return; } - results[currentIndex] = await fn(items[currentIndex]!); + results[currentIndex] = await fn(items[currentIndex]); } }); await Promise.all(workers); diff --git a/src/main/services/team/taskLogs/exact/fileVersions.ts b/src/main/services/team/taskLogs/exact/fileVersions.ts index 720b64ed..100c916d 100644 --- a/src/main/services/team/taskLogs/exact/fileVersions.ts +++ b/src/main/services/team/taskLogs/exact/fileVersions.ts @@ -19,7 +19,7 @@ async function mapLimit( if (currentIndex >= items.length) { return; } - results[currentIndex] = await fn(items[currentIndex]!); + results[currentIndex] = await fn(items[currentIndex]); } }); await Promise.all(workers); diff --git a/src/main/services/team/taskLogs/stream/BoardTaskLogStreamService.ts b/src/main/services/team/taskLogs/stream/BoardTaskLogStreamService.ts index cf0e8799..92f9a998 100644 --- a/src/main/services/team/taskLogs/stream/BoardTaskLogStreamService.ts +++ b/src/main/services/team/taskLogs/stream/BoardTaskLogStreamService.ts @@ -1487,7 +1487,7 @@ function mergeSegments( function chooseDefaultFilter(participants: BoardTaskLogParticipant[]): 'all' | string { const namedParticipants = participants.filter((participant) => !participant.isLead); - return namedParticipants.length === 1 ? namedParticipants[0]!.key : 'all'; + return namedParticipants.length === 1 ? namedParticipants[0].key : 'all'; } function mergeRuntimeFallbackResponse( diff --git a/src/main/services/team/taskLogs/stream/OpenCodeTaskLogStreamSource.ts b/src/main/services/team/taskLogs/stream/OpenCodeTaskLogStreamSource.ts index 31d11d87..bea26f9b 100644 --- a/src/main/services/team/taskLogs/stream/OpenCodeTaskLogStreamSource.ts +++ b/src/main/services/team/taskLogs/stream/OpenCodeTaskLogStreamSource.ts @@ -1,5 +1,5 @@ -import { createLogger } from '@shared/utils/logger'; import { sanitizeDisplayContent } from '@shared/utils/contentSanitizer'; +import { createLogger } from '@shared/utils/logger'; import { ClaudeMultimodelBridgeService } from '../../../runtime/ClaudeMultimodelBridgeService'; import { canonicalizeAgentTeamsToolName } from '../../agentTeamsToolNames'; diff --git a/src/preload/index.ts b/src/preload/index.ts index 50d07719..47fbbe23 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -118,8 +118,8 @@ import { TEAM_CANCEL_PROVISIONING, TEAM_CHANGE, TEAM_CREATE, - TEAM_CREATE_INITIAL_GIT_COMMIT, TEAM_CREATE_CONFIG, + TEAM_CREATE_INITIAL_GIT_COMMIT, TEAM_CREATE_TASK, TEAM_DELETE_DRAFT, TEAM_DELETE_TASK_ATTACHMENT, diff --git a/src/renderer/api/httpClient.ts b/src/renderer/api/httpClient.ts index d231be2e..451149dd 100644 --- a/src/renderer/api/httpClient.ts +++ b/src/renderer/api/httpClient.ts @@ -67,12 +67,12 @@ import type { TeamProvisioningModelVerificationMode, TeamProvisioningPrepareResult, TeamProvisioningProgress, - TeamWorktreeGitStatus, TeamsAPI, TeamSummary, TeamTask, TeamTaskStatus, TeamViewSnapshot, + TeamWorktreeGitStatus, TmuxAPI, TmuxStatus, TriggerTestResult, diff --git a/src/renderer/components/team/dialogs/SendMessageDialog.tsx b/src/renderer/components/team/dialogs/SendMessageDialog.tsx index 7e94716f..f241e23d 100644 --- a/src/renderer/components/team/dialogs/SendMessageDialog.tsx +++ b/src/renderer/components/team/dialogs/SendMessageDialog.tsx @@ -27,7 +27,6 @@ import { buildReplyBlock } from '@renderer/utils/agentMessageFormatting'; import { removeChipTokenFromText } from '@renderer/utils/chipUtils'; import { formatAgentRole } from '@renderer/utils/formatAgentRole'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; -import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics'; import { extractTaskRefsFromText, stripEncodedTaskReferenceMetadata, @@ -41,6 +40,7 @@ import { MemberBadge } from '../MemberBadge'; import type { ActionMode } from '@renderer/components/team/messages/ActionModeSelector'; import type { InlineChip } from '@renderer/types/inlineChip'; import type { MentionSuggestion } from '@renderer/types/mention'; +import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics'; import type { AttachmentPayload, ResolvedTeamMember, diff --git a/src/renderer/components/team/dialogs/WorktreeGitReadinessBanner.tsx b/src/renderer/components/team/dialogs/WorktreeGitReadinessBanner.tsx index b9dc9559..d86f7b03 100644 --- a/src/renderer/components/team/dialogs/WorktreeGitReadinessBanner.tsx +++ b/src/renderer/components/team/dialogs/WorktreeGitReadinessBanner.tsx @@ -147,13 +147,13 @@ export function getWorktreeGitControlDisabledReason( return state.status.canUseWorktrees ? null : (state.status.message ?? null); } -export function WorktreeGitReadinessBanner({ +export const WorktreeGitReadinessBanner = ({ state, showReady = false, }: { state: WorktreeGitReadinessState; showReady?: boolean; -}): React.JSX.Element | null { +}): React.JSX.Element | null => { const { status, loading, actionLoading, error, initializeRepository, createInitialCommit } = state; @@ -240,4 +240,4 @@ export function WorktreeGitReadinessBanner({ ); -} +}; diff --git a/src/renderer/components/team/members/CurrentTaskIndicator.tsx b/src/renderer/components/team/members/CurrentTaskIndicator.tsx index 7bd3f765..4781c199 100644 --- a/src/renderer/components/team/members/CurrentTaskIndicator.tsx +++ b/src/renderer/components/team/members/CurrentTaskIndicator.tsx @@ -1,5 +1,5 @@ -import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity'; import { SyncedLoader2 } from '@renderer/components/ui/SyncedLoader2'; +import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity'; import type { TeamTaskWithKanban } from '@shared/types'; diff --git a/src/renderer/components/team/members/MemberCard.tsx b/src/renderer/components/team/members/MemberCard.tsx index 2433d062..b2cc28a3 100644 --- a/src/renderer/components/team/members/MemberCard.tsx +++ b/src/renderer/components/team/members/MemberCard.tsx @@ -1,12 +1,12 @@ import { useMemo, useState } from 'react'; import { Badge } from '@renderer/components/ui/badge'; +import { SyncedLoader2 } from '@renderer/components/ui/SyncedLoader2'; import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip'; import { getTeamColorSet } from '@renderer/constants/teamColors'; import { useTheme } from '@renderer/hooks/useTheme'; import { useStore } from '@renderer/store'; import { selectResolvedMembersForTeamName } from '@renderer/store/slices/teamSlice'; -import { SyncedLoader2 } from '@renderer/components/ui/SyncedLoader2'; import { formatAgentRole } from '@renderer/utils/formatAgentRole'; import { agentAvatarUrl, diff --git a/src/renderer/components/team/members/MemberDetailHeader.tsx b/src/renderer/components/team/members/MemberDetailHeader.tsx index 37500f5a..7d2563b4 100644 --- a/src/renderer/components/team/members/MemberDetailHeader.tsx +++ b/src/renderer/components/team/members/MemberDetailHeader.tsx @@ -15,8 +15,8 @@ import { import { isLeadMember } from '@shared/utils/leadDetection'; import { Pencil } from 'lucide-react'; -import { MemberRoleEditor } from './MemberRoleEditor'; import { MemberPresenceDot } from './MemberPresenceDot'; +import { MemberRoleEditor } from './MemberRoleEditor'; import type { LeadActivityState, diff --git a/src/renderer/components/team/members/MemberPresenceDot.tsx b/src/renderer/components/team/members/MemberPresenceDot.tsx index 00978751..aab1f38b 100644 --- a/src/renderer/components/team/members/MemberPresenceDot.tsx +++ b/src/renderer/components/team/members/MemberPresenceDot.tsx @@ -8,7 +8,10 @@ interface MemberPresenceDotProps { label: string; } -export function MemberPresenceDot({ className, label }: MemberPresenceDotProps): React.JSX.Element { +export const MemberPresenceDot = ({ + className, + label, +}: MemberPresenceDotProps): React.JSX.Element => { const shouldSyncPulse = className?.includes('animate-pulse') === true; const syncedPulseStyle = useSyncedAnimationStyle(shouldSyncPulse, PULSE_DURATION_MS); @@ -22,4 +25,4 @@ export function MemberPresenceDot({ className, label }: MemberPresenceDotProps): aria-label={label} /> ); -} +}; diff --git a/src/renderer/components/team/messages/MessageComposer.tsx b/src/renderer/components/team/messages/MessageComposer.tsx index ebdcbac0..24df1ab2 100644 --- a/src/renderer/components/team/messages/MessageComposer.tsx +++ b/src/renderer/components/team/messages/MessageComposer.tsx @@ -19,7 +19,6 @@ import { isTeamProvisioningActive } from '@renderer/store/slices/teamSlice'; import { serializeChipsWithText } from '@renderer/types/inlineChip'; import { formatAgentRole } from '@renderer/utils/formatAgentRole'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; -import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics'; import { nameColorSet } from '@renderer/utils/projectColor'; import { getSuggestedSlashCommandsForProvider } from '@renderer/utils/providerSlashCommands'; import { buildSlashCommandSuggestions } from '@renderer/utils/skillCommandSuggestions'; @@ -39,6 +38,7 @@ import { useShallow } from 'zustand/react/shallow'; import type { ActionMode } from '@renderer/components/team/messages/ActionModeSelector'; import type { MentionSuggestion } from '@renderer/types/mention'; +import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics'; import type { AttachmentPayload, ResolvedTeamMember, diff --git a/src/renderer/components/team/messages/MessagesPanel.tsx b/src/renderer/components/team/messages/MessagesPanel.tsx index dbe67299..1784de68 100644 --- a/src/renderer/components/team/messages/MessagesPanel.tsx +++ b/src/renderer/components/team/messages/MessagesPanel.tsx @@ -18,7 +18,6 @@ import { useTeamMessagesExpanded } from '@renderer/hooks/useTeamMessagesExpanded import { useTeamMessagesRead } from '@renderer/hooks/useTeamMessagesRead'; import { useStore } from '@renderer/store'; import { selectTeamMessages } from '@renderer/store/slices/teamSlice'; -import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics'; import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering'; import { toMessageKey } from '@renderer/utils/teamMessageKey'; import { shouldExcludeInboxTextFromReplyCandidates } from '@shared/utils/idleNotificationSemantics'; @@ -59,6 +58,7 @@ import type { TimelineItem } from '../activity/LeadThoughtsGroup'; import type { ActionMode } from './ActionModeSelector'; import type { MessagesFilterState } from './MessagesFilterPopover'; import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode'; +import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics'; import type { InboxMessage, ResolvedTeamMember, TaskRef, TeamTaskWithKanban } from '@shared/types'; interface TimeWindow { @@ -186,8 +186,7 @@ export function hasVisibleReplyForSendMessageDiagnostics( const sentMessage = messages.find((message) => message.messageId === messageId); if ( - !sentMessage || - sentMessage.from !== 'user' || + sentMessage?.from !== 'user' || typeof sentMessage.to !== 'string' || sentMessage.to.length === 0 ) { diff --git a/src/renderer/components/team/messages/OpenCodeDeliveryWarning.tsx b/src/renderer/components/team/messages/OpenCodeDeliveryWarning.tsx index 1e92bc7a..8d1e2197 100644 --- a/src/renderer/components/team/messages/OpenCodeDeliveryWarning.tsx +++ b/src/renderer/components/team/messages/OpenCodeDeliveryWarning.tsx @@ -14,11 +14,11 @@ interface OpenCodeDeliveryWarningProps { pendingDelayMs?: number; } -export function OpenCodeDeliveryWarning({ +export const OpenCodeDeliveryWarning = ({ warning, debugDetails, pendingDelayMs = 10_000, -}: OpenCodeDeliveryWarningProps): JSX.Element | null { +}: OpenCodeDeliveryWarningProps): JSX.Element | null => { const detailsKey = `${warning ?? ''}:${debugDetails?.messageId ?? ''}`; const delayPendingWarning = debugDetails?.responsePending === true && debugDetails.delivered !== false; @@ -148,4 +148,4 @@ export function OpenCodeDeliveryWarning({ ) : null} ); -} +}; diff --git a/src/renderer/components/ui/SyncedLoader2.tsx b/src/renderer/components/ui/SyncedLoader2.tsx index 9da4f382..50144c34 100644 --- a/src/renderer/components/ui/SyncedLoader2.tsx +++ b/src/renderer/components/ui/SyncedLoader2.tsx @@ -10,12 +10,12 @@ export type SyncedLoader2Props = ComponentProps & { spinDurationMs?: number; }; -export function SyncedLoader2({ +export const SyncedLoader2 = ({ className, style, spinDurationMs = DEFAULT_SPIN_DURATION_MS, ...props -}: SyncedLoader2Props): React.JSX.Element { +}: SyncedLoader2Props): React.JSX.Element => { const syncedStyle = useSyncedAnimationStyle(true, spinDurationMs); return ( @@ -25,4 +25,4 @@ export function SyncedLoader2({ style={{ ...syncedStyle, ...style }} /> ); -} +}; diff --git a/src/renderer/hooks/useKeyboardShortcuts.ts b/src/renderer/hooks/useKeyboardShortcuts.ts index d1c4d10a..70956717 100644 --- a/src/renderer/hooks/useKeyboardShortcuts.ts +++ b/src/renderer/hooks/useKeyboardShortcuts.ts @@ -29,7 +29,7 @@ export function isEditableShortcutTarget(target: EventTarget | null): boolean { } const contentEditable = editableElement.getAttribute('contenteditable'); - return contentEditable == null || contentEditable.toLowerCase() !== 'false'; + return contentEditable?.toLowerCase() !== 'false'; } export function useKeyboardShortcuts(): void { diff --git a/src/renderer/store/slices/teamSlice.ts b/src/renderer/store/slices/teamSlice.ts index 8e0f52fa..b9adaedf 100644 --- a/src/renderer/store/slices/teamSlice.ts +++ b/src/renderer/store/slices/teamSlice.ts @@ -1,5 +1,6 @@ import { api } from '@renderer/api'; import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages'; +import { buildOpenCodeRuntimeDeliveryDiagnostics } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics'; import { normalizePath } from '@renderer/utils/pathNormalize'; import { buildTaskChangePresenceKey, @@ -10,7 +11,6 @@ import { import { toMessageKey } from '@renderer/utils/teamMessageKey'; import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext'; import { IpcError, unwrapIpc } from '@renderer/utils/unwrapIpc'; -import { buildOpenCodeRuntimeDeliveryDiagnostics } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics'; import { stripAgentBlocks } from '@shared/constants/agentBlocks'; import { DEFAULT_TOOL_APPROVAL_SETTINGS } from '@shared/types/team'; import { isLeadMember } from '@shared/utils/leadDetection'; @@ -25,8 +25,8 @@ import { getWorktreeNavigationState } from '../utils/stateResetHelpers'; import type { AppState } from '../types'; import type { GraphLayoutMode, GraphOwnerSlotAssignment } from '@claude-teams/agent-graph'; import type { AppConfig } from '@renderer/types/data'; -import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics'; import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode'; +import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics'; import type { ActiveToolCall, AddMemberRequest, diff --git a/src/renderer/utils/memberLaunchDiagnostics.ts b/src/renderer/utils/memberLaunchDiagnostics.ts index 8eb52c00..97f04069 100644 --- a/src/renderer/utils/memberLaunchDiagnostics.ts +++ b/src/renderer/utils/memberLaunchDiagnostics.ts @@ -55,7 +55,7 @@ function boundedNumber(value: number | undefined): number | undefined { } function uniqueDiagnostics( - ...groups: Array + ...groups: (readonly (string | undefined)[] | undefined)[] ): string[] | undefined { const seen = new Set(); const diagnostics: string[] = []; diff --git a/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts b/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts index bc87c17b..a7317841 100644 --- a/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +++ b/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts @@ -26,7 +26,7 @@ export function buildOpenCodeRuntimeDeliveryDiagnostics( result: SendMessageResult ): OpenCodeRuntimeDeliveryDiagnostics { const runtimeDelivery = result.runtimeDelivery; - if (!runtimeDelivery || runtimeDelivery.attempted !== true) { + if (runtimeDelivery?.attempted !== true) { return { warning: null, debugDetails: null }; } diff --git a/src/renderer/utils/streamJsonParser.ts b/src/renderer/utils/streamJsonParser.ts index 5903d478..ab3fa125 100644 --- a/src/renderer/utils/streamJsonParser.ts +++ b/src/renderer/utils/streamJsonParser.ts @@ -51,7 +51,7 @@ interface ContentBlock { input?: Record; } -type CodexNativeJsonEvent = { +interface CodexNativeJsonEvent { type?: string; thread_id?: string; item?: { @@ -70,16 +70,16 @@ type CodexNativeJsonEvent = { cached_input_tokens?: number; output_tokens?: number; }; -}; +} -type CodexNativeProjectedSystemEvent = { +interface CodexNativeProjectedSystemEvent { type?: string; subtype?: string; content?: string; level?: string; codexNativeThreadStatus?: string; codexNativeThreadId?: string; -}; +} /** * Content-based hash for deterministic fallback IDs that survive @@ -265,7 +265,7 @@ function codexNativeProjectedSystemToDisplayItems( timestamp: Date ): AIGroupDisplayItem[] | null { const event = asRecord(parsed) as CodexNativeProjectedSystemEvent | null; - if (!event || event.type !== 'system' || typeof event.subtype !== 'string') { + if (event?.type !== 'system' || typeof event.subtype !== 'string') { return null; } diff --git a/test/main/services/team/TeamAgentLaunchMatrix.safe-e2e.test.ts b/test/main/services/team/TeamAgentLaunchMatrix.safe-e2e.test.ts index f5ea6146..9d5e3348 100644 --- a/test/main/services/team/TeamAgentLaunchMatrix.safe-e2e.test.ts +++ b/test/main/services/team/TeamAgentLaunchMatrix.safe-e2e.test.ts @@ -10398,7 +10398,7 @@ describe('Team agent launch matrix safe e2e', () => { const launchAdapter = new FakeOpenCodeRuntimeAdapter(); const firstService = new TeamProvisioningService(); firstService.setRuntimeAdapterRegistry(new TeamRuntimeAdapterRegistry([launchAdapter])); - await firstService.createTeam( + const launch = await firstService.createTeam( { teamName, cwd: projectPath, @@ -10426,6 +10426,7 @@ describe('Team agent launch matrix safe e2e', () => { }); expect(messageAdapter.messageInputs).toHaveLength(1); expect(messageAdapter.messageInputs[0]).toMatchObject({ + runId: launch.runId, teamName, laneId: 'primary', memberName: 'alice', @@ -10433,7 +10434,6 @@ describe('Team agent launch matrix safe e2e', () => { text: 'message recovered pure opencode lane', messageId: 'msg-recovered-pure-opencode', }); - expect(messageAdapter.messageInputs[0]?.runId).toBeUndefined(); }); it('delivers direct OpenCode member messages to recovered pure OpenCode lanes despite stale terminal provisioning state', async () => { diff --git a/test/main/services/team/TeamProvisioningService.test.ts b/test/main/services/team/TeamProvisioningService.test.ts index 4d2f82fc..a478b475 100644 --- a/test/main/services/team/TeamProvisioningService.test.ts +++ b/test/main/services/team/TeamProvisioningService.test.ts @@ -7752,7 +7752,7 @@ describe('TeamProvisioningService', () => { ); await (svc as any).launchMixedSecondaryLaneIfNeeded(run); - await vi.waitFor(() => expect(adapterLaunch).toHaveBeenCalledTimes(2)); + await vi.waitFor(() => expect(adapterLaunch).toHaveBeenCalledTimes(2), { timeout: 5_000 }); expect(adapterLaunch).toHaveBeenCalledWith( expect.objectContaining({ laneId: 'secondary:opencode:bob', @@ -7783,20 +7783,23 @@ describe('TeamProvisioningService', () => { ], }) ); - await vi.waitFor(() => { - expect(run.mixedSecondaryLanes).toEqual([ - expect.objectContaining({ - laneId: 'secondary:opencode:bob', - state: 'finished', - result: expect.objectContaining({ teamLaunchState: 'clean_success' }), - }), - expect.objectContaining({ - laneId: 'secondary:opencode:tom', - state: 'finished', - result: expect.objectContaining({ teamLaunchState: 'clean_success' }), - }), - ]); - }); + await vi.waitFor( + () => { + expect(run.mixedSecondaryLanes).toEqual([ + expect.objectContaining({ + laneId: 'secondary:opencode:bob', + state: 'finished', + result: expect.objectContaining({ teamLaunchState: 'clean_success' }), + }), + expect.objectContaining({ + laneId: 'secondary:opencode:tom', + state: 'finished', + result: expect.objectContaining({ teamLaunchState: 'clean_success' }), + }), + ]); + }, + { timeout: 5_000 } + ); const publicStatuses = await svc.getMemberSpawnStatuses('safe-mixed-codex-opencode-launch'); expect(publicStatuses.statuses.bob).toMatchObject({ status: 'online',