fix(ci): restore dev validation

This commit is contained in:
Илия 2026-04-28 16:08:05 +03:00 committed by GitHub
parent 6c43380846
commit a8d53ca5cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 298 additions and 129 deletions

View file

@ -1353,6 +1353,7 @@ describe('agent-teams-mcp tools', () => {
members: [
{ name: 'lead', role: 'team-lead' },
{ name: 'alice', role: 'developer' },
{ name: 'bob', role: 'reviewer' },
],
});

View file

@ -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",

View file

@ -4,11 +4,11 @@ import type {
RuntimeProviderManagementDirectoryResponse,
RuntimeProviderManagementForgetInput,
RuntimeProviderManagementLoadDirectoryInput,
RuntimeProviderManagementLoadModelsInput,
RuntimeProviderManagementLoadSetupFormInput,
RuntimeProviderManagementLoadViewInput,
RuntimeProviderManagementLoadModelsInput,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementModelsResponse,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementProviderResponse,
RuntimeProviderManagementSetDefaultModelInput,
RuntimeProviderManagementSetupFormResponse,

View file

@ -5,11 +5,11 @@ import type {
RuntimeProviderManagementDirectoryResponse,
RuntimeProviderManagementForgetInput,
RuntimeProviderManagementLoadDirectoryInput,
RuntimeProviderManagementLoadSetupFormInput,
RuntimeProviderManagementLoadModelsInput,
RuntimeProviderManagementLoadSetupFormInput,
RuntimeProviderManagementLoadViewInput,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementModelsResponse,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementProviderResponse,
RuntimeProviderManagementSetDefaultModelInput,
RuntimeProviderManagementSetupFormResponse,

View file

@ -18,11 +18,11 @@ import type {
RuntimeProviderManagementDirectoryResponse,
RuntimeProviderManagementForgetInput,
RuntimeProviderManagementLoadDirectoryInput,
RuntimeProviderManagementLoadSetupFormInput,
RuntimeProviderManagementLoadModelsInput,
RuntimeProviderManagementLoadSetupFormInput,
RuntimeProviderManagementLoadViewInput,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementModelsResponse,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementProviderResponse,
RuntimeProviderManagementSetDefaultModelInput,
RuntimeProviderManagementSetupFormResponse,

View file

@ -8,11 +8,11 @@ import type {
RuntimeProviderManagementDirectoryResponse,
RuntimeProviderManagementForgetInput,
RuntimeProviderManagementLoadDirectoryInput,
RuntimeProviderManagementLoadSetupFormInput,
RuntimeProviderManagementLoadModelsInput,
RuntimeProviderManagementLoadSetupFormInput,
RuntimeProviderManagementLoadViewInput,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementModelsResponse,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementProviderResponse,
RuntimeProviderManagementSetDefaultModelInput,
RuntimeProviderManagementSetupFormResponse,

View file

@ -11,8 +11,8 @@ import type {
RuntimeProviderManagementErrorDto,
RuntimeProviderManagementForgetInput,
RuntimeProviderManagementLoadDirectoryInput,
RuntimeProviderManagementLoadSetupFormInput,
RuntimeProviderManagementLoadModelsInput,
RuntimeProviderManagementLoadSetupFormInput,
RuntimeProviderManagementLoadViewInput,
RuntimeProviderManagementModelsResponse,
RuntimeProviderManagementModelTestResponse,

View file

@ -17,11 +17,11 @@ import type {
RuntimeProviderManagementDirectoryResponse,
RuntimeProviderManagementForgetInput,
RuntimeProviderManagementLoadDirectoryInput,
RuntimeProviderManagementLoadSetupFormInput,
RuntimeProviderManagementLoadModelsInput,
RuntimeProviderManagementLoadSetupFormInput,
RuntimeProviderManagementLoadViewInput,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementModelsResponse,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementProviderResponse,
RuntimeProviderManagementSetDefaultModelInput,
RuntimeProviderManagementSetupFormResponse,

View file

@ -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' },

View file

@ -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;

View file

@ -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,

View file

@ -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';

View file

@ -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';

View file

@ -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<void>>();
vi.mock('node:fs/promises', () => ({

View file

@ -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<string>(['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({

View file

@ -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';

View file

@ -3959,10 +3959,10 @@ export class TeamProvisioningService {
Promise<OpenCodeMemberInboxRelayResult>
>();
private readonly openCodePromptDeliveryWatchdogTimers = new Map<string, NodeJS.Timeout>();
private readonly openCodePromptDeliveryWatchdogQueue: Array<{
private readonly openCodePromptDeliveryWatchdogQueue: {
teamName: string;
run: () => Promise<void>;
}> = [];
}[] = [];
private openCodePromptDeliveryWatchdogInFlight = 0;
private openCodePromptDeliveryWatchdogDisabledLogged = false;
private readonly openCodePromptDeliveryWatchdogInFlightByTeam = new Map<string, number>();
@ -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<number>();
const rows: Array<{ pid: number; command: string }> = [];
const rows: { pid: number; command: string }[] = [];
if (process.platform === 'win32') {
try {

View file

@ -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[];
}

View file

@ -4,13 +4,13 @@ import type {
OpenCodeTeamLaunchReadinessState,
} from '../readiness/OpenCodeTeamLaunchReadiness';
import type {
OpenCodeBackfillTaskLedgerCommandBody,
OpenCodeBackfillTaskLedgerCommandData,
OpenCodeBridgeCommandName,
OpenCodeBridgeDiagnosticEvent,
OpenCodeBridgeFailureKind,
OpenCodeBridgeResult,
OpenCodeBridgeRuntimeSnapshot,
OpenCodeBackfillTaskLedgerCommandBody,
OpenCodeBackfillTaskLedgerCommandData,
OpenCodeCleanupHostsCommandBody,
OpenCodeCleanupHostsCommandData,
OpenCodeLaunchTeamCommandBody,

View file

@ -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({

View file

@ -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;

View file

@ -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,

View file

@ -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",
'ill start',
'i will start',
'i am starting',
"i'll check",
'ill 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' };
}

View file

@ -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';

View file

@ -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']);

View file

@ -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';

View file

@ -45,7 +45,7 @@ async function mapLimit<T, R>(
if (currentIndex >= items.length) {
return;
}
results[currentIndex] = await fn(items[currentIndex]!);
results[currentIndex] = await fn(items[currentIndex]);
}
});
await Promise.all(workers);

View file

@ -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<T, R>(
if (currentIndex >= items.length) {
return;
}
results[currentIndex] = await fn(items[currentIndex]!);
results[currentIndex] = await fn(items[currentIndex]);
}
});
await Promise.all(workers);

View file

@ -40,7 +40,7 @@ async function mapLimit<T, R>(
if (currentIndex >= items.length) {
return;
}
results[currentIndex] = await fn(items[currentIndex]!);
results[currentIndex] = await fn(items[currentIndex]);
}
});
await Promise.all(workers);

View file

@ -19,7 +19,7 @@ async function mapLimit<T, R>(
if (currentIndex >= items.length) {
return;
}
results[currentIndex] = await fn(items[currentIndex]!);
results[currentIndex] = await fn(items[currentIndex]);
}
});
await Promise.all(workers);

View file

@ -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(

View file

@ -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';

View file

@ -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,

View file

@ -67,12 +67,12 @@ import type {
TeamProvisioningModelVerificationMode,
TeamProvisioningPrepareResult,
TeamProvisioningProgress,
TeamWorktreeGitStatus,
TeamsAPI,
TeamSummary,
TeamTask,
TeamTaskStatus,
TeamViewSnapshot,
TeamWorktreeGitStatus,
TmuxAPI,
TmuxStatus,
TriggerTestResult,

View file

@ -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,

View file

@ -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({
</div>
</div>
);
}
};

View file

@ -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';

View file

@ -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,

View file

@ -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,

View file

@ -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}
/>
);
}
};

View file

@ -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,

View file

@ -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
) {

View file

@ -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}
</span>
);
}
};

View file

@ -10,12 +10,12 @@ export type SyncedLoader2Props = ComponentProps<typeof Loader2> & {
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 }}
/>
);
}
};

View file

@ -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 {

View file

@ -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,

View file

@ -55,7 +55,7 @@ function boundedNumber(value: number | undefined): number | undefined {
}
function uniqueDiagnostics(
...groups: Array<readonly (string | undefined)[] | undefined>
...groups: (readonly (string | undefined)[] | undefined)[]
): string[] | undefined {
const seen = new Set<string>();
const diagnostics: string[] = [];

View file

@ -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 };
}

View file

@ -51,7 +51,7 @@ interface ContentBlock {
input?: Record<string, unknown>;
}
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;
}

View file

@ -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 () => {

View file

@ -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',