fix(ci): restore dev validation
This commit is contained in:
parent
6c43380846
commit
a8d53ca5cb
51 changed files with 298 additions and 129 deletions
|
|
@ -1353,6 +1353,7 @@ describe('agent-teams-mcp tools', () => {
|
|||
members: [
|
||||
{ name: 'lead', role: 'team-lead' },
|
||||
{ name: 'alice', role: 'developer' },
|
||||
{ name: 'bob', role: 'reviewer' },
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import type {
|
|||
RuntimeProviderManagementDirectoryResponse,
|
||||
RuntimeProviderManagementForgetInput,
|
||||
RuntimeProviderManagementLoadDirectoryInput,
|
||||
RuntimeProviderManagementLoadModelsInput,
|
||||
RuntimeProviderManagementLoadSetupFormInput,
|
||||
RuntimeProviderManagementLoadViewInput,
|
||||
RuntimeProviderManagementLoadModelsInput,
|
||||
RuntimeProviderManagementModelTestResponse,
|
||||
RuntimeProviderManagementModelsResponse,
|
||||
RuntimeProviderManagementModelTestResponse,
|
||||
RuntimeProviderManagementProviderResponse,
|
||||
RuntimeProviderManagementSetDefaultModelInput,
|
||||
RuntimeProviderManagementSetupFormResponse,
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import type {
|
|||
RuntimeProviderManagementDirectoryResponse,
|
||||
RuntimeProviderManagementForgetInput,
|
||||
RuntimeProviderManagementLoadDirectoryInput,
|
||||
RuntimeProviderManagementLoadSetupFormInput,
|
||||
RuntimeProviderManagementLoadModelsInput,
|
||||
RuntimeProviderManagementLoadSetupFormInput,
|
||||
RuntimeProviderManagementLoadViewInput,
|
||||
RuntimeProviderManagementModelTestResponse,
|
||||
RuntimeProviderManagementModelsResponse,
|
||||
RuntimeProviderManagementModelTestResponse,
|
||||
RuntimeProviderManagementProviderResponse,
|
||||
RuntimeProviderManagementSetDefaultModelInput,
|
||||
RuntimeProviderManagementSetupFormResponse,
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ import type {
|
|||
RuntimeProviderManagementDirectoryResponse,
|
||||
RuntimeProviderManagementForgetInput,
|
||||
RuntimeProviderManagementLoadDirectoryInput,
|
||||
RuntimeProviderManagementLoadSetupFormInput,
|
||||
RuntimeProviderManagementLoadModelsInput,
|
||||
RuntimeProviderManagementLoadSetupFormInput,
|
||||
RuntimeProviderManagementLoadViewInput,
|
||||
RuntimeProviderManagementModelTestResponse,
|
||||
RuntimeProviderManagementModelsResponse,
|
||||
RuntimeProviderManagementModelTestResponse,
|
||||
RuntimeProviderManagementProviderResponse,
|
||||
RuntimeProviderManagementSetDefaultModelInput,
|
||||
RuntimeProviderManagementSetupFormResponse,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import type {
|
|||
RuntimeProviderManagementDirectoryResponse,
|
||||
RuntimeProviderManagementForgetInput,
|
||||
RuntimeProviderManagementLoadDirectoryInput,
|
||||
RuntimeProviderManagementLoadSetupFormInput,
|
||||
RuntimeProviderManagementLoadModelsInput,
|
||||
RuntimeProviderManagementLoadSetupFormInput,
|
||||
RuntimeProviderManagementLoadViewInput,
|
||||
RuntimeProviderManagementModelTestResponse,
|
||||
RuntimeProviderManagementModelsResponse,
|
||||
RuntimeProviderManagementModelTestResponse,
|
||||
RuntimeProviderManagementProviderResponse,
|
||||
RuntimeProviderManagementSetDefaultModelInput,
|
||||
RuntimeProviderManagementSetupFormResponse,
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import type {
|
|||
RuntimeProviderManagementErrorDto,
|
||||
RuntimeProviderManagementForgetInput,
|
||||
RuntimeProviderManagementLoadDirectoryInput,
|
||||
RuntimeProviderManagementLoadSetupFormInput,
|
||||
RuntimeProviderManagementLoadModelsInput,
|
||||
RuntimeProviderManagementLoadSetupFormInput,
|
||||
RuntimeProviderManagementLoadViewInput,
|
||||
RuntimeProviderManagementModelsResponse,
|
||||
RuntimeProviderManagementModelTestResponse,
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ import type {
|
|||
RuntimeProviderManagementDirectoryResponse,
|
||||
RuntimeProviderManagementForgetInput,
|
||||
RuntimeProviderManagementLoadDirectoryInput,
|
||||
RuntimeProviderManagementLoadSetupFormInput,
|
||||
RuntimeProviderManagementLoadModelsInput,
|
||||
RuntimeProviderManagementLoadSetupFormInput,
|
||||
RuntimeProviderManagementLoadViewInput,
|
||||
RuntimeProviderManagementModelTestResponse,
|
||||
RuntimeProviderManagementModelsResponse,
|
||||
RuntimeProviderManagementModelTestResponse,
|
||||
RuntimeProviderManagementProviderResponse,
|
||||
RuntimeProviderManagementSetDefaultModelInput,
|
||||
RuntimeProviderManagementSetupFormResponse,
|
||||
|
|
|
|||
|
|
@ -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' },
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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', () => ({
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import type {
|
|||
OpenCodeTeamLaunchReadinessState,
|
||||
} from '../readiness/OpenCodeTeamLaunchReadiness';
|
||||
import type {
|
||||
OpenCodeBackfillTaskLedgerCommandBody,
|
||||
OpenCodeBackfillTaskLedgerCommandData,
|
||||
OpenCodeBridgeCommandName,
|
||||
OpenCodeBridgeDiagnosticEvent,
|
||||
OpenCodeBridgeFailureKind,
|
||||
OpenCodeBridgeResult,
|
||||
OpenCodeBridgeRuntimeSnapshot,
|
||||
OpenCodeBackfillTaskLedgerCommandBody,
|
||||
OpenCodeBackfillTaskLedgerCommandData,
|
||||
OpenCodeCleanupHostsCommandBody,
|
||||
OpenCodeCleanupHostsCommandData,
|
||||
OpenCodeLaunchTeamCommandBody,
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -15,28 +15,185 @@ export interface TaskProgressTouchClassification {
|
|||
reason: string;
|
||||
}
|
||||
|
||||
const CONCRETE_FILE_OR_PATH_RE =
|
||||
/(?:^|\s)(?:\.{1,2}\/|~\/|\/|\w[\w.-]*\/)[\w./\s-]+|\b[\w.-]+\.(?:[cm]?[tj]sx?|json|md|css|scss|py|go|rs|java|kt|swift|ya?ml|toml|lock|sh|sql)\b/i;
|
||||
const TASK_OR_ISSUE_REF_RE = /#[a-f0-9]{6,}|\btask-[\w-]+/i;
|
||||
const TEST_OR_BUILD_RESULT_RE =
|
||||
/\b(?:test(?:s|ed|ing)?|vitest|jest|playwright|pnpm|npm|bun|build|typecheck|lint|passed|failed|green|red|error|exception|stack trace)\b|тест|сборк|линт|ошибк|упал|прош[её]л/i;
|
||||
const SUBSTANTIVE_WORK_RE =
|
||||
/\b(?:implemented|fixed|added|updated|changed|removed|found|verified|confirmed|completed|created|refactored|patched|root cause|next step)\b|исправ|добав|обнов|измен|удал|наш[её]л|подтверд|готово|сделал|сделана|причин|следующ/i;
|
||||
const BLOCKER_OR_CLARIFICATION_RE =
|
||||
/\?|(?:^|\b)(?:blocked|blocker|cannot|can't|need|needs|waiting|clarification|question|permission|access denied|not enough context)\b|не могу|не получается|нужн|жду|блок|уточн|вопрос|нет доступа|недостаточно контекст/i;
|
||||
const WEAK_START_ONLY_RE =
|
||||
/^(?:я\s+)?(?:начинаю(?:\s+работу)?|начну|приступаю(?:\s+к\s+работе)?|беру\s+в\s+работу|проверю|сейчас\s+проверю|посмотрю|разберусь|готов(?:а)?\s+приступить|готов(?:а)?\s+к\s+работе|will\s+start|starting\s+work|starting|taking\s+this|i(?:'|’)?ll\s+start|i\s+will\s+start|i\s+am\s+starting|i(?:'|’)?ll\s+check|i\s+will\s+check|checking\s+now|on\s+it)(?:[.!…\s]*)$/i;
|
||||
const FILE_EXTENSIONS = [
|
||||
'.ts',
|
||||
'.tsx',
|
||||
'.js',
|
||||
'.jsx',
|
||||
'.cts',
|
||||
'.mts',
|
||||
'.ctsx',
|
||||
'.mtsx',
|
||||
'.json',
|
||||
'.md',
|
||||
'.css',
|
||||
'.scss',
|
||||
'.py',
|
||||
'.go',
|
||||
'.rs',
|
||||
'.java',
|
||||
'.kt',
|
||||
'.swift',
|
||||
'.yaml',
|
||||
'.yml',
|
||||
'.toml',
|
||||
'.lock',
|
||||
'.sh',
|
||||
'.sql',
|
||||
] as const;
|
||||
|
||||
const TEST_OR_BUILD_KEYWORDS = [
|
||||
'test',
|
||||
'tests',
|
||||
'tested',
|
||||
'testing',
|
||||
'vitest',
|
||||
'jest',
|
||||
'playwright',
|
||||
'pnpm',
|
||||
'npm',
|
||||
'bun',
|
||||
'build',
|
||||
'typecheck',
|
||||
'lint',
|
||||
'passed',
|
||||
'failed',
|
||||
'green',
|
||||
'red',
|
||||
'error',
|
||||
'exception',
|
||||
'stack trace',
|
||||
'тест',
|
||||
'сборк',
|
||||
'линт',
|
||||
'ошибк',
|
||||
'упал',
|
||||
'прошел',
|
||||
'прошёл',
|
||||
] as const;
|
||||
|
||||
const SUBSTANTIVE_WORK_KEYWORDS = [
|
||||
'implemented',
|
||||
'fixed',
|
||||
'added',
|
||||
'updated',
|
||||
'changed',
|
||||
'removed',
|
||||
'found',
|
||||
'verified',
|
||||
'confirmed',
|
||||
'completed',
|
||||
'created',
|
||||
'refactored',
|
||||
'patched',
|
||||
'root cause',
|
||||
'next step',
|
||||
'исправ',
|
||||
'добав',
|
||||
'обнов',
|
||||
'измен',
|
||||
'удал',
|
||||
'нашел',
|
||||
'нашёл',
|
||||
'подтверд',
|
||||
'готово',
|
||||
'сделал',
|
||||
'сделана',
|
||||
'причин',
|
||||
'следующ',
|
||||
] as const;
|
||||
|
||||
const BLOCKER_OR_CLARIFICATION_KEYWORDS = [
|
||||
'blocked',
|
||||
'blocker',
|
||||
'cannot',
|
||||
"can't",
|
||||
'need',
|
||||
'needs',
|
||||
'waiting',
|
||||
'clarification',
|
||||
'question',
|
||||
'permission',
|
||||
'access denied',
|
||||
'not enough context',
|
||||
'не могу',
|
||||
'не получается',
|
||||
'нужн',
|
||||
'жду',
|
||||
'блок',
|
||||
'уточн',
|
||||
'вопрос',
|
||||
'нет доступа',
|
||||
'недостаточно контекст',
|
||||
] as const;
|
||||
|
||||
const WEAK_START_ONLY_PHRASES = [
|
||||
'начинаю',
|
||||
'начинаю работу',
|
||||
'начну',
|
||||
'приступаю',
|
||||
'приступаю к работе',
|
||||
'беру в работу',
|
||||
'проверю',
|
||||
'сейчас проверю',
|
||||
'посмотрю',
|
||||
'разберусь',
|
||||
'готов приступить',
|
||||
'готова приступить',
|
||||
'готов к работе',
|
||||
'готова к работе',
|
||||
'will start',
|
||||
'starting work',
|
||||
'starting',
|
||||
'taking this',
|
||||
"i'll start",
|
||||
'i’ll start',
|
||||
'i will start',
|
||||
'i am starting',
|
||||
"i'll check",
|
||||
'i’ll check',
|
||||
'i will check',
|
||||
'checking now',
|
||||
'on it',
|
||||
] as const;
|
||||
|
||||
function normalizeCommentText(text: string): string {
|
||||
return stripAgentBlocks(text).replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function includesAnyKeyword(text: string, keywords: readonly string[]): boolean {
|
||||
return keywords.some((keyword) => text.includes(keyword));
|
||||
}
|
||||
|
||||
function containsTaskOrIssueRef(text: string): boolean {
|
||||
return text.includes('task-') || /#[a-f0-9]{6,}/i.test(text);
|
||||
}
|
||||
|
||||
function containsConcreteFileOrPath(text: string): boolean {
|
||||
const parts = text.split(/\s+/);
|
||||
return (
|
||||
parts.some(
|
||||
(part) => part.startsWith('./') || part.startsWith('../') || part.startsWith('~/')
|
||||
) ||
|
||||
parts.some((part) => part.includes('/') && /[a-z0-9_]/i.test(part)) ||
|
||||
FILE_EXTENSIONS.some((extension) => text.includes(extension))
|
||||
);
|
||||
}
|
||||
|
||||
function isWeakStartOnly(text: string): boolean {
|
||||
const normalized = text
|
||||
.replace(/[.!…\s]+$/g, '')
|
||||
.replace(/^я\s+/, '')
|
||||
.trim();
|
||||
return WEAK_START_ONLY_PHRASES.includes(normalized as (typeof WEAK_START_ONLY_PHRASES)[number]);
|
||||
}
|
||||
|
||||
function isConcreteProgress(text: string): boolean {
|
||||
return (
|
||||
CONCRETE_FILE_OR_PATH_RE.test(text) ||
|
||||
TASK_OR_ISSUE_REF_RE.test(text) ||
|
||||
TEST_OR_BUILD_RESULT_RE.test(text) ||
|
||||
SUBSTANTIVE_WORK_RE.test(text)
|
||||
containsConcreteFileOrPath(text) ||
|
||||
containsTaskOrIssueRef(text) ||
|
||||
includesAnyKeyword(text, TEST_OR_BUILD_KEYWORDS) ||
|
||||
includesAnyKeyword(text, SUBSTANTIVE_WORK_KEYWORDS)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -46,18 +203,20 @@ function classifyTaskCommentText(text: string): TaskProgressTouchClassification
|
|||
return { signal: 'unknown', reason: 'comment_text_empty' };
|
||||
}
|
||||
|
||||
if (BLOCKER_OR_CLARIFICATION_RE.test(normalized)) {
|
||||
const lowerText = normalized.toLowerCase();
|
||||
|
||||
if (lowerText.includes('?') || includesAnyKeyword(lowerText, BLOCKER_OR_CLARIFICATION_KEYWORDS)) {
|
||||
return {
|
||||
signal: 'blocker_or_clarification',
|
||||
reason: 'comment_mentions_blocker_or_clarification',
|
||||
};
|
||||
}
|
||||
|
||||
if (isConcreteProgress(normalized)) {
|
||||
if (isConcreteProgress(lowerText)) {
|
||||
return { signal: 'strong_progress', reason: 'comment_contains_concrete_progress' };
|
||||
}
|
||||
|
||||
if (normalized.length <= 120 && WEAK_START_ONLY_RE.test(normalized)) {
|
||||
if (lowerText.length <= 120 && isWeakStartOnly(lowerText)) {
|
||||
return { signal: 'weak_start_only', reason: 'comment_is_start_only' };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -67,12 +67,12 @@ import type {
|
|||
TeamProvisioningModelVerificationMode,
|
||||
TeamProvisioningPrepareResult,
|
||||
TeamProvisioningProgress,
|
||||
TeamWorktreeGitStatus,
|
||||
TeamsAPI,
|
||||
TeamSummary,
|
||||
TeamTask,
|
||||
TeamTaskStatus,
|
||||
TeamViewSnapshot,
|
||||
TeamWorktreeGitStatus,
|
||||
TmuxAPI,
|
||||
TmuxStatus,
|
||||
TriggerTestResult,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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[] = [];
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue