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: [ members: [
{ name: 'lead', role: 'team-lead' }, { name: 'lead', role: 'team-lead' },
{ name: 'alice', role: 'developer' }, { 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", "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", "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", "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": "electron-builder --mac --win --linux",
"dist:mac": "electron-builder --mac", "dist:mac": "electron-builder --mac",
"dist:mac:arm64": "electron-builder --mac --arm64", "dist:mac:arm64": "electron-builder --mac --arm64",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -77,7 +77,7 @@ interface ProviderRowProps {
readonly actions: RuntimeProviderManagementActions; readonly actions: RuntimeProviderManagementActions;
} }
const DIRECTORY_FILTERS: Array<{ id: RuntimeProviderDirectoryFilterDto; label: string }> = [ const DIRECTORY_FILTERS: { id: RuntimeProviderDirectoryFilterDto; label: string }[] = [
{ id: 'all', label: 'All' }, { id: 'all', label: 'All' },
{ id: 'connectable', label: 'Connectable' }, { id: 'connectable', label: 'Connectable' },
{ id: 'connected', label: 'Connected' }, { id: 'connected', label: 'Connected' },

View file

@ -2,10 +2,10 @@ import { useEffect, useState } from 'react';
import type { CSSProperties, JSX } from 'react'; import type { CSSProperties, JSX } from 'react';
type ProviderBrand = { interface ProviderBrand {
providerId: string; providerId: string;
displayName: string; displayName: string;
}; }
interface SvgPath { interface SvgPath {
d: string; d: string;

View file

@ -11,8 +11,8 @@ import {
TEAM_ALIVE_LIST, TEAM_ALIVE_LIST,
TEAM_CANCEL_PROVISIONING, TEAM_CANCEL_PROVISIONING,
TEAM_CREATE, TEAM_CREATE,
TEAM_CREATE_INITIAL_GIT_COMMIT,
TEAM_CREATE_CONFIG, TEAM_CREATE_CONFIG,
TEAM_CREATE_INITIAL_GIT_COMMIT,
TEAM_CREATE_TASK, TEAM_CREATE_TASK,
TEAM_DELETE_DRAFT, TEAM_DELETE_DRAFT,
TEAM_DELETE_TASK_ATTACHMENT, TEAM_DELETE_TASK_ATTACHMENT,
@ -209,8 +209,8 @@ import type {
TeamTask, TeamTask,
TeamTaskStatus, TeamTaskStatus,
TeamUpdateConfigRequest, TeamUpdateConfigRequest,
TeamWorktreeGitStatus,
TeamViewSnapshot, TeamViewSnapshot,
TeamWorktreeGitStatus,
ToolApprovalFileContent, ToolApprovalFileContent,
ToolApprovalSettings, ToolApprovalSettings,
UpdateKanbanPatch, UpdateKanbanPatch,

View file

@ -18,9 +18,10 @@ import { countTokens } from '@main/utils/tokenizer';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import * as path from 'path'; import * as path from 'path';
import { buildSemanticStepGroups } from './SemanticStepGrouper';
import { resolveProjectStorageDir } from '../discovery/projectStorageDir'; import { resolveProjectStorageDir } from '../discovery/projectStorageDir';
import { buildSemanticStepGroups } from './SemanticStepGrouper';
import type { SubagentResolver } from '../discovery/SubagentResolver'; import type { SubagentResolver } from '../discovery/SubagentResolver';
import type { FileSystemProvider } from '../infrastructure/FileSystemProvider'; import type { FileSystemProvider } from '../infrastructure/FileSystemProvider';
import type { SessionParser } from '../parsing/SessionParser'; import type { SessionParser } from '../parsing/SessionParser';

View file

@ -19,9 +19,9 @@ import * as path from 'path';
import { startMainSpan } from '../../sentry'; import { startMainSpan } from '../../sentry';
import { resolveProjectStorageDir } from './projectStorageDir';
import { SearchTextCache } from './SearchTextCache'; import { SearchTextCache } from './SearchTextCache';
import { extractSearchableEntries } from './SearchTextExtractor'; import { extractSearchableEntries } from './SearchTextExtractor';
import { resolveProjectStorageDir } from './projectStorageDir';
import { subprojectRegistry } from './SubprojectRegistry'; import { subprojectRegistry } from './SubprojectRegistry';
import type { SearchableEntry } from './SearchTextExtractor'; import type { SearchableEntry } from './SearchTextExtractor';

View file

@ -1,10 +1,11 @@
// @vitest-environment node // @vitest-environment node
import { constants as fsConstants } from 'node:fs'; import { constants as fsConstants } from 'node:fs';
import type { PathLike } from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; 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>>(); const accessMock = vi.fn<(filePath: PathLike, mode?: number) => Promise<void>>();
vi.mock('node:fs/promises', () => ({ 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 { appendOpenCodeTaskChangeDiag } from '@main/utils/openCodeTaskChangeDiagLog';
import { getTasksBasePath, getTeamsBasePath } from '@main/utils/pathDecoder';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import { resolveTaskChangePresenceFromResult } from '@shared/utils/taskChangePresence'; import { resolveTaskChangePresenceFromResult } from '@shared/utils/taskChangePresence';
import { import {
@ -9,19 +9,18 @@ import {
} from '@shared/utils/taskChangeState'; } from '@shared/utils/taskChangeState';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import { existsSync } from 'fs'; 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 os from 'os';
import * as path from 'path'; import * as path from 'path';
import { JsonTaskChangeSummaryCacheRepository } from './cache/JsonTaskChangeSummaryCacheRepository'; import { JsonTaskChangeSummaryCacheRepository } from './cache/JsonTaskChangeSummaryCacheRepository';
import { TeamMetaStore } from './TeamMetaStore';
import { TaskChangeComputer } from './TaskChangeComputer';
import { TaskChangeLedgerReader } from './TaskChangeLedgerReader';
import { import {
getOpenCodeLaneScopedRuntimeFilePath, getOpenCodeLaneScopedRuntimeFilePath,
getOpenCodeTeamRuntimeDirectory, getOpenCodeTeamRuntimeDirectory,
readOpenCodeRuntimeLaneIndex, readOpenCodeRuntimeLaneIndex,
} from './opencode/store/OpenCodeRuntimeManifestEvidenceReader'; } from './opencode/store/OpenCodeRuntimeManifestEvidenceReader';
import { TaskChangeComputer } from './TaskChangeComputer';
import { TaskChangeLedgerReader } from './TaskChangeLedgerReader';
import { import {
buildTaskChangePresenceDescriptor, buildTaskChangePresenceDescriptor,
computeTaskChangePresenceProjectFingerprint, computeTaskChangePresenceProjectFingerprint,
@ -34,14 +33,15 @@ import {
type TaskChangeTaskMeta, type TaskChangeTaskMeta,
} from './taskChangeWorkerTypes'; } from './taskChangeWorkerTypes';
import { TeamConfigReader } from './TeamConfigReader'; import { TeamConfigReader } from './TeamConfigReader';
import { TeamMetaStore } from './TeamMetaStore';
import type { TaskChangePresenceRepository } from './cache/TaskChangePresenceRepository'; 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 { TaskBoundaryParser } from './TaskBoundaryParser';
import type { TaskChangeWorkerClient } from './TaskChangeWorkerClient'; import type { TaskChangeWorkerClient } from './TaskChangeWorkerClient';
import type { TeamLogSourceTracker } from './TeamLogSourceTracker'; import type { TeamLogSourceTracker } from './TeamLogSourceTracker';
import type { TeamMemberLogsFinder } from './TeamMemberLogsFinder'; 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'; import type { AgentChangeSet, ChangeStats, TaskChangeSetV2 } from '@shared/types';
const logger = createLogger('Service:ChangeExtractorService'); const logger = createLogger('Service:ChangeExtractorService');
@ -678,7 +678,7 @@ export class ChangeExtractorService {
teamName: string, teamName: string,
taskId: string taskId: string
): Promise< ): Promise<
Array<{ {
memberName: string; memberName: string;
laneId?: string; laneId?: string;
runtimeSessionId: string | null; runtimeSessionId: string | null;
@ -687,8 +687,8 @@ export class ChangeExtractorService {
observedAssistantMessageId: string | null; observedAssistantMessageId: string | null;
prePromptCursor: string | null; prePromptCursor: string | null;
postPromptCursor: string | null; postPromptCursor: string | null;
taskRefs: Array<{ taskId: string; displayId: string; teamName: string }>; taskRefs: { taskId: string; displayId: string; teamName: string }[];
}> }[]
> { > {
const teamsBasePath = getTeamsBasePath(); const teamsBasePath = getTeamsBasePath();
const laneIds = new Set<string>(['primary']); const laneIds = new Set<string>(['primary']);
@ -704,7 +704,7 @@ export class ChangeExtractorService {
laneIds.add(laneId); laneIds.add(laneId);
} }
const records: Array<{ const records: {
memberName: string; memberName: string;
laneId?: string; laneId?: string;
runtimeSessionId: string | null; runtimeSessionId: string | null;
@ -713,8 +713,8 @@ export class ChangeExtractorService {
observedAssistantMessageId: string | null; observedAssistantMessageId: string | null;
prePromptCursor: string | null; prePromptCursor: string | null;
postPromptCursor: 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) { for (const laneId of laneIds) {
const filePath = getOpenCodeLaneScopedRuntimeFilePath({ const filePath = getOpenCodeLaneScopedRuntimeFilePath({

View file

@ -1,6 +1,6 @@
import { getAppDataPath, getClaudeBasePath } from '@main/utils/pathDecoder'; import { getAppDataPath, getClaudeBasePath } from '@main/utils/pathDecoder';
import { createHash } from 'crypto';
import { execFile } from 'child_process'; import { execFile } from 'child_process';
import { createHash } from 'crypto';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';

View file

@ -3959,10 +3959,10 @@ export class TeamProvisioningService {
Promise<OpenCodeMemberInboxRelayResult> Promise<OpenCodeMemberInboxRelayResult>
>(); >();
private readonly openCodePromptDeliveryWatchdogTimers = new Map<string, NodeJS.Timeout>(); private readonly openCodePromptDeliveryWatchdogTimers = new Map<string, NodeJS.Timeout>();
private readonly openCodePromptDeliveryWatchdogQueue: Array<{ private readonly openCodePromptDeliveryWatchdogQueue: {
teamName: string; teamName: string;
run: () => Promise<void>; run: () => Promise<void>;
}> = []; }[] = [];
private openCodePromptDeliveryWatchdogInFlight = 0; private openCodePromptDeliveryWatchdogInFlight = 0;
private openCodePromptDeliveryWatchdogDisabledLogged = false; private openCodePromptDeliveryWatchdogDisabledLogged = false;
private readonly openCodePromptDeliveryWatchdogInFlightByTeam = new Map<string, number>(); private readonly openCodePromptDeliveryWatchdogInFlightByTeam = new Map<string, number>();
@ -13625,7 +13625,7 @@ export class TeamProvisioningService {
(config) => config?.members?.find((member) => isLeadMember(member))?.name?.trim() || null (config) => config?.members?.find((member) => isLeadMember(member))?.name?.trim() || null
) )
.catch(() => null); .catch(() => null);
if (leadName && inboxName.trim().toLowerCase() === leadName.toLowerCase()) { if (inboxName.trim().toLowerCase() === leadName?.toLowerCase()) {
if (await this.isOpenCodeRuntimeRecipient(teamName, inboxName)) { if (await this.isOpenCodeRuntimeRecipient(teamName, inboxName)) {
const diagnostic = const diagnostic =
'opencode_lead_runtime_session_missing: OpenCode lead inbox relay is unsupported in v1; leaving inbox unread for durable retry/diagnostics.'; '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 ? this.runs.get(this.getTrackedRunId(teamName)!)?.child?.pid
: undefined; : undefined;
const pids = new Set<number>(); const pids = new Set<number>();
const rows: Array<{ pid: number; command: string }> = []; const rows: { pid: number; command: string }[] = [];
if (process.platform === 'win32') { if (process.platform === 'win32') {
try { try {

View file

@ -131,7 +131,7 @@ export interface OpenCodeCleanupHostsCommandBody {
export interface OpenCodeCleanupHostsCommandData { export interface OpenCodeCleanupHostsCommandData {
cleaned: number; cleaned: number;
remaining: number; remaining: number;
hosts: Array<{ hosts: {
hostKey: string; hostKey: string;
projectPath: string; projectPath: string;
pid: number; pid: number;
@ -146,7 +146,7 @@ export interface OpenCodeCleanupHostsCommandData {
| 'failed'; | 'failed';
reason: string; reason: string;
leaseCount: number; leaseCount: number;
}>; }[];
diagnostics: string[]; diagnostics: string[];
} }
@ -270,7 +270,7 @@ export interface OpenCodeBackfillTaskLedgerCommandData {
importedEvents: number; importedEvents: number;
skippedEvents: number; skippedEvents: number;
outcome: OpenCodeBackfillTaskLedgerOutcome; outcome: OpenCodeBackfillTaskLedgerOutcome;
notices: Array<{ severity: 'warning'; message: string; code: string }>; notices: { severity: 'warning'; message: string; code: string }[];
diagnostics: string[]; diagnostics: string[];
} }

View file

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

View file

@ -1,12 +1,12 @@
import { stableHash } from '../bridge/OpenCodeBridgeCommandContract'; import { stableHash } from '../bridge/OpenCodeBridgeCommandContract';
import { VersionedJsonStore, VersionedJsonStoreError } from '../store/VersionedJsonStore'; import { VersionedJsonStore, VersionedJsonStoreError } from '../store/VersionedJsonStore';
import type { AgentActionMode, TaskRef } from '@shared/types/team';
import type { import type {
OpenCodeDeliveryResponseObservation, OpenCodeDeliveryResponseObservation,
OpenCodeDeliveryResponseState, OpenCodeDeliveryResponseState,
OpenCodeDeliveryVisibleReplyCorrelation, OpenCodeDeliveryVisibleReplyCorrelation,
} from '../bridge/OpenCodeBridgeCommandContract'; } 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_LEDGER_SCHEMA_VERSION = 1;
export const OPENCODE_PROMPT_DELIVERY_RESPONDED_RETENTION_MS = 7 * 24 * 60 * 60 * 1000; export const OPENCODE_PROMPT_DELIVERY_RESPONDED_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
@ -603,7 +603,7 @@ export function hashOpenCodePromptDeliveryPayload(input: {
replyRecipient: string; replyRecipient: string;
actionMode?: AgentActionMode | null; actionMode?: AgentActionMode | null;
taskRefs?: TaskRef[]; taskRefs?: TaskRef[];
attachments?: Array<{ id?: string; filename?: string; mimeType?: string; size?: number }>; attachments?: { id?: string; filename?: string; mimeType?: string; size?: number }[];
source?: string; source?: string;
}): string { }): string {
return `sha256:${stableHash({ 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 { OpenCodeDeliveryResponseState } from '../bridge/OpenCodeBridgeCommandContract';
import type { OpenCodePromptDeliveryStatus } from './OpenCodePromptDeliveryLedger'; 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_OBSERVE_DELAY_MS = 3_000;
export const OPENCODE_PROMPT_DELIVERY_RETRY_DELAY_MS = 15_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 { canonicalizeAgentTeamsToolName } from '../agentTeamsToolNames';
import { ClaudeBinaryResolver } from '../ClaudeBinaryResolver'; import { ClaudeBinaryResolver } from '../ClaudeBinaryResolver';
import { ClaudeMultimodelBridgeService } from '../../runtime/ClaudeMultimodelBridgeService';
import type { import type {
OpenCodeRuntimeTranscriptLogMessage, OpenCodeRuntimeTranscriptLogMessage,

View file

@ -15,28 +15,185 @@ export interface TaskProgressTouchClassification {
reason: string; reason: string;
} }
const CONCRETE_FILE_OR_PATH_RE = const FILE_EXTENSIONS = [
/(?:^|\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; '.ts',
const TASK_OR_ISSUE_REF_RE = /#[a-f0-9]{6,}|\btask-[\w-]+/i; '.tsx',
const TEST_OR_BUILD_RESULT_RE = '.js',
/\b(?:test(?:s|ed|ing)?|vitest|jest|playwright|pnpm|npm|bun|build|typecheck|lint|passed|failed|green|red|error|exception|stack trace)\b|тест|сборк|линт|ошибк|упал|прош[её]л/i; '.jsx',
const SUBSTANTIVE_WORK_RE = '.cts',
/\b(?:implemented|fixed|added|updated|changed|removed|found|verified|confirmed|completed|created|refactored|patched|root cause|next step)\b|исправ|добав|обнов|измен|удал|наш[её]л|подтверд|готово|сделал|сделана|причин|следующ/i; '.mts',
const BLOCKER_OR_CLARIFICATION_RE = '.ctsx',
/\?|(?:^|\b)(?:blocked|blocker|cannot|can't|need|needs|waiting|clarification|question|permission|access denied|not enough context)\b|не могу|не получается|нужн|жду|блок|уточн|вопрос|нет доступа|недостаточно контекст/i; '.mtsx',
const WEAK_START_ONLY_RE = '.json',
/^(?:я\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; '.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 { function normalizeCommentText(text: string): string {
return stripAgentBlocks(text).replace(/\s+/g, ' ').trim(); 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 { function isConcreteProgress(text: string): boolean {
return ( return (
CONCRETE_FILE_OR_PATH_RE.test(text) || containsConcreteFileOrPath(text) ||
TASK_OR_ISSUE_REF_RE.test(text) || containsTaskOrIssueRef(text) ||
TEST_OR_BUILD_RESULT_RE.test(text) || includesAnyKeyword(text, TEST_OR_BUILD_KEYWORDS) ||
SUBSTANTIVE_WORK_RE.test(text) includesAnyKeyword(text, SUBSTANTIVE_WORK_KEYWORDS)
); );
} }
@ -46,18 +203,20 @@ function classifyTaskCommentText(text: string): TaskProgressTouchClassification
return { signal: 'unknown', reason: 'comment_text_empty' }; 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 { return {
signal: 'blocker_or_clarification', signal: 'blocker_or_clarification',
reason: 'comment_mentions_blocker_or_clarification', reason: 'comment_mentions_blocker_or_clarification',
}; };
} }
if (isConcreteProgress(normalized)) { if (isConcreteProgress(lowerText)) {
return { signal: 'strong_progress', reason: 'comment_contains_concrete_progress' }; 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' }; 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 { createLogger } from '@shared/utils/logger';
import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity';
import { TeamInboxReader } from '../TeamInboxReader'; import { TeamInboxReader } from '../TeamInboxReader';
import { TeamInboxWriter } from '../TeamInboxWriter'; import { TeamInboxWriter } from '../TeamInboxWriter';
import type { TeamDataService } from '../TeamDataService'; import type { TeamDataService } from '../TeamDataService';
import type { TeamProvisioningService } from '../TeamProvisioningService'; import type { TeamProvisioningService } from '../TeamProvisioningService';
import type { TaskStallAlert } from './TeamTaskStallTypes'; 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 { classifyTaskProgressTouch, type TaskProgressSignal } from './TaskProgressSignalClassifier';
import type { BoardTaskActivityRecord } from '../taskLogs/activity/BoardTaskActivityRecord';
import type { import type {
ReviewTaskContext, ReviewTaskContext,
TaskStallBranch, TaskStallBranch,
@ -9,7 +11,6 @@ import type {
TeamTaskStallSnapshot, TeamTaskStallSnapshot,
WorkTaskContext, WorkTaskContext,
} from './TeamTaskStallTypes'; } from './TeamTaskStallTypes';
import { getOpenCodeWeakStartStallThresholdMs } from './featureGates';
import type { TaskHistoryEvent, TaskWorkInterval, TeamTask } from '@shared/types'; import type { TaskHistoryEvent, TaskWorkInterval, TeamTask } from '@shared/types';
const WORK_TOUCH_TOOLS = new Set(['task_start', 'task_add_comment', 'task_set_status']); 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 { BoardTaskActivityTranscriptReader } from '../taskLogs/activity/BoardTaskActivityTranscriptReader';
import { isBoardTaskActivityReadEnabled } from '../taskLogs/activity/featureGates'; import { isBoardTaskActivityReadEnabled } from '../taskLogs/activity/featureGates';
import { TeamTranscriptSourceLocator } from '../taskLogs/discovery/TeamTranscriptSourceLocator'; import { TeamTranscriptSourceLocator } from '../taskLogs/discovery/TeamTranscriptSourceLocator';
import { isBoardTaskExactLogsReadEnabled } from '../taskLogs/exact/featureGates'; import { isBoardTaskExactLogsReadEnabled } from '../taskLogs/exact/featureGates';
import { TeamKanbanManager } from '../TeamKanbanManager'; import { TeamKanbanManager } from '../TeamKanbanManager';
import { TeamTaskReader } from '../TeamTaskReader';
import { TeamMembersMetaStore } from '../TeamMembersMetaStore'; import { TeamMembersMetaStore } from '../TeamMembersMetaStore';
import { TeamTaskReader } from '../TeamTaskReader';
import { BoardTaskActivityBatchIndexer } from './BoardTaskActivityBatchIndexer'; import { BoardTaskActivityBatchIndexer } from './BoardTaskActivityBatchIndexer';
import { OpenCodeTaskStallEvidenceSource } from './OpenCodeTaskStallEvidenceSource'; import { OpenCodeTaskStallEvidenceSource } from './OpenCodeTaskStallEvidenceSource';
import { buildResolvedReviewerIndex } from './reviewerResolution'; import { buildResolvedReviewerIndex } from './reviewerResolution';
import { TeamTaskLogFreshnessReader } from './TeamTaskLogFreshnessReader'; import { TeamTaskLogFreshnessReader } from './TeamTaskLogFreshnessReader';
import { TeamTaskStallExactRowReader } from './TeamTaskStallExactRowReader'; import { TeamTaskStallExactRowReader } from './TeamTaskStallExactRowReader';
import {
inferTeamProviderIdFromModel,
normalizeOptionalTeamProviderId,
} from '@shared/utils/teamProvider';
import type { BoardTaskActivityRecord } from '../taskLogs/activity/BoardTaskActivityRecord'; import type { BoardTaskActivityRecord } from '../taskLogs/activity/BoardTaskActivityRecord';
import type { TeamTaskStallExactRow, TeamTaskStallSnapshot } from './TeamTaskStallTypes'; import type { TeamTaskStallExactRow, TeamTaskStallSnapshot } from './TeamTaskStallTypes';

View file

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

View file

@ -1,8 +1,7 @@
import { createLogger } from '@shared/utils/logger';
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
import * as path from 'path'; import * as path from 'path';
import { createLogger } from '@shared/utils/logger';
import { TeamTranscriptProjectResolver } from '../../TeamTranscriptProjectResolver'; import { TeamTranscriptProjectResolver } from '../../TeamTranscriptProjectResolver';
import type { TeamConfig } from '@shared/types'; import type { TeamConfig } from '@shared/types';
@ -35,7 +34,7 @@ async function mapLimit<T, R>(
if (currentIndex >= items.length) { if (currentIndex >= items.length) {
return; return;
} }
results[currentIndex] = await fn(items[currentIndex]!); results[currentIndex] = await fn(items[currentIndex]);
} }
}); });
await Promise.all(workers); await Promise.all(workers);

View file

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

View file

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

View file

@ -1487,7 +1487,7 @@ function mergeSegments(
function chooseDefaultFilter(participants: BoardTaskLogParticipant[]): 'all' | string { function chooseDefaultFilter(participants: BoardTaskLogParticipant[]): 'all' | string {
const namedParticipants = participants.filter((participant) => !participant.isLead); 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( function mergeRuntimeFallbackResponse(

View file

@ -1,5 +1,5 @@
import { createLogger } from '@shared/utils/logger';
import { sanitizeDisplayContent } from '@shared/utils/contentSanitizer'; import { sanitizeDisplayContent } from '@shared/utils/contentSanitizer';
import { createLogger } from '@shared/utils/logger';
import { ClaudeMultimodelBridgeService } from '../../../runtime/ClaudeMultimodelBridgeService'; import { ClaudeMultimodelBridgeService } from '../../../runtime/ClaudeMultimodelBridgeService';
import { canonicalizeAgentTeamsToolName } from '../../agentTeamsToolNames'; import { canonicalizeAgentTeamsToolName } from '../../agentTeamsToolNames';

View file

@ -118,8 +118,8 @@ import {
TEAM_CANCEL_PROVISIONING, TEAM_CANCEL_PROVISIONING,
TEAM_CHANGE, TEAM_CHANGE,
TEAM_CREATE, TEAM_CREATE,
TEAM_CREATE_INITIAL_GIT_COMMIT,
TEAM_CREATE_CONFIG, TEAM_CREATE_CONFIG,
TEAM_CREATE_INITIAL_GIT_COMMIT,
TEAM_CREATE_TASK, TEAM_CREATE_TASK,
TEAM_DELETE_DRAFT, TEAM_DELETE_DRAFT,
TEAM_DELETE_TASK_ATTACHMENT, TEAM_DELETE_TASK_ATTACHMENT,

View file

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

View file

@ -27,7 +27,6 @@ import { buildReplyBlock } from '@renderer/utils/agentMessageFormatting';
import { removeChipTokenFromText } from '@renderer/utils/chipUtils'; import { removeChipTokenFromText } from '@renderer/utils/chipUtils';
import { formatAgentRole } from '@renderer/utils/formatAgentRole'; import { formatAgentRole } from '@renderer/utils/formatAgentRole';
import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import { import {
extractTaskRefsFromText, extractTaskRefsFromText,
stripEncodedTaskReferenceMetadata, stripEncodedTaskReferenceMetadata,
@ -41,6 +40,7 @@ import { MemberBadge } from '../MemberBadge';
import type { ActionMode } from '@renderer/components/team/messages/ActionModeSelector'; import type { ActionMode } from '@renderer/components/team/messages/ActionModeSelector';
import type { InlineChip } from '@renderer/types/inlineChip'; import type { InlineChip } from '@renderer/types/inlineChip';
import type { MentionSuggestion } from '@renderer/types/mention'; import type { MentionSuggestion } from '@renderer/types/mention';
import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import type { import type {
AttachmentPayload, AttachmentPayload,
ResolvedTeamMember, ResolvedTeamMember,

View file

@ -147,13 +147,13 @@ export function getWorktreeGitControlDisabledReason(
return state.status.canUseWorktrees ? null : (state.status.message ?? null); return state.status.canUseWorktrees ? null : (state.status.message ?? null);
} }
export function WorktreeGitReadinessBanner({ export const WorktreeGitReadinessBanner = ({
state, state,
showReady = false, showReady = false,
}: { }: {
state: WorktreeGitReadinessState; state: WorktreeGitReadinessState;
showReady?: boolean; showReady?: boolean;
}): React.JSX.Element | null { }): React.JSX.Element | null => {
const { status, loading, actionLoading, error, initializeRepository, createInitialCommit } = const { status, loading, actionLoading, error, initializeRepository, createInitialCommit } =
state; state;
@ -240,4 +240,4 @@ export function WorktreeGitReadinessBanner({
</div> </div>
</div> </div>
); );
} };

View file

@ -1,5 +1,5 @@
import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity';
import { SyncedLoader2 } from '@renderer/components/ui/SyncedLoader2'; import { SyncedLoader2 } from '@renderer/components/ui/SyncedLoader2';
import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity';
import type { TeamTaskWithKanban } from '@shared/types'; import type { TeamTaskWithKanban } from '@shared/types';

View file

@ -1,12 +1,12 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { Badge } from '@renderer/components/ui/badge'; import { Badge } from '@renderer/components/ui/badge';
import { SyncedLoader2 } from '@renderer/components/ui/SyncedLoader2';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { getTeamColorSet } from '@renderer/constants/teamColors'; import { getTeamColorSet } from '@renderer/constants/teamColors';
import { useTheme } from '@renderer/hooks/useTheme'; import { useTheme } from '@renderer/hooks/useTheme';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import { selectResolvedMembersForTeamName } from '@renderer/store/slices/teamSlice'; import { selectResolvedMembersForTeamName } from '@renderer/store/slices/teamSlice';
import { SyncedLoader2 } from '@renderer/components/ui/SyncedLoader2';
import { formatAgentRole } from '@renderer/utils/formatAgentRole'; import { formatAgentRole } from '@renderer/utils/formatAgentRole';
import { import {
agentAvatarUrl, agentAvatarUrl,

View file

@ -15,8 +15,8 @@ import {
import { isLeadMember } from '@shared/utils/leadDetection'; import { isLeadMember } from '@shared/utils/leadDetection';
import { Pencil } from 'lucide-react'; import { Pencil } from 'lucide-react';
import { MemberRoleEditor } from './MemberRoleEditor';
import { MemberPresenceDot } from './MemberPresenceDot'; import { MemberPresenceDot } from './MemberPresenceDot';
import { MemberRoleEditor } from './MemberRoleEditor';
import type { import type {
LeadActivityState, LeadActivityState,

View file

@ -8,7 +8,10 @@ interface MemberPresenceDotProps {
label: string; 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 shouldSyncPulse = className?.includes('animate-pulse') === true;
const syncedPulseStyle = useSyncedAnimationStyle(shouldSyncPulse, PULSE_DURATION_MS); const syncedPulseStyle = useSyncedAnimationStyle(shouldSyncPulse, PULSE_DURATION_MS);
@ -22,4 +25,4 @@ export function MemberPresenceDot({ className, label }: MemberPresenceDotProps):
aria-label={label} aria-label={label}
/> />
); );
} };

View file

@ -19,7 +19,6 @@ import { isTeamProvisioningActive } from '@renderer/store/slices/teamSlice';
import { serializeChipsWithText } from '@renderer/types/inlineChip'; import { serializeChipsWithText } from '@renderer/types/inlineChip';
import { formatAgentRole } from '@renderer/utils/formatAgentRole'; import { formatAgentRole } from '@renderer/utils/formatAgentRole';
import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import { nameColorSet } from '@renderer/utils/projectColor'; import { nameColorSet } from '@renderer/utils/projectColor';
import { getSuggestedSlashCommandsForProvider } from '@renderer/utils/providerSlashCommands'; import { getSuggestedSlashCommandsForProvider } from '@renderer/utils/providerSlashCommands';
import { buildSlashCommandSuggestions } from '@renderer/utils/skillCommandSuggestions'; 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 { ActionMode } from '@renderer/components/team/messages/ActionModeSelector';
import type { MentionSuggestion } from '@renderer/types/mention'; import type { MentionSuggestion } from '@renderer/types/mention';
import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import type { import type {
AttachmentPayload, AttachmentPayload,
ResolvedTeamMember, ResolvedTeamMember,

View file

@ -18,7 +18,6 @@ import { useTeamMessagesExpanded } from '@renderer/hooks/useTeamMessagesExpanded
import { useTeamMessagesRead } from '@renderer/hooks/useTeamMessagesRead'; import { useTeamMessagesRead } from '@renderer/hooks/useTeamMessagesRead';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import { selectTeamMessages } from '@renderer/store/slices/teamSlice'; import { selectTeamMessages } from '@renderer/store/slices/teamSlice';
import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering'; import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering';
import { toMessageKey } from '@renderer/utils/teamMessageKey'; import { toMessageKey } from '@renderer/utils/teamMessageKey';
import { shouldExcludeInboxTextFromReplyCandidates } from '@shared/utils/idleNotificationSemantics'; import { shouldExcludeInboxTextFromReplyCandidates } from '@shared/utils/idleNotificationSemantics';
@ -59,6 +58,7 @@ import type { TimelineItem } from '../activity/LeadThoughtsGroup';
import type { ActionMode } from './ActionModeSelector'; import type { ActionMode } from './ActionModeSelector';
import type { MessagesFilterState } from './MessagesFilterPopover'; import type { MessagesFilterState } from './MessagesFilterPopover';
import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode'; import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode';
import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import type { InboxMessage, ResolvedTeamMember, TaskRef, TeamTaskWithKanban } from '@shared/types'; import type { InboxMessage, ResolvedTeamMember, TaskRef, TeamTaskWithKanban } from '@shared/types';
interface TimeWindow { interface TimeWindow {
@ -186,8 +186,7 @@ export function hasVisibleReplyForSendMessageDiagnostics(
const sentMessage = messages.find((message) => message.messageId === messageId); const sentMessage = messages.find((message) => message.messageId === messageId);
if ( if (
!sentMessage || sentMessage?.from !== 'user' ||
sentMessage.from !== 'user' ||
typeof sentMessage.to !== 'string' || typeof sentMessage.to !== 'string' ||
sentMessage.to.length === 0 sentMessage.to.length === 0
) { ) {

View file

@ -14,11 +14,11 @@ interface OpenCodeDeliveryWarningProps {
pendingDelayMs?: number; pendingDelayMs?: number;
} }
export function OpenCodeDeliveryWarning({ export const OpenCodeDeliveryWarning = ({
warning, warning,
debugDetails, debugDetails,
pendingDelayMs = 10_000, pendingDelayMs = 10_000,
}: OpenCodeDeliveryWarningProps): JSX.Element | null { }: OpenCodeDeliveryWarningProps): JSX.Element | null => {
const detailsKey = `${warning ?? ''}:${debugDetails?.messageId ?? ''}`; const detailsKey = `${warning ?? ''}:${debugDetails?.messageId ?? ''}`;
const delayPendingWarning = const delayPendingWarning =
debugDetails?.responsePending === true && debugDetails.delivered !== false; debugDetails?.responsePending === true && debugDetails.delivered !== false;
@ -148,4 +148,4 @@ export function OpenCodeDeliveryWarning({
) : null} ) : null}
</span> </span>
); );
} };

View file

@ -10,12 +10,12 @@ export type SyncedLoader2Props = ComponentProps<typeof Loader2> & {
spinDurationMs?: number; spinDurationMs?: number;
}; };
export function SyncedLoader2({ export const SyncedLoader2 = ({
className, className,
style, style,
spinDurationMs = DEFAULT_SPIN_DURATION_MS, spinDurationMs = DEFAULT_SPIN_DURATION_MS,
...props ...props
}: SyncedLoader2Props): React.JSX.Element { }: SyncedLoader2Props): React.JSX.Element => {
const syncedStyle = useSyncedAnimationStyle(true, spinDurationMs); const syncedStyle = useSyncedAnimationStyle(true, spinDurationMs);
return ( return (
@ -25,4 +25,4 @@ export function SyncedLoader2({
style={{ ...syncedStyle, ...style }} style={{ ...syncedStyle, ...style }}
/> />
); );
} };

View file

@ -29,7 +29,7 @@ export function isEditableShortcutTarget(target: EventTarget | null): boolean {
} }
const contentEditable = editableElement.getAttribute('contenteditable'); const contentEditable = editableElement.getAttribute('contenteditable');
return contentEditable == null || contentEditable.toLowerCase() !== 'false'; return contentEditable?.toLowerCase() !== 'false';
} }
export function useKeyboardShortcuts(): void { export function useKeyboardShortcuts(): void {

View file

@ -1,5 +1,6 @@
import { api } from '@renderer/api'; import { api } from '@renderer/api';
import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages'; import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages';
import { buildOpenCodeRuntimeDeliveryDiagnostics } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import { normalizePath } from '@renderer/utils/pathNormalize'; import { normalizePath } from '@renderer/utils/pathNormalize';
import { import {
buildTaskChangePresenceKey, buildTaskChangePresenceKey,
@ -10,7 +11,6 @@ import {
import { toMessageKey } from '@renderer/utils/teamMessageKey'; import { toMessageKey } from '@renderer/utils/teamMessageKey';
import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext'; import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext';
import { IpcError, unwrapIpc } from '@renderer/utils/unwrapIpc'; import { IpcError, unwrapIpc } from '@renderer/utils/unwrapIpc';
import { buildOpenCodeRuntimeDeliveryDiagnostics } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import { stripAgentBlocks } from '@shared/constants/agentBlocks'; import { stripAgentBlocks } from '@shared/constants/agentBlocks';
import { DEFAULT_TOOL_APPROVAL_SETTINGS } from '@shared/types/team'; import { DEFAULT_TOOL_APPROVAL_SETTINGS } from '@shared/types/team';
import { isLeadMember } from '@shared/utils/leadDetection'; import { isLeadMember } from '@shared/utils/leadDetection';
@ -25,8 +25,8 @@ import { getWorktreeNavigationState } from '../utils/stateResetHelpers';
import type { AppState } from '../types'; import type { AppState } from '../types';
import type { GraphLayoutMode, GraphOwnerSlotAssignment } from '@claude-teams/agent-graph'; import type { GraphLayoutMode, GraphOwnerSlotAssignment } from '@claude-teams/agent-graph';
import type { AppConfig } from '@renderer/types/data'; import type { AppConfig } from '@renderer/types/data';
import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode'; import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode';
import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import type { import type {
ActiveToolCall, ActiveToolCall,
AddMemberRequest, AddMemberRequest,

View file

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

View file

@ -26,7 +26,7 @@ export function buildOpenCodeRuntimeDeliveryDiagnostics(
result: SendMessageResult result: SendMessageResult
): OpenCodeRuntimeDeliveryDiagnostics { ): OpenCodeRuntimeDeliveryDiagnostics {
const runtimeDelivery = result.runtimeDelivery; const runtimeDelivery = result.runtimeDelivery;
if (!runtimeDelivery || runtimeDelivery.attempted !== true) { if (runtimeDelivery?.attempted !== true) {
return { warning: null, debugDetails: null }; return { warning: null, debugDetails: null };
} }

View file

@ -51,7 +51,7 @@ interface ContentBlock {
input?: Record<string, unknown>; input?: Record<string, unknown>;
} }
type CodexNativeJsonEvent = { interface CodexNativeJsonEvent {
type?: string; type?: string;
thread_id?: string; thread_id?: string;
item?: { item?: {
@ -70,16 +70,16 @@ type CodexNativeJsonEvent = {
cached_input_tokens?: number; cached_input_tokens?: number;
output_tokens?: number; output_tokens?: number;
}; };
}; }
type CodexNativeProjectedSystemEvent = { interface CodexNativeProjectedSystemEvent {
type?: string; type?: string;
subtype?: string; subtype?: string;
content?: string; content?: string;
level?: string; level?: string;
codexNativeThreadStatus?: string; codexNativeThreadStatus?: string;
codexNativeThreadId?: string; codexNativeThreadId?: string;
}; }
/** /**
* Content-based hash for deterministic fallback IDs that survive * Content-based hash for deterministic fallback IDs that survive
@ -265,7 +265,7 @@ function codexNativeProjectedSystemToDisplayItems(
timestamp: Date timestamp: Date
): AIGroupDisplayItem[] | null { ): AIGroupDisplayItem[] | null {
const event = asRecord(parsed) as CodexNativeProjectedSystemEvent | 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; return null;
} }

View file

@ -10398,7 +10398,7 @@ describe('Team agent launch matrix safe e2e', () => {
const launchAdapter = new FakeOpenCodeRuntimeAdapter(); const launchAdapter = new FakeOpenCodeRuntimeAdapter();
const firstService = new TeamProvisioningService(); const firstService = new TeamProvisioningService();
firstService.setRuntimeAdapterRegistry(new TeamRuntimeAdapterRegistry([launchAdapter])); firstService.setRuntimeAdapterRegistry(new TeamRuntimeAdapterRegistry([launchAdapter]));
await firstService.createTeam( const launch = await firstService.createTeam(
{ {
teamName, teamName,
cwd: projectPath, cwd: projectPath,
@ -10426,6 +10426,7 @@ describe('Team agent launch matrix safe e2e', () => {
}); });
expect(messageAdapter.messageInputs).toHaveLength(1); expect(messageAdapter.messageInputs).toHaveLength(1);
expect(messageAdapter.messageInputs[0]).toMatchObject({ expect(messageAdapter.messageInputs[0]).toMatchObject({
runId: launch.runId,
teamName, teamName,
laneId: 'primary', laneId: 'primary',
memberName: 'alice', memberName: 'alice',
@ -10433,7 +10434,6 @@ describe('Team agent launch matrix safe e2e', () => {
text: 'message recovered pure opencode lane', text: 'message recovered pure opencode lane',
messageId: 'msg-recovered-pure-opencode', 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 () => { 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 (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(adapterLaunch).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
laneId: 'secondary:opencode:bob', laneId: 'secondary:opencode:bob',
@ -7783,20 +7783,23 @@ describe('TeamProvisioningService', () => {
], ],
}) })
); );
await vi.waitFor(() => { await vi.waitFor(
expect(run.mixedSecondaryLanes).toEqual([ () => {
expect.objectContaining({ expect(run.mixedSecondaryLanes).toEqual([
laneId: 'secondary:opencode:bob', expect.objectContaining({
state: 'finished', laneId: 'secondary:opencode:bob',
result: expect.objectContaining({ teamLaunchState: 'clean_success' }), state: 'finished',
}), result: expect.objectContaining({ teamLaunchState: 'clean_success' }),
expect.objectContaining({ }),
laneId: 'secondary:opencode:tom', expect.objectContaining({
state: 'finished', laneId: 'secondary:opencode:tom',
result: expect.objectContaining({ teamLaunchState: 'clean_success' }), state: 'finished',
}), result: expect.objectContaining({ teamLaunchState: 'clean_success' }),
]); }),
}); ]);
},
{ timeout: 5_000 }
);
const publicStatuses = await svc.getMemberSpawnStatuses('safe-mixed-codex-opencode-launch'); const publicStatuses = await svc.getMemberSpawnStatuses('safe-mixed-codex-opencode-launch');
expect(publicStatuses.statuses.bob).toMatchObject({ expect(publicStatuses.statuses.bob).toMatchObject({
status: 'online', status: 'online',